diff --git a/filters/sheets/excel/sidewinder/chartsubstreamhandler.cpp b/filters/sheets/excel/sidewinder/chartsubstreamhandler.cpp index d54d3d5df78..c3599340306 100644 --- a/filters/sheets/excel/sidewinder/chartsubstreamhandler.cpp +++ b/filters/sheets/excel/sidewinder/chartsubstreamhandler.cpp @@ -1,1420 +1,1430 @@ /* Swinder - Portable library for spreadsheet Copyright (C) 2009-2010 Sebastian Sauer Copyright (C) 2010 Carlos Licea This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "chartsubstreamhandler.h" #include "globalssubstreamhandler.h" #include "worksheetsubstreamhandler.h" #include "xmltk.h" #include -#include #include // splitCellRange() et al. #include namespace Swinder { class BRAIRecord : public Record { public: KoChart::Value* m_value; static const unsigned int id; unsigned int rtti() const { return this->id; } virtual const char* name() const { return "BRAI"; } static Record *createRecord(Workbook *book, void *arg) { return new BRAIRecord(book, arg); } BRAIRecord(Swinder::Workbook *book, void *arg) : Record(book), m_handler(static_cast(arg)) { m_worksheetHandler = dynamic_cast(m_handler->parentHandler()); m_value = 0; } virtual ~BRAIRecord() { delete m_value; } virtual void dump(std::ostream&) const { /*TODO*/ } virtual void setData(unsigned size, const unsigned char* data, const unsigned int* /*continuePositions*/) { if (size < 8) { setIsValid(false); return; } KoChart::Value::DataId dataId = (KoChart::Value::DataId) readU8(data); KoChart::Value::DataType type = (KoChart::Value::DataType) readU8(data + 1); bool isUnlinkedFormat = readU16(data + 2) & 0x01; unsigned numberFormat = readU16(data + 4); QString formula; if (m_worksheetHandler) { FormulaTokens tokens = m_worksheetHandler->decodeFormula(size, 6, data, version()); formula = m_worksheetHandler->decodeFormula(0, 0, true, tokens); } else { FormulaTokens tokens = m_handler->globals()->decodeFormula(size, 6, data, version()); formula = m_handler->globals()->decodeFormula(0, 0, true, tokens); } delete m_value; m_value = new KoChart::Value(dataId, type, formula, isUnlinkedFormat, numberFormat); } private: ChartSubStreamHandler* m_handler; WorksheetSubStreamHandler* m_worksheetHandler; }; class CrtMlFrtRecord : public Record { public: static const unsigned int id; unsigned int rtti() const { return this->id; } virtual const char* name() const { return "CrtMlFrt"; } static Record *createRecord(Workbook *book, void *arg) { return new CrtMlFrtRecord(book, arg); } CrtMlFrtRecord(Swinder::Workbook *book, void *arg) : Record(book), m_handler(static_cast(arg)), m_xmlTkParent(0) { m_worksheetHandler = dynamic_cast(m_handler->parentHandler()); } virtual ~CrtMlFrtRecord() { qDeleteAll(m_tokens); } unsigned xmlTkParent() const { return m_xmlTkParent; } const QList& tokens() const { return m_tokens; } virtual void dump(std::ostream& out) const { QStringList tokens; foreach(XmlTk* t, m_tokens) tokens.append( QString("%1(%2)=%3").arg(QString::fromUtf8(xmlTkTagName(XmlTkTags(t->m_xmlTkTag)))).arg(t->type()).arg(t->value()) ); out << qPrintable(QString("[%1]").arg(tokens.join(", "))); } virtual void setData(unsigned size, const unsigned char* data, const unsigned int* /*continuePositions*/) { if (size < 20) { setIsValid(false); return; } //unsigned rt = readU16(data); //unsigned grbitFrt = readU16(data + 2); //unsigned cb = readU32(data + 12); //unsigned recordVersion = readU8(data + 16); m_xmlTkParent = readU16(data + 18); qDeleteAll(m_tokens); m_tokens = parseXmlTkChain(data + 20, size - 20); } private: ChartSubStreamHandler* m_handler; WorksheetSubStreamHandler* m_worksheetHandler; unsigned m_xmlTkParent; QList m_tokens; }; const unsigned BRAIRecord::id = 0x1051; const unsigned CrtMlFrtRecord::id = 0x89E; } // namespace Swinder using namespace Swinder; /// This represents the internal chart data cache aka the "local-table" that /// is embedded into the charts content.xml and not fetched from the application /// embedding the chart (e.g. from a Calligra Sheets sheet). class ChartSubStreamHandler::InternalDataCache { public: InternalDataCache(ChartSubStreamHandler *chartSubStreamHandler, unsigned index) : m_chartSubStreamHandler(chartSubStreamHandler), m_siIndex(index) {} ~InternalDataCache() { QString cellRegion = m_cellRegion.isNull() ? QString() : Swinder::encodeAddress("local", m_cellRegion); bool isBubble = dynamic_cast(m_chartSubStreamHandler->m_chart->m_impl); bool isScatter = dynamic_cast(m_chartSubStreamHandler->m_chart->m_impl); foreach(KoChart::Series *series, m_chartSubStreamHandler->m_chart->m_series) { switch (m_siIndex) { case 0x0001: { // Series values or vertical values (for scatter or bubble chart groups) if (isBubble || isScatter) { bool change = !series->m_datasetValue.contains(KoChart::Value::VerticalValues) || (series->m_datasetValue[KoChart::Value::VerticalValues]->m_type == KoChart::Value::TextOrValue && series->m_datasetValue[KoChart::Value::VerticalValues]->m_formula.isEmpty()); if (change) { if (isBubble) { QString y = series->m_domainValuesCellRangeAddress.isEmpty() ? QString() : series->m_domainValuesCellRangeAddress[0]; series->m_domainValuesCellRangeAddress = QStringList() << y << cellRegion; } else if (isScatter) { series->m_domainValuesCellRangeAddress = QStringList() << cellRegion; } //m_chartSubStreamHandler->m_chart->m_verticalCellRangeAddress = cellRegion; } } else { if (series->m_valuesCellRangeAddress.isEmpty()) series->m_valuesCellRangeAddress = cellRegion; } } break; case 0x0002: { // Category labels or horizontal values (for scatter or bubble chart groups) if (isBubble || isScatter) { bool change = !series->m_datasetValue.contains(KoChart::Value::HorizontalValues) || (series->m_datasetValue[KoChart::Value::HorizontalValues]->m_type == KoChart::Value::TextOrValue && series->m_datasetValue[KoChart::Value::HorizontalValues]->m_formula.isEmpty()); if (change) { if (isBubble) { QString x = series->m_domainValuesCellRangeAddress.count() < 2 ? QString() : series->m_domainValuesCellRangeAddress[1]; series->m_domainValuesCellRangeAddress = QStringList() << cellRegion << x; } //series->m_valuesCellRangeAddress = cellRegion; } } else { if (m_chartSubStreamHandler->m_chart->m_verticalCellRangeAddress.isEmpty()) m_chartSubStreamHandler->m_chart->m_verticalCellRangeAddress = cellRegion; } } break; case 0x0003: { // Bubble sizes if (isBubble) { if (series->m_valuesCellRangeAddress.isEmpty()) series->m_valuesCellRangeAddress = cellRegion; } } break; default: break; } } } void add(unsigned column, unsigned row) { QRect r(column, row, 1, 1); if (m_cellRegion.isNull()) { m_cellRegion = r; } else { m_cellRegion |= r; } } private: ChartSubStreamHandler *m_chartSubStreamHandler; unsigned m_siIndex; QRect m_cellRegion; }; ChartSubStreamHandler::ChartSubStreamHandler(GlobalsSubStreamHandler* globals, SubStreamHandler* parentHandler) : SubStreamHandler() , m_globals(globals) , m_parentHandler(parentHandler) , m_sheet(0) , m_chartObject(0) , m_chart(0) , m_currentSeries(0) , m_currentObj(0) , m_internalDataCache(0) , m_defaultTextId(-1) , m_axisId(-1) , m_disableAutoMarker( false ) { RecordRegistry::registerRecordClass(BRAIRecord::id, BRAIRecord::createRecord, this); RecordRegistry::registerRecordClass(CrtMlFrtRecord::id, CrtMlFrtRecord::createRecord, this); WorksheetSubStreamHandler* worksheetHandler = dynamic_cast(parentHandler); if (worksheetHandler) { m_sheet = worksheetHandler->sheet(); Q_ASSERT(m_sheet); std::vector& charts = worksheetHandler->charts(); if (charts.empty()) { - std::cerr << "Got a chart substream without having charts in the worksheet"; + qCWarning(lcSidewinder) << "Got a chart substream without having charts in the worksheet"; return; } const unsigned long id = charts.back(); std::map::iterator it = worksheetHandler->sharedObjects().find(id); if (it == worksheetHandler->sharedObjects().end()) { - std::cerr << "Got a chart substream without having a chart in the worksheet"; + qCWarning(lcSidewinder) << "Got a chart substream without having a chart in the worksheet"; return; } m_chartObject = dynamic_cast(it->second); worksheetHandler->sharedObjects().erase(id); // remove from the sharedObjects and take over ownership Q_ASSERT(m_chartObject); m_chart = m_chartObject->m_chart; Q_ASSERT(m_chart); m_currentObj = m_chart; Cell* cell = m_sheet->cell(m_chartObject->m_colL, m_chartObject->m_rwT, true); cell->addChart(m_chartObject); } else { Q_ASSERT(globals); if (globals->chartSheets().isEmpty()) { - std::cerr << "ChartSubStreamHandler: Got a chart substream without having enough chart sheets..." << std::endl; + qCWarning(lcSidewinder) << "ChartSubStreamHandler: Got a chart substream without having enough chart sheets..."; } else { #if 0 m_sheet = globals->chartSheets().takeFirst(); m_chartObject = new ChartObject(m_chartObject->id()); m_chart = m_chartObject->m_chart; Q_ASSERT(m_chart); m_currentObj = m_chart; #if 0 DrawingObject* drawing = new DrawingObject; drawing->m_properties[DrawingObject::pid] = m_chartObject->id(); drawing->m_properties[DrawingObject::itxid] = m_chartObject->id(); drawing->m_colL = drawing->m_dxL = drawing->m_rwT = drawing->m_dyT = drawing->m_dxR = drawing->m_dyB = 0; drawing->m_colR = 10; drawing->m_rwB = 30; //FIXME use sheet "fullscreen" rather then hardcode m_chartObject->setDrawingObject(drawing); #else m_chartObject->m_colL = m_chartObject->m_dxL = m_chartObject->m_rwT = m_chartObject->m_dyT = m_chartObject->m_dxR = m_chartObject->m_dyB = 0; m_chartObject->m_colR = 10; m_chartObject->m_rwB = 30; //FIXME use sheet "fullscreen" rather then hardcode #endif Cell* cell = m_sheet->cell(0, 0, true); // anchor to the first cell cell->addChart(m_chartObject); #else - std::cerr << "ChartSubStreamHandler: FIXME" << std::endl; + qCWarning(lcSidewinder) << "ChartSubStreamHandler: FIXME"; #endif } } } ChartSubStreamHandler::~ChartSubStreamHandler() { // Set the chart's title once everything is done. if (m_chart && m_chart->m_title.isEmpty()) { if (!m_chart->m_texts.isEmpty()) { // If defined direct within the chart using a ObjectLinkRecord then we use that as title. m_chart->m_title = m_chart->m_texts.first()->m_text; } if (m_chart->m_title.isEmpty() && m_chart->m_series.count() == 1) { // Else we are using the same logic that is used in the 2007 filter and fetch the title // from the series collection of TextRecord's. KoChart::Series* series = m_chart->m_series.first(); if (!series->m_texts.isEmpty() ) m_chart->m_title = series->m_texts.first()->m_text; } } delete m_internalDataCache; RecordRegistry::unregisterRecordClass(BRAIRecord::id); RecordRegistry::unregisterRecordClass(CrtMlFrtRecord::id); } -std::string whitespaces(int number) -{ - std::string s; - for (int i = 0; i < number; ++i) - s += " "; - return s; -} - #define DEBUG \ - std::cout << whitespaces(m_stack.count()) << "ChartSubStreamHandler::" << __FUNCTION__ << " " + qCDebug(lcSidewinder) << QString(m_stack.count(), QChar(' ')) << "ChartSubStreamHandler::" << __FUNCTION__ void ChartSubStreamHandler::handleRecord(Record* record) { if (!record) return; if (!m_chart) return; const unsigned type = record->rtti(); if (m_internalDataCache && type != NumberRecord::id) { delete m_internalDataCache; m_internalDataCache = 0; } if (type == BOFRecord::id) handleBOF(static_cast(record)); else if (type == EOFRecord::id) handleEOF(static_cast(record)); else if (type == FooterRecord::id) handleFooter(static_cast(record)); else if (type == HeaderRecord::id) handleHeader(static_cast(record)); else if (type == SetupRecord::id) handleSetup(static_cast(record)); else if (type == HCenterRecord::id) handleHCenter(static_cast(record)); else if (type == VCenterRecord::id) handleVCenter(static_cast(record)); else if (type == ZoomLevelRecord::id) handleZoomLevel(static_cast(record)); else if (type == DimensionRecord::id) handleDimension(static_cast(record)); else if (type == ChartRecord::id) handleChart(static_cast(record)); else if (type == BeginRecord::id) handleBegin(static_cast(record)); else if (type == EndRecord::id) handleEnd(static_cast(record)); else if (type == FrameRecord::id) handleFrame(static_cast(record)); else if (type == SeriesRecord::id) handleSeries(static_cast(record)); else if (type == SeriesListRecord::id) handleSeriesList(static_cast(record)); else if (type == NumberRecord::id) handleNumber(static_cast(record)); else if (type == DataFormatRecord::id) handleDataFormat(static_cast(record)); else if (type == Chart3DBarShapeRecord::id) handleChart3DBarShape(static_cast(record)); else if (type == Chart3dRecord::id) handleChart3d(static_cast(record)); else if (type == LineFormatRecord::id) handleLineFormat(static_cast(record)); else if (type == AreaFormatRecord::id) handleAreaFormat(static_cast(record)); else if (type == PieFormatRecord::id) handlePieFormat(static_cast(record)); else if (type == MarkerFormatRecord::id) handleMarkerFormat(static_cast(record)); else if (type == ChartFormatRecord::id) handleChartFormat(static_cast(record)); else if (type == GelFrameRecord::id) handleGelFrame(static_cast(record)); else if (type == SerToCrtRecord::id) handleSerToCrt(static_cast(record)); else if (type == ShtPropsRecord::id) handleShtProps(static_cast(record)); else if (type == DefaultTextRecord::id) handleDefaultText(static_cast(record)); else if (type == TextRecord::id) handleText(static_cast(record)); else if (type == SeriesTextRecord::id) handleSeriesText(static_cast(record)); else if (type == PosRecord::id) handlePos(static_cast(record)); else if (type == FontXRecord::id) handleFontX(static_cast(record)); else if (type == PlotGrowthRecord::id) handlePlotGrowth(static_cast(record)); else if (type == LegendRecord::id) handleLegend(static_cast(record)); else if (type == AxesUsedRecord::id) handleAxesUsed(static_cast(record)); else if (type == AxisParentRecord::id) handleAxisParent(static_cast(record)); else if (type == BRAIRecord::id) handleBRAI(static_cast(record)); else if (type == PieRecord::id) handlePie(static_cast(record)); else if (type == BarRecord::id) handleBar(static_cast(record)); else if (type == AreaRecord::id) handleArea(static_cast(record)); else if (type == LineRecord::id) handleLine(static_cast(record)); else if (type == ScatterRecord::id) handleScatter(static_cast(record)); else if (type == RadarRecord::id) handleRadar(static_cast(record)); else if (type == RadarAreaRecord::id) handleRadarArea(static_cast(record)); else if (type == SurfRecord::id) handleSurf(static_cast(record)); else if (type == AxisRecord::id) handleAxis(static_cast(record)); else if (type == AxisLineRecord::id) handleAxisLine(static_cast(record)); else if (type == CatLabRecord::id) handleCatLab(static_cast(record)); else if (type == ValueRangeRecord::id) handleValueRange(static_cast(record)); else if (type == TickRecord::id) handleTick(static_cast(record)); else if (type == AxcExtRecord::id) handleAxcExt(static_cast(record)); else if (type == CrtLineRecord::id) handleCrtLine(static_cast(record)); else if (type == CatSerRangeRecord::id) handleCatSerRange(static_cast(record)); else if (type == AttachedLabelRecord::id) handleAttachedLabel(static_cast(record)); else if (type == DataLabelExtContentsRecord::id) handleDataLabelExtContents(static_cast(record)); else if (type == XFRecord::id) handleXF(static_cast(record)); else if (type == LabelRecord::id) handleLabel(static_cast(record)); else if (type == IFmtRecord::id) handleIFmt(static_cast(record)); else if (type == CrtMlFrtRecord::id) handleCrtMlFrt(static_cast(record)); else if (type == SIIndexRecord::id) handleSIIndex(static_cast(record)); else if (type == MsoDrawingRecord::id) handleMsoDrawing(static_cast(record)); else if (type == LeftMarginRecord::id) handleLeftMargin(static_cast(record)); else if (type == RightMarginRecord::id) handleRightMargin(static_cast(record)); else if (type == TopMarginRecord::id) handleTopMargin(static_cast(record)); else if (type == BottomMarginRecord::id) handleBottomMargin(static_cast(record)); else if (type == ShapePropsStreamRecord::id) handleShapePropsStream(static_cast(record)); else if (type == TextPropsStreamRecord::id) handleTextPropsStream(static_cast(record)); else if (type == ObjectLinkRecord::id) handleObjectLink(static_cast(record)); else if (type == PlotAreaRecord::id) handlePlotArea(static_cast(record)); else if (type == CrtLinkRecord::id) {} // written but unused record else if (type == UnitsRecord::id) {} // written but must be ignored else if (type == StartBlockRecord::id || type == EndBlockRecord::id) {} // not evaluated atm else { - DEBUG << "Unhandled chart record with type=" << type << " name=" << record->name() << std::endl; + DEBUG << "Unhandled chart record with type=" << type << "name=" << record->name(); //record->dump(std::cout); } } void ChartSubStreamHandler::handleBOF(BOFRecord*) { - //DEBUG << std::endl; } void ChartSubStreamHandler::handleEOF(EOFRecord *) { - //DEBUG << std::endl; } void ChartSubStreamHandler::handleFooter(FooterRecord *) { } void ChartSubStreamHandler::handleHeader(HeaderRecord *) { } void ChartSubStreamHandler::handleSetup(SetupRecord *) { } void ChartSubStreamHandler::handleHCenter(HCenterRecord *) { } void ChartSubStreamHandler::handleVCenter(VCenterRecord *) { } void ChartSubStreamHandler::handleZoomLevel(ZoomLevelRecord *) { } void ChartSubStreamHandler::handleLeftMargin(LeftMarginRecord* record) { if (!record) return; m_chart->m_leftMargin = record->leftMargin(); } void ChartSubStreamHandler::handleRightMargin(RightMarginRecord* record) { if (!record) return; m_chart->m_rightMargin = record->rightMargin(); } void ChartSubStreamHandler::handleTopMargin(TopMarginRecord* record) { if (!record) return; m_chart->m_topMargin = record->topMargin(); } void ChartSubStreamHandler::handleBottomMargin(BottomMarginRecord* record) { if (!record) return; m_chart->m_bottomMargin = record->bottomMargin(); } void ChartSubStreamHandler::handleDimension(DimensionRecord *record) { if (!record) return; - DEBUG << "firstRow=" << record->firstRow() << " lastRowPlus1=" << record->lastRowPlus1() << " firstColumn=" << record->firstColumn() << " lastColumnPlus1=" << record->lastColumnPlus1() << " lastRow=" << record->lastRow() << " lastColumn=" << record->lastColumn() << std::endl; + DEBUG << "firstRow=" << record->firstRow() << "lastRowPlus1=" << record->lastRowPlus1() + << "firstColumn=" << record->firstColumn() << "lastColumnPlus1=" << record->lastColumnPlus1() + << "lastRow=" << record->lastRow() << "lastColumn=" << record->lastColumn(); } void ChartSubStreamHandler::handleChart(ChartRecord *record) { if (!record) return; - DEBUG << "x=" << record->x() << " y=" << record->y() << " width=" << record->width() << " height=" << record->height() << std::endl; + DEBUG << "x=" << record->x() << "y=" << record->y() << "width=" << record->width() << "height=" << record->height(); m_chart->m_x1 = record->x(); m_chart->m_y1 = record->y(); m_chart->m_x2 = record->width() - m_chart->m_x1; m_chart->m_y2 = record->height() - m_chart->m_y1; } // specifies the begin of a collection of records void ChartSubStreamHandler::handleBegin(BeginRecord *) { m_stack.push(m_currentObj); } // specified the end of a collection of records void ChartSubStreamHandler::handleEnd(EndRecord *) { m_currentObj = m_stack.pop(); if (!m_seriesStack.isEmpty()) m_currentSeries = m_seriesStack.pop(); else if (KoChart::Series* series = dynamic_cast(m_currentObj)) m_currentSeries = series; } void ChartSubStreamHandler::handleFrame(FrameRecord *record) { if (!record) return; - DEBUG << "autoPosition=" << record->isAutoPosition() << " autoSize=" << record->isAutoSize() << std::endl; + DEBUG << "autoPosition=" << record->isAutoPosition() << "autoSize=" << record->isAutoSize(); if ( dynamic_cast< KoChart::Chart* > ( m_currentObj ) ) { if (record->isAutoPosition()) { m_chart->m_x1 = -1; m_chart->m_y1 = -1; } if (record->isAutoSize()) { m_chart->m_x2 = -1; m_chart->m_y2 = -1; } } else if ( dynamic_cast< KoChart::PlotArea* > ( m_currentObj ) ) { } } // properties of the data for series, trendlines or errorbars void ChartSubStreamHandler::handleSeries(SeriesRecord *record) { if (!record) return; - DEBUG << "dataTypeX=" << record->dataTypeX() << " dataTypeY=" << record->dataTypeY() << " countXValues=" << record->countXValues() << " countYValues=" << record->countYValues() << " bubbleSizeDataType=" << record->bubbleSizeDataType() << " countBubbleSizeValues=" << record->countBubbleSizeValues() << std::endl; + DEBUG << "dataTypeX=" << record->dataTypeX() << "dataTypeY=" << record->dataTypeY() << "countXValues=" << record->countXValues() + << "countYValues=" << record->countYValues() << "bubbleSizeDataType=" << record->bubbleSizeDataType() + << "countBubbleSizeValues=" << record->countBubbleSizeValues(); m_currentSeries = new KoChart::Series; m_currentSeries->m_dataTypeX = record->dataTypeX(); m_currentSeries->m_countXValues = record->countXValues(); m_currentSeries->m_countYValues = record->countYValues(); m_currentSeries->m_countBubbleSizeValues = record->countBubbleSizeValues(); m_chart->m_series << m_currentSeries; m_currentObj = m_currentSeries; } void ChartSubStreamHandler::handleSeriesList(SeriesListRecord *record) { - DEBUG << "cser=" << record->cser() << std::endl; + DEBUG << "cser=" << record->cser(); for(unsigned i = 0; i < record->cser(); ++i) - DEBUG << "number=" << i << " rgiser=" << record->rgiser(i) << std::endl; + DEBUG << "number=" << i << "rgiser=" << record->rgiser(i); //TODO } void ChartSubStreamHandler::handleNumber(NumberRecord *record) { - DEBUG << "row=" << record->row() << " column=" << record->column() << " xfIndex=" << record->xfIndex() << " number=" << record->number() << std::endl; + DEBUG << "row=" << record->row() << "column=" << record->column() << "xfIndex=" << record->xfIndex() << "number=" << record->number(); // The formatting of the value doesn't really matter or does it? Well, maybe for data-value-label's that should be displayed as formatted? //m_xfTable[record->xfIndex()] KoChart::Cell *cell = m_chart->m_internalTable.cell(record->column() + 1, record->row() + 1, true); cell->m_value = QString::number(record->number(), 'f'); cell->m_valueType = "float"; if (m_internalDataCache) m_internalDataCache->add(record->column(), record->row()); } // specifies a reference to data in a sheet that is used by a part of a series, legend entry, trendline or error bars. void ChartSubStreamHandler::handleBRAI(BRAIRecord *record) { if (!record) return; - DEBUG << "dataId=" << record->m_value->m_dataId << " type=" << record->m_value->m_type << " isUnlinkedNumberFormat=" << record->m_value->m_isUnlinkedFormat << " numberFormat=" << record->m_value->m_numberFormat << " formula=" << record->m_value->m_formula.toUtf8().constData() << std::endl; + DEBUG << "dataId=" << record->m_value->m_dataId << "type=" << record->m_value->m_type + << "isUnlinkedNumberFormat=" << record->m_value->m_isUnlinkedFormat + << "numberFormat=" << record->m_value->m_numberFormat << "formula=" << record->m_value->m_formula.toUtf8(); if (m_currentSeries) { // FIXME: Is that correct or do we need to take the series // somehow into account to provide one cellRangeAddress // per series similar to valuesCellRangeAddress? // // FIXME: Handle VerticalValues and BubbleSizeValues if (!record->m_value->m_formula.isEmpty()) { if (record->m_value->m_type == KoChart::Value::TextOrValue || record->m_value->m_type == KoChart::Value::CellRange) { if (record->m_value->m_dataId == KoChart::Value::HorizontalValues) { m_currentSeries->m_valuesCellRangeAddress = record->m_value->m_formula; } else if (record->m_value->m_dataId == KoChart::Value::VerticalValues) { m_chart->m_verticalCellRangeAddress = record->m_value->m_formula; } // FIXME: We are ignoring the sheetname here but we // probably should handle the case where a // series is made from different sheets... QPair result = splitCellRange( record->m_value->m_formula ); m_chart->addRange(result.second); } } // FIXME: Is it ok to only accept the first or should we merge them somehow? if (!m_currentSeries->m_datasetValue.contains(record->m_value->m_dataId)) { m_currentSeries->m_datasetValue[record->m_value->m_dataId] = record->m_value; record->m_value = 0; // take over ownership } } } // This record specifies the data point or series that the formatting information that follows applies to. void ChartSubStreamHandler::handleDataFormat(DataFormatRecord *record) { if (!record) return; - DEBUG << "xi=" << record->xi() << " yi=" << record->yi() << " iss=" << record->iss() << std::endl; + DEBUG << "xi=" << record->xi() << "yi=" << record->yi() << "iss=" << record->iss(); if (record->yi() >= uint(m_chart->m_series.count())) { - DEBUG << "Invalid series index=" << record->yi() << std::endl; + DEBUG << "Invalid series index=" << record->yi(); m_currentObj = 0; // be sure to ignore all defined sub-elements return; } m_seriesStack.push(m_currentSeries); m_currentSeries = m_chart->m_series[record->yi()]; if ( record->xi() == 0xFFFF ) { // applies to series m_currentObj = m_currentSeries; } else { // applies to data-point KoChart::DataPoint *dataPoint = 0; if (record->xi() > uint(m_currentSeries->m_dataPoints.count())) { - DEBUG << "Invalid data-point index=" << record->yi() << std::endl; + DEBUG << "Invalid data-point index=" << record->yi(); } else if (record->xi() == uint(m_currentSeries->m_dataPoints.count())) { dataPoint = new KoChart::DataPoint(); m_currentSeries->m_dataPoints << dataPoint; } else { dataPoint = m_currentSeries->m_dataPoints[record->xi()]; } m_currentObj = dataPoint; } } void ChartSubStreamHandler::handleChart3DBarShape(Chart3DBarShapeRecord * record) { if (!record) return; - DEBUG << "riser=" << record->riser() << " taper=" << record->taper() << std::endl; + DEBUG << "riser=" << record->riser() << "taper=" << record->taper(); //TODO } // specifies that chart is rendered in 3d scene void ChartSubStreamHandler::handleChart3d(Chart3dRecord *record) { if (!record) return; - DEBUG << "anRot=" << record->anRot() << " anElev=" << record->anElev() << " pcDist=" << record->pcDist() << " pcHeight=" << record->pcHeight() << " pcDepth=" << record->pcDepth() << std::endl; + DEBUG << "anRot=" << record->anRot() << "anElev=" << record->anElev() << "pcDist=" << record->pcDist() + << "pcHeight=" << record->pcHeight() << "pcDepth=" << record->pcDepth(); m_chart->m_is3d = true; //TODO } void ChartSubStreamHandler::handleLineFormat(LineFormatRecord *record) { if (!record) return; - DEBUG << "lns=" << record->lns() << " we=" << record->we() << " fAxisOn=" << record->isFAxisOn() << std::endl; + DEBUG << "lns=" << record->lns() << "we=" << record->we() << "fAxisOn=" << record->isFAxisOn(); if (KoChart::Axis* axis = dynamic_cast(m_currentObj)) { KoChart::LineFormat format(KoChart::LineFormat::Style(record->lns()), KoChart::LineFormat::Thickness(record->we())); switch(m_axisId) { case 0x0000: // The axis line itself axis->m_format = format; break; case 0x0001: // The major gridlines along the axis axis->m_majorGridlines = KoChart::Axis::Gridline(format); break; case 0x0002: // The minor gridlines along the axis axis->m_minorGridlines = KoChart::Axis::Gridline(format); break; case 0x0003: // The walls or floor of a 3-D chart //TODO break; } m_axisId = -1; } else if ( dynamic_cast< KoChart::Legend* > ( m_currentObj ) ) { if ( record->lns() == 0x0005 ) m_chart->m_showLines = false; else if ( record->lns() == 0x0000 ) m_chart->m_showLines = true; // } else if ( dynamic_cast< KoChart::Text* > ( m_currentObj ) ) { // return; } else if ( KoChart::Series* series = dynamic_cast< KoChart::Series* > ( m_currentObj/*m_currentSeries*/ ) ) { //Q_ASSERT( false ); if ( !series->spPr ) series->spPr = new KoChart::ShapeProperties; m_chart->m_showLines = false; const int index = m_chart->m_series.indexOf( series ); const QColor color = record->isFAuto() ? globals()->workbook()->colorTable().at( 24 + index ) : QColor( record->red(), record->green(), record->blue() ); series->spPr->lineFill.setColor( color ); switch ( record->lns() ) { case( 0x0000 ): series->spPr->lineFill.setType( KoChart::Fill::Solid ); break; case( 0x0005 ): { series->spPr->lineFill.setType( KoChart::Fill::None ); // KoChart::ScatterImpl* impl = dynamic_cast< KoChart::ScatterImpl* >( m_chart->m_impl ); // if ( impl ) // { // if ( impl->style == KoChart::ScatterImpl::Marker || impl->style == KoChart::ScatterImpl::LineMarker ) // impl->style = KoChart::ScatterImpl::Marker; // else // impl->style = KoChart::ScatterImpl::None; // } } break; default: series->spPr->lineFill.setType( KoChart::Fill::None ); } //series->spPr->lineFill.type = KoChart::Fill::Solid; } else if ( dynamic_cast< KoChart::ChartImpl* > ( m_currentObj ) ) { Q_ASSERT( false ); } else if ( dynamic_cast< KoChart::Chart* > ( m_currentObj ) ) { - DEBUG << "color=" << QColor( record->red(), record->green(), record->blue() ).name() << "automatic=" << record->isFAuto() << std::endl; + DEBUG << "color=" << QColor( record->red(), record->green(), record->blue() ).name() << "automatic=" << record->isFAuto(); //m_chart->m_showLines = record->isFAuto(); Q_ASSERT( !dynamic_cast< KoChart::Series* > ( m_currentSeries ) ); } else if ( KoChart::DataPoint *dataPoint = dynamic_cast< KoChart::DataPoint* > ( m_currentObj ) ) { Q_UNUSED( dataPoint ); } } // This record specifies the patterns and colors used in a filled region of a chart. If this record is not // present in the sequence of records that conforms to the SS rule of the Chart Sheet Substream // ABNF, the patterns and colors used are specified by the default values of the fields of this record. void ChartSubStreamHandler::handleAreaFormat(AreaFormatRecord *record) { if (!record || !m_currentObj || m_currentObj->m_areaFormat) return; bool fill = record->fls() != 0x0000; QColor foreground, background; if ( record->isFAuto() ) { int index = 0; if ( KoChart::Series* series = dynamic_cast< KoChart::Series* > ( m_currentObj ) ) { index = m_chart->m_series.indexOf( series ) % 8; Q_ASSERT(index >= 0); foreground = globals()->workbook()->colorTable().at( 16 + index ); } else if ( KoChart::DataPoint *dataPoint = dynamic_cast< KoChart::DataPoint* > ( m_currentObj ) ) { index = m_currentSeries->m_dataPoints.indexOf( dataPoint ) % 8; Q_ASSERT(index >= 0); foreground = globals()->workbook()->colorTable().at( 16 + index ); } else { // The specs say that the default background-color is white but it is not clear // what automatic means for the case of the PlotArea. So, let's just not use any // color in that case what means the chart's color will be used (PlotArea is // transparent). That is probably not correct and we would need to just use // white as color but since so far I did not found any test-doc that indicates // that we are just going with transparent for now. fill = false; //foreground = background = QColor("#FFFFFF"); } //background = QColor("#FFFFFF"); } else { foreground = QColor(record->redForeground(), record->greenForeground(), record->blueForeground()); background = QColor(record->redBackground(), record->greenBackground(), record->blueBackground()); } - DEBUG << "foreground=" << foreground.name() << " background=" << background.name() << " fillStyle=" << record->fls() << " fAuto=" << record->isFAuto() << std::endl; + DEBUG << "foreground=" << foreground.name() << "background=" << background.name() + << "fillStyle=" << record->fls() << "fAuto=" << record->isFAuto(); m_currentObj->m_areaFormat = new KoChart::AreaFormat(foreground, background, fill); if ( KoChart::Series* series = dynamic_cast< KoChart::Series* > ( m_currentObj ) ) { if ( !series->spPr ) series->spPr = new KoChart::ShapeProperties; series->spPr->areaFill.setColor( foreground ); } //else if ( KoChart::PlotArea* plotArea = dynamic_cast< KoChart::PlotArea* > ( m_currentObj ) ) { //} //else if ( KoChart::DataPoint *dataPoint = dynamic_cast< KoChart::DataPoint* > ( m_currentObj ) ) { //} } void ChartSubStreamHandler::handlePieFormat(PieFormatRecord *record) { if (!record) return; if (!m_currentSeries) return; - DEBUG << "pcExplode=" << record->pcExplode() << std::endl; + DEBUG << "pcExplode=" << record->pcExplode(); m_currentSeries->m_datasetFormat << new KoChart::PieFormat(record->pcExplode()); } void ChartSubStreamHandler::handleMarkerFormat(MarkerFormatRecord *record) { if (!record) return; - DEBUG << "fAuto=" << record->fAuto() << " imk=" << record->imk() << std::endl; + DEBUG << "fAuto=" << record->fAuto() << "imk=" << record->imk(); const bool legend = dynamic_cast< KoChart::Legend* >( m_currentObj ); if ( m_disableAutoMarker && legend ) return; m_chart->m_markerType = KoChart::NoMarker; if ( KoChart::DataPoint *dataPoint = dynamic_cast(m_currentObj) ) { Q_UNUSED(dataPoint); } else if ( KoChart::Series *series = dynamic_cast(m_currentObj) ) { if ( !series->spPr ) series->spPr = new KoChart::ShapeProperties; const int index = m_chart->m_series.indexOf( series ) % 8; if ( record->fAuto() ) { if ( !m_disableAutoMarker ) m_chart->m_markerType = KoChart::AutoMarker; if ( !series->spPr->areaFill.valid ) series->spPr->areaFill.setColor( globals()->workbook()->colorTable().at( 24 + index ) ); switch ( index ) { case( 0x0000 ): series->m_markerType = KoChart::SquareMarker; break; case( 0x0001 ): series->m_markerType = KoChart::DiamondMarker; break; case( 0x0002 ): series->m_markerType = KoChart::SymbolXMarker; break; case( 0x0003 ): series->m_markerType = KoChart::SquareMarker; break; case( 0x0004 ): series->m_markerType = KoChart::DashMarker; break; case( 0x0005 ): series->m_markerType = KoChart::DashMarker; break; case( 0x0006 ): series->m_markerType = KoChart::CircleMarker; break; case( 0x0007 ): series->m_markerType = KoChart::PlusMarker; break; default: series->m_markerType = KoChart::SquareMarker; break; } } else { if ( series ) { switch ( record->imk() ) { case( 0x0000 ): series->m_markerType = KoChart::NoMarker; m_disableAutoMarker = true; break; case( 0x0001 ): series->m_markerType = KoChart::SquareMarker; break; case( 0x0002 ): series->m_markerType = KoChart::DiamondMarker; break; case( 0x0003 ): series->m_markerType = KoChart::SymbolXMarker; break; case( 0x0004 ): series->m_markerType = KoChart::SquareMarker; break; case( 0x0005 ): series->m_markerType = KoChart::DashMarker; break; case( 0x0006 ): series->m_markerType = KoChart::DashMarker; break; case( 0x0007 ): series->m_markerType = KoChart::CircleMarker; break; case( 0x0008 ): series->m_markerType = KoChart::PlusMarker; break; default: series->m_markerType = KoChart::SquareMarker; break; } if ( !series->spPr->areaFill.valid ) series->spPr->areaFill.setColor( QColor( record->redBackground(), record->greenBackground(), record->blueBackground() ) ); } } } } void ChartSubStreamHandler::handleChartFormat(ChartFormatRecord *record) { if (!record) return; - DEBUG << "fVaried=" << record->isFVaried() << std::endl; + DEBUG << "fVaried=" << record->isFVaried(); //TODO } void ChartSubStreamHandler::handleGelFrame(GelFrameRecord *record) { if (!record) return; - DEBUG << std::endl; + DEBUG << ""; //TODO } // specifies the chartgroup for the current series void ChartSubStreamHandler::handleSerToCrt(SerToCrtRecord *record) { if (!record) return; - DEBUG << "id=" << record->identifier() << std::endl; + DEBUG << "id=" << record->identifier(); } // properties void ChartSubStreamHandler::handleShtProps(ShtPropsRecord *record) { if (!record) return; - DEBUG << "fManSerAlloc=" << record->isFManSerAlloc() << " fPlotVisOnly=" << record->isFPlotVisOnly() << " fNotSizeWIth=" << record->isFNotSizeWIth() << " fManPlotArea=" << record->isFManPlotArea() << " fAlwaysAutoPlotArea=" << record->isFAlwaysAutoPlotArea() << " mdBlank=" << record->mdBlank() << std::endl; + DEBUG << "fManSerAlloc=" << record->isFManSerAlloc() << "fPlotVisOnly=" << record->isFPlotVisOnly() + << "fNotSizeWIth=" << record->isFNotSizeWIth() << "fManPlotArea=" << record->isFManPlotArea() + << "fAlwaysAutoPlotArea=" << record->isFAlwaysAutoPlotArea() << "mdBlank=" << record->mdBlank(); //TODO } // Specifies the text elements that are formatted using the information specified by the Text record // immediately following this record. The identifier is one of; // * 0x0000 Format all Text records in the chart group where fShowPercent is equal to 0 or // fShowValue is equal to 0. // * 0x0001 Format all Text records in the chart group where fShowPercent is equal to 1 or // fShowValue is equal to 1. // * 0x0002 Format all Text records in the chart where the value of fScalable of the associated // FontInfo structure is equal to 0. // * 0x0003 Format all Text records in the chart where the value of fScalable of the associated // FontInfo structure is equal to 1. void ChartSubStreamHandler::handleDefaultText(DefaultTextRecord *record) { if (!record) return; - DEBUG << "id=" << record->identifier() << std::endl; + DEBUG << "id=" << record->identifier(); m_defaultTextId = record->identifier(); } // specifies the properties of an attached label void ChartSubStreamHandler::handleText(TextRecord *record) { if (!record || record->isFDeleted()) return; DEBUG << "at=" << record->at() - << " vat=" << record->vat() - << " x=" << record->x() - << " y=" << record->y() - << " dx=" << record->dx() - << " dy=" << record->dy() - << " fShowKey=" << record->isFShowKey() - << " fShowValue=" << record->isFShowValue() << std::endl; + << "vat=" << record->vat() + << "x=" << record->x() + << "y=" << record->y() + << "dx=" << record->dx() + << "dy=" << record->dy() + << "fShowKey=" << record->isFShowKey() + << "fShowValue=" << record->isFShowValue(); m_currentObj = new KoChart::Text; if (m_defaultTextId >= 0) { //m_defaultObjects[m_currentObj] = m_defaultTextId; m_defaultTextId = -1; } } void ChartSubStreamHandler::handleSeriesText(SeriesTextRecord* record) { if (!record || !m_currentSeries) return; - DEBUG << "text=" << record->text() << std::endl; + DEBUG << "text=" << record->text(); if (KoChart::Text *t = dynamic_cast(m_currentObj)) { t->m_text = record->text(); } else if (KoChart::Legend *l = dynamic_cast(m_currentObj)) { //TODO Q_UNUSED(l); } else if (KoChart::Series* series = dynamic_cast(m_currentObj)) { series->m_texts << new KoChart::Text(record->text()); } else { //m_currentSeries->m_texts << new KoChart::Text(string(record->text())); } } void ChartSubStreamHandler::handlePos(PosRecord *record) { if (!record) return; - DEBUG << "mdTopLt=" << record->mdTopLt() << " mdBotRt=" << record->mdBotRt() << " x1=" << record->x1() << " y1=" << record->y1() << " x2=" << record->x2() << " y2=" << record->y2() << std::endl; + DEBUG << "mdTopLt=" << record->mdTopLt() << "mdBotRt=" << record->mdBotRt() + << "x1=" << record->x1() << "y1=" << record->y1() << "x2=" << record->x2() << "y2=" << record->y2(); if (m_currentObj) { m_currentObj->m_mdBotRt = record->mdBotRt(); m_currentObj->m_mdTopLt = record->mdTopLt(); m_currentObj->m_x1 = record->x1(); m_currentObj->m_y1 = record->y1(); m_currentObj->m_x2 = record->x2(); m_currentObj->m_y2 = record->y2(); } } void ChartSubStreamHandler::handleFontX(FontXRecord *record) { if (!record) return; - DEBUG << std::endl; + DEBUG << ""; //TODO } void ChartSubStreamHandler::handlePlotGrowth(PlotGrowthRecord *record) { if (!record) return; - DEBUG << std::endl; + DEBUG << ""; //TODO } void ChartSubStreamHandler::handleLegend(LegendRecord *record) { if (!record) return; - DEBUG << "fAutoPosition=" << record->isFAutoPosition() << " fAutoPosX=" << record->isFAutoPosX() << " fAutoPosY=" << record->isFAutoPosY() << " fVert=" << record->isFVert() << " fWasDataTable=" << record->isFWasDataTable() << std::endl; + DEBUG << "fAutoPosition=" << record->isFAutoPosition() << "fAutoPosX=" << record->isFAutoPosX() + << "fAutoPosY=" << record->isFAutoPosY() << "fVert=" << record->isFVert() + << "fWasDataTable=" << record->isFWasDataTable(); m_currentObj = m_chart->m_legend = new KoChart::Legend(); } // specifies the number of axis groups on the chart. // cAxes specifies the number of axis groups on the chart. // 0x0001 A single primary axis group is present // 0x0002 Both a primary axis group and a secondary axis group are present void ChartSubStreamHandler::handleAxesUsed(AxesUsedRecord *record) { if (!record) return; - DEBUG << "cAxes=" << record->cAxes() << std::endl; + DEBUG << "cAxes=" << record->cAxes(); //TODO } // specifies properties of an axis group. // iax specifies whether the axis group is primary or secondary. // 0x0000 Axis group is primary. // 0x0001 Axis group is secondary. void ChartSubStreamHandler::handleAxisParent(AxisParentRecord *record) { if (!record) return; - DEBUG << "iax=" << record->iax() << std::endl; + DEBUG << "iax=" << record->iax(); //TODO } // specifies that the chartgroup is a pie chart void ChartSubStreamHandler::handlePie(PieRecord *record) { if (!record || m_chart->m_impl) return; - DEBUG << "anStart=" << record->anStart() << " pcDonut=" << record->pcDonut() << std::endl; + DEBUG << "anStart=" << record->anStart() << "pcDonut=" << record->pcDonut(); if (record->pcDonut() > 0) m_chart->m_impl = new KoChart::RingImpl(record->anStart(), record->pcDonut()); else m_chart->m_impl = new KoChart::PieImpl(record->anStart()); } // specifies that the chartgroup is a bar chart void ChartSubStreamHandler::handleBar(BarRecord *record) { if (!record || m_chart->m_impl) return; - DEBUG << "pcOverlap=" << record->pcOverlap() << " pcGap=" << record->pcGap() << " fTranspose=" << record->isFTranspose() << " fStacked=" << record->isFStacked() << " f100=" << record->isF100() << std::endl; + DEBUG << "pcOverlap=" << record->pcOverlap() << "pcGap=" << record->pcGap() << "fTranspose=" << record->isFTranspose() + << "fStacked=" << record->isFStacked() << "f100=" << record->isF100(); m_chart->m_impl = new KoChart::BarImpl(); m_chart->m_transpose = record->isFTranspose(); m_chart->m_stacked = record->isFStacked(); m_chart->m_f100 = record->isF100(); } // specifies that the chartgroup is a area chart void ChartSubStreamHandler::handleArea(AreaRecord* record) { if (!record || m_chart->m_impl) return; - DEBUG << std::endl; + DEBUG << ""; m_chart->m_impl = new KoChart::AreaImpl(); m_chart->m_stacked = record->isFStacked(); m_chart->m_f100 = record->isF100(); } // specifies that the chartgroup is a line chart void ChartSubStreamHandler::handleLine(LineRecord* record) { if (!record || m_chart->m_impl) return; - DEBUG << std::endl; + DEBUG << ""; m_chart->m_impl = new KoChart::LineImpl(); m_chart->m_stacked = record->isFStacked(); m_chart->m_f100 = record->isF100(); if ( !m_disableAutoMarker ) m_chart->m_markerType = KoChart::AutoMarker; // Q_FOREACH( const KoChart::Series* const series, m_chart->m_series ) // { // if ( series->m_markerType == KoChart::Series::None ) // m_chart->m_markerType = KoChart::NoMarker; // } } // specifies that the chartgroup is a scatter chart void ChartSubStreamHandler::handleScatter(ScatterRecord* record) { if (!record || m_chart->m_impl) return; - DEBUG << std::endl; + DEBUG << ""; if (record->isFBubbles()) m_chart->m_impl = new KoChart::BubbleImpl(KoChart::BubbleImpl::SizeType(record->wBubbleSize()), record->pcBubbleSizeRatio(), record->isFShowNegBubbles()); else m_chart->m_impl = new KoChart::ScatterImpl(); // For scatter charts, one element shall exist. Its table:cell-range-address // attribute references the x coordinate values for the scatter chart. // For bubble charts, two elements shall exist. The values for the y-coordinates are // given by the first element. The values for the x-coordinates are given by the // second element. QString x, y; if (m_currentSeries->m_datasetValue.contains(KoChart::Value::VerticalValues)) x = m_currentSeries->m_datasetValue[KoChart::Value::VerticalValues]->m_formula; if (m_currentSeries->m_datasetValue.contains(KoChart::Value::HorizontalValues)) y = m_currentSeries->m_datasetValue[KoChart::Value::HorizontalValues]->m_formula; foreach(KoChart::Series *series, m_chart->m_series) { Q_ASSERT(series->m_domainValuesCellRangeAddress.isEmpty()); // what should we do if that happens? if (!series->m_domainValuesCellRangeAddress.isEmpty()) continue; if (record->isFBubbles()) { series->m_domainValuesCellRangeAddress << y << x; if (series->m_datasetValue.contains(KoChart::Value::BubbleSizeValues)) series->m_valuesCellRangeAddress = series->m_datasetValue[KoChart::Value::BubbleSizeValues]->m_formula; //m_chart->m_verticalCellRangeAddress = series->m_valuesCellRangeAddress; } else { series->m_domainValuesCellRangeAddress << x; } } if ( !m_disableAutoMarker ) { m_chart->m_markerType = KoChart::AutoMarker; } // KoChart::ScatterImpl* impl = dynamic_cast< KoChart::ScatterImpl* >( m_chart->m_impl ); // if ( impl ) // impl->style = KoChart::ScatterImpl::Marker; } // specifies that the chartgroup is a radar chart void ChartSubStreamHandler::handleRadar(RadarRecord *record) { if (!record || m_chart->m_impl) return; - DEBUG << std::endl; + DEBUG << ""; m_chart->m_impl = new KoChart::RadarImpl(false); m_chart->m_markerType = KoChart::AutoMarker; } // specifies that the chartgroup is a filled radar chart void ChartSubStreamHandler::handleRadarArea(RadarAreaRecord *record) { if (!record || m_chart->m_impl) return; - DEBUG << std::endl; + DEBUG << ""; m_chart->m_impl = new KoChart::RadarImpl(true); } // specifies that the chartgroup is a surface chart void ChartSubStreamHandler::handleSurf(SurfRecord *record) { if (!record || m_chart->m_impl) return; - DEBUG << std::endl; + DEBUG << ""; m_chart->m_impl = new KoChart::SurfaceImpl(record->isFFillSurface()); } void ChartSubStreamHandler::handleAxis(AxisRecord* record) { if (!record) return; - DEBUG << "wType=" << record->wType() << std::endl; + DEBUG << "wType=" << record->wType(); KoChart::Axis* axis = new KoChart::Axis(KoChart::Axis::Type(record->wType())); m_chart->m_axes << axis; m_currentObj = axis; } // This record specifies which part of the axis is specified by the LineFormat record that follows. void ChartSubStreamHandler::handleAxisLine(AxisLineRecord* record) { if (!record) return; - DEBUG << "identifier=" << record->identifier() << std::endl; + DEBUG << "identifier=" << record->identifier(); m_axisId = record->identifier(); } // This record specifies the attributes of the axis label. // * wOffset: Specifies the distance between the axis and axis label. It contains the // offset as a percentage of the default distance. The default distance is equal to 1/3 the // height of the font calculated in pixels. MUST be a value greater than or equal to 0 (0%) // and less than or equal to 1000 (1000%). // * at: An unsigned integer that specifies the alignment of the axis label. MUST be // a value from the following table: // * 0x0001: Top-aligned if the trot field of the Text record of the axis is not equal to 0. // Left-aligned if the iReadingOrder field of the Text record of the axis specifies left // to-right reading order; otherwise, right-aligned. // * 0x0002: Center-alignment // * 0x0003: Bottom-aligned if the trot field of the Text record of the axis is not equal to 0. // Right-aligned if the iReadingOrder field of the Text record of the axis specifies // left-to-right reading order; otherwise, left-aligned. // * cAutoCatLabelReal: Specifies whether the number of categories (3). // between axis labels is set to the default value. MUST be a value from the following table: // * 0: The value is set to catLabel field as specified by CatSerRange record. // * 1: The value is set to the default value. The number of category (3) labels is // automatically calculated by the application based on the data in the chart. void ChartSubStreamHandler::handleCatLab(CatLabRecord* record) { if (!record) return; void handleCatLab(CatLabRecord* record); - DEBUG << "wOffset=" << record->wOffset() << " at=" << record->at() << " cAutoCatLabelReal=" << record->cAutoCatLabelReal() << std::endl; + DEBUG << "wOffset=" << record->wOffset() << "at=" << record->at() << "cAutoCatLabelReal=" << record->cAutoCatLabelReal(); //TODO } // Type of data contained in the Number records following. void ChartSubStreamHandler::handleSIIndex(SIIndexRecord *record) { if (!record) return; - DEBUG << "numIndex=" << record->numIndex() << std::endl; + DEBUG << "numIndex=" << record->numIndex(); Q_ASSERT(!m_internalDataCache); m_internalDataCache = new InternalDataCache(this, record->numIndex()); } void ChartSubStreamHandler::handleMsoDrawing(MsoDrawingRecord* record) { if (!record) return; - DEBUG << std::endl; + DEBUG << ""; //TODO } void ChartSubStreamHandler::handleShapePropsStream(ShapePropsStreamRecord* record) { if (!record) return; - DEBUG << "wObjContext=" << record->wObjContext() << "rgbLength=" << record->rgb().length() << " rgbString=" << record->rgb() << std::endl; + DEBUG << "wObjContext=" << record->wObjContext() << "rgbLength=" << record->rgb().length() << "rgbString=" << record->rgb(); //TODO } void ChartSubStreamHandler::handleTextPropsStream(TextPropsStreamRecord* record) { if (!record) return; - DEBUG << "rgbLength=" << record->rgb().length() << " rgbString=" << record->rgb() << std::endl; + DEBUG << "rgbLength=" << record->rgb().length() << "rgbString=" << record->rgb(); //TODO } void ChartSubStreamHandler::handleObjectLink(ObjectLinkRecord *record) { if (!record) return; - DEBUG << "wLinkObj=" << record->wLinkObj() << " wLinkVar1=" << record->wLinkVar1() << " wLinkVar2=" << record->wLinkVar2() << std::endl; + DEBUG << "wLinkObj=" << record->wLinkObj() << "wLinkVar1=" << record->wLinkVar1() << "wLinkVar2=" << record->wLinkVar2(); KoChart::Text *t = dynamic_cast(m_currentObj); if (!t) return; // if the current object is not text, just do nothing // Q_ASSERT(t); // if the current object is not text, terminate switch(record->wLinkObj()) { case ObjectLinkRecord::EntireChart: { m_chart->m_texts << t; } break; case ObjectLinkRecord::ValueOrVerticalAxis: //TODO break; case ObjectLinkRecord::CategoryOrHorizontalAxis: //TODO break; case ObjectLinkRecord::SeriesOrDatapoints: { if ((int)record->wLinkVar1() >= m_chart->m_series.count()) return; //KoChart::Series* series = m_chart->m_series[ record->wLinkVar1() ]; if (record->wLinkVar2() == 0xFFFF) { //TODO series->texts << t; } else { //TODO series->category[record->wLinkVar2()]; } } break; case ObjectLinkRecord::SeriesAxis: break; //TODO case ObjectLinkRecord::DisplayUnitsLabelsOfAxis: break; //TODO } } // This empty record specifies that the Frame record that immediately // follows this record specifies properties of the plot area. void ChartSubStreamHandler::handlePlotArea(PlotAreaRecord *record) { if (!record) return; - DEBUG << std::endl; + DEBUG << ""; m_currentObj = m_chart->m_plotArea = new KoChart::PlotArea(); } void ChartSubStreamHandler::handleValueRange(ValueRangeRecord *record) { if (!record) return; - DEBUG << "fAutoMin=" << record->isFAutoMin() << " fAutoMax=" << record->isFAutoMax() << " fAutoMajor=" << record->isFAutoMajor() << " fAutoMinor=" << record->isFAutoMinor() << " fAutoCross=" << record->isFAutoCross() << " fLog=" << record->isFLog() << " fReversed=" << record->isFReversed() << " fMaxCross=" << record->isFMaxCross() << std::endl; + DEBUG << "fAutoMin=" << record->isFAutoMin() << "fAutoMax=" << record->isFAutoMax() << "fAutoMajor=" << record->isFAutoMajor() + << "fAutoMinor=" << record->isFAutoMinor() << "fAutoCross=" << record->isFAutoCross() << "fLog=" << record->isFLog() + << "fReversed=" << record->isFReversed() << "fMaxCross=" << record->isFMaxCross(); if ( KoChart::Axis *axis = dynamic_cast< KoChart::Axis* > ( m_currentObj ) ) { axis->m_reversed = record->isFReversed(); axis->m_logarithmic = record->isFLog(); axis->m_autoMinimum = record->isFAutoMin(); axis->m_autoMaximum = record->isFAutoMax(); axis->m_minimum = record->isFAutoMin() ? 0 : record->numMin(); axis->m_maximum = record->isFAutoMax() ? 0 : record->numMax(); } } void ChartSubStreamHandler::handleTick(TickRecord *record) { if (!record) return; DEBUG << "tktMajor=" << record->tktMajor() - << " tktMinor=" << record->tktMinor() - << " tlt=" << record->tlt() << std::endl; + << "tktMinor=" << record->tktMinor() + << "tlt=" << record->tlt(); //TODO } void ChartSubStreamHandler::handleAxcExt(AxcExtRecord *record) { if (!record) return; DEBUG << "fAutoMin=" << record->isFAutoMin() - << " fAutoMax=" << record->isFAutoMax() - << " fAutoMajor=" << record->isFAutoMajor() - << " fAutoMinor=" << record->isFAutoMinor() - << " fDateAxis=" << record->isFDateAxis() - << " fAutoBase=" << record->isFAutoBase() - << " fAutoCross=" << record->isFAutoCross() - << " fAutoDate=" << record->isFAutoDate() << std::endl; + << "fAutoMax=" << record->isFAutoMax() + << "fAutoMajor=" << record->isFAutoMajor() + << "fAutoMinor=" << record->isFAutoMinor() + << "fDateAxis=" << record->isFDateAxis() + << "fAutoBase=" << record->isFAutoBase() + << "fAutoCross=" << record->isFAutoCross() + << "fAutoDate=" << record->isFAutoDate(); //TODO } // Specifies the presence of drop lines, high-low lines, series lines or leader lines on the chart group. This record is followed by a LineFormat record which specifies the format of the lines. void ChartSubStreamHandler::handleCrtLine(CrtLineRecord *record) { if (!record) return; - DEBUG << "identifier=" << record->identifier() << std::endl; + DEBUG << "identifier=" << record->identifier(); switch(record->identifier()) { case 0x0000: // Drop lines below the data points of line, area, and stock chart groups. //TODO break; case 0x0001: // High-Low lines around the data points of line and stock chart groups. if (KoChart::LineImpl* line = dynamic_cast(m_chart->m_impl)) { // It seems that a stockchart is always a linechart with a CrtLine record that defines High-Low lines. delete line; m_chart->m_impl = new KoChart::StockImpl(); } break; case 0x0002: // Series lines connecting data points of stacked column and bar chart groups, and the primary pie to the secondary bar/pie of bar of pie and pie of pie chart groups. //TODO break; case 0x0003: // Leader lines with non-default formatting connecting data labels to the data point of pie and pie of pie chart groups. //TODO break; } } void ChartSubStreamHandler::handleCatSerRange(CatSerRangeRecord *record) { if (!record) return; - DEBUG << "fBetween=" << record->isFBetween() << " fMaxCross=" << record->isFMaxCross() << " fReverse=" << record->isFReverse() << std::endl; + DEBUG << "fBetween=" << record->isFBetween() << "fMaxCross=" << record->isFMaxCross() << "fReverse=" << record->isFReverse(); //TODO } void ChartSubStreamHandler::handleAttachedLabel(AttachedLabelRecord *record) { if (!record) return; - DEBUG << "fShowValue=" << record->isFShowValue() << " fShowPercent=" << record->isFShowPercent() << " fShowLabelAndPerc=" << record->isFShowLabelAndPerc() << " fShowLabel=" << record->isFShowLabel() << " fShowBubbleSizes=" << record->isFShowBubbleSizes() << " fShowSeriesName=" << record->isFShowSeriesName() << std::endl; + DEBUG << "fShowValue=" << record->isFShowValue() << "fShowPercent=" << record->isFShowPercent() + << "fShowLabelAndPerc=" << record->isFShowLabelAndPerc() << "fShowLabel=" << record->isFShowLabel() + << "fShowBubbleSizes=" << record->isFShowBubbleSizes() << "fShowSeriesName=" << record->isFShowSeriesName(); if (m_currentSeries) { m_currentSeries->m_showDataLabelValues = record->isFShowValue(); m_currentSeries->m_showDataLabelPercent = record->isFShowPercent() || record->isFShowLabelAndPerc(); m_currentSeries->m_showDataLabelCategory = record->isFShowLabel() || record->isFShowLabelAndPerc(); m_currentSeries->m_showDataLabelSeries = record->isFShowSeriesName(); } } void ChartSubStreamHandler::handleDataLabelExtContents(DataLabelExtContentsRecord *record) { if (!record) return; - DEBUG << "rt=" << record->rt() << " grbitFrt=" << record->grbitFrt() << " fSerName=" << record->isFSerName() << " fCatName=" << record->isFCatName() << " fValue=" << record->isFValue() << " fPercent=" << record->isFPercent() << " fBubSize=" << record->isFBubSize() << std::endl; + DEBUG << "rt=" << record->rt() << "grbitFrt=" << record->grbitFrt() << "fSerName=" << record->isFSerName() + << "fCatName=" << record->isFCatName() << "fValue=" << record->isFValue() << "fPercent=" << record->isFPercent() + << "fBubSize=" << record->isFBubSize(); //TODO } void ChartSubStreamHandler::handleXF(XFRecord *record) { if (!record) return; - DEBUG << "formatIndex=" << record->formatIndex() << std::endl; + DEBUG << "formatIndex=" << record->formatIndex(); m_xfTable.push_back(*record); } // This record specifies a label on the category (3) axis for each series. void ChartSubStreamHandler::handleLabel(LabelRecord *record) { if (!record) return; - DEBUG << "row=" << record->row() << " column=" << record->column() << " xfIndex=" << record->xfIndex() << " label=" << record->label().toUtf8().constData() << std::endl; + DEBUG << "row=" << record->row() << "column=" << record->column() << "xfIndex=" << record->xfIndex() + << "label=" << record->label().toUtf8(); //TODO } // This record specifies the number format to use for the text on an axis. void ChartSubStreamHandler::handleIFmt(IFmtRecord *record) { if (!record) return; //Q_ASSERT(record->ifmt() >= 0x00A4 && record->ifmt() <= 0x0188); const Format *f = globals()->convertedFormat(record->ifmt()); if (!f) return; - DEBUG << "ifmt=" << record->ifmt() << " valueFormat=" << qPrintable(f->valueFormat()) << std::endl; + DEBUG << "ifmt=" << record->ifmt() << "valueFormat=" << qPrintable(f->valueFormat()); // if (!f->valueFormat().isEmpty() && f->valueFormat() != "General") { // Q_ASSERT(false); // } //TODO } void ChartSubStreamHandler::handleCrtMlFrt(CrtMlFrtRecord *record) { if (!record) return; std::stringstream out; - record->dump(out); - DEBUG << "xmlTkParent=" << QString::number(record->xmlTkParent(), 16) << " tokens=" << out.str() << std::endl; + record->dump(out); + DEBUG << "xmlTkParent=" << QString::number(record->xmlTkParent(), 16) << "tokens=" << QString::fromStdString(out.str()); //TODO } diff --git a/filters/sheets/excel/sidewinder/excel.cpp b/filters/sheets/excel/sidewinder/excel.cpp index 19db6a1e955..72515664a22 100644 --- a/filters/sheets/excel/sidewinder/excel.cpp +++ b/filters/sheets/excel/sidewinder/excel.cpp @@ -1,2749 +1,2746 @@ /* Swinder - Portable library for spreadsheet Copyright (C) 2003-2005 Ariya Hidayat Copyright (C) 2006 Marijn Kruisselbrink Copyright (C) 2009,2010 Sebastian Sauer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA */ #include "excel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "swinder.h" #include "utils.h" #include "globalssubstreamhandler.h" #include "worksheetsubstreamhandler.h" #include "chartsubstreamhandler.h" #include "XlsRecordOutputStream.h" //#define SWINDER_XLS2RAW using namespace Swinder; //============================================= // EString //============================================= class EString::Private { public: bool unicode; bool richText; QString str; unsigned size; std::map formatRuns; }; EString::EString() { d = new EString::Private(); d->unicode = false; d->richText = false; d->size = 0; } EString::EString(const EString& es) { d = new EString::Private(); operator=(es); } EString& EString::operator=(const EString & es) { d->unicode = es.d->unicode; d->richText = es.d->richText; d->size = es.d->size; d->str = es.d->str; return *this; } EString::~EString() { delete d; } bool EString::unicode() const { return d->unicode; } void EString::setUnicode(bool u) { d->unicode = u; } bool EString::richText() const { return d->richText; } void EString::setRichText(bool r) { d->richText = r; } QString EString::str() const { return d->str; } void EString::setStr(const QString& str) { d->str = str; } std::map EString::formatRuns() const { return d->formatRuns; } void EString::setFormatRuns(const std::map& formatRuns) { d->formatRuns = formatRuns; } unsigned EString::size() const { return d->size; } void EString::setSize(unsigned s) { d->size = s; } // FIXME use maxsize for sanity check EString EString::fromUnicodeString(const void* p, bool longString, unsigned /* maxsize */, const unsigned* continuePositions, unsigned continuePositionsOffset) { const unsigned char* data = (const unsigned char*) p; QString str; unsigned offset = longString ? 2 : 1; unsigned len = longString ? readU16(data) : data[0]; unsigned char flag = data[ offset ]; offset++; // for flag (1 byte) bool unicode = flag & 0x01; bool asianPhonetics = flag & 0x04; bool richText = flag & 0x08; unsigned formatRuns = 0; unsigned asianPhoneticsSize = 0; if (richText) { formatRuns = readU16(data + offset); offset += 2; } if (asianPhonetics) { asianPhoneticsSize = readU32(data + offset); offset += 4; } // find out total bytes used in this string unsigned size = offset; if (richText) size += (formatRuns * 4); if (asianPhonetics) size += asianPhoneticsSize; str.clear(); for (unsigned k = 0; k < len; ++k) { unsigned uchar; if (unicode) { uchar = readU16(data + offset); offset += 2; size += 2; } else { uchar = data[offset++]; size++; } str.append(QString(QChar(uchar))); if (continuePositions && offset == *continuePositions - continuePositionsOffset && k < len - 1) { unicode = data[offset] & 1; size++; offset++; continuePositions++; } } // read format runs std::map formatRunsMap; for (unsigned k = 0; k < formatRuns; ++k) { unsigned index = readU16(data + offset); unsigned font = readU16(data + offset + 2); if (index < len) formatRunsMap[index] = font; offset += 4; } EString result; result.setUnicode(unicode); result.setRichText(richText); result.setSize(size); result.setStr(str); result.setFormatRuns(formatRunsMap); return result; } // FIXME use maxsize for sanity check EString EString::fromByteString(const void* p, bool longString, unsigned /* maxsize */) { const unsigned char* data = (const unsigned char*) p; QString str; unsigned offset = longString ? 2 : 1; unsigned len = longString ? readU16(data) : data[0]; char* buffer = new char[ len+1 ]; memcpy(buffer, data + offset, len); buffer[ len ] = 0; str = QString(buffer); delete[] buffer; unsigned size = offset + len; EString result; result.setUnicode(false); result.setRichText(false); result.setSize(size); result.setStr(str); return result; } // why different ? see BoundSheetRecord EString EString::fromSheetName(const void* p, unsigned datasize) { const unsigned char* data = (const unsigned char*) p; QString str; bool richText = false; // unsigned formatRuns = 0; unsigned len = data[0]; unsigned flag = data[1]; bool unicode = flag & 1; if (len > datasize - 2) len = datasize - 2; if (len == 0) return EString(); unsigned offset = 2; if (!unicode) { char* buffer = new char[ len+1 ]; memcpy(buffer, data + offset, len); buffer[ len ] = 0; str = QString(buffer); delete[] buffer; } else { for (unsigned k = 0; k < len; ++k) { unsigned uchar = readU16(data + offset + k * 2); str.append(QString(QChar(uchar))); } } EString result; result.setUnicode(unicode); result.setRichText(richText); result.setSize(datasize); result.setStr(str); return result; } //============================================= // CellInfo //============================================= class CellInfo::Private { public: unsigned row; unsigned column; unsigned xfIndex; }; CellInfo::CellInfo() { info = new CellInfo::Private(); info->row = 0; info->column = 0; info->xfIndex = 0; } CellInfo::~CellInfo() { delete info; } unsigned CellInfo::row() const { return info->row; } void CellInfo::setRow(unsigned r) { info->row = r; } unsigned CellInfo::column() const { return info->column; } void CellInfo::setColumn(unsigned c) { info->column = c; } unsigned CellInfo::xfIndex() const { return info->xfIndex; } void CellInfo::setXfIndex(unsigned i) { info->xfIndex = i; } //============================================= // ColumnSpanInfo //============================================= class ColumnSpanInfo::Private { public: unsigned firstColumn; unsigned lastColumn; }; ColumnSpanInfo::ColumnSpanInfo() { spaninfo = new ColumnSpanInfo::Private(); spaninfo->firstColumn = 0; spaninfo->lastColumn = 0; } ColumnSpanInfo::~ColumnSpanInfo() { delete spaninfo; } unsigned ColumnSpanInfo::firstColumn() const { return spaninfo->firstColumn; } void ColumnSpanInfo::setFirstColumn(unsigned c) { spaninfo->firstColumn = c; } unsigned ColumnSpanInfo::lastColumn() const { return spaninfo->lastColumn; } void ColumnSpanInfo::setLastColumn(unsigned c) { spaninfo->lastColumn = c; } // ========== EXTERNBOOK ========== const unsigned int ExternBookRecord::id = 0x01ae; class ExternBookRecord::Private { public: unsigned sheetCount; QString name; }; ExternBookRecord::ExternBookRecord(Workbook *book) : Record(book), d(new Private) { d->sheetCount = 0; } ExternBookRecord::~ExternBookRecord() { delete d; } QString ExternBookRecord::bookName() const { return d->name; } void ExternBookRecord::setData(unsigned size, const unsigned char* data, const unsigned int*) { if (size < 4) return; d->sheetCount = readU16(data); if (data[2] == 0x01 && data[3] == 0x04) { // self-referencing supporting link d->name = QString("\004"); } else if (data[2] == 0x01 && data[3] == ':') { // add-in referencing type of supporting link d->name = QString(":"); } else { d->name = EString::fromUnicodeString(data + 2, true, size - 2).str(); if (d->name.length() > 2 && d->name[0] == 0x0001) { if (d->name[1] == 0x0001) { // 'unc-volume' d->name = "unc://" + d->name.remove(0, 3).replace(0x0003, '/'); } else if (d->name[1] == 0x0002) { // relative to drive volume d->name.remove(0, 2).replace(0x0003, '/'); } else if (d->name[1] == 0x0005) { // full url d->name.remove(0, 3); } else { // TODO other options d->name.remove(0, 2).replace(0x0003, '/'); } } } } void ExternBookRecord::dump(std::ostream& out) const { out << "EXTERNBOOK" << std::endl; out << " Sheet count : " << d->sheetCount << std::endl; out << " Name : " << d->name << std::endl; } // ========== EXTERNNAME ========== const unsigned int ExternNameRecord::id = 0x0023; class ExternNameRecord::Private { public: unsigned optionFlags; unsigned sheetIndex; // one-based, not zero-based QString externName; }; ExternNameRecord::ExternNameRecord(Workbook *book) : Record(book), d(new Private) { d->optionFlags = 0; d->sheetIndex = 0; } ExternNameRecord::~ExternNameRecord() { delete d; } void ExternNameRecord::setSheetIndex(unsigned sheetIndex) { d->sheetIndex = sheetIndex; } unsigned ExternNameRecord::sheetIndex() const { return d->sheetIndex; } void ExternNameRecord::setExternName(const QString& name) { d->externName = name; } QString ExternNameRecord::externName() const { return d->externName; } void ExternNameRecord::setData(unsigned size, const unsigned char* data, const unsigned int*) { if (size < 6) return; if (version() == Excel97) { d->optionFlags = readU16(data); d->sheetIndex = readU16(data + 2); d->externName = EString::fromUnicodeString(data + 6, false, size).str(); } if (version() == Excel95) { d->optionFlags = 0; d->sheetIndex = 0; d->externName = EString::fromByteString(data + 6, false, size).str(); } } void ExternNameRecord::dump(std::ostream& /*out*/) const { } // ========== FORMULA ========== const unsigned int FormulaRecord::id = 0x0006; class FormulaRecord::Private { public: Value result; FormulaTokens tokens; bool shared; }; FormulaRecord::FormulaRecord(Workbook *book): Record(book) { d = new FormulaRecord::Private(); d->shared = false; } FormulaRecord::~FormulaRecord() { delete d; } Value FormulaRecord::result() const { return d->result; } void FormulaRecord::setResult(const Value& r) { d->result = r; } FormulaTokens FormulaRecord::tokens() const { return d->tokens; } void FormulaRecord::addToken(const FormulaToken &token) { d->tokens.push_back(token); } bool FormulaRecord::isShared() const { return d->shared; } void FormulaRecord::setData(unsigned size, const unsigned char* data, const unsigned int*) { if (size < 20) return; // cell setRow(readU16(data)); setColumn(readU16(data + 2)); setXfIndex(readU16(data + 4)); // val if (readU16(data + 12) != 0xffff) { // Floating-point setResult(Value(readFloat64(data + 6))); } else { switch (data[6]) { case 0: // string, real value in subsequent string record setResult(Value(Value::String)); break; case 1: // boolean setResult(Value(data[8] ? true : false)); break; case 2: // error code setResult(errorAsValue(data[8])); break; case 3: // empty setResult(Value::empty()); break; default: // fallback setResult(Value::empty()); break; }; } unsigned opts = readU16(data + 14); //const bool fAlwaysCalc = opts & 0x01; //const bool reserved1 = opts & 0x02; //const bool fFill = opts & 0x04; d->shared = opts & 0x08; //const bool reserved2 = opts & 0x10; //const bool fClearErrors = opts & 0x20; // 4 bytes chn... FormulaDecoder decoder; d->tokens = decoder.decodeFormula(size, 20, data, version()); } void FormulaRecord::writeData(XlsRecordOutputStream &o) const { o.writeUnsigned(16, row()); o.writeUnsigned(16, column()); o.writeUnsigned(16, xfIndex()); if (d->result.isNumber()) { o.writeFloat(64, d->result.asFloat()); } else if (d->result.isString()) { o.writeUnsigned(8, 0); // type o.writeUnsigned(24, 0); // reserved o.writeUnsigned(16, 0); // reserved o.writeUnsigned(16, 0xFFFF); } else if (d->result.isBoolean()) { o.writeUnsigned(8, 1); // type o.writeUnsigned(8, 0); // reserved o.writeUnsigned(8, d->result.asBoolean() ? 1 : 0); o.writeUnsigned(24, 0); // reserved o.writeUnsigned(16, 0xFFFF); } else if (d->result.isError()) { o.writeUnsigned(8, 2); // type o.writeUnsigned(8, 0); // reserved Value v = d->result; if (v == Value::errorNULL()) { o.writeUnsigned(8, 0x00); } else if (v == Value::errorDIV0()) { o.writeUnsigned(8, 0x07); } else if (v == Value::errorVALUE()) { o.writeUnsigned(8, 0x0F); } else if (v == Value::errorREF()) { o.writeUnsigned(8, 0x17); } else if (v == Value::errorNAME()) { o.writeUnsigned(8, 0x1D); } else if (v == Value::errorNUM()) { o.writeUnsigned(8, 0x24); } else if (v == Value::errorNA()) { o.writeUnsigned(8, 0x2A); } else { o.writeUnsigned(8, 0x2A); } o.writeUnsigned(24, 0); // reserved o.writeUnsigned(16, 0xFFFF); } else { o.writeUnsigned(8, 3); // type o.writeUnsigned(24, 0); // reserved o.writeUnsigned(16, 0); // reserved o.writeUnsigned(16, 0xFFFF); } o.writeUnsigned(1, 1); // fAlwaysRecalc o.writeUnsigned(1, 0); // reserved o.writeUnsigned(1, 0); // fFill o.writeUnsigned(1, d->shared ? 1 : 0); o.writeUnsigned(1, 0); // reserved o.writeUnsigned(1, 0); // fClearErrors o.writeUnsigned(10, 0); // reserved o.writeUnsigned(32, 0); // chn // actual formula unsigned totalSize = 0; for (unsigned i = 0; i < d->tokens.size(); ++i) { totalSize += d->tokens[i].size() + 1; } o.writeUnsigned(16, totalSize); for (unsigned i = 0; i < d->tokens.size(); ++i) { o.writeUnsigned(8, d->tokens[i].id()); // ptg std::vector data = d->tokens[i].data(); o.writeBlob(QByteArray::fromRawData(reinterpret_cast(&data[0]), data.size())); } } void FormulaRecord::dump(std::ostream& out) const { out << "FORMULA" << std::endl; out << " Row : " << row() << std::endl; out << " Column : " << column() << std::endl; out << " XF Index : " << xfIndex() << std::endl; out << " Result : " << result() << std::endl; FormulaTokens ts = tokens(); out << " Tokens : " << ts.size() << std::endl; for (unsigned i = 0; i < ts.size(); ++i) out << " " << ts[i] << std::endl; } // SHAREDFMLA const unsigned int SharedFormulaRecord::id = 0x04BC; class SharedFormulaRecord::Private { public: // range int numCells; FormulaTokens tokens; }; SharedFormulaRecord::SharedFormulaRecord(Workbook *book): Record(book) { d = new SharedFormulaRecord::Private(); } SharedFormulaRecord::~SharedFormulaRecord() { delete d; } FormulaTokens SharedFormulaRecord::tokens() const { return d->tokens; } void SharedFormulaRecord::setData(unsigned size, const unsigned char* data, const unsigned int*) { if (size < 8) return; // maybe read range d->numCells = data[7]; unsigned formula_len = readU16(data + 8); // reconstruct all tokens d->tokens.clear(); for (unsigned j = 10; j < size;) { unsigned ptg = data[j++]; ptg = ((ptg & 0x40) ? (ptg | 0x20) : ptg) & 0x3F; FormulaToken token(ptg); token.setVersion(version()); if (token.id() == FormulaToken::String) { // find bytes taken to represent the string EString estr = (version() == Excel97) ? EString::fromUnicodeString(data + j, false, formula_len) : EString::fromByteString(data + j, false, formula_len); token.setData(estr.size(), data + j); j += estr.size(); } else { // normal, fixed-size token if (token.size() > 1) { token.setData(token.size(), data + j); j += token.size(); } } d->tokens.push_back(token); } } void SharedFormulaRecord::dump(std::ostream& out) const { out << "SHAREDFMLA" << std::endl; // range out << " Num cells : " << d->numCells << std::endl; FormulaTokens ts = tokens(); out << " Tokens : " << ts.size() << std::endl; for (unsigned i = 0; i < ts.size(); ++i) out << " " << ts[i] << std::endl; } // ========== MULRK ========== const unsigned int MulRKRecord::id = 0x00bd; class MulRKRecord::Private { public: std::vector xfIndexes; std::vector isIntegers; std::vector intValues; std::vector floatValues; std::vector rkValues; }; MulRKRecord::MulRKRecord(Workbook *book): Record(book), CellInfo(), ColumnSpanInfo() { d = new MulRKRecord::Private(); } MulRKRecord::~MulRKRecord() { delete d; } unsigned MulRKRecord::xfIndex(unsigned i) const { if (i >= d->xfIndexes.size()) return 0; return d->xfIndexes[ i ]; } bool MulRKRecord::isInteger(unsigned i) const { if (i >= d->isIntegers.size()) return true; return d->isIntegers[ i ]; } int MulRKRecord::asInteger(unsigned i) const { if (i >= d->intValues.size()) return 0; return d->intValues[ i ]; } double MulRKRecord::asFloat(unsigned i) const { if (i >= d->floatValues.size()) return 0.0; return d->floatValues[ i ]; } unsigned MulRKRecord::encodedRK(unsigned i) const { if (i >= d->rkValues.size()) return 0; return d->rkValues[ i ]; } void MulRKRecord::setData(unsigned size, const unsigned char* data, const unsigned int*) { if (size < 6) return; setRow(readU16(data)); setFirstColumn(readU16(data + 2)); setLastColumn(readU16(data + size - 2)); d->xfIndexes.clear(); d->isIntegers.clear(); d->intValues.clear(); d->floatValues.clear(); for (unsigned i = 4; i < size - 2; i += 6) { d->xfIndexes.push_back(readU16(data + i)); unsigned rk = readU32(data + i + 2); d->rkValues.push_back(rk); bool isInteger = true; int iv = 0; double fv = 0.0; decodeRK(rk, isInteger, iv, fv); d->isIntegers.push_back(isInteger); d->intValues.push_back(isInteger ? iv : (int)fv); d->floatValues.push_back(!isInteger ? fv : (double)iv); } // FIXME sentinel ! } void MulRKRecord::dump(std::ostream& out) const { out << "MULRK" << std::endl; out << " Row : " << row() << std::endl; out << " First Column : " << firstColumn() << std::endl; out << " Last Column : " << lastColumn() << std::endl; for (unsigned c = firstColumn(); c <= lastColumn(); ++c) { out << " Column " << c << " : " << asFloat(c - firstColumn()); out << " Encoded: " << std::hex << encodedRK(c - firstColumn()); out << " Xf: " << std::dec << xfIndex(c - firstColumn()); out << std::endl; } } // ========== NAME ========== const unsigned int NameRecord::id = 0x0018; // Lbl record class NameRecord::Private { public: unsigned optionFlags; QString definedName; int sheetIndex; // 0 for global bool builtin; }; NameRecord::NameRecord(Workbook *book) : Record(book) { d = new Private; d->optionFlags = 0; } NameRecord::~NameRecord() { delete d; } void NameRecord::setDefinedName(const QString& name) { d->definedName = name; } QString NameRecord::definedName() const { return d->definedName; } unsigned NameRecord::sheetIndex() const { return d->sheetIndex; } bool NameRecord::isBuiltin() const { return d->builtin; } void NameRecord::setData(unsigned size, const unsigned char* data, const unsigned int*) { if (size < 14) { setIsValid(false); return; } d->optionFlags = readU16(data); //const bool fHidden = d->optionFlags & 0x01; //const bool fFunc = d->optionFlags & 0x02; //const bool fOB = d->optionFlags & 0x04; //const bool fProc = d->optionFlags & 0x08; //const bool fCalcExp = d->optionFlags & 0x10; d->builtin = d->optionFlags & 0x20; // 6 bits fGrp //const bool reserved1 = d->optionFlags & 0x1800; //const bool fPublished = d->optionFlags & 0x3000; //const bool fWorkbookParam = d->optionFlags & 0x6000; //const bool reserved2 = d->optionFlags & 0xC000; const unsigned len = readU8(data + 3); // cch const unsigned cce = readU16(data + 4); // len of rgce // 2 bytes reserved d->sheetIndex = readU16(data + 8); // if !=0 then its a local name // 4 bytes reserved if (version() == Excel95) { char* buffer = new char[ len+1 ]; memcpy(buffer, data + 14, len); buffer[ len ] = 0; d->definedName = QString(buffer); delete[] buffer; } else if (version() == Excel97) { if (d->builtin) { // field is for a build-in name const unsigned opts = readU8(data + 14); const bool fHighByte = opts & 0x01; const unsigned id = fHighByte ? readU16(data + 15) : readU8(data + 15) + 0x0*256; switch(id) { case 0x00: d->definedName = "Consolidate_Area"; break; case 0x01: d->definedName = "Auto_Open"; break; case 0x02: d->definedName = "Auto_Close"; break; case 0x03: d->definedName = "Extract"; break; case 0x04: d->definedName = "Database"; break; case 0x05: d->definedName = "Criteria"; break; case 0x06: d->definedName = "Print_Area"; break; case 0x07: d->definedName = "Print_Titles"; break; case 0x08: d->definedName = "Recorder"; break; case 0x09: d->definedName = "Data_Form"; break; case 0x0A: d->definedName = "Auto_Activate"; break; case 0x0B: d->definedName = "Auto_Deactivate"; break; case 0x0C: d->definedName = "Sheet_Title"; break; case 0x0D: d->definedName = "_FilterDatabase"; break; default: break; } } else { // must satisfy same restrictions then name field on XLNameUnicodeString const unsigned opts = readU8(data + 14); const bool fHighByte = opts & 0x01; // XLUnicodeStringNoCch QString str; if (fHighByte) { for (unsigned k = 0; k < len*2; ++k) { unsigned zc = readU16(data + 15 + k * 2); str.append(QString(zc)); } } else { for (unsigned k = 0; k < len; ++k) { unsigned char uc = readU8(data + 15 + k) + 0x0 * 256; str.append(QString(uc)); } } // This is rather illogical and seems there is nothing in the specs about this, // but the string "_xlfn." may in front of the string we are looking for. So, // remove that one and ignore whatever it means... if (str.startsWith("_xlfn.")) str.remove(0, 6); d->definedName = str; } } else { setIsValid(false); } // rgce, NamedParsedFormula if(cce >= 1) { /* FormulaDecoder decoder; m_formula = decoder.decodeNamedFormula(cce, data + size - cce, version()); - std::cout << ">>" << m_formula.ascii() << std::endl; + qCDebug(lcSidewinder) << ">>" << m_formula.ascii(); */ const unsigned char* startNamedParsedFormula = data + size - cce; unsigned ptg = readU8(startNamedParsedFormula); ptg = ((ptg & 0x40) ? (ptg | 0x20) : ptg) & 0x3F; FormulaToken t(ptg); t.setVersion(version()); t.setData(cce - 1, startNamedParsedFormula + 1); m_formula = t; } - std::cout << "NameRecord name=" << d->definedName << " iTab=" << d->sheetIndex << " fBuiltin=" << d->builtin << " formula=" << m_formula.id() << " (" << m_formula.idAsString() << ")" << std::endl; + qCDebug(lcSidewinder) << "NameRecord name=" << d->definedName << "iTab=" << d->sheetIndex + << "fBuiltin=" << d->builtin << "formula=" << m_formula.id() << "(" << m_formula.idAsString() << ")"; } void NameRecord::dump(std::ostream& /*out*/) const { } // ========== RK ========== const unsigned int RKRecord::id = 0x027e; class RKRecord::Private { public: bool integer; unsigned rk; int i; double f; }; RKRecord::RKRecord(Workbook *book): Record(book), CellInfo() { d = new RKRecord::Private(); d->integer = true; d->rk = 0; d->i = 0; d->f = 0.0; } RKRecord::~RKRecord() { delete d; } bool RKRecord::isInteger() const { return d->integer; } bool RKRecord::isFloat() const { return !d->integer; } int RKRecord::asInteger() const { if (d->integer) return d->i; else return (int)d->f; } double RKRecord::asFloat() const { if (!d->integer) return d->f; else return (double)d->i; } void RKRecord::setInteger(int i) { d->integer = true; d->i = i; d->f = (double)i; } void RKRecord::setFloat(double f) { d->integer = false; d->i = (int)f; d->f = f; } unsigned RKRecord::encodedRK() const { return d->rk; } // FIXME check sizeof(int) is 32 // big vs little endian problem void RKRecord::setData(unsigned size, const unsigned char* data, const unsigned int*) { if (size < 10) return; setRow(readU16(data)); setColumn(readU16(data + 2)); setXfIndex(readU16(data + 4)); int i = 0; double f = 0.0; d->rk = readU32(data + 6); decodeRK(d->rk, d->integer, i, f); if (d->integer) setInteger(i); else setFloat(f); } void RKRecord::dump(std::ostream& out) const { out << "RK" << std::endl; out << " Row : " << row() << std::endl; out << " Column : " << column() << std::endl; out << " XF Index : " << xfIndex() << std::endl; out << " Value : " << asFloat() << std::endl; out << " Encoded RK : 0x" << std::hex << encodedRK() << std::endl; out << std::dec; } // ========== RSTRING ========== const unsigned int RStringRecord::id = 0x00d6; class RStringRecord::Private { public: QString label; }; RStringRecord::RStringRecord(Workbook *book): Record(book), CellInfo() { d = new RStringRecord::Private(); } RStringRecord::~RStringRecord() { delete d; } QString RStringRecord::label() const { return d->label; } void RStringRecord::setLabel(const QString& l) { d->label = l; } // FIXME formatting runs ? in EString perhaps ? void RStringRecord::setData(unsigned size, const unsigned char* data, const unsigned int*) { if (size < 6) return; setRow(readU16(data)); setColumn(readU16(data + 2)); setXfIndex(readU16(data + 4)); // FIXME check Excel97 QString label = (version() >= Excel97) ? EString::fromUnicodeString(data + 6, true, size - 6).str() : EString::fromByteString(data + 6, true, size - 6).str(); setLabel(label); } void RStringRecord::dump(std::ostream& out) const { out << "RSTRING" << std::endl; out << " Row : " << row() << std::endl; out << " Column : " << column() << std::endl; out << " XF Index : " << xfIndex() << std::endl; out << " Label : " << label() << std::endl; } // ========== SST ========== const unsigned int SSTRecord::id = 0x00fc; class SSTRecord::Private { public: unsigned total; std::vector strings; std::vector > formatRuns; ExtSSTRecord* esst; }; SSTRecord::SSTRecord(Workbook *book): Record(book) { d = new SSTRecord::Private(); d->total = 0; d->esst = 0; } SSTRecord::~SSTRecord() { delete d; } QString sstrecord_get_plain_string(const unsigned char* data, unsigned length) { char* buffer = new char[ length+1 ]; memcpy(buffer, data, length); buffer[ length ] = 0; QString str = QString(buffer); delete[] buffer; return str; } void SSTRecord::setData(unsigned size, const unsigned char* data, const unsigned int* continuePositions) { if (size < 8) return; d->total = readU32(data); unsigned count = readU32(data + 4); unsigned offset = 8; unsigned int nextContinuePosIdx = 0; unsigned int nextContinuePos = continuePositions[0]; d->strings.clear(); for (unsigned i = 0; i < count; ++i) { // check against size if (offset >= size) { - std::cerr << "Warning: reached end of SST record, but not all strings have been read!" << std::endl; + qCWarning(lcSidewinder) << "Warning: reached end of SST record, but not all strings have been read!"; break; } EString es = EString::fromUnicodeString(data + offset, true, size - offset, continuePositions + nextContinuePosIdx, offset); d->strings.push_back(es.str()); d->formatRuns.push_back(es.formatRuns()); offset += es.size(); while (nextContinuePos < offset) nextContinuePos = continuePositions[++nextContinuePosIdx]; } // sanity check, adjust to safer condition if (count > d->strings.size()) { - std::cerr << "Warning: mismatch number of string in SST record, expected " << count << ", got " << d->strings.size() << "!" << std::endl; + qCWarning(lcSidewinder) << "Warning: mismatch number of string in SST record, expected" << count << ", got" << d->strings.size() << "!"; } } void SSTRecord::writeData(XlsRecordOutputStream &out) const { unsigned dsst = qMax(8, (count() / 128)+1); if (d->esst) { d->esst->setDsst(dsst); d->esst->setGroupCount((count() + dsst-1) / dsst); } out.writeUnsigned(32, d->total); out.writeUnsigned(32, count()); for (unsigned i = 0; i < count(); ++i) { if (i % dsst == 0 && d->esst) { d->esst->setIb(i/dsst, out.pos()); d->esst->setCbOffset(i/dsst, out.recordPos() + 4); } out.writeUnicodeStringWithFlagsAndLength(stringAt(i)); } } unsigned SSTRecord::count() const { return d->strings.size(); } unsigned SSTRecord::useCount() const { return d->total; } void SSTRecord::setUseCount(unsigned count) { d->total = count; } void SSTRecord::setExtSSTRecord(ExtSSTRecord *esst) { d->esst = esst; } // why not just string() ? to avoid easy confusion with std::string QString SSTRecord::stringAt(unsigned index) const { if (index >= count()) return QString(); return d->strings[ index ]; } std::map SSTRecord::formatRunsAt(unsigned index) const { if (index >= count()) return std::map(); return d->formatRuns[ index ]; } unsigned SSTRecord::addString(const QString &string) { d->strings.push_back(string); return d->strings.size()-1; } void SSTRecord::dump(std::ostream& out) const { out << "SST" << std::endl; out << " Occurrences : " << d->total << std::endl; out << " Count : " << count() << std::endl; for (unsigned i = 0; i < count(); ++i) out << " String #" << std::setw(2) << i << " : " << stringAt(i) << std::endl; } // ========== Obj ========== const unsigned ObjRecord::id = 0x5D; ObjRecord::ObjRecord(Workbook *book) : Record(book), m_object(0) {} ObjRecord::~ObjRecord() { delete m_object; } void ObjRecord::dump(std::ostream& out) const { out << "Obj" << std::endl; if (m_object) { out << " id: " << m_object->id() << std::endl; out << " type: " << m_object->type() << std::endl; } } void ObjRecord::setData(unsigned size, const unsigned char* data, const unsigned* /* continuePositions */) { if (size < 4) { setIsValid(false); return; } // FtCmo struct const unsigned char* startFtCmo = data; const unsigned long ftcmo = readU16(startFtCmo); const unsigned long cbcmo = readU16(startFtCmo + 2); if (ftcmo != 0x15 || cbcmo != 0x12) { - std::cerr << "ObjRecord::setData: invalid ObjRecord" << std::endl; + qCWarning(lcSidewinder) << "ObjRecord::setData: invalid ObjRecord"; setIsValid(false); return; } // cmo struct const unsigned long ot = readU16(startFtCmo + 4); const unsigned long id = readU16(startFtCmo + 6); //const unsigned long opts = readU16(startFtCmo + 8); //const bool fLocked = opts & 0x01; //const bool reserved = opts & 0x02; //const bool fDefaultSize = opts & 0x04; //const bool fPublished = opts & 0x08; //const bool fPrint = opts & 0x10; //const bool unused1 = opts & 0x20; //const bool unused2 = opts & 0x60; //const bool fDisabled = opts & 0xC0; //const bool fUIObj = opts & 0x180; //const bool fRecalcObj = opts & 0x300; //const bool unused3 = opts & 0x600; //const bool unused4 = opts & 0xC00; //const bool fRecalcObjAlways = opts & 0x1800; //const bool unused5 = opts & 0x3000; //const bool unused6 = opts & 0x6000; //const bool unused7 = opts & 0xC000; //const unsigned long unused8 = readU32(startFtCmo + 10); //const unsigned long unused9 = readU32(startFtCmo + 14); //const unsigned long unused10 = readU32(startFtCmo + 18); bool fDde = false; // dynamic data exchange reference? bool fCtl = false; // ActiveX control? bool fPrstm = false; // false=embedded store or true=control stream const unsigned char* startPict = data + 22; switch (ot) { case Object::Group: // gmo - printf("ObjRecord::setData group\n"); + qCDebug(lcSidewinder) << "ObjRecord::setData group"; startPict += 6; break; case Object::Picture: { // pictFormat and pictFlags m_object = new Object(Object::Picture, id); //const unsigned long ft = readU16(startPict); //const unsigned long cb = readU16(startPict + 2); // cf specifies Windows Clipboard format -- so far unused const unsigned long cf = readU16(startPict + 4); switch (cf) { case 0x0002: // enhanced metafile break; case 0x0009: // bitmap break; case 0xFFFF: // unspecified format, neither enhanced metafile nor a bitmap break; default: - std::cerr << "ObjRecord::setData: invalid ObjRecord Picture" << std::endl; + qCWarning(lcSidewinder) << "ObjRecord::setData: invalid ObjRecord Picture"; setIsValid(false); delete m_object; m_object = 0; return; } const unsigned long ft2 = readU16(startPict + 6); Q_ASSERT(ft2 == 0x0008); Q_UNUSED(ft2); const unsigned long cb2 = readU16(startPict + 8); Q_ASSERT(cb2 == 0x0002); Q_UNUSED(cb2); const unsigned long opts2 = readU16(startPict + 10); //const bool fAutoPict = opts2 & 0x01; fDde = opts2 & 0x02; // dynamic data exchange reference? //const bool dPrintCalc = opts2 & 0x04; //const bool fIcon = opts2 & 0x08; fCtl = opts2 & 0x10; // ActiveX control? Q_ASSERT(!(fCtl && fDde)); fPrstm = opts2 & 0x20; //const bool unused1 = opts2 & 0x60; //const bool fCamera = opts2 & 0xC0; //const bool fDefaultSize = opts2 & 0x180; //const bool fAutoload = opts2 & 0x300; //const bool unused2 = opts2 & 0x600; //const bool unused3 = opts2 & 0xC00; //const bool unused4 = opts2 & 0x1800; //const bool unused5 = opts2 & 0x3000; //const bool unused6 = opts2 & 0x6000; //const bool unused7 = opts2 & 0xC000; - std::cout << "ObjRecord::setData picture id=" << id << " fDde=" << fDde << " FCtl=" << fCtl << " fPrstm=" << fPrstm << std::endl; + qCDebug(lcSidewinder) << "ObjRecord::setData picture id=" << id << "fDde=" << fDde << "FCtl=" << fCtl << "fPrstm=" << fPrstm; startPict += 12; } break; case Object::Checkbox: // cbls - printf("ObjRecord::setData checkbox\n"); + qCDebug(lcSidewinder) << "ObjRecord::setData checkbox"; startPict += 16; break; case Object::RadioButton: // cbls and rbo - printf("ObjRecord::setData RadioButton\n"); + qCDebug(lcSidewinder) << "ObjRecord::setData RadioButton"; startPict += 26; break; case Object::SpinControl: // sbs - printf("ObjRecord::setData SpinControl\n"); + qCDebug(lcSidewinder) << "ObjRecord::setData SpinControl"; startPict += 24; break; case Object::Scrollbar: // sbs - printf("ObjRecord::setData Scrollbar\n"); + qCDebug(lcSidewinder) << "ObjRecord::setData Scrollbar"; startPict += 24; break; case Object::List: // sbs - printf("ObjRecord::setData List\n"); + qCDebug(lcSidewinder) << "ObjRecord::setData List"; startPict += 24; break; case Object::DropdownList: // sbs - printf("ObjRecord::setData DropdownList\n"); + qCDebug(lcSidewinder) << "ObjRecord::setData DropdownList"; startPict += 24; break; case Object::Note: { // nts - std::cout << "ObjRecord::setData note id=" << id << std::endl; + qCDebug(lcSidewinder) << "ObjRecord::setData note id=" << id; m_object = new NoteObject(id); const unsigned long ft = readU16(startPict); const unsigned long cb = readU16(startPict + 2); startPict += 20; // skip guid if (ft != 0x000D || cb != 0x0016) { - std::cerr << "ObjRecord::setData: invalid ObjRecord note with id=" << id << std::endl; + qCWarning(lcSidewinder) << "ObjRecord::setData: invalid ObjRecord note with id=" << id; setIsValid(false); delete m_object; m_object = 0; return; } //const unsigned long isShared = readU16(startPict); // 0x0000 = Not shared, 0x0001 = Shared. //Q_ASSERT( isShared == 0x0000 || isShared == 0x0001 ); startPict += 6; // includes 4 unused bytes //the TxO record that contains the text comes after this record... //static_cast(m_object)->setNote( ); } break; case Object::Chart: - std::cout << "ObjRecord::setData chart id=" << id << std::endl; + qCDebug(lcSidewinder) << "ObjRecord::setData chart id=" << id; m_object = new ChartObject(id); break; - case Object::Rectangle: printf("ObjRecord::setData Rectangle\n"); break; - case Object::Line: printf("ObjRecord::setData Line\n"); break; - case Object::Oval: printf("ObjRecord::setData Oval\n"); break; - case Object::Arc: printf("ObjRecord::setData Arc\n"); break; - case Object::Text: printf("ObjRecord::setData Text\n"); break; - case Object::Button: printf("ObjRecord::setData Button\n"); break; - case Object::Polygon: printf("ObjRecord::setData Polygon\n"); break; - case Object::EditBox: printf("ObjRecord::setData EditBox\n"); break; - case Object::Label: printf("ObjRecord::setData Label\n"); break; - case Object::DialogBox: printf("ObjRecord::setData DialogBox\n"); break; - case Object::GroupBox: printf("ObjRecord::setData GroupBox\n"); break; - case Object::OfficeArt: printf("ObjRecord::setData OfficeArt\n"); break; + case Object::Rectangle: qCDebug(lcSidewinder) << "ObjRecord::setData Rectangle"; break; + case Object::Line: qCDebug(lcSidewinder) << "ObjRecord::setData Line"; break; + case Object::Oval: qCDebug(lcSidewinder) << "ObjRecord::setData Oval"; break; + case Object::Arc: qCDebug(lcSidewinder) << "ObjRecord::setData Arc"; break; + case Object::Text: qCDebug(lcSidewinder) << "ObjRecord::setData Text"; break; + case Object::Button: qCDebug(lcSidewinder) << "ObjRecord::setData Button"; break; + case Object::Polygon: qCDebug(lcSidewinder) << "ObjRecord::setData Polygon"; break; + case Object::EditBox: qCDebug(lcSidewinder) << "ObjRecord::setData EditBox"; break; + case Object::Label: qCDebug(lcSidewinder) << "ObjRecord::setData Label"; break; + case Object::DialogBox: qCDebug(lcSidewinder) << "ObjRecord::setData DialogBox"; break; + case Object::GroupBox: qCDebug(lcSidewinder) << "ObjRecord::setData GroupBox"; break; + case Object::OfficeArt: qCDebug(lcSidewinder) << "ObjRecord::setData OfficeArt"; break; default: - std::cerr << "ObjRecord::setData: Unexpected objecttype " << ot << " in ObjRecord" << std::endl; + qCWarning(lcSidewinder) << "ObjRecord::setData: Unexpected objecttype" << ot << "in ObjRecord"; setIsValid(false); delete m_object; m_object = 0; return; } { // FtMacro. Specs say it's optional by not when it's used. So, we need to check it by assuming // a valid FtMacro starts with 0x0004... The following code is untested. The only thing we are // interested in here is seeking to the structs after this one anyway. const unsigned long ft = readU16(startPict); if (ft == 0x0004) { const unsigned long cmFmla = readU16(startPict + 2); startPict += 4; int sizeFmla = 0; if (cmFmla > 0x0000) { // ObjectParseFormula const unsigned long cce = readU16(startPict) >> 1; // 15 bits cce + 1 bit reserved // 4 bytes unused sizeFmla = 2 + 4 + cce; //startPict += sizeFmla; } // skip embedInfo cause we are not a FtPictFmla startPict += cmFmla - sizeFmla - 0; // padding } } // pictFmla if (ot == Object::Picture && readU16(startPict) == 0x0009 /* checks ft */) { //const unsigned long cb = readU16(startPict + 2); startPict += 4; /* from the specs; fmla (variable): An ObjFmla that specifies the location of the data for the object associated with the Obj record that contains this FtPictFmla. If the pictFlags.fDde field of the Obj record that contains this FtPictFmla is 1, fmla MUST refer to a name which is defined in an ExternName record whose fOle field is 1. If the pictFlags.fCamera field of the Obj record that contains this FtPictFmla is 1, fmla MUST refer to a range. Otherwise, the fmla.cce field of this fmla MUST be 0x5 and the fmla.rgce field of this fmla MUST contain a PtgTbl followed by four bytes that are undefined and MUST be ignored. */ // fmla variable, an ObjFmla struct FormulaToken token; const unsigned long cbFmla = readU16(startPict); int cbFmlaSize = 0; int embedInfoSize = 0; if (cbFmla > 0x0000) { // fmla variable, optional ObjectParsedFormula struct const unsigned long cce = readU16(startPict + cbFmlaSize + 2) >> 1; // 15 bits cce + 1 bit reserved cbFmlaSize += 2 + 2 + 4; // 4 bytes unused // rgce unsigned ptg = readU8(startPict + cbFmlaSize); cbFmlaSize += 1; ptg = ((ptg & 0x40) ? (ptg | 0x20) : ptg) & 0x3F; token = FormulaToken(ptg); token.setVersion(version()); - std::cout << "ObjRecord::setData: Picture is of type id=" << token.id() << " name=" << token.idAsString() << std::endl; + qCDebug(lcSidewinder) << "ObjRecord::setData: Picture is of type id=" << token.id() << "name=" << token.idAsString(); if (token.size() > 0) { token.setData(token.size(), startPict + cbFmlaSize); cbFmlaSize += token.size(); } if (cce == 0x5 && token.id() == FormulaToken::Table) { cbFmlaSize += 4; } // embededInfo variable, an optional PictFmlaEmbedInfo if (token.id() == FormulaToken::Table) { const unsigned ttb = readU8(startPict + cbFmlaSize); if (ttb == 0x03) { const unsigned cbClass = readU8(startPict + cbFmlaSize + embedInfoSize + 1); //const unsigned reserved = readU8(startPict + cbFmlaSize + embedInfoSize + 2); embedInfoSize += 3; if (cbClass > 0x0000) { // strClass specifies the class name of the embedded control unsigned size = 0; QString className = readUnicodeString(startPict + cbFmlaSize + embedInfoSize, cbClass, -1, 0, &size); embedInfoSize += size; //TODO - std::cout << "ObjRecord::setData: className=" << qPrintable(className) << std::endl; + qCDebug(lcSidewinder) << "ObjRecord::setData: className=" << className; } } } } startPict += cbFmla + 2; // IPosInCtlStm variable if (token.id() == FormulaToken::Table) { const unsigned int iposInCtlStm = readU32(startPict); if (fPrstm) { // iposInCtlStm specifies the zero-based offset of this object's data within the control stream. const unsigned int cbBufInCtlStm = readU32(startPict + 4); startPict += 8; Q_UNUSED(iposInCtlStm); // it was used in PictureObject as offset, but nobody used it Q_UNUSED(cbBufInCtlStm); // it was used in PictureObject as size, but nobody used it } else { // The object‘s data MUST reside in an embedding storage. std::stringstream out; out << std::setw(8) << std::setfill('0') << std::uppercase << std::hex << iposInCtlStm; // out.str() was used as embedding storage, but nobody used it } } // key variable, PictFmlaKey struct if (fCtl) { std::string key; const unsigned int cbKey = readU32(startPict); startPict += 4; for (uint i = 0; i < cbKey; ++i) { if (key.size() > 0) key += "."; key = readU32(startPict); startPict += 4; } //fmlaLinkedCell //fmlaListFillRange - std::cout << "ObjRecord::setData: Runtime license key is: " << key.c_str() << std::endl; + qCDebug(lcSidewinder) << "ObjRecord::setData: Runtime license key is: " << key.c_str(); } } // linkFmla // checkBox // radionButton // edit // list // gbo } // ========== TxO ========== class TxORecord::Private { public: QString text; QSharedPointer richText; // NULL if plainText else it defines the richText TxORecord::HorizontalAlignment hAlign; TxORecord::VerticalAlignment vAlign; }; const unsigned TxORecord::id = 0x1B6; TxORecord::TxORecord(Workbook *book) : Record(book) { d = new TxORecord::Private(); } TxORecord::TxORecord(const TxORecord& other) : Record(other) { d = new TxORecord::Private(); operator=(other); } TxORecord::~TxORecord() { delete d; } TxORecord& TxORecord::operator=(const TxORecord &other) { d->text = other.d->text; d->richText = other.d->richText; d->hAlign = other.d->hAlign; d->vAlign = other.d->vAlign; return *this; } void TxORecord::dump(std::ostream& out) const { out << "TxO" << std::endl; out << " " << d->text << " " << d->hAlign << " " << d->vAlign; } void TxORecord::setData(unsigned size, const unsigned char* data, const unsigned* continuePositions) { const unsigned long opts1 = readU16(data); //const bool reserved1 = opts1 & 0x01; d->hAlign = static_cast((opts1 & 0x000e) >> 1); // 3 bits d->vAlign = static_cast((opts1 & 0x0070) >> 4); // 3 bits //const unsigned long rot = readU16(data + 2); // 4 bytes reserved // controlInfo (6 bytes): An optional ControlInfo structure that specifies the properties for some // form controls. The field MUST exist if and only if the value of cmo.ot in the preceding Obj // record is 0, 5, 7, 11, 12, or 14. const unsigned long cchText = readU16(data + 14); const unsigned char* startPict = data + 16; const unsigned char* endPict = data + size; if(cchText > 0) { //const unsigned long cbRuns = readU16(startPict); const unsigned long cbFmla = readU16(startPict + 2); // fmla, ObjFmla structure startPict += 4 + cbFmla; } else { //const unsigned long ifntEmpty = readU16(startPict); // FontIndex startPict += 2; const unsigned *endOffset = continuePositions; while (data + *endOffset <= startPict && *endOffset < size) endOffset++; endPict = data + *endOffset; } const unsigned opts = readU8(startPict); const bool fHighByte = opts & 0x01; // this seems to assert with some documents... //Q_ASSERT((opts << 1) == 0x0); // XLUnicodeStringNoCch d->text.clear(); unsigned k = 1; if(fHighByte) { for (; startPict + k + 1 < endPict; k += 2) { unsigned zc = readU16(startPict + k); if (!zc) break; if (!QChar(zc).isPrint() && zc != 10) { d->text.clear(); break; } d->text.append(QChar(zc)); } } else { for (; startPict + k < endPict; k += 1) { unsigned char uc = readU8(startPict + k) + 0x0*256; if (!uc) break; if (!QChar(uc).isPrint() && uc != 10) { d->text.clear(); break; } d->text.append(QChar(uc)); } } d->richText.clear(); // Now look for TxORun structures that specify the formatting run information for the TxO record. int ToXRunsPositionIndex = 0; do { unsigned pos = continuePositions[ToXRunsPositionIndex]; if (pos + 8 > size) { ToXRunsPositionIndex = 0; break; // not found } if (pos >= k) { break; // we have it } ++ToXRunsPositionIndex; } while(true); if (ToXRunsPositionIndex > 0) { d->richText = QSharedPointer(new QTextDocument()); // also add a textrangemanager, as KoTextWriter assumes one KoTextDocument(d->richText.data()).setTextRangeManager(new KoTextRangeManager); d->richText->setPlainText(d->text); QTextCursor cursor(d->richText.data()); //cursor.setVisualNavigation(true); QTextCharFormat format; unsigned pos = continuePositions[ToXRunsPositionIndex]; for(;pos + 8 <= size; pos += 8) { // now walk through the array of Run records const unsigned ich = readU16(data + pos); const unsigned ifnt = readU16(data + pos + 2); if (format.isValid()) { cursor.setPosition(ich, QTextCursor::KeepAnchor); cursor.setCharFormat(format); cursor.setPosition(ich, QTextCursor::MoveAnchor); } if (ich >= unsigned(d->text.length())) { break; } FormatFont font = m_workbook->font(ifnt); Q_ASSERT(!font.isNull()); format.setFontFamily(font.fontFamily()); format.setFontPointSize(font.fontSize()); format.setForeground(QBrush(font.color())); format.setFontWeight(font.bold() ? QFont::Bold : QFont::Normal); format.setFontItalic(font.italic()); format.setFontUnderline(font.underline()); format.setFontStrikeOut(font.strikeout()); //TODO font.subscript() //TODO font.superscript() } } - std::cout << "TxORecord::setData size=" << size << " text=" << qPrintable(d->text) << std::endl; + qCDebug(lcSidewinder) << "TxORecord::setData size=" << size << " text=" << d->text; } const QString& TxORecord::text() const { return d->text; } TxORecord::HorizontalAlignment TxORecord::hAlign() const { return d->hAlign; } TxORecord::VerticalAlignment TxORecord::vAlign() const { return d->vAlign; } const QTextDocument* TxORecord::richText() const { return d->richText.data(); } // ========== MsoDrawing ========== const unsigned MsoDrawingRecord::id = 0xEC; class MsoDrawingRecord::Private { public: MSO::OfficeArtDgContainer container; }; MsoDrawingRecord::MsoDrawingRecord(Workbook *book) : Record(book) { d = new MsoDrawingRecord::Private(); } MsoDrawingRecord::~MsoDrawingRecord() { delete d; } const MSO::OfficeArtDgContainer& MsoDrawingRecord::dgContainer() const { return d->container; } void MsoDrawingRecord::dump(std::ostream& out) const { out << "MsoDrawingRecord" << std::endl; } void MsoDrawingRecord::setData(unsigned size, const unsigned char* data, const unsigned* continuePositions) { Q_UNUSED(continuePositions); QByteArray byteArr = QByteArray::fromRawData(reinterpret_cast(data), size); QBuffer buff(&byteArr); buff.open(QIODevice::ReadOnly); LEInputStream in(&buff); MSO::OfficeArtDgContainer container; // First try to parse a OfficeArtDgContainer and if that fails try to parse a single OfficeArtSpgrContainerFileBlock. Note // that the xls-specs say that the rgChildRec of a MsoDrawing-record always is a OfficeArtDgContainer but that's just wrong // since at some documents it's direct the OfficeArtSpContainer we are interested in. LEInputStream::Mark _m = in.setMark(); try { MSO::parseOfficeArtDgContainer(in, container); } catch(const IOException&) { in.rewind(_m); container.groupShape = QSharedPointer(new MSO::OfficeArtSpgrContainer(&container)); container.groupShape->rgfb.append(MSO::OfficeArtSpgrContainerFileBlock(&container)); try { parseOfficeArtSpgrContainerFileBlock(in, container.groupShape->rgfb.last()); } catch(const IOException& e) { - std::cerr << "Invalid MsoDrawingRecord record: " << qPrintable(e.msg) << std::endl; + qCWarning(lcSidewinder) << "Invalid MsoDrawingRecord record:" << e.msg; setIsValid(false); return; } catch(...) { - std::cerr << "Invalid MsoDrawingRecord record: Unexpected error" << std::endl; + qCWarning(lcSidewinder) << "Invalid MsoDrawingRecord record: Unexpected error"; setIsValid(false); return; } } // Be sure we got at least something useful we can work with. if(!container.groupShape) { - std::cerr << "Invalid MsoDrawingRecord record: Expected groupShape missing in the container." << std::endl; + qCWarning(lcSidewinder) << "Invalid MsoDrawingRecord record: Expected groupShape missing in the container."; setIsValid(false); return; } // Finally remember the container to be able to extract later content out of it. d->container = container; } // ========== MsoDrawingGroup ========== const unsigned MsoDrawingGroupRecord::id = 0xEB; class MsoDrawingGroupRecord::Private { public: MSO::OfficeArtDggContainer container; QMap pictureNames; }; MsoDrawingGroupRecord::MsoDrawingGroupRecord(Workbook *book) : Record(book) { d = new Private(); } MsoDrawingGroupRecord::~MsoDrawingGroupRecord() { delete d; } const MSO::OfficeArtDggContainer& MsoDrawingGroupRecord::dggContainer() const { return d->container; } const QMap< QByteArray, QString > MsoDrawingGroupRecord::pictureNames() const { return d->pictureNames; } void MsoDrawingGroupRecord::dump(std::ostream& out) const { out << "MsoDrawingGroupRecord" << std::endl; } void MsoDrawingGroupRecord::setData(unsigned size, const unsigned char* data, const unsigned* continuePositions) { - printf("MsoDrawingGroupRecord::setData size=%i data=%i continuePositions=%i\n",size,*data,*continuePositions); + qCDebug(lcSidewinder) << QString("MsoDrawingGroupRecord::setData size=%1 data=%2 continuePositions=%3").arg(size).arg(*data).arg(*continuePositions); if(size < 32) { setIsValid(false); return; } QByteArray byteArr = QByteArray::fromRawData(reinterpret_cast(data), size); QBuffer buff(&byteArr); buff.open(QIODevice::ReadOnly); LEInputStream lei(&buff); try { MSO::parseOfficeArtDggContainer(lei, d->container); } catch (const IOException& e) { - std::cerr << "Invalid MsoDrawingGroup record:" << qPrintable(e.msg) << std::endl; + qCWarning(lcSidewinder) << "Invalid MsoDrawingGroup record:" << e.msg; setIsValid(false); return; } if(d->container.blipStore.data() && m_workbook->store()) { m_workbook->store()->enterDirectory("Pictures"); d->pictureNames = createPictures(m_workbook->store(), 0, &d->container.blipStore->rgfb); m_workbook->store()->leaveDirectory(); } } // ========== BkHimRecord ========== const unsigned BkHimRecord::id = 0x00e9; class BkHimRecord::Private { public: Format format; QString imagePath; }; BkHimRecord::BkHimRecord(Workbook *book) : Record(book), d(new Private) { } BkHimRecord::~BkHimRecord() { delete d; } BkHimRecord::BkHimRecord( const BkHimRecord& record ) : Record(record), d(new Private) { *this = record; } BkHimRecord& BkHimRecord::operator=( const BkHimRecord& record ) { *d = *record.d; return *this; } QString BkHimRecord::formatToString(Format format) { switch (format) { case WindowsBitMap: return QString("WindowsBitMap"); case NativeFormat: return QString("NativeFormat"); default: return QString("Unknown: %1").arg(format); } } BkHimRecord::Format BkHimRecord::format() const { return d->format; } void BkHimRecord::setFormat(Format format ) { d->format = format; } QString BkHimRecord::imagePath() const { return d->imagePath; } void BkHimRecord::setImagePath(const QString &imagePath ) { d->imagePath = imagePath; } void BkHimRecord::setData( unsigned size, const unsigned char* data, const unsigned int* ) { unsigned curOffset = 0; if (size < 8) { setIsValid(false); return; } setFormat(static_cast(readU16(data + curOffset))); curOffset += 2; //16 reserved bits curOffset += 2; quint32 imageSize = readU32(data + curOffset); curOffset += 4; static int counter = 1; //we need unique file names QString filename = QString("Pictures/sheetBackground%1").arg(counter++); if(format() == WindowsBitMap) { filename.append(QString(".bmp")); } setImagePath(filename); KoStore *store = m_workbook->store(); Q_ASSERT(store); if(store->open(filename)) { //Excel doesn't include the file header, only the pixmap header, //we need to convert it to a standard BMP header //see http://www.fileformat.info/format/bmp/egff.htm for details //quint32 headerSize = readU32(data + curOffset); // this header size is always 12 curOffset += 4; const quint16 width = readU16(data + curOffset); //in px curOffset += 2; const qint16 height = readU16(data + curOffset); // in px curOffset += 2; //const qint16 planes = data + curOffset; //must be 1 curOffset += 2; qint16 bitsPerPixel = readU16(data + curOffset); //usually 24 curOffset += 2; //For the standard header see wikipedia or //http://www.fastgraph.com/help/bmp_header_format.html QByteArray newHeader; newHeader.fill(0x0, 54); int currentHeaderOffset = 0; //signature newHeader[0] = 0x42; newHeader[1] = 0x4d; currentHeaderOffset += 2; char* newHeaderChar = newHeader.data(); //size imageSize -= 12; //remove the header size const qint32 fileSize = qToLittleEndian(imageSize + 54); memcpy(newHeaderChar + currentHeaderOffset, reinterpret_cast(&fileSize), 4); currentHeaderOffset += 4; //4 reserved bytes currentHeaderOffset += 4; //offset to the start of the image, the size of this new header: always 54 bytes const qint32 startImageData = qToLittleEndian(qint32(54)); memcpy(newHeaderChar + currentHeaderOffset, reinterpret_cast(&startImageData), 4); currentHeaderOffset += 4; const quint32 sizeOfBitmapInfoHeader = qToLittleEndian(qint32(40)); memcpy(newHeaderChar + currentHeaderOffset, reinterpret_cast(&sizeOfBitmapInfoHeader), 4); currentHeaderOffset += 4; const quint32 imageWidth = qToLittleEndian(qint32(width)); memcpy(newHeaderChar + currentHeaderOffset, reinterpret_cast(&imageWidth), 4); currentHeaderOffset += 4; const quint32 imageHeight = qToLittleEndian(qint32(height)); memcpy(newHeaderChar + currentHeaderOffset, reinterpret_cast(&imageHeight), 4); currentHeaderOffset += 4; const quint32 planes = qToLittleEndian(quint16(1)); memcpy(newHeaderChar + currentHeaderOffset, reinterpret_cast(&planes), 2); currentHeaderOffset += 2; bitsPerPixel = qToLittleEndian(bitsPerPixel); memcpy(newHeaderChar + currentHeaderOffset, reinterpret_cast(&bitsPerPixel), 2); currentHeaderOffset += 2; //compresion type, for this case always 0 currentHeaderOffset += 4; //size of image bits const quint32 litEndimageSize = qToLittleEndian(imageSize); memcpy(newHeaderChar + currentHeaderOffset, reinterpret_cast(&litEndimageSize), 4); currentHeaderOffset += 4; //we leave the remaining bits to zero store->write(newHeaderChar, 54); store->write((const char*)(data + curOffset), imageSize); store->close(); } else { - std::cerr << "BkHimRecord: Failed to open file=" << filename << std::endl; + qCWarning(lcSidewinder) << "BkHimRecord: Failed to open file=" << filename; } } void BkHimRecord::dump( std::ostream& out ) const { out << "BkHim" << std::endl; out << " Format : " << formatToString(format()) << std::endl; out << " ImagePath : " << imagePath() << std::endl; } static Record* createBkHimRecord(Workbook *book) { return new BkHimRecord(book); } //============================================= // ExcelReader //============================================= class ExcelReader::Private { public: // the workbook Workbook* workbook; GlobalsSubStreamHandler* globals; std::vector handlerStack; // active sheet, all cell records will be stored here Sheet* activeSheet; }; ExcelReader::ExcelReader() { d = new ExcelReader::Private(); d->workbook = 0; d->activeSheet = 0; d->globals = 0; } ExcelReader::~ExcelReader() { delete d; } static Record* createBOFRecord(Workbook *book) { return new BOFRecord(book); } static Record* createExternBookRecord(Workbook *book) { return new ExternBookRecord(book); } static Record* createExternNameRecord(Workbook *book) { return new ExternNameRecord(book); } static Record* createFormulaRecord(Workbook *book) { return new FormulaRecord(book); } static Record* createSharedFormulaRecord(Workbook *book) { return new SharedFormulaRecord(book); } static Record* createMulRKRecord(Workbook *book) { return new MulRKRecord(book); } static Record* createNameRecord(Workbook *book) { return new NameRecord(book); } static Record* createRKRecord(Workbook *book) { return new RKRecord(book); } static Record* createRStringRecord(Workbook *book) { return new RStringRecord(book); } static Record* createSSTRecord(Workbook *book) { return new SSTRecord(book); } static Record* createObjRecord(Workbook *book) { return new ObjRecord(book); } static Record* createTxORecord(Workbook *book) { return new TxORecord(book); } static Record* createRecordMsoDrawingRecord(Workbook *book) { return new MsoDrawingRecord(book); } static Record* createMsoDrawingGroupRecord(Workbook *book) { return new MsoDrawingGroupRecord(book); } static void registerAllRecordClasses() { registerRecordClasses(); RecordRegistry::registerRecordClass(BOFRecord::id, createBOFRecord); RecordRegistry::registerRecordClass(ExternBookRecord::id, createExternBookRecord); RecordRegistry::registerRecordClass(ExternNameRecord::id, createExternNameRecord); RecordRegistry::registerRecordClass(FormulaRecord::id, createFormulaRecord); RecordRegistry::registerRecordClass(SharedFormulaRecord::id, createSharedFormulaRecord); RecordRegistry::registerRecordClass(MulRKRecord::id, createMulRKRecord); RecordRegistry::registerRecordClass(NameRecord::id, createNameRecord); RecordRegistry::registerRecordClass(RKRecord::id, createRKRecord); RecordRegistry::registerRecordClass(RStringRecord::id, createRStringRecord); RecordRegistry::registerRecordClass(SSTRecord::id, createSSTRecord); RecordRegistry::registerRecordClass(ObjRecord::id, createObjRecord); RecordRegistry::registerRecordClass(TxORecord::id, createTxORecord); RecordRegistry::registerRecordClass(MsoDrawingRecord::id, createRecordMsoDrawingRecord); RecordRegistry::registerRecordClass(MsoDrawingGroupRecord::id, createMsoDrawingGroupRecord); RecordRegistry::registerRecordClass(BkHimRecord::id, createBkHimRecord); } -void printEntries(POLE::Storage &storage, const std::string path = "/", int level = 0) +#ifdef SWINDER_XLS2RAW +static void printEntries(POLE::Storage &storage, const std::string path = "/", int level = 0) { - std::cout << std::setw(level) << "PATH=" << path << std::endl; + qCDebug(lcSidewinder) << "PATH=" << QString::fromStdString(path); std::list entries = storage.entries(path); for (std::list::iterator it = entries.begin(); it != entries.end(); ++it) { - std::cout << std::setw(level + 1) << "ENTRY=" << *it << std::endl; + qCDebug(lcSidewinder) << "ENTRY=" << QString::fromStdString(*it); std::string p = path == "/" ? "/" + *it + "/" : path + "/" + *it + "/"; if (storage.isDirectory(p)) { printEntries(storage, p, level + 1); } } } +#endif bool ExcelReader::load(Workbook* workbook, const char* filename) { registerAllRecordClasses(); POLE::Storage storage(filename); if (!storage.open()) { - std::cerr << "Cannot open " << filename << std::endl; + qCWarning(lcSidewinder) << "Cannot open" << filename; return false; } #ifdef SWINDER_XLS2RAW - std::cout << "Streams:" << std::endl; + qCDebug(lcSidewinder) << "Streams:"; printEntries(storage); #endif unsigned streamVersion = Swinder::Excel97; POLE::Stream* stream; stream = new POLE::Stream(&storage, "/Workbook"); if (stream->fail()) { delete stream; stream = new POLE::Stream(&storage, "/Book"); streamVersion = Swinder::Excel95; } if (stream->fail()) { - std::cerr << filename << " is not Excel workbook" << std::endl; + qCWarning(lcSidewinder) << filename << "is not Excel workbook"; delete stream; return false; } unsigned int buffer_size = 65536; // current size of the buffer unsigned char *buffer = (unsigned char *) malloc(buffer_size); unsigned char small_buffer[128]; // small, fixed size buffer { // read document meta information POLE::Stream *summarystream = new POLE::Stream(&storage, "/SummaryInformation"); const unsigned long streamStartPosition = summarystream->tell(); unsigned bytes_read = summarystream->read(buffer, 8); //const unsigned long byteorder = readU16( buffer ); // must be 0xFFFE //const unsigned long version = readU16( buffer + 2 ); // must be 0x0000 or 0x0001 //const unsigned long systemId = readU32( buffer + 4 ); QTextCodec* codec = QTextCodec::codecForLocale(); summarystream->seek(summarystream->tell() + 16); // skip CLSID bytes_read = summarystream->read(buffer, 4); const unsigned long numPropertySets = bytes_read == 4 ? readU32(buffer) : 0; // must be 0x00000001 or 0x00000002 for (uint i = 0; i < numPropertySets; ++ i) { summarystream->seek(summarystream->tell() + 16); // skip FMTIDO bytes_read = summarystream->read(buffer, 4); if (bytes_read != 4) break; const unsigned long firstPropertyOffset = readU32(buffer); const unsigned long p = summarystream->tell(); const unsigned long propertysetStartPosition = streamStartPosition + firstPropertyOffset; summarystream->seek(propertysetStartPosition); bytes_read = summarystream->read(buffer, 8); if (bytes_read != 8) break; //unsigned long size = readU32( buffer ); unsigned long propertyCount = readU32(buffer + 4); for (uint i = 0; i < propertyCount; ++i) { bytes_read = summarystream->read(buffer, 8); if (bytes_read != 8) break; Workbook::PropertyType propertyId = Workbook::PropertyType(readU32(buffer)); // Offset (4 bytes): An unsigned integer representing the offset in bytes from the beginning of // the PropertySet packet to the beginning of the Property field for the property represented. // MUST be a multiple of 4 bytes. unsigned long propertyOffset = readU32(buffer + 4); unsigned long p2 = summarystream->tell(); summarystream->seek(propertysetStartPosition + propertyOffset); bytes_read = summarystream->read(buffer, 4); if (bytes_read != 4) break; unsigned long type = readU16(buffer); //unsigned long padding = readU16( buffer + 2 ); switch (propertyId) { case Workbook::PIDSI_TITLE: case Workbook::PIDSI_SUBJECT: case Workbook::PIDSI_AUTHOR: case Workbook::PIDSI_KEYWORDS: case Workbook::PIDSI_COMMENTS: case Workbook::PIDSI_TEMPLATE: case Workbook::PIDSI_LASTAUTHOR: case Workbook::PIDSI_REVNUMBER: case Workbook::PIDSI_EDITTIME: case Workbook::PIDSI_LASTPRINTED_DTM: case Workbook::PIDSI_CREATE_DTM: case Workbook::PIDSI_LASTSAVED_DTM: case Workbook::PIDSI_APPNAME: switch (type) { case 0x001E: { //VT_LPSTR bytes_read = summarystream->read(buffer, 4); if (bytes_read != 4) break; const unsigned long length = readU32(buffer); bytes_read = summarystream->read(buffer, length); if (bytes_read != length) break; QString s = codec->toUnicode(reinterpret_cast(buffer), static_cast(length)); workbook->setProperty(propertyId, s); } break; case 0x0040: { //VT_FILETIME bytes_read = summarystream->read(buffer, 8); if (bytes_read != 8) break; const unsigned long dwLowDateTime = readU32(buffer); const unsigned long dwHighDateTime = readU32(buffer + 4); long long int time = dwHighDateTime; time <<= 32; time += (unsigned long) dwLowDateTime; time -= 116444736000000000LL; QString s(QDateTime::fromTime_t(time / 10000000.0).toString(Qt::ISODate)); workbook->setProperty(propertyId, s); } break; default: - std::cout << "Ignoring property with known id=" << propertyId << " and unknown type=" << type; + qCDebug(lcSidewinder) << "Ignoring property with known id=" << propertyId << "and unknown type=" << type; break; } break; case Workbook::PIDSI_CODEPAGE: { if (type != 0x0002) break; // type should always be 2 bytes_read = summarystream->read(buffer, 4); unsigned int codepage = readU32(buffer); QTextCodec* newCodec = QTextCodec::codecForName("CP" + QByteArray::number(codepage)); if (newCodec) { codec = newCodec; } - std::cout << "Codepage:" << codepage << std::endl; + qCDebug(lcSidewinder) << "Codepage:" << codepage; } break; default: if (propertyId != 0x0013 /* GKPIDDSI_SHAREDDOC */) { - std::cout << "Ignoring property with unknown id=" << propertyId << " and type=" << type << std::endl; + qCDebug(lcSidewinder) << "Ignoring property with unknown id=" << propertyId << " and type=" << type; } break; } summarystream->seek(p2); } summarystream->seek(p); } delete summarystream; } { // read CompObj stream POLE::Stream *combObjStream = new POLE::Stream(&storage, "/CompObj"); // header unsigned bytes_read = combObjStream->read(buffer, 28); unsigned long length = 0; bool hasCombObjStream = bytes_read == 28; if(hasCombObjStream) { //const unsigned long version = readU32( buffer + 5 ); //printf(">>>> combObjStream->fullName=%s\n",combObjStream->fullName().c_str()); //printEntries(storage,"CompObj"); // AnsiUserType bytes_read = combObjStream->read( buffer, 4 ); length = readU32( buffer ); bytes_read = combObjStream->read( buffer, length ); if(bytes_read != length) hasCombObjStream = false; } if(hasCombObjStream) { QString ansiUserType = readByteString(buffer, length); - printf( "length=%lu ansiUserType=%s\n",length, qPrintable(ansiUserType) ); + qCDebug(lcSidewinder) << QString("length=%1 ansiUserType=%2").arg(length).arg(ansiUserType); // AnsiClipboardFormat bytes_read = combObjStream->read( buffer, 4 ); const unsigned long markerOrLength = readU32( buffer ); switch (markerOrLength) { case 0x00000000: break; // Must not be present, do nothing... case 0xFFFFFFFF: // fall through case 0xFFFFFFFE: { // must be 4 bytes and contains a clipboard identifier bytes_read = combObjStream->read( buffer, 4 ); //const unsigned long standardFormat = readU32( buffer ); // switch(standardFormat) { // case 0x00000002: standardFormat=CF_BITMAP; // case 0x00000003: standardFormat=CF_METAFILEPICT // case 0x00000008: standardFormat=CF_DIB // case 0x0000000E: standardFormat=CF_ENHMETAFILE // } //TODO... } break; default: if (markerOrLength > 65535) { - printf("invalid length reading compobj stream: %lu\n", markerOrLength); + qCDebug(lcSidewinder) << "invalid length reading compobj stream:" << markerOrLength; } else { bytes_read = combObjStream->read( buffer, markerOrLength ); QString ansiString = readByteString(buffer, markerOrLength); //TODO... //printf( "markerOrLength=%i ansiString=%s\n",markerOrLength,ansiString.ascii() ); } } //TODO Reserved1, UnicodeMarker, UnicodeUserType, UnicodeClipboardFormat, Reserved2 } delete combObjStream; } const unsigned long stream_size = stream->size(); unsigned int continuePositionsSize = 128; // size of array for continue positions unsigned int *continuePositions = (unsigned int *) malloc(continuePositionsSize * sizeof(int)); workbook->clear(); d->workbook = workbook; d->globals = new GlobalsSubStreamHandler(workbook, streamVersion); d->handlerStack.clear(); bool useMsoDrawingRecordWorkaround = false; while (stream->tell() < stream_size) { const int percent = int(stream->tell() / double(stream_size) * 100.0 + 0.5); workbook->emitProgress(percent); // this is set by FILEPASS record // subsequent records will need to be decrypted // since we do not support it yet, we have to bail out if (d->globals->passwordProtected() && !d->globals->encryptionTypeSupported()) { d->workbook->setPasswordProtected(true); break; } // get record type and data size unsigned long pos = stream->tell(); unsigned bytes_read = stream->read(buffer, 4); if (bytes_read != 4) break; unsigned long type = readU16(buffer); // Work around a known bug in Excel. See below for a more detailed description of the problem. if (useMsoDrawingRecordWorkaround) { useMsoDrawingRecordWorkaround = false; type = MsoDrawingRecord::id; } unsigned long size = readU16(buffer + 2); d->globals->decryptionSkipBytes(4); unsigned int continuePositionsCount = 0; // verify buffer is large enough to hold the record data if (size > buffer_size) { buffer = (unsigned char *) realloc(buffer, size); buffer_size = size; } // load actual record data bytes_read = stream->read(buffer, size); if (bytes_read != size) break; d->globals->decryptRecord(type, size, buffer); // save current position in stream, to be able to restore the position later on unsigned long saved_pos; // repeatedly check if the next record is type 0x3C, a continuation record unsigned long next_type; // the type of the next record do { saved_pos = stream->tell(); bytes_read = stream->read(small_buffer, 4); if (bytes_read != 4) break; next_type = readU16(small_buffer); unsigned long next_size = readU16(small_buffer + 2); if(next_type == MsoDrawingGroupRecord::id) { if (type != MsoDrawingGroupRecord::id) break; } else if(next_type == 0x3C) { // 0x3C are continues records which are always just merged... // if the previous record is an Obj record, than the Continue record // was supposed to be a MsoDrawingRecord (known bug in MS Excel...) // a similar problem exists with TxO/Continue records, but there it is // harder to find out which Continue records are part of the TxO and which // are part of the MsoDrawing record if (type == ObjRecord::id || type == TxORecord::id) { unsigned long saved_pos_2 = stream->tell(); bool isMsoDrawingRecord = false; bytes_read = stream->read(small_buffer, 8); if (bytes_read == 8) { QByteArray byteArr = QByteArray::fromRawData(reinterpret_cast(small_buffer), 8); QBuffer buff(&byteArr); buff.open(QIODevice::ReadOnly); LEInputStream in(&buff); MSO::OfficeArtRecordHeader rh; parseOfficeArtRecordHeader(in, rh); isMsoDrawingRecord = (rh.recVer == 0xF && rh.recInstance == 0x0 && rh.recType == 0xF002) || // OfficeArtDgContainer (rh.recVer == 0xF && rh.recInstance == 0x0 && rh.recType == 0x0F004) || // OfficeArtSpContainer (rh.recVer == 0xF && rh.recInstance == 0x0 && rh.recType == 0x0F003) || // OfficeArtSpgrContainer (rh.recVer == 0x2 && rh.recInstance <= 202 && rh.recType == 0x0F00A && rh.recLen == 8) || // OfficeArtFSP (rh.recVer == 0x1 && rh.recInstance == 0x0 && rh.recType == 0x0F009 && rh.recLen == 0x10) || // OfficeArtFSPGR (rh.recVer == 0x0 && rh.recInstance == 0x0 && rh.recType == 0xF010 && (rh.recLen == 0x8 || rh.recLen == 0x12)) || // XlsOfficeArtClientAnchor (rh.recVer == 0x0 && rh.recInstance == 0x0 && rh.recType == 0xF011 && rh.recLen == 0); // XlsOfficeArtClientData } stream->seek(saved_pos_2); if (isMsoDrawingRecord) { useMsoDrawingRecordWorkaround = true; break; } } } else { break; // and abort merging of records } // compress multiple records into one. continuePositions[continuePositionsCount++] = size; if (continuePositionsCount >= continuePositionsSize) { continuePositionsSize *= 2; continuePositions = (unsigned int *) realloc(continuePositions, continuePositionsSize * sizeof(int)); } // first verify the buffer is large enough to hold all the data if ((size + next_size) > buffer_size) { buffer = (unsigned char *) realloc(buffer, size + next_size); buffer_size = size + next_size; } // next read the data of the record bytes_read = stream->read(buffer + size, next_size); if (bytes_read != next_size) { - std::cout << "ERROR!" << std::endl; + qCDebug(lcSidewinder) << "ERROR!"; break; } d->globals->decryptionSkipBytes(4); d->globals->decryptRecord(next_type, next_size, buffer+size); // and finally update size size += next_size; } while (true); // append total size as last continue position continuePositions[continuePositionsCount] = size; // restore position in stream to the beginning of the next record stream->seek(saved_pos); // skip record type 0, this is just for filler if (type == 0) continue; // create the record using the factory Record* record = Record::create(type, workbook); if (!record) { //#ifdef SWINDER_XLS2RAW - std::cout << "Unhandled Record 0x"; - std::cout << std::setfill('0') << std::setw(4) << std::hex << type; - std::cout << std::dec; - std::cout << " (" << type << ")"; - std::cout << std::endl; + qCDebug(lcSidewinder) << "Unhandled Record" << QString("0x%1").arg(QString::number(type, 16)) + << "(" << type << ")"; //#endif } else { // setup the record and invoke handler record->setVersion(d->globals->version()); record->setData(size, buffer, continuePositions); record->setPosition(pos); #ifdef SWINDER_XLS2RAW - std::cout << std::setfill('0') << std::setw(8) << std::dec << record->position() << " "; - if (!record->isValid()) std::cout << "Invalid "; - std::cout << "Record 0x"; - std::cout << std::setfill('0') << std::setw(4) << std::hex << record->rtti(); - std::cout << " ("; - std::cout << std::dec; - std::cout << record->rtti() << ") "; - record->dump(std::cout); - std::cout << std::endl; + QString debug = QString("%1 ").arg(record->position(), 8, 10, QChar('0')); + if (!record->isValid()) debug.append("Invalid "); + debug.append(QString("Record 0x%1 (%2)").arg(record->rtti(), 4, 16, QChar('0')).arg(record->rtti())); + std::stringstream out; + record->dump(out); + qCDebug(lcSidewinder) << debug << QString::fromStdString(out.str()); #endif if (record->isValid()) { if (record->rtti() == BOFRecord::id) handleRecord(record); if (!d->handlerStack.empty() && d->handlerStack.back()) d->handlerStack.back()->handleRecord(record); if (record->rtti() == EOFRecord::id) handleRecord(record); } delete record; } } free(buffer); free(continuePositions); delete d->globals; delete stream; storage.close(); return true; } void ExcelReader::handleRecord(Record* record) { if (!record) return; unsigned type = record->rtti(); if (type == BOFRecord::id) handleBOF(static_cast(record)); else if (type == EOFRecord::id) handleEOF(static_cast(record)); } void ExcelReader::handleBOF(BOFRecord* record) { if (!record) return; if (record->type() == BOFRecord::Workbook) { d->handlerStack.push_back(d->globals); qDebug() << "figuring out version" << record->version() << record->rawVersion(); if (record->version() == Swinder::Excel95) { d->workbook->setVersion(Workbook::Excel95); } else if (record->version() == Swinder::Excel97) { if (record->recordSize() >= 8) { switch (record->verLastXLSaved()) { case BOFRecord::LExcel97: d->workbook->setVersion(Workbook::Excel97); break; case BOFRecord::LExcel2000: d->workbook->setVersion(Workbook::Excel2000); break; case BOFRecord::LExcel2002: d->workbook->setVersion(Workbook::Excel2002); break; case BOFRecord::LExcel2003: d->workbook->setVersion(Workbook::Excel2003); break; case BOFRecord::LExcel2007: d->workbook->setVersion(Workbook::Excel2007); break; case BOFRecord::LExcel2010: d->workbook->setVersion(Workbook::Excel2010); break; default: // pretend that anything newer than 2010 is 2010 d->workbook->setVersion(Workbook::Excel2010); break; } } else { d->workbook->setVersion(Workbook::Excel97); } } else { d->workbook->setVersion(Workbook::Unknown); } } else if (record->type() == BOFRecord::Worksheet) { // find the sheet and make it active // which sheet ? look from from previous BoundSheet Sheet* sheet = d->globals->sheetFromPosition(record->position()); if (sheet) d->activeSheet = sheet; d->handlerStack.push_back(new WorksheetSubStreamHandler(sheet, d->globals)); } else if (record->type() == BOFRecord::Chart) { SubStreamHandler* parentHandler = d->handlerStack.empty() ? 0 : d->handlerStack.back(); d->handlerStack.push_back(new Swinder::ChartSubStreamHandler(d->globals, parentHandler)); } else { - std::cout << "ExcelReader::handleBOF Unhandled type=" << record->type() << std::endl; + qCDebug(lcSidewinder) << "ExcelReader::handleBOF Unhandled type=" << record->type(); } } void ExcelReader::handleEOF(EOFRecord* record) { if (!record) return; if (d->handlerStack.empty()) return; SubStreamHandler* handler = d->handlerStack.back(); d->handlerStack.pop_back(); if (handler != d->globals) delete handler; } #ifdef SWINDER_XLS2RAW #include int main(int argc, char ** argv) { QApplication app(argc, argv); if (argc < 2) { std::cout << "Usage: sidewinder filename" << std::endl; return 0; } char* filename = argv[1]; std::cout << "Checking " << filename << std::endl; Workbook* workbook = new Workbook(); ExcelReader* reader = new ExcelReader(); reader->load(workbook, filename); workbook->dumpStats(); delete reader; delete workbook; return 0; } #endif // XLS2RAW diff --git a/filters/sheets/excel/sidewinder/formulas.cpp b/filters/sheets/excel/sidewinder/formulas.cpp index b656a26f6bb..62747469a49 100644 --- a/filters/sheets/excel/sidewinder/formulas.cpp +++ b/filters/sheets/excel/sidewinder/formulas.cpp @@ -1,1915 +1,1915 @@ /* Swinder - Portable library for spreadsheet Copyright (C) 2003-2005 Ariya Hidayat Copyright (C) 2006,2009 Marijn Kruisselbrink Copyright (C) 2009,2010 Sebastian Sauer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "formulas.h" #include #include #include #include #include #include "excel.h" #include "utils.h" namespace Swinder { //============================================= // FormulaToken //============================================= class FormulaToken::Private { public: unsigned ver; unsigned id; std::vector data; }; FormulaToken::FormulaToken() { d = new Private; d->ver = Excel97; d->id = Unused; } FormulaToken::FormulaToken(unsigned t) { d = new Private; d->ver = Excel97; d->id = t; } FormulaToken::FormulaToken(const FormulaToken& token) { d = new Private; d->ver = token.d->ver; d->id = token.id(); d->data.resize(token.d->data.size()); for (unsigned i = 0; i < d->data.size(); ++i) d->data[i] = token.d->data[i]; } FormulaToken& FormulaToken::operator=(const FormulaToken& token) { d->ver = token.d->ver; d->id = token.id(); d->data.resize(token.d->data.size()); for (unsigned i = 0; i < d->data.size(); ++i) d->data[i] = token.d->data[i]; return *this; } FormulaToken::~FormulaToken() { delete d; } FormulaToken FormulaToken::createBool(bool value) { FormulaToken t(Bool); unsigned char data = value ? 1: 0; t.setData(1, &data); return t; } FormulaToken FormulaToken::createNum(double value) { FormulaToken t(Float); QBuffer b; b.open(QIODevice::WriteOnly); QDataStream ds(&b); ds.setByteOrder(QDataStream::LittleEndian); ds.setFloatingPointPrecision(QDataStream::DoublePrecision); ds << value; Q_ASSERT(b.data().size() == 8); t.setData(b.data().size(), reinterpret_cast(b.data().data())); return t; } FormulaToken FormulaToken::createStr(const QString &value) { FormulaToken t(String); QBuffer b; b.open(QIODevice::WriteOnly); QDataStream ds(&b); ds.setByteOrder(QDataStream::LittleEndian); ds << quint8(value.length()); ds << quint8(1); // unicode for (int i = 0; i < value.length(); i++) { ds << quint16(value[i].unicode()); } t.setData(b.data().size(), reinterpret_cast(b.data().data())); return t; } FormulaToken FormulaToken::createRef(const QPoint& pos, bool rowFixed, bool colFixed) { FormulaToken t(Ref); QBuffer b; b.open(QIODevice::WriteOnly); QDataStream ds(&b); ds.setByteOrder(QDataStream::LittleEndian); unsigned row = pos.y(); unsigned col = pos.x(); bool rowRel = !rowFixed; bool colRel = !colFixed; if (rowRel) col |= 0x4000; if (colRel) col |= 0x8000; ds << quint16(row); ds << quint16(col); t.setData(b.data().size(), reinterpret_cast(b.data().data())); return t; } FormulaToken FormulaToken::createRefErr() { FormulaToken t(RefErr); quint32 zero = 0; t.setData(4, reinterpret_cast(&zero)); return t; } FormulaToken FormulaToken::createArea(const QRect& area, bool topFixed, bool bottomFixed, bool leftFixed, bool rightFixed) { FormulaToken t(Area); QBuffer b; b.open(QIODevice::WriteOnly); QDataStream ds(&b); ds.setByteOrder(QDataStream::LittleEndian); unsigned row1 = area.top(); unsigned col1 = area.left(); bool rowRel1 = !topFixed; bool colRel1 = !leftFixed; unsigned row2 = area.bottom(); unsigned col2 = area.right(); bool rowRel2 = !bottomFixed; bool colRel2 = !rightFixed; if (rowRel1) col1 |= 0x4000; if (colRel1) col1 |= 0x8000; if (rowRel2) col2 |= 0x4000; if (colRel2) col2 |= 0x8000; ds << quint16(row1); ds << quint16(row2); ds << quint16(col1); ds << quint16(col2); t.setData(b.data().size(), reinterpret_cast(b.data().data())); return t; } FormulaToken FormulaToken::createAreaErr() { FormulaToken t(AreaErr); quint64 zero = 0; t.setData(8, reinterpret_cast(&zero)); return t; } FormulaToken FormulaToken::createFunc(const QString &func, unsigned argCount) { unsigned paramCount = functionParams(func); bool isVarArgs = !fixedFunctionParams(func) || argCount != paramCount; FormulaToken t(isVarArgs ? FunctionVar : Function); QBuffer b; b.open(QIODevice::WriteOnly); QDataStream ds(&b); ds.setByteOrder(QDataStream::LittleEndian); if (isVarArgs) { ds << quint8(argCount); } ds << quint16(functionIndex(func)); t.setData(b.data().size(), reinterpret_cast(b.data().data())); return t; } unsigned FormulaToken::version() const { return d->ver; } void FormulaToken::setVersion(unsigned v) { d->ver = v; } unsigned FormulaToken::id() const { return d->id; } const char* FormulaToken::idAsString() const { const char* s = 0; switch (d->id) { case Matrix: s = "Matrix"; break; case Table: s = "Table"; break; case Add: s = "Add"; break; case Sub: s = "Sub"; break; case Mul: s = "Mul"; break; case Div: s = "Div"; break; case Power: s = "Power"; break; case Concat: s = "Concat"; break; case LT: s = "LT"; break; case LE: s = "LE"; break; case EQ: s = "EQ"; break; case GE: s = "GE"; break; case GT: s = "GT"; break; case NE: s = "NE"; break; case Intersect: s = "Intersect"; break; case Union: s = "Union"; break; case Range: s = "Range"; break; case UPlus: s = "UPlus"; break; case UMinus: s = "UMinus"; break; case Percent: s = "Percent"; break; case Paren: s = "Paren"; break; case String: s = "String"; break; case MissArg: s = "MissArg"; break; case ErrorCode: s = "ErrorCode"; break; case Bool: s = "Bool"; break; case Integer: s = "Integer"; break; case Array: s = "Array"; break; case Function: s = "Function"; break; case FunctionVar: s = "FunctionVar"; break; case Name: s = "Name"; break; case Ref: s = "Ref"; break; case RefErr: s = "RefErr"; break; case RefN: s = "RefN"; break; case Area: s = "Area"; break; case AreaErr: s = "AreaErr"; break; case AreaN: s = "AreaN"; break; case NameX: s = "NameX"; break; case Ref3d: s = "Ref3d"; break; case RefErr3d: s = "RefErr3d"; break; case Float: s = "Float"; break; case Area3d: s = "Area3d"; break; case AreaErr3d: s = "AreaErr3d"; break; case NatFormula: s = "NatFormula"; break; case Sheet: s = "Sheet"; break; case EndSheet: s = "EndSheet"; break; case MemArea: s = "MemArea"; break; case MemErr: s = "MemErr"; break; case MemNoMem: s = "MemNoMem"; break; case MemFunc: s = "MemFunc"; break; case MemAreaN: s = "MemAreaN"; break; case MemNoMemN: s = "MemNoMemN"; break; case Attr: switch (attr()) { case AttrChoose: s = "AttrChoose"; break; default: s = "Attr"; break; } break; case 0: s = ""; break; // NOPE... default: s = "Unknown"; - printf("Unhandled formula id %i as string\n", d->id); + qCDebug(lcSidewinder) << QString("Unhandled formula id %1 as string").arg(d->id); break; } return s; } unsigned FormulaToken::size() const { unsigned s = 0; // on most cases no data switch (d->id) { case Add: case Sub: case Mul: case Div: case Power: case Concat: case LT: case LE: case EQ: case GE: case GT: case NE: case Intersect: case Union: case Range: case UPlus: case UMinus: case Percent: case Paren: case MissArg: s = 0; break; case Attr: switch (attr()) { case AttrChoose: s = 3 + 2 * (1+readU16(&(d->data[1]))); break; default: s = 3; break; } break; case ErrorCode: case Bool: s = 1; break; case Integer: s = 2; break; case Array: s = 7; break; case Function: s = 2; break; case FunctionVar: s = 3; break; case Matrix: case Table: s = (d->ver == Excel97) ? 4 : 3; break; case Name: s = (d->ver == Excel97) ? 4 : 14; break; case Ref: case RefErr: case RefN: s = (d->ver == Excel97) ? 4 : 3; break; case Area: case AreaErr: case AreaN: s = (d->ver == Excel97) ? 8 : 6; break; case NameX: s = (d->ver == Excel97) ? 6 : 24; break; case Ref3d: case RefErr3d: s = (d->ver == Excel97) ? 6 : 17; break; case Float: s = 8; break; case Area3d: case AreaErr3d: s = (d->ver == Excel97) ? 10 : 20; break; case MemArea: s = 6; break; case MemErr: s = 6; break; case 0: // NOPE s = 0; break; case MemFunc: s = 2; break; case NatFormula: case Sheet: case EndSheet: case MemNoMem: case MemAreaN: case MemNoMemN: default: if (d->data.empty()) // WARNING this is unhandled case - printf("Unhandled formula token with id %i\n", d->id); + qCDebug(lcSidewinder) << "Unhandled formula token with id" << d->id; else s = d->data.size(); break; } return s; } void FormulaToken::setData(unsigned size, const unsigned char* data) { d->data.resize(size); for (unsigned i = 0; i < size; ++i) d->data[i] = data[i]; } std::vector FormulaToken::data() const { return d->data; } Value FormulaToken::value() const { Value result; unsigned char* buf; buf = new unsigned char[d->data.size()]; for (unsigned k = 0; k < d->data.size(); ++k) buf[k] = d->data[k]; // FIXME sanity check: verify size of data switch (d->id) { case ErrorCode: result = errorAsValue(buf[0]); break; case Bool: result = Value(buf[0] != 0); break; case Integer: result = Value((int)readU16(buf)); break; case Float: result = Value(readFloat64(buf)); break; case String: { EString estr = (version() == Excel97) ? EString::fromUnicodeString(buf, false, d->data.size()) : EString::fromByteString(buf, false, d->data.size()); result = Value(estr.str()); break; } default: break; } delete [] buf; return result; } unsigned FormulaToken::functionIndex() const { // FIXME check data size unsigned index = 0; unsigned char buf[2]; if (d->id == Function) { buf[0] = d->data[0]; buf[1] = d->data[1]; index = readU16(buf); } if (d->id == FunctionVar) { buf[0] = d->data[1]; buf[1] = d->data[2]; index = readU16(buf); } return index; } struct FunctionEntry { const char *name; int params; bool varParams; }; static const FunctionEntry FunctionEntries[] = { { "COUNT", 1, true }, // 0 { "IF", 0, true }, // 1 { "ISNA", 1, false }, // 2 { "ISERROR", 1, false }, // 3 { "SUM", 0, true }, // 4 { "AVERAGE", 0, true }, // 5 { "MIN", 0, true }, // 6 { "MAX", 0, true }, // 7 { "ROW", 0, true }, // 8 { "COLUMN", 0, true }, // 9 { "NA", 0, false }, // 10 { "NPV", 0, true }, // 11 { "STDEV", 0, true }, // 12 { "DOLLAR", 0, true }, // 13 { "FIXED", 0, true }, // 14 { "SIN", 1, false }, // 15 { "COS", 1, false }, // 16 { "TAN", 1, false }, // 17 { "ATAN", 1, false }, // 18 { "PI", 0, false }, // 19 { "SQRT", 1, false }, // 20 { "EXP", 1, false }, // 21 { "LN", 1, false }, // 22 { "LOG10", 1, false }, // 23 { "ABS", 1, false }, // 24 { "INT", 1, false }, // 25 { "SIGN", 1, false }, // 26 { "ROUND", 2, false }, // 27 { "LOOKUP", 0, true }, // 28 { "INDEX", 0, true }, // 29 { "REPT", 2, false }, // 30 { "MID", 3, false }, // 31 { "LEN", 1, false }, // 32 { "VALUE", 1, false }, // 33 { "TRUE", 0, false }, // 34 { "FALSE", 0, false }, // 35 { "AND", 0, true }, // 36 { "OR", 0, true }, // 37 { "NOT", 1, false }, // 38 { "MOD", 2, false }, // 39 { "DCOUNT", 3, false }, // 40 { "DSUM", 3, false }, // 41 { "DAVERAGE", 3, false }, // 42 { "DMIN", 3, false }, // 43 { "DMAX", 3, false }, // 44 { "DSTDEV", 3, false }, // 45 { "VAR", 0, true }, // 46 { "DVAR", 3, false }, // 47 { "TEXT", 2, false }, // 48 { "LINEST", 0, true }, // 49 { "TREND", 0, true }, // 50 { "LOGEST", 0, true }, // 51 { "GROWTH", 0, true }, // 52 { "GOTO", 0, false }, // 53 { "HALT", 0, true }, // 54 { "RETURN", 0, true }, // 55 { "PV", 0, true }, // 56 { "FV", 0, true }, // 57 { "NPER", 0, true }, // 58 { "PMT", 0, true }, // 59 { "RATE", 0, true }, // 60 { "MIRR", 3, false }, // 61 { "IRR", 0, true }, // 62 { "RAND", 0, false }, // 63 { "MATCH", 0, true }, // 64 { "DATE", 3, false }, // 65 { "TIME", 3, false }, // 66 { "DAY", 1, false }, // 67 { "MONTH", 1, false }, // 68 { "YEAR", 1, false }, // 69 { "WEEKDAY", 0, true }, // 70 { "HOUR", 1, false }, // 71 { "MINUTE", 1, false }, // 72 { "SECOND", 1, false }, // 73 { "NOW", 0, false }, // 74 { "AREAS", 1, false }, // 75 { "ROWS", 1, false }, // 76 { "COLUMNS", 1, false }, // 77 { "OFFSET", 0, true }, // 78 { "ABSREF", 2, false }, // 79 { "RELREF", 2, false }, // 80 { "ARGUMENT", 0, true }, // 81 { "SEARCH", 0, true }, // 82 { "TRANSPOSE", 1, false }, // 83 { "ERROR", 0, true }, // 84 { "STEP", 0, false }, // 85 { "TYPE", 1, false }, // 86 { "ECHO", 0, true }, { "SETNAME", 0, true }, // deprecated... { "CALLER", 0, false }, { "DEREF", 1, false }, { "WINDOWS", 0, true }, { "SERIES", 4, true }, { "DOCUMENTS", 0, true }, { "ACTIVECELL", 0, false }, // deprecated... { "SELECTION", 0, false }, { "RESULT", 0, true }, { "ATAN2", 2, false }, // 97 { "ASIN", 1, false }, // 98 { "ACOS", 1, false }, // 99 { "CHOOSE", 0, true }, // 100 { "HLOOKUP", 0, true }, // 101 { "VLOOKUP", 0, true }, // 102 { "LINKS", 0, true }, { "INPUT", 0, true }, { "ISREF", 1, false }, // 105 { "GETFORMULA", 1, false }, // deprecated... { "GETNAME", 0, true }, // deprecated... { "SETVALUE", 2, false }, // deprecated... { "LOG", 0, true }, // 109 { "EXEC", 0, true }, { "CHAR", 1, false }, // 111 { "LOWER", 1, false }, // 112 { "UPPER", 1, false }, // 113 { "PROPER", 1, false }, // 114 { "LEFT", 0, true }, // 115 { "RIGHT", 0, true }, // 116 { "EXACT", 2, false }, // 117 { "TRIM", 1, false }, // 118 { "REPLACE", 4, false }, // 119 { "SUBSTITUTE", 0, true }, // 120 { "CODE", 1, false }, // 121 { "NAMES", 0, true }, { "DIRECTORY", 0, true }, { "FIND", 0, true }, // 124 { "CELL", 0, true }, // 125 { "ISERR", 1, false }, // 126 { "ISTEXT", 1, false }, // 127 { "ISNUMBER", 1, false }, // 128 { "ISBLANK", 1, false }, // 129 { "T", 1, false }, // 130 { "N", 1, false }, // 131 { "FOPEN", 0, true }, // not portable, insecure, deprecated { "FCLOSE", 1, false }, // not portable, insecure, deprecated { "FSIZE", 1, false }, // not portable, insecure, deprecated { "FREADLN", 1, false }, // not portable, insecure, deprecated { "FREAD", 2, false }, // not portable, insecure, deprecated { "FWRITELN", 2, false }, // not portable, insecure, deprecated { "FWRITE", 2, false }, // not portable, insecure, deprecated { "FPOS", 0, true }, // not portable, insecure, deprecated { "DATEVALUE", 1, false }, // 140 { "TIMEVALUE", 1, false }, // 141 { "SLN", 3, false }, // 142 { "SYD", 4, false }, // 143 { "DDB", 0, true }, // 144 { "GETDEF", 0, true }, { "REFTEXT", 0, true }, { "TEXTREF", 0, true }, { "INDIRECT", 0, true }, // 148 { "REGISTER", 0, true }, { "CALL", 0, true }, { "ADDBAR", 0, true }, // deprecated { "ADDMENU", 0, true }, // deprecated { "ADDCOMMAND", 0, true }, // deprecated { "ENABLECOMMAND", 0, true }, // deprecated { "CHECKCOMMAND", 0, true }, // deprecated { "RENAMECOMMAND", 0, true }, // deprecated { "SHOWBAR", 0, true }, // deprecated { "DELETEMENU", 0, true }, // deprecated { "DELETECOMMAND", 0, true }, // deprecated { "GETCHARTITEM", 0, true }, // deprecated { "DIALOGBOX", 0, true }, // deprecated { "CLEAN", 1, false }, // 162 { "MDETERM", 1, false }, // 163 { "MINVERSE", 1, false }, // 164 { "MMULT", 2, false }, // 165 { "FILES", 0, true }, // not portable, insecure, deprecated { "IPMT", 0, true }, // 167 { "PPMT", 0, true }, // 168 { "COUNTA", 0, true }, // 169 { "CANCELKEY", 1, true }, { "FOR", 0, true }, { "WHILE", 1, false }, { "BREAK", 0, false }, { "NEXT", 0, false }, { "INITIATE", 2, false }, { "REQUEST", 2, false }, { "POKE", 3, false }, { "EXECUTE", 2, false }, { "TERMINATE", 1, false }, { "RESTART", 0, true }, { "HELP", 0, true }, { "GETBAR", 0, true }, { "PRODUCT", 0, true }, // 183 { "FACT", 1, false }, // 184 { "GETCELL", 0, true }, { "GETWORKSPACE", 1, false }, { "GETWINDOW", 0, true }, { "GETDOCUMENT", 0, true }, { "DPRODUCT", 3, false }, // 189 { "ISNONTEXT", 1, false }, // 190 { "GETNOTE", 0, true }, { "NOTE", 0, true }, { "STDEVP", 0, true }, // 193 { "VARP", 0, true }, // 194 { "DSTDEVP", 3, false }, // 195 { "DVARP", 3, false }, // 196 { "TRUNC", 0, true }, // 197 { "ISLOGICAL", 1, false }, // 198 { "DCOUNTA", 3, false }, // 199 { "DELETEBAR", 1, false }, { "UNREGISTER", 1, false }, { "Unknown202", 0, true }, { "Unknown203", 0, true }, { "USDOLLAR", 0, true }, { "FINDB", 0, true }, { "SEARCHB", 0, true }, { "REPLACEB", 4, false }, { "LEFTB", 0, true }, { "RIGHTB", 0, true }, { "MIDB", 3, false }, { "LENB", 1, false }, { "ROUNDUP", 2, false }, // 212 { "ROUNDDOWN", 2, false }, // 213 { "ASC", 1, false }, { "DBCS", 1, false }, { "RANK", 0, true }, // 216 { "Unknown217", 0, true }, { "Unknown218", 0, true }, { "ADDRESS", 0, true }, // 219 { "DAYS360", 0, true }, // 220 { "CURRENTDATE", 0, false }, // 221 { "VBD", 0, true }, // 222 { "ELSE", 0, false }, { "ELSE.IF", 1, false }, { "END.IF", 0, false }, { "FOR.CELL", 0, true }, { "MEDIAN", 0, true }, // 227 { "SUMPRODUCT", 0, true }, // 228 { "SINH", 1, false }, // 229 { "COSH", 1, false }, // 230 { "TANH", 1, false }, // 231 { "ASINH", 1, false }, // 232 { "ACOSH", 1, false }, // 233 { "ATANH", 1, false }, // 234 { "DGET", 3, false }, // 235 { "CREATEOBJECT", 0, true }, { "VOLATILE", 0, true }, { "LASTERROR", 0, false }, { "CUSTOMUNDO", 0, true }, { "CUSTOMREPEAT", 0, true }, { "FORMULACONVERT", 0, true }, { "GETLINKINFO", 0, true }, { "TEXTBOX", 0, true }, { "INFO", 1, false }, // 244 { "GROUP", 0, false }, { "GETOBJECT", 0, true }, { "DB", 0, true }, // 247 { "PAUSE", 0, true }, { "Unknown249", 0, true }, { "Unknown250", 0, true }, { "RESUME", 0, true }, { "FREQUENCY", 2, false }, // 252 { "ADDTOOLBAR", 0, true }, { "DELETETOOLBAR", 0, true }, { "USER.DEFINED.FUNCTION", 0, true }, { "RESETTOOLBAR", 1, false }, { "EVALUATE", 1, false }, { "GETTOOLBAR", 0, true }, { "GETTOOL", 0, true }, { "SPELLINGCHECK", 0, true }, { "ERRORTYPE", 1, false }, // 261 { "APPTITLE", 0, true }, { "WINDOWTITLE", 0, true }, { "SAVETOOLBAR", 0, true }, { "ENABLETOOL", 3, false }, { "PRESSTOOL", 3, false }, { "REGISTERID", 0, true }, { "GETWORKBOOK", 0, true }, { "AVEDEV", 0, true }, // 269 { "BETADIST", 0, true }, // 270 { "GAMMALN", 1, false }, // 271 { "BETAINV", 0, true }, // 272 { "BINOMDIST", 4, false }, // 273 { "CHIDIST", 2, false }, // 274 { "CHIINV", 2, false }, // 275 { "COMBIN", 2, false }, // 276 { "CONFIDENCE", 3, false }, // 277 { "CRITBINOM", 3, false }, // 278 { "EVEN", 1, false }, // 279 { "EXPONDIST", 3, false }, // 280 { "FDIST", 3, false }, // 281 { "FINV", 3, false }, // 282 { "FISHER", 1, false }, // 283 { "FISHERINV", 1, false }, // 284 { "FLOOR", 2, false }, // 285 { "GAMMADIST", 4, false }, // 286 { "GAMMAINV", 3, false }, // 287 { "CEILING", 2, false }, // 288 { "HYPGEOMDIST", 4, false }, // 289 { "LOGNORMDIST", 3, false }, // 290 { "LOGINV", 3, false }, // 291 { "NEGBINOMDIST", 3, false }, // 292 { "NORMDIST", 4, false }, // 293 { "NORMSDIST", 1, false }, // 294 { "NORMINV", 3, false }, // 295 { "NORMSINV", 1, false }, // 296 { "STANDARDIZE", 3, false }, // 297 { "ODD", 1, false }, // 298 { "PERMUT", 2, false }, // 299 { "POISSON", 3, false }, // 300 { "TDIST", 3, false }, // 301 { "WEIBULL", 4, false }, // 302 { "SUMXMY2", 2, false }, // 303 { "SUMX2MY2", 2, false }, // 304 { "SUMX2DY2", 2, false }, // 305 { "CHITEST", 2, false }, // 306 { "CORREL", 2, false }, // 307 { "COVAR", 2, false }, // 308 { "FORECAST", 3, false }, // 309 { "FTEST", 2, false }, // 310 { "INTERCEPT", 2, false }, // 311 { "PEARSON", 2, false }, // 312 { "RSQ", 2, false }, // 313 { "STEYX", 2, false }, // 314 { "SLOPE", 2, false }, // 315 { "TTEST", 4, false }, // 316 { "PROB", 0, true }, // 317 { "DEVSQ", 0, true }, // 318 { "GEOMEAN", 0, true }, // 319 { "HARMEAN", 0, true }, // 320 { "SUMSQ", 0, true }, // 321 { "KURT", 0, true }, // 322 { "SKEW", 0, true }, // 323 { "ZTEST", 0, true }, // 324 { "LARGE", 2, false }, // 325 { "SMALL", 2, false }, // 326 { "QUARTILE", 2, false }, // 327 { "PERCENTILE", 2, false }, // 328 { "PERCENTRANK", 0, true }, // 329 { "MODALVALUE", 0, true }, // 330 { "TRIMMEAN", 2, false }, // 331 { "TINV", 2, false }, // 332 { "Unknown333", 0, true }, { "MOVIECOMMAND", 0, true }, { "GETMOVIE", 0, true }, { "CONCATENATE", 0, true }, // 336 { "POWER", 2, false }, // 337 { "PIVOTADDDATA", 0, true }, { "GETPIVOTTABLE", 0, true }, { "GETPIVOTFIELD", 0, true }, { "GETPIVOTITEM", 0, true }, { "RADIANS", 1, false }, // 342 { "DEGREES", 1, false }, // 343 { "SUBTOTAL", 0, true }, // 344 { "SUMIF", 0, true }, // 345 { "COUNTIF", 2, true }, // 346 { "COUNTBLANK", 1, true }, // 347 { "SCENARIOGET", 0, true }, { "OPTIONSLISTSGET", 1, false }, { "ISPMT", 4, false }, { "DATEDIF", 3, false }, { "DATESTRING", 1, false }, { "NUMBERSTRING", 2, false }, { "ROMAN", 0, true }, // 354 { "OPENDIALOG", 0, true }, { "SAVEDIALOG", 0, true }, { "VIEWGET", 0, true }, { "GETPIVOTDATA", 2, true }, // 358 { "HYPERLINK", 1, true }, { "PHONETIC", 1, false }, { "AVERAGEA", 0, true }, // 361 { "MAXA", 0, true }, // 362 { "MINA", 0, true }, // 363 { "STDEVPA", 0, true }, // 364 { "VARPA", 0, true }, // 365 { "STDEVA", 0, true }, // 366 { "VARA", 0, true }, // 367 { "BAHTTEXT", 1, false }, // 368 //TODO; following formulas are not supported in Calligra Tables yet { "THAIDAYOFWEEK", 1, false }, // 369 { "THAIDIGIT", 1, false }, // 370 { "THAIMONTHOFYEAR", 1, false }, // 371 { "THAINUMSOUND", 1, false }, // 372 { "THAINUMSTRING", 1, false }, // 373 { "THAISTRINGLENGTH", 1, false }, // 374 { "ISTHAIDIGIT", 1, false }, // 375 { "ROUNDBAHTDOWN", 1, false }, // 376 { "ROUNDBAHTUP", 1, false }, // 377 { "THAIYEAR", 1, false }, // 378 { "RTD", 1, false }, // 379 { "ISHYPERLINK", 1, false } // 380 }; static const FunctionEntry* functionEntry(const QString& functionName) { static QHash entries; if (entries.isEmpty()) { for (int i = 0; i <= 380; i++) { entries[QString::fromLatin1(FunctionEntries[i].name)] = &FunctionEntries[i]; } } return entries.value(functionName); } const char* FormulaToken::functionName() const { if (functionIndex() > 367) return 0; return FunctionEntries[ functionIndex()].name; } unsigned FormulaToken::functionParams() const { unsigned params = 0; if (d->id == Function) { if (functionIndex() > 367) return 0; params = FunctionEntries[ functionIndex()].params; } if (d->id == FunctionVar) { params = (unsigned)d->data[0]; params &= 0x7f; } return params; } unsigned FormulaToken::functionIndex(const QString &functionName) { const FunctionEntry* e = functionEntry(functionName); if (e) return e - FunctionEntries; return -1; } unsigned FormulaToken::functionParams(const QString &functionName) { const FunctionEntry* e = functionEntry(functionName); if (e) return e->params; return 0; } bool FormulaToken::fixedFunctionParams(const QString &functionName) { const FunctionEntry* e = functionEntry(functionName); if (e) return !e->varParams; return false; } unsigned FormulaToken::attr() const { unsigned attr = 0; if (d->id == Attr && d->data.size() > 0) { attr = (unsigned) d->data[0]; } return attr; } unsigned long FormulaToken::nameIndex() const { // FIXME check data size ! unsigned long ni = 0; unsigned char buf[4]; if (d->id == Name) { if (d->ver == Excel97) { buf[0] = d->data[0]; buf[1] = d->data[1]; buf[2] = d->data[2]; buf[3] = d->data[3]; ni = readU32(buf); } else if (d->ver == Excel95) { buf[0] = d->data[8]; buf[1] = d->data[9]; ni = readU16(buf); } } return ni; } unsigned long FormulaToken::nameXIndex() const { // FIXME check data size ! unsigned long ni = 0; unsigned char buf[4]; if (d->id == NameX) { if (d->ver == Excel97) { buf[0] = d->data[2]; buf[1] = d->data[3]; buf[2] = d->data[4]; buf[3] = d->data[5]; ni = readU32(buf); } else if (d->ver == Excel95) { buf[0] = d->data[10]; buf[1] = d->data[11]; ni = readU16(buf); } } return ni; } static QString escapeSheetName(const QString& sheetName) { bool hasSpecial = false; for (int i = 0; i < sheetName.length(); i++) { if (!sheetName[i].isLetterOrNumber()) { hasSpecial = true; break; } } if (!hasSpecial) return sheetName; QString res = sheetName; while(res.startsWith(QLatin1Char('\'')) && res.endsWith(QLatin1Char('\''))) { res.remove(0, 1).chop(1); } return "$'" + res.replace('\'', QLatin1String("\'\'")) + "'"; } QString FormulaToken::area(unsigned row, unsigned col, bool relative) const { // FIXME check data size ! unsigned char buf[2]; int row1Ref, row2Ref, col1Ref, col2Ref; bool row1Relative, col1Relative; bool row2Relative, col2Relative; if (version() == Excel97) { buf[0] = d->data[0]; buf[1] = d->data[1]; row1Ref = readU16(buf); buf[0] = d->data[2]; buf[1] = d->data[3]; row2Ref = readU16(buf); buf[0] = d->data[4]; buf[1] = d->data[5]; col1Ref = readU16(buf); buf[0] = d->data[6]; buf[1] = d->data[7]; col2Ref = readU16(buf); row1Relative = col1Ref & 0x8000; col1Relative = col1Ref & 0x4000; col1Ref &= 0x3fff; row2Relative = col2Ref & 0x8000; col2Relative = col2Ref & 0x4000; col2Ref &= 0x3fff; if (relative) { if (row1Ref & 0x8000) row1Ref -= 0x10000; if (row2Ref & 0x8000) row2Ref -= 0x10000; if (col1Ref & 0x80) col1Ref -= 0x100; if (col2Ref & 0x80) col2Ref -= 0x100; } } else { buf[0] = d->data[0]; buf[1] = d->data[1]; row1Ref = readU16(buf); buf[0] = d->data[2]; buf[1] = d->data[3]; row2Ref = readU16(buf); buf[0] = d->data[4]; buf[1] = 0; col1Ref = readU16(buf); buf[0] = d->data[5]; buf[1] = 0; col2Ref = readU16(buf); row1Relative = row2Ref & 0x8000; col1Relative = row2Ref & 0x4000; row1Ref &= 0x3fff; row2Relative = row2Ref & 0x8000; col2Relative = row2Ref & 0x4000; row2Ref &= 0x3fff; if (relative) { if (row1Ref & 0x2000) row1Ref -= 0x4000; if (row2Ref & 0x2000) row2Ref -= 0x4000; if (col1Ref & 0x80) col1Ref -= 0x100; if (col2Ref & 0x80) col2Ref -= 0x100; } } if (relative) { row1Ref += row; row2Ref += row; col1Ref += col; col2Ref += col; } QString result; result.append(QString("[")); // OpenDocument format if (!col1Relative) result.append(QString("$")); result.append(Cell::columnLabel(col1Ref)); if (!row1Relative) result.append(QString("$")); result.append(QString::number(row1Ref + 1)); result.append(QString(":")); if (!col2Relative) result.append(QString("$")); result.append(Cell::columnLabel(col2Ref)); if (!row2Relative) result.append(QString("$")); result.append(QString::number(row2Ref + 1)); result.append(QString("]")); // OpenDocument format return result; } QString FormulaToken::area3d(const std::vector& externSheets, unsigned /*row*/, unsigned /*col*/) const { if (version() != Excel97) { return QString("Unknown"); } unsigned sheetRef = readU16(&d->data[0]); // FIXME check data size ! unsigned char buf[2]; int row1Ref, row2Ref, col1Ref, col2Ref; bool row1Relative, col1Relative; bool row2Relative, col2Relative; buf[0] = d->data[2]; buf[1] = d->data[3]; row1Ref = readU16(buf); buf[0] = d->data[4]; buf[1] = d->data[5]; row2Ref = readU16(buf); buf[0] = d->data[6]; buf[1] = d->data[7]; col1Ref = readU16(buf); buf[0] = d->data[8]; buf[1] = d->data[9]; col2Ref = readU16(buf); row1Relative = col1Ref & 0x8000; col1Relative = col1Ref & 0x4000; col1Ref &= 0x3fff; row2Relative = col2Ref & 0x8000; col2Relative = col2Ref & 0x4000; col2Ref &= 0x3fff; QString result; result.append(QString("[")); // OpenDocument format if (sheetRef >= externSheets.size()) result.append(QString("Error")); else result.append(escapeSheetName(externSheets[sheetRef])); result.append(QString(".")); if (!col1Relative) result.append(QString("$")); result.append(Cell::columnLabel(col1Ref)); if (!row1Relative) result.append(QString("$")); result.append(QString::number(row1Ref + 1)); result.append(QString(":")); if (!col2Relative) result.append(QString("$")); result.append(Cell::columnLabel(col2Ref)); if (!row2Relative) result.append(QString("$")); result.append(QString::number(row2Ref + 1)); result.append(QString("]")); // OpenDocument format return result; } std::pair FormulaToken::filterArea3d() const { if (version() != Excel97) { return std::make_pair(0, QRect()); } unsigned sheetRef = readU16(&d->data[0]); // FIXME check data size ! unsigned char buf[2]; int row1Ref, row2Ref, col1Ref, col2Ref; buf[0] = d->data[2]; buf[1] = d->data[3]; row1Ref = readU16(buf); buf[0] = d->data[4]; buf[1] = d->data[5]; row2Ref = readU16(buf); buf[0] = d->data[6]; buf[1] = d->data[7]; col1Ref = readU16(buf); buf[0] = d->data[8]; buf[1] = d->data[9]; col2Ref = readU16(buf); col1Ref &= 0x3fff; col2Ref &= 0x3fff; QRect range(col1Ref, row1Ref, col2Ref - col1Ref + 1, row2Ref - row1Ref + 1); return std::make_pair(sheetRef, range); } QString FormulaToken::areaMap(unsigned row, unsigned col) { unsigned char buf[4]; buf[0] = d->data[0]; unsigned ptg = readU8(buf); const int type = (ptg & 0x20 ? 1 : 0) + (ptg & 0x60 ? 2 : 0); //Q_ASSERT(type == 1 || type == 2 || type == 3); buf[0] = d->data[5]; buf[1] = d->data[6]; unsigned cce = readU16(buf); //printf( "SIZE=%i\n", cce ); if (cce < 7) { - printf("Error: Invalid size %i for formula areaMap of type %i\n", cce, type); + qCDebug(lcSidewinder) << QString("Error: Invalid size %1 for formula areaMap of type %2").arg(cce).arg(type); return QString(); } // remove the first seven elements cause they are done d->data.erase(d->data.begin(), d->data.begin() + 7); //unsigned size, const unsigned char* data QString result; switch (type) { case 0x01: // REFERENCE, specifies a reference to a range. result = ref(row, col); break; case 0x02: // VALUE, specifies a single value of a simple type. The type can be a Boolean, a number, a string, or an error code. result = value().asString(); break; case 0x03: // ARRAY, specifies an array of values. result = array(row, col); break; } //d->data.erase(d->data.begin(), d->data.begin() + cce); return result; } QString FormulaToken::ref(unsigned /*row*/, unsigned /*col*/) const { // FIXME check data size ! // FIXME handle shared formula unsigned char buf[2]; int rowRef, colRef; bool rowRelative, colRelative; if (version() == Excel97) { buf[0] = d->data[0]; buf[1] = d->data[1]; rowRef = readU16(buf); buf[0] = d->data[2]; buf[1] = d->data[3]; colRef = readU16(buf); rowRelative = colRef & 0x8000; colRelative = colRef & 0x4000; colRef &= 0x3fff; } else { buf[0] = d->data[0]; buf[1] = d->data[1]; rowRef = readU16(buf); buf[0] = d->data[2]; buf[1] = 0; colRef = readU16(buf); rowRelative = rowRef & 0x8000; colRelative = rowRef & 0x4000; rowRef &= 0x3fff; } QString result; result.append(QString("[")); // OpenDocument format if (!colRelative) result.append(QString("$")); result.append(Cell::columnLabel(colRef)); if (!rowRelative) result.append(QString("$")); result.append(QString::number(rowRef + 1)); result.append(QString("]")); // OpenDocument format return result; } QString FormulaToken::refn(unsigned row, unsigned col) const { // FIXME check data size ! // FIXME handle shared formula unsigned char buf[2]; int rowRef, colRef; bool rowRelative, colRelative; if (version() == Excel97) { buf[0] = d->data[0]; buf[1] = d->data[1]; rowRef = readS16(buf); buf[0] = d->data[2]; buf[1] = d->data[3]; colRef = readU16(buf); rowRelative = colRef & 0x8000; colRelative = colRef & 0x4000; colRef &= 0xff; if (colRef & 0x80) { colRef = colRef - 0x100; } } else { buf[0] = d->data[0]; buf[1] = d->data[1]; rowRef = readU16(buf); buf[0] = d->data[2]; colRef = readS8(buf); rowRelative = rowRef & 0x8000; colRelative = rowRef & 0x4000; rowRef &= 0x3fff; if (rowRef & 0x2000) { rowRef = rowRef - 0x4000; } } if (colRelative) colRef += col; if (rowRelative) rowRef += row; QString result; result.append(QString("[")); // OpenDocument format if (!colRelative) result.append(QString("$")); result.append(Cell::columnLabel(qMax(0, colRef))); if (!rowRelative) result.append(QString("$")); result.append(QString::number(rowRef + 1)); result.append(QString("]")); // OpenDocument format return result; } QString FormulaToken::ref3d(const std::vector& externSheets, unsigned /*row*/, unsigned /*col*/) const { if (version() != Excel97) { return QString("Unknown"); } unsigned sheetRef = readU16(&d->data[0]); // FIXME check data size ! // FIXME handle shared formula unsigned char buf[2]; int rowRef, colRef; bool rowRelative, colRelative; buf[0] = d->data[2]; buf[1] = d->data[3]; rowRef = readU16(buf); buf[0] = d->data[4]; buf[1] = d->data[5]; colRef = readU16(buf); rowRelative = colRef & 0x8000; colRelative = colRef & 0x4000; colRef &= 0x3fff; QString result; result.append(QString("[")); // OpenDocument format if (sheetRef >= externSheets.size()) result.append(QString("Error")); else result.append(escapeSheetName(externSheets[sheetRef])); result.append(QString(".")); if (!colRelative) result.append(QString("$")); result.append(Cell::columnLabel(colRef)); if (!rowRelative) result.append(QString("$")); result.append(QString::number(rowRef + 1)); result.append(QString("]")); // OpenDocument format return result; } QString FormulaToken::array(unsigned row, unsigned col) const { Q_UNUSED(row); Q_UNUSED(col); #ifdef __GNUC__ #warning TODO Implement FormulaToken::array() #endif - printf("Unhandled formula array-token with row=%i and column=%i\n", row, col); + qCDebug(lcSidewinder) << QString("Unhandled formula array-token with row=%1 and column=%2").arg(row).arg(col); /* unsigned char buf[2]; buf[0] = d->data[1]; // specs say this should be at the first byte but seems its not true... const unsigned opts = readU8(buf); const bool reserved1 = opts & 0x01; const bool reserved2 = opts & 0x02; const bool reserved3 = opts & 0x04; const bool reserved4 = opts & 0x08; const bool reserved5 = opts & 0x10; Q_ASSERT(!reserved1 && !reserved2 && !reserved3 && !reserved4 && !reserved5); const int type = ((opts & 0x20) ? 1 : 0) + ((opts & 0x60) ? 2 : 0); printf("%i\n",type); Q_ASSERT(type == 2 || type == 3); // remove the first two elements cause they are done d->data.erase(d->data.begin(), d->data.begin() + 2); QString result; switch (type) { case 0x01: // REFERENCE, specifies a reference to a range. result = ref(row, col); break; case 0x02: // VALUE, specifies a single value of a simple type. The type can be a Boolean, a number, a string, or an error code. result = value().asString(); break; case 0x03: // ARRAY, specifies an array of values. result = array(row, col); break; } //Q_ASSERT(false); return result; */ return QString(); } std::pair FormulaToken::baseFormulaRecord() const { if (version() == Excel97) { return std::make_pair(readU16(&d->data[0]), readU16(&d->data[2])); } else { return std::make_pair(readU16(&d->data[0]), (unsigned)d->data[2]); } } std::ostream& operator<<(std::ostream& s, Swinder::FormulaToken token) { s << std::setw(2) << std::hex << token.id() << std::dec; // s << " Size: " << std::dec << token.size(); s << " "; switch (token.id()) { case FormulaToken::ErrorCode: case FormulaToken::Bool: case FormulaToken::Integer: case FormulaToken::Float: case FormulaToken::String: { Value v = token.value(); s << v; } break; case FormulaToken::Function: s << "Function " << token.functionName(); break; default: s << token.idAsString(); break; } return s; } FormulaTokens FormulaDecoder::decodeFormula(unsigned size, unsigned pos, const unsigned char* data, unsigned version) { FormulaTokens tokens; const unsigned formula_len = readU16(data + pos); if (formula_len + pos + 2 > size) { - std::cerr << "formula is longer than available data" << std::endl; + qCWarning(lcSidewinder) << "formula is longer than available data"; return tokens; } for (unsigned j = pos + 2; j < size;) { unsigned ptg = data[j++]; ptg = ((ptg & 0x40) ? (ptg | 0x20) : ptg) & 0x3F; FormulaToken t(ptg); t.setVersion(version); if (t.id() == FormulaToken::String) { // find bytes taken to represent the string EString estr = (version == Excel97) ? EString::fromUnicodeString(data + j, false, formula_len) : EString::fromByteString(data + j, false, formula_len); t.setData(estr.size(), data + j); j += estr.size(); } else { // normal, fixed-size token if (t.size() > 0) { t.setData(t.size(), data + j); j += t.size(); } } tokens.push_back(t); } return tokens; } typedef std::vector UStringStack; static void mergeTokens(UStringStack* stack, unsigned count, const QString &mergeString) { if (!stack) return; if (stack->size() < count) return; QString s1, s2; while (count) { count--; QString last = (*stack)[stack->size()-1]; QString tmp = last; tmp.append(s1); s1 = tmp; if (count) { tmp = mergeString; tmp.append(s1); s1 = tmp; } stack->resize(stack->size() - 1); } stack->push_back(s1); } #ifdef SWINDER_XLS2RAW static void dumpStack(std::vector stack) { std::cout << std::endl; std::cout << "Stack now is: " ; if (stack.empty()) std::cout << "(empty)" ; for (unsigned i = 0; i < stack.size(); ++i) std::cout << " " << i << ": " << stack[i] << std::endl; std::cout << std::endl; } #endif QString FormulaDecoder::decodeFormula(unsigned row, unsigned col, bool isShared, const FormulaTokens& tokens) { UStringStack stack; for (unsigned c = 0; c < tokens.size(); ++c) { FormulaToken token = tokens[c]; #ifdef SWINDER_XLS2RAW std::cout << "Formula Token " << c << ": "; std::cout << token.id() << " "; std::cout << token.idAsString() << std::endl; #endif switch (token.id()) { case FormulaToken::Add: mergeTokens(&stack, 2, QString("+")); break; case FormulaToken::Sub: mergeTokens(&stack, 2, QString("-")); break; case FormulaToken::Mul: mergeTokens(&stack, 2, QString("*")); break; case FormulaToken::Div: mergeTokens(&stack, 2, QString("/")); break; case FormulaToken::Power: mergeTokens(&stack, 2, QString("^")); break; case FormulaToken::Concat: mergeTokens(&stack, 2, QString("&")); break; case FormulaToken::LT: mergeTokens(&stack, 2, QString("<")); break; case FormulaToken::LE: mergeTokens(&stack, 2, QString("<=")); break; case FormulaToken::EQ: mergeTokens(&stack, 2, QString("=")); break; case FormulaToken::GE: mergeTokens(&stack, 2, QString(">=")); break; case FormulaToken::GT: mergeTokens(&stack, 2, QString(">")); break; case FormulaToken::NE: mergeTokens(&stack, 2, QString("<>")); break; case FormulaToken::Intersect: mergeTokens(&stack, 2, QString("!")); break; case FormulaToken::Union: mergeTokens(&stack, 2, QString("~")); break; case FormulaToken::Range: mergeTokens(&stack, 2, QString(":")); break; case FormulaToken::UPlus: { QString str("+"); str.append(stack[stack.size()-1]); stack[stack.size()-1] = str; break; } case FormulaToken::UMinus: { QString str("-"); str.append(stack[ stack.size()-1 ]); stack[stack.size()-1] = str; break; } case FormulaToken::Percent: stack[stack.size()-1].append(QString("%")); break; case FormulaToken::Paren: { QString str("("); str.append(stack[ stack.size()-1 ]); str.append(QString(")")); stack[stack.size()-1] = str; break; } case FormulaToken::MissArg: // just ignore stack.push_back(QString(" ")); break; case FormulaToken::String: { QString str('\"'); str.append(token.value().asString()); str.append(QString('\"')); stack.push_back(str); break; } case FormulaToken::Bool: if (token.value().asBoolean()) stack.push_back(QString("TRUE")); else stack.push_back(QString("FALSE")); break; case FormulaToken::Integer: stack.push_back(QString::number(token.value().asInteger())); break; case FormulaToken::Float: stack.push_back(QString::number(token.value().asFloat())); break; case FormulaToken::Array: stack.push_back(token.array(row, col)); break; case FormulaToken::Ref: stack.push_back(token.ref(row, col)); break; case FormulaToken::RefN: stack.push_back(token.refn(row, col)); break; case FormulaToken::Ref3d: stack.push_back(token.ref3d(externSheets(), row, col)); break; case FormulaToken::Area: stack.push_back(token.area(row, col)); break; case FormulaToken::AreaN: stack.push_back(token.area(row, col, true)); break; case FormulaToken::Area3d: stack.push_back(token.area3d(externSheets(), row, col)); break; case FormulaToken::Function: { mergeTokens(&stack, token.functionParams(), QString(";")); if (!stack.empty()) { QString str(token.functionName() ? token.functionName() : "??"); str.append(QString("(")); str.append(stack[stack.size()-1]); str.append(QString(")")); stack[stack.size()-1] = str; } break; } case FormulaToken::FunctionVar: if (token.functionIndex() != 255) { mergeTokens(&stack, token.functionParams(), QString(";")); if (!stack.empty()) { QString str; if (token.functionIndex() != 255) str = token.functionName() ? token.functionName() : "??"; str.append(QString("(")); str.append(stack[stack.size()-1]); str.append(QString(")")); stack[stack.size()-1] = str; } } else { unsigned count = token.functionParams() - 1; mergeTokens(&stack, count, QString(";")); if (!stack.empty()) { QString str; str.append(QString("(")); str.append(stack[ stack.size()-1 ]); str.append(QString(")")); stack[stack.size()-1] = str; } } break; case FormulaToken::Attr: if (token.attr() & 0x10) { // SUM mergeTokens(&stack, 1, QString(";")); if (!stack.empty()) { QString str("SUM"); str.append(QString("(")); str.append(stack[ stack.size()-1 ]); str.append(QString(")")); stack[stack.size()-1] = str; } } break; case FormulaToken::Name: stack.push_back(nameFromIndex(token.nameIndex()-1)); break; case FormulaToken::NameX: stack.push_back(externNameFromIndex(token.nameXIndex()-1)); break; case FormulaToken::Matrix: { std::pair formulaCellPos = token.baseFormulaRecord(); if( isShared ) { FormulaTokens ft = sharedFormulas(formulaCellPos); if (!ft.empty()) stack.push_back(decodeFormula(row, col, isShared, ft)); } else { // "2.5.198.58 PtgExp" says that if its not a sharedFormula then it's an indication that the // result is an reference to cells. So, we can savly ignore that case... - std::cout << "MATRIX first=%i second=" << formulaCellPos.first << " " << formulaCellPos.second << std::endl; + qCDebug(lcSidewinder) << "MATRIX first=%i second=" << formulaCellPos.first << formulaCellPos.second; } break; } case FormulaToken::Table: { std::pair formulaCellPos = token.baseFormulaRecord(); if( isShared ) { DataTableRecord* dt = tableRecord(formulaCellPos); if(dt) stack.push_back(dataTableFormula(row, col, dt)); } else { - std::cout << "TABLE first=%i second=" << formulaCellPos.first << " " << formulaCellPos.second << std::endl; + qCDebug(lcSidewinder) << "TABLE first=%i second=" << formulaCellPos.first << formulaCellPos.second; } break; } case FormulaToken::MemArea: // does nothing break; case FormulaToken::AreaErr: case FormulaToken::AreaErr3d: case FormulaToken::RefErr: case FormulaToken::RefErr3d: stack.push_back(QString("#REF!")); break; case FormulaToken::MemErr: // specifies that the result is an error-code break; case FormulaToken::ErrorCode: stack.push_back(token.value().asString()); break; case 0: break; // NOPE case FormulaToken::MemFunc: // as far as I can tell this is only meta-data break; case FormulaToken::NatFormula: case FormulaToken::Sheet: case FormulaToken::EndSheet: case FormulaToken::MemNoMem: case FormulaToken::MemAreaN: case FormulaToken::MemNoMemN: default: // FIXME handle this ! - std::cout << "Unhandled token=" << token.idAsString() << std::endl; + qCDebug(lcSidewinder) << "Unhandled token=" << token.idAsString(); stack.push_back(QString("Unknown")); break; }; #ifdef SWINDER_XLS2RAW dumpStack(stack); #endif } QString result; for (unsigned i = 0; i < stack.size(); ++i) result.append(stack[i]); #ifdef SWINDER_XLS2RAW std::cout << "FORMULA Result: " << result << std::endl; #endif return result; } QString FormulaDecoder::dataTableFormula(unsigned row, unsigned col, const DataTableRecord* record) { QString result("MULTIPLE.OPERATIONS("); unsigned formulaRow = 0, formulaCol = 0; switch (record->direction()) { case DataTableRecord::InputRow: formulaRow = row; formulaCol = record->firstColumn() - 1; break; case DataTableRecord::InputColumn: formulaRow = record->firstRow() - 1; formulaCol = col; break; case DataTableRecord::Input2D: formulaRow = record->firstRow() - 1; formulaCol = record->firstColumn() - 1; break; } result.append(QString("[.$")); result.append(Cell::columnLabel(formulaCol)); result.append(QString("$")); result.append(QString::number(formulaRow + 1)); result.append(QString("]")); if (record->direction() == DataTableRecord::Input2D) { result.append(QString(";[.$")); result.append(Cell::columnLabel(record->inputColumn2())); result.append(QString("$")); result.append(QString::number(record->inputRow2() + 1)); result.append(QString("]")); } else { result.append(QString(";[.$")); result.append(Cell::columnLabel(record->inputColumn1())); result.append(QString("$")); result.append(QString::number(record->inputRow1() + 1)); result.append(QString("]")); } if (record->direction() == DataTableRecord::Input2D || record->direction() == DataTableRecord::InputColumn) { result.append(QString(";[.$")); result.append(Cell::columnLabel(record->firstColumn() - 1)); result.append(QString::number(row + 1)); result.append(QString("]")); } if (record->direction() == DataTableRecord::Input2D) { result.append(QString(";[.$")); result.append(Cell::columnLabel(record->inputColumn1())); result.append(QString("$")); result.append(QString::number(record->inputRow1() + 1)); result.append(QString("]")); } if (record->direction() == DataTableRecord::Input2D || record->direction() == DataTableRecord::InputRow) { result.append(QString(";[.")); result.append(Cell::columnLabel(col)); result.append(QString("$")); result.append(QString::number(record->firstRow() - 1 + 1)); result.append(QString("]")); } result.append(QString(")")); #ifdef SWINDER_XLS2RAW std::cout << "DATATABLE Result: " << result << std::endl; #endif return result; } } // namespace Swinder diff --git a/filters/sheets/excel/sidewinder/globalssubstreamhandler.cpp b/filters/sheets/excel/sidewinder/globalssubstreamhandler.cpp index 5ac1fc1f512..201c2dc72ea 100644 --- a/filters/sheets/excel/sidewinder/globalssubstreamhandler.cpp +++ b/filters/sheets/excel/sidewinder/globalssubstreamhandler.cpp @@ -1,792 +1,793 @@ /* Swinder - Portable library for spreadsheet Copyright (C) 2003-2005 Ariya Hidayat Copyright (C) 2006,2009 Marijn Kruisselbrink Copyright (C) 2009,2010 Sebastian Sauer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "globalssubstreamhandler.h" #include #include #include "excel.h" #include "sheet.h" #include "workbook.h" #include "decrypt.h" namespace Swinder { class GlobalsSubStreamHandler::Private { public: Workbook* workbook; // version of workbook unsigned version; // mapping from BOF pos to actual Sheet std::map bofMap; // for EXTERNBOOK and EXTERNSHEET std::vector externBookTable; std::vector externSheetTable; // for NAME std::vector nameTable; // for EXTERNNAME std::vector externNameTable; // password protection flag // TODO: password hash for record decryption bool passwordProtected; RC4Decryption* decryption; // table of font std::vector fontTable; // table of format std::map formatsTable; // cache of formats std::map formatCache; // shared-string table std::vector stringTable; std::vector > formatRunsTable; // table of Xformat std::vector xfTable; // list of chart sheets QList< Sheet* > chartSheets; }; GlobalsSubStreamHandler::GlobalsSubStreamHandler(Workbook* workbook, unsigned version) : SubStreamHandler(), FormulaDecoder(), d(new Private) { d->workbook = workbook; d->version = version; d->passwordProtected = false; d->decryption = 0; } GlobalsSubStreamHandler::~GlobalsSubStreamHandler() { delete d->decryption; delete d; } Workbook* GlobalsSubStreamHandler::workbook() const { return d->workbook; } bool GlobalsSubStreamHandler::passwordProtected() const { return d->passwordProtected; } bool GlobalsSubStreamHandler::encryptionTypeSupported() const { return d->decryption; } void GlobalsSubStreamHandler::decryptionSkipBytes(int count) { if (d->decryption) d->decryption->skipBytes(count); } void GlobalsSubStreamHandler::decryptRecord(unsigned type, unsigned size, unsigned char* buffer) { if (!d->decryption) return; if (type == BOFRecord::id || type == FilepassRecord::id || type == UsrExclRecord::id || type == FileLockRecord::id || type == InterfaceHdrRecord::id || type == RRDInfoRecord::id || type == RRDHeadRecord::id) { d->decryption->skipBytes(size); } else if (type == BoundSheetRecord::id && size >= 4) { /* skip only first 4 bytes */ d->decryption->skipBytes(4); d->decryption->decryptBytes(size-4, buffer+4); } else { d->decryption->decryptBytes(size, buffer); } } unsigned GlobalsSubStreamHandler::version() const { return d->version; } Sheet* GlobalsSubStreamHandler::sheetFromPosition(unsigned position) const { std::map::iterator iter = d->bofMap.find(position); if (iter != d->bofMap.end()) return iter->second; else return 0; } QString GlobalsSubStreamHandler::stringFromSST(unsigned index) const { if (index < d->stringTable.size()) return d->stringTable[index]; else return QString(); } std::map GlobalsSubStreamHandler::formatRunsFromSST(unsigned index) const { if (index < d->formatRunsTable.size()) return d->formatRunsTable[index]; else return std::map(); } unsigned GlobalsSubStreamHandler::fontCount() const { return d->fontTable.size(); } FontRecord GlobalsSubStreamHandler::fontRecord(unsigned index) const { if (index < d->fontTable.size()) return d->fontTable[index]; else return FontRecord(d->workbook); } unsigned GlobalsSubStreamHandler::xformatCount() const { return d->xfTable.size(); } XFRecord GlobalsSubStreamHandler::xformat(unsigned index) const { if (index < d->xfTable.size()) return d->xfTable[index]; else return XFRecord(d->workbook); } QString GlobalsSubStreamHandler::valueFormat(unsigned index) const { std::map::iterator it = d->formatsTable.find(index); if (it != d->formatsTable.end()) return it->second; else return QString(); } const std::vector& GlobalsSubStreamHandler::externSheets() const { return d->externSheetTable; } QString GlobalsSubStreamHandler::nameFromIndex(unsigned index) const { if (index < d->nameTable.size()) return d->nameTable[index]; - std::cerr << "Invalid index in GlobalsSubStreamHandler::nameFromIndex index=" << index << " size=" << d->externNameTable.size() << std::endl; + qCWarning(lcSidewinder) << "Invalid index in GlobalsSubStreamHandler::nameFromIndex index=" << index << "size=" << d->externNameTable.size(); return QString(); } QString GlobalsSubStreamHandler::externNameFromIndex(unsigned index) const { if (index < d->externNameTable.size()) return d->externNameTable[index]; - std::cerr << "Invalid index in GlobalsSubStreamHandler::externNameFromIndex index=" << index << " size=" << d->externNameTable.size() << std::endl; + qCWarning(lcSidewinder) << "Invalid index in GlobalsSubStreamHandler::externNameFromIndex index=" << index + << "size=" << d->externNameTable.size(); return QString(); } // convert border style, e.g MediumDashed to a Pen static Pen convertBorderStyle(unsigned style) { Pen pen; switch (style) { case XFRecord::NoLine: pen.width = 0; pen.style = Pen::NoLine; break; case XFRecord::Thin: pen.width = 0.5f; pen.style = Pen::SolidLine; break; case XFRecord::Medium: pen.width = 1; pen.style = Pen::SolidLine; break; case XFRecord::Dashed: pen.width = 0.5f; pen.style = Pen::DashLine; break; case XFRecord::Dotted: pen.width = 0.5f; pen.style = Pen::DotLine; break; case XFRecord::Thick: pen.width = 2; pen.style = Pen::SolidLine; break; case XFRecord::Double: pen.width = 0.5f; pen.style = Pen::DoubleLine; break; case XFRecord::Hair: // FIXME no equivalent ? pen.width = 0.1f; pen.style = Pen::DotLine; break; case XFRecord::MediumDashed: pen.width = 1; pen.style = Pen::DashLine; break; case XFRecord::ThinDashDotted: pen.width = 0.5f; pen.style = Pen::DashDotLine; break; case XFRecord::MediumDashDotted: pen.width = 1; pen.style = Pen::DashDotLine; break; case XFRecord::ThinDashDotDotted: pen.width = 0.5f; pen.style = Pen::DashDotDotLine; break; case XFRecord::MediumDashDotDotted: pen.width = 1; pen.style = Pen::DashDotDotLine; break; case XFRecord::SlantedMediumDashDotted: // FIXME no equivalent ? pen.width = 1; pen.style = Pen::DashDotLine; break; default: // fallback, simple solid line pen.width = 0.5f; pen.style = Pen::SolidLine; break; } return pen; } static unsigned convertPatternStyle(unsigned pattern) { switch (pattern) { case 0x00: return FormatBackground::EmptyPattern; case 0x01: return FormatBackground::SolidPattern; case 0x02: return FormatBackground::Dense4Pattern; case 0x03: return FormatBackground::Dense3Pattern; case 0x04: return FormatBackground::Dense5Pattern; case 0x05: return FormatBackground::HorPattern; case 0x06: return FormatBackground::VerPattern; case 0x07: return FormatBackground::FDiagPattern; case 0x08: return FormatBackground::BDiagPattern; case 0x09: return FormatBackground::Dense1Pattern; case 0x0A: return FormatBackground::Dense2Pattern; case 0x0B: return FormatBackground::HorPattern; case 0x0C: return FormatBackground::VerPattern; case 0x0D: return FormatBackground::FDiagPattern; case 0x0E: return FormatBackground::BDiagPattern; case 0x0F: return FormatBackground::CrossPattern; case 0x10: return FormatBackground::DiagCrossPattern; case 0x11: return FormatBackground::Dense6Pattern; case 0x12: return FormatBackground::Dense7Pattern; default: return FormatBackground::SolidPattern; // fallback } } // big task: convert Excel XFormat into Swinder::Format const Format* GlobalsSubStreamHandler::convertedFormat(unsigned index) const { static const Format blankFormat; if (index >= xformatCount()) return &blankFormat; int& formatIt = d->formatCache[index]; if (formatIt) return workbook()->format(formatIt-1); Format format; XFRecord xf = xformat(index); QString valueFormat = this->valueFormat(xf.formatIndex()); if (valueFormat.isEmpty()) { const unsigned ifmt = xf.formatIndex(); switch (ifmt) { case 0: valueFormat = "General"; break; case 1: valueFormat = "0"; break; case 2: valueFormat = "0.00"; break; case 3: valueFormat = "#,##0"; break; case 4: valueFormat = "#,##0.00"; break; case 5: valueFormat = "\"$\"#,##0_);(\"S\"#,##0)"; break; case 6: valueFormat = "\"$\"#,##0_);[Red](\"S\"#,##0)"; break; case 7: valueFormat = "\"$\"#,##0.00_);(\"S\"#,##0.00)"; break; case 8: valueFormat = "\"$\"#,##0.00_);[Red](\"S\"#,##0.00)"; break; case 9: valueFormat = "0%"; break; case 10: valueFormat = "0.00%"; break; case 11: valueFormat = "0.00E+00"; break; case 12: valueFormat = "#?/?"; break; case 13: valueFormat = "#\?\?/\?\?"; break; case 14: valueFormat = "M/D/YY"; break; case 15: valueFormat = "D-MMM-YY"; break; case 16: valueFormat = "D-MMM"; break; case 17: valueFormat = "MMM-YY"; break; case 18: valueFormat = "h:mm AM/PM"; break; case 19: valueFormat = "h:mm:ss AM/PM"; break; case 20: valueFormat = "h:mm"; break; case 21: valueFormat = "h:mm:ss"; break; case 22: valueFormat = "M/D/YY h:mm"; break; case 37: valueFormat = "_(#,##0_);(#,##0)"; break; case 38: valueFormat = "_(#,##0_);[Red](#,##0)"; break; case 39: valueFormat = "_(#,##0.00_);(#,##0)"; break; case 40: valueFormat = "_(#,##0.00_);[Red](#,##0)"; break; case 41: valueFormat = "_(\"$\"*#,##0_);_(\"$\"*#,##0_);_(\"$\"*\"-\");(@_)"; break; case 42: valueFormat = "_(*#,##0_);(*(#,##0);_(*\"-\");_(@_)"; break; case 43: valueFormat = "_(\"$\"*#,##0.00_);_(\"$\"*#,##0.00_);_(\"$\"*\"-\");(@_)"; break; case 44: valueFormat = "_(\"$\"*#,##0.00_);_(\"$\"*#,##0.00_);_(\"$\"*\"-\");(@_)"; break; case 45: valueFormat = "mm:ss"; break; case 46: valueFormat = "[h]:mm:ss"; break; case 47: valueFormat = "mm:ss.0"; break; case 48: valueFormat = "##0.0E+0"; break; case 49: valueFormat = "@"; break; default: { if (ifmt >= 164 && ifmt <= 392) { // custom format valueFormat = d->formatsTable[ifmt]; } else { - std::cout << "Unhandled format with index " << xf.formatIndex() << ". Using general format." << std::endl; + qCDebug(lcSidewinder) << "Unhandled format with index" << xf.formatIndex() << ". Using general format."; valueFormat = "General"; } } break; } } format.setValueFormat(valueFormat); format.setFont(d->workbook->font(xf.fontIndex())); FormatAlignment alignment; switch (xf.horizontalAlignment()) { case XFRecord::Left: alignment.setAlignX(Format::Left); break; case XFRecord::Right: alignment.setAlignX(Format::Right); break; case XFRecord::Centered: alignment.setAlignX(Format::Center); break; case XFRecord::Justified: alignment.setAlignX(Format::Justify); break; case XFRecord::Distributed: alignment.setAlignX(Format::Distributed); break; case XFRecord::Filled: case XFRecord::CenteredSelection: default: break; // FIXME still unsupported: CenteredSelection, Filled } switch (xf.verticalAlignment()) { case XFRecord::Top: alignment.setAlignY(Format::Top); break; case XFRecord::VCentered: alignment.setAlignY(Format::Middle); break; case XFRecord::Bottom: alignment.setAlignY(Format::Bottom); break; case XFRecord::VJustified: alignment.setAlignY(Format::VJustify); break; case XFRecord::VDistributed: alignment.setAlignY(Format::VDistributed); break; default: break; // FIXME still unsupported: Justified, Distributed } alignment.setWrap(xf.isTextWrap()); unsigned angle = xf.rotationAngle(); if (angle > 90) angle = 360 - (angle - 90); alignment.setRotationAngle(angle); alignment.setStackedLetters(xf.stackedLetters()); alignment.setShrinkToFit(xf.isShrinkToFit()); alignment.setIndentLevel(xf.indentationLevel()); format.setAlignment(alignment); FormatBorders borders; Pen pen; pen = convertBorderStyle(xf.leftBorderStyle()); pen.color = d->workbook->color(xf.leftBorderColor()); borders.setLeftBorder(pen); pen = convertBorderStyle(xf.rightBorderStyle()); pen.color = d->workbook->color(xf.rightBorderColor()); borders.setRightBorder(pen); pen = convertBorderStyle(xf.topBorderStyle()); pen.color = d->workbook->color(xf.topBorderColor()); borders.setTopBorder(pen); pen = convertBorderStyle(xf.bottomBorderStyle()); pen.color = d->workbook->color(xf.bottomBorderColor()); borders.setBottomBorder(pen); if(xf.isDiagonalTopLeftBorder()) { pen = convertBorderStyle(xf.diagonalBorderStyle()); pen.color = d->workbook->color(xf.diagonalBorderColor()); borders.setTopLeftBorder(pen); } if(xf.isDiagonalBottomLeftBorder()) { pen = convertBorderStyle(xf.diagonalBorderStyle()); pen.color = d->workbook->color(xf.diagonalBorderColor()); borders.setBottomLeftBorder(pen); } format.setBorders(borders); FormatBackground background; background.setForegroundColor(d->workbook->color(xf.patternForeColor())); background.setBackgroundColor(d->workbook->color(xf.patternBackColor())); background.setPattern(convertPatternStyle(xf.fillPattern())); format.setBackground(background); formatIt = workbook()->addFormat(format) + 1; return workbook()->format(formatIt-1); } void GlobalsSubStreamHandler::handleRecord(Record* record) { if (!record) return; const unsigned type = record->rtti(); if (type == BOFRecord::id) handleBOF(static_cast(record)); else if (type == BoundSheetRecord::id) handleBoundSheet(static_cast(record)); else if (type == ExternBookRecord::id) handleExternBook(static_cast(record)); else if (type == ExternNameRecord::id) handleExternName(static_cast(record)); else if (type == ExternSheetRecord::id) handleExternSheet(static_cast(record)); else if (type == FilepassRecord::id) handleFilepass(static_cast(record)); else if (type == FormatRecord::id) handleFormat(static_cast(record)); else if (type == FontRecord::id) handleFont(static_cast(record)); else if (type == NameRecord::id) handleName(static_cast(record)); else if (type == PaletteRecord::id) handlePalette(static_cast(record)); else if (type == SSTRecord::id) handleSST(static_cast(record)); else if (type == XFRecord::id) handleXF(static_cast(record)); else if (type == ProtectRecord::id) handleProtect(static_cast(record)); else if (type == MsoDrawingGroupRecord::id) handleMsoDrawingGroup(static_cast(record)); else if (type == Window1Record::id) handleWindow1(static_cast(record)); else if (type == PasswordRecord::id) handlePassword(static_cast(record)); else if (type == DateModeRecord::id) handleDateMode(static_cast(record)); else if (type == 0x40) {} //BackupRecord else if (type == 0xA) {} //EofRecord //else if (type == 0xEC) Q_ASSERT(false); // MsoDrawing else { - //std::cout << "Unhandled global record with type=" << type << " name=" << record->name() << std::endl; + //qCDebug(lcSidewinder) << "Unhandled global record with type=" << type << " name=" << record->name(); } } void GlobalsSubStreamHandler::handleBOF(BOFRecord* record) { if (!record) return; if (record->type() == BOFRecord::Workbook) { d->version = record->version(); } else { - std::cout << "GlobalsSubStreamHandler::handleBOF: Unhandled type=" << record->type() << std::endl; + qCDebug(lcSidewinder) << "GlobalsSubStreamHandler::handleBOF: Unhandled type=" << record->type(); } } void GlobalsSubStreamHandler::handleBoundSheet(BoundSheetRecord* record) { if (!record) return; switch (record->sheetType()) { case BoundSheetRecord::Chart: // chartsheets are worksheets too case BoundSheetRecord::Worksheet: { // create a new sheet Sheet* sheet = new Sheet(d->workbook); sheet->setName(record->sheetName()); sheet->setVisible(record->sheetState() == BoundSheetRecord::Visible); d->workbook->appendSheet(sheet); if(record->sheetType() == BoundSheetRecord::Chart) d->chartSheets << sheet; // update bof position map unsigned bofPos = record->bofPosition(); d->bofMap[ bofPos ] = sheet; } break; default: - std::cout << "GlobalsSubStreamHandler::handleBoundSheet: Unhandled type=" << record->sheetType() << std::endl; + qCDebug(lcSidewinder) << "GlobalsSubStreamHandler::handleBoundSheet: Unhandled type=" << record->sheetType(); break; } } void GlobalsSubStreamHandler::handleDateMode(DateModeRecord* record) { if (!record) return; if (record->isBase1904()) d->workbook->setBaseDate(QDateTime(QDate(1904, 1, 1))); else d->workbook->setBaseDate(QDateTime(QDate(1899, 12, 30))); } void GlobalsSubStreamHandler::handleExternBook(ExternBookRecord* record) { if (!record) return; d->externBookTable.push_back(record->bookName()); } void GlobalsSubStreamHandler::handleExternName(ExternNameRecord* record) { if (!record) return; d->externNameTable.push_back(record->externName()); } void GlobalsSubStreamHandler::handleExternSheet(ExternSheetRecord* record) { if (!record) return; d->externSheetTable.resize(record->refCount()); for (unsigned i = 0; i < record->refCount(); ++i) { unsigned bookRef = record->bookRef(i); QString result; if (bookRef >= d->externBookTable.size()) { result = QString("Error"); } else { QString book = d->externBookTable[bookRef]; if (book == "\004") { unsigned sheetRef = record->firstSheetRef(i); if (sheetRef >= d->workbook->sheetCount()) { result = QString("Error"); } else { result = d->workbook->sheet(sheetRef)->name(); } } else { result = book; } } if (result.indexOf(' ') != -1 || result.indexOf('\'') != -1) { // escape string QString outp("'"); for (int idx = 0; idx < result.length(); idx++) { if (result[idx] == '\'') outp.append(QString("''")); else outp.append(QString(result[idx])); } result = outp + QString("'"); } d->externSheetTable[i] = result; } } void GlobalsSubStreamHandler::handleFilepass(FilepassRecord* record) { if (!record) return; if (record->encryptionType() == FilepassRecord::RC4Encryption && record->encryptionVersionMajor() == 1) { d->decryption = new RC4Decryption(record->salt(), record->encryptedVerifier(), record->encryptedVerifierHash()); if (!d->decryption->checkPassword("VelvetSweatshop")) { delete d->decryption; d->decryption = 0; - fprintf(stderr, "Invalid password\n"); + qCWarning(lcSidewinder) << "Invalid password"; } else { d->decryption->setInitialPosition(record->position() + 54+4); } } d->passwordProtected = true; } void GlobalsSubStreamHandler::handleFont(FontRecord* record) { if (!record) return; d->fontTable.push_back(*record); // font #4 is never used, so add a dummy one if (d->fontTable.size() == 4) { d->fontTable.push_back(FontRecord(d->workbook)); } else { FormatFont font; font.setFontSize(record->height() / 20.0); font.setFontFamily(record->fontName()); font.setColor(d->workbook->color(record->colorIndex())); font.setBold(record->fontWeight() > 500); font.setItalic(record->isItalic()); font.setStrikeout(record->isStrikeout()); font.setSubscript(record->escapement() == FontRecord::Subscript); font.setSuperscript(record->escapement() == FontRecord::Superscript); font.setUnderline(record->underline() != FontRecord::None); d->workbook->setFont(d->fontTable.size() - 1, font); } } void GlobalsSubStreamHandler::handleFormat(FormatRecord* record) { if (!record) return; d->formatsTable[record->index()] = record->formatString(); } void GlobalsSubStreamHandler::handleName(NameRecord* record) { if (!record) return; d->nameTable.push_back(record->definedName()); if(record->m_formula.id() != FormulaToken::Unused) { if (record->isBuiltin()) { if (record->definedName() == "_FilterDatabase") { if (record->m_formula.id() == FormulaToken::Area3d) { std::pair area = record->m_formula.filterArea3d(); QString sheetName = "Error"; if (area.first < d->externSheetTable.size()) { sheetName = d->externSheetTable[area.first]; } d->workbook->addFilterRange(sheetName, area.second); } } } else { FormulaTokens tokens; tokens.push_back(record->m_formula); QString f = decodeFormula(0, 0, false, tokens); if(!f.isEmpty()) { QString n = record->definedName(); d->workbook->setNamedArea(record->sheetIndex(), n, f); } } } } void GlobalsSubStreamHandler::handlePalette(PaletteRecord* record) { if (!record) return; QList colorTable; for (unsigned i = 0; i < record->count(); ++i) colorTable.append(QColor(record->red(i), record->green(i), record->blue(i))); d->workbook->setColorTable(colorTable); } void GlobalsSubStreamHandler::handleSST(SSTRecord* record) { if (!record) return; d->stringTable.clear(); d->formatRunsTable.clear(); for (unsigned i = 0; i < record->count(); ++i) { QString str = record->stringAt(i); d->stringTable.push_back(str); std::map formatRunsRaw = record->formatRunsAt(i); std::map formatRuns; for (std::map::iterator it = formatRunsRaw.begin(); it != formatRunsRaw.end(); ++it) { formatRuns[it->first] = d->workbook->font(it->second); } d->formatRunsTable.push_back(formatRuns); } } void GlobalsSubStreamHandler::handleXF(XFRecord* record) { if (!record) return; d->xfTable.push_back(*record); } void GlobalsSubStreamHandler::handleProtect(ProtectRecord* record) { if (!record) return; if (record->isLocked()) { - std::cout << "TODO: The workbook is protected but protected workbooks is not supported yet!" << std::endl; + qCDebug(lcSidewinder) << "TODO: The workbook is protected but protected workbooks is not supported yet!"; } } void GlobalsSubStreamHandler::handleWindow1(Window1Record* record) { d->workbook->setActiveTab( record->itabCur() ); } void GlobalsSubStreamHandler::handlePassword(PasswordRecord* record) { if (!record) return; if (!record->wPassword()) return; - std::cout << "GlobalsSubStreamHandler::handlePassword passwordHash=" << record->wPassword() << std::endl; + qCDebug(lcSidewinder) << "GlobalsSubStreamHandler::handlePassword passwordHash=" << record->wPassword(); d->workbook->setPassword(record->wPassword()); } void GlobalsSubStreamHandler::handleMsoDrawingGroup(MsoDrawingGroupRecord* record) { if (!record) return; - printf("GlobalsSubStreamHandler::handleMsoDrawingGroup\n"); + qCDebug(lcSidewinder) << "GlobalsSubStreamHandler::handleMsoDrawingGroup"; static int validMsoDrawingGroups = 0; // if this pass then multiple MsoDrawingGroupRecord can exist what we need to handle! if (validMsoDrawingGroups > 0) { - std::cerr << "Warning: multiple valid MsoDrawingGroupRecord exists : " << validMsoDrawingGroups << std::endl; + qCWarning(lcSidewinder) << "Warning: multiple valid MsoDrawingGroupRecord exists:" << validMsoDrawingGroups; } validMsoDrawingGroups++; d->workbook->setPictureNames(record->pictureNames()); d->workbook->setOfficeArtDggContainer(record->dggContainer()); } QList< Sheet* >& GlobalsSubStreamHandler::chartSheets() { return d->chartSheets; } KoStore* GlobalsSubStreamHandler::store() const { return d->workbook->store(); } } // namespace Swinder diff --git a/filters/sheets/excel/sidewinder/objects.cpp b/filters/sheets/excel/sidewinder/objects.cpp index e1d73d106ed..7531dd798a1 100644 --- a/filters/sheets/excel/sidewinder/objects.cpp +++ b/filters/sheets/excel/sidewinder/objects.cpp @@ -1,141 +1,150 @@ /* Swinder - Portable library for spreadsheet Copyright (C) 2009,2010 Sebastian Sauer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "objects.h" #include "generated/simpleParser.h" #include "generated/leinputstream.h" //#include "drawstyle.h" //#include "ODrawToOdf.h" //#include "pictures.h" using namespace Swinder; Object::Object(Type t, unsigned long id) : m_colL(0) , m_dxL(0) , m_rwT(0) , m_dyT(0) , m_colR(0) , m_dxR(0) , m_rwB(0) , m_dyB(0) , m_type(t) , m_id(id) { } Object::~Object() { } bool Object::applyDrawing(const MSO::OfficeArtDgContainer &container) { bool ok = false; foreach(const MSO::OfficeArtSpgrContainerFileBlock &b, container.groupShape->rgfb) { - std::cout << "Object(" << m_type << ") isOfficeArtSpContainer=" << b.anon.is() << " isOfficeArtSpgrContainer=" << b.anon.is() << " isOfficeArtFSP=" << b.anon.is() << " isOfficeArtFSPGR=" << b.anon.is() << " isOfficeArtClientAnchor=" << b.anon.is() << " isOfficeArtClientData=" << b.anon.is() << std::endl; + qCDebug(lcSidewinder) << "Object(" << m_type << ") isOfficeArtSpContainer=" << b.anon.is() + << "isOfficeArtSpgrContainer=" << b.anon.is() + << "isOfficeArtFSP=" << b.anon.is() + << "isOfficeArtFSPGR=" << b.anon.is() + << "isOfficeArtClientAnchor=" << b.anon.is() + << "isOfficeArtClientData=" << b.anon.is(); const MSO::OfficeArtSpContainer* spc = b.anon.get(); if(spc && spc->shapePrimaryOptions) { // Extract the unique identifier for the drawing to be able to access the drawing again from outside. foreach(const MSO::OfficeArtFOPTEChoice &c, spc->shapePrimaryOptions->fopt) { if(c.anon.is()) m_id = c.anon.get()->pib; else if(c.anon.is()) m_id = c.anon.get()->iTxid; } // The drawing may attached to an anchor which contains the information where our drawing will be located. MSO::XlsOfficeArtClientAnchor* anchor = spc->clientAnchor ? spc->clientAnchor->anon.get() : 0; if(anchor) { m_colL = anchor->colL; m_dxL = anchor->dxL; m_rwT = anchor->rwT; m_dyT = anchor->dyT; m_colR = anchor->colR; m_dxR = anchor->dxR; m_rwB = anchor->rwB; m_dyB = anchor->dyB; } ok = true; continue; } const MSO::OfficeArtSpgrContainer* spgr = b.anon.get(); if(spgr) { foreach(const MSO::OfficeArtSpgrContainerFileBlock &b, spgr->rgfb) { - std::cout << " spgr isOfficeArtSpContainer=" << b.anon.is() << " isOfficeArtSpgrContainer=" << b.anon.is() << " isOfficeArtFSP=" << b.anon.is() << " isOfficeArtFSPGR=" << b.anon.is() << " isOfficeArtClientAnchor=" << b.anon.is() << " isOfficeArtClientData=" << b.anon.is() << std::endl; + qCDebug(lcSidewinder) << " spgr isOfficeArtSpContainer=" << b.anon.is() + << "isOfficeArtSpgrContainer=" << b.anon.is() + << "isOfficeArtFSP=" << b.anon.is() + << "isOfficeArtFSPGR=" << b.anon.is() + << "isOfficeArtClientAnchor=" << b.anon.is() + << "isOfficeArtClientData=" << b.anon.is(); const MSO::OfficeArtSpContainer* spc2 = b.anon.get(); if(spc2) { - std::cout << " sp" - << " shapeGroup=" << (spc2->shapeGroup?1:0) - << " deletedshape=" << (spc2->deletedshape?1:0) - << " shapePrimaryOptions=" << (spc2->shapePrimaryOptions?1:0) - << " shapeSecondaryOptions1=" << (spc2->shapeSecondaryOptions1?1:0) - << " shapeTertiaryOptions1=" << (spc2->shapeTertiaryOptions1?1:0) - << " childAnchor=" << (spc2->childAnchor?1:0) - << " clientAnchor=" << (spc2->clientAnchor?1:0) - << " clientData=" << (spc2->clientData?1:0) - << " clientTextbox=" << (spc2->clientTextbox?1:0) - << " shapeSecondaryOptions2=" << (spc2->shapeSecondaryOptions2?1:0) - << " shapeTertiaryOptions2=" << (spc2->shapeTertiaryOptions2?1:0) - << std:: endl; + qCDebug(lcSidewinder) << " sp" + << "shapeGroup=" << (spc2->shapeGroup?1:0) + << "deletedshape=" << (spc2->deletedshape?1:0) + << "shapePrimaryOptions=" << (spc2->shapePrimaryOptions?1:0) + << "shapeSecondaryOptions1=" << (spc2->shapeSecondaryOptions1?1:0) + << "shapeTertiaryOptions1=" << (spc2->shapeTertiaryOptions1?1:0) + << "childAnchor=" << (spc2->childAnchor?1:0) + << "clientAnchor=" << (spc2->clientAnchor?1:0) + << "clientData=" << (spc2->clientData?1:0) + << "clientTextbox=" << (spc2->clientTextbox?1:0) + << "shapeSecondaryOptions2=" << (spc2->shapeSecondaryOptions2?1:0) + << "shapeTertiaryOptions2=" << (spc2->shapeTertiaryOptions2?1:0); } } } } return ok; } OfficeArtObject::OfficeArtObject(const MSO::OfficeArtSpContainer &object, quint32 index) : m_object(object), m_index(index) { } OfficeArtObject::~OfficeArtObject() { } MSO::OfficeArtSpContainer OfficeArtObject::object() const { return m_object; } void OfficeArtObject::setText(const TxORecord &text) { m_text = text; } TxORecord OfficeArtObject::text() const { return m_text; } void OfficeArtObject::setIndex(quint32 index) { m_index = index; } quint32 OfficeArtObject::index() const { return m_index; } diff --git a/filters/sheets/excel/sidewinder/utils.cpp b/filters/sheets/excel/sidewinder/utils.cpp index 267caa6f746..3146a36065a 100644 --- a/filters/sheets/excel/sidewinder/utils.cpp +++ b/filters/sheets/excel/sidewinder/utils.cpp @@ -1,344 +1,346 @@ /* Swinder - Portable library for spreadsheet Copyright (C) 2003-2005 Ariya Hidayat Copyright (C) 2006,2009 Marijn Kruisselbrink Copyright (C) 2009,2010 Sebastian Sauer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "utils.h" #include #include #include "XlsRecordOutputStream.h" +Q_LOGGING_CATEGORY(lcSidewinder, "calligra.filter.sidewinder") + namespace Swinder { // Returns A for 1, B for 2, C for 3, etc. QString columnName(uint column) { QString s; unsigned digits = 1; unsigned offset = 0; for (unsigned limit = 26; column >= limit + offset; limit *= 26, ++digits) offset += limit; for (unsigned col = column - offset; digits; --digits, col /= 26) s.prepend(QChar('A' + (col % 26))); return s; } QString encodeSheetName(const QString& name) { QString sheetName = name; if (sheetName.contains(' ') || sheetName.contains('.') || sheetName.contains('\'')) sheetName = '\'' + sheetName.replace('\'', "''") + '\''; return sheetName; } QString encodeAddress(const QString& sheetName, uint column, uint row) { return QString("%1.%2%3").arg(encodeSheetName(sheetName)).arg(columnName(column)).arg(row+1); } QString encodeAddress(const QString& sheetName, const QRect &rect) { int startColumn = rect.left(); int startRow = rect.top(); int endColumn = rect.right(); int endRow = rect.bottom(); if (rect.width() == 1 && rect.height() == 1) return encodeAddress(sheetName, startColumn, startRow); return QString("%1.%2%3:%4%5").arg(encodeSheetName(sheetName)).arg(columnName(startColumn)).arg(startRow+1).arg(columnName(endColumn)).arg(endRow+1); } QString readByteString(const void* p, unsigned length, unsigned maxSize, bool* error, unsigned* size) { const unsigned char* data = reinterpret_cast(p); if (size) *size = length; if (length > maxSize) { if (*error) *error = true; return QString(); } char* buffer = new char[length+1]; memcpy(buffer, data, length); buffer[length] = 0; QString str(buffer); delete[] buffer; return str; } QString readTerminatedUnicodeChars(const void* p, unsigned* pSize, unsigned maxSize, bool* error) { const unsigned char* data = reinterpret_cast(p); QString str; unsigned offset = 0; unsigned size = offset; while (true) { if (size+2 > maxSize) { if (*error) *error = true; return QString(); } unsigned uchar = readU16(data + offset); size += 2; if (uchar == '\0') break; offset += 2; str.append(QChar(uchar)); } if (pSize) *pSize = size; return str; } QString readUnicodeChars(const void* p, unsigned length, unsigned maxSize, bool* error, unsigned* pSize, unsigned continuePosition, unsigned offset, bool unicode, bool asianPhonetics, bool richText) { const unsigned char* data = reinterpret_cast(p); if (maxSize < 1) { if (*error) *error = true; return QString(); } unsigned formatRuns = 0; unsigned asianPhoneticsSize = 0; if (richText) { if (offset + 2 > maxSize) { if (*error) *error = true; return QString(); } formatRuns = readU16(data + offset); offset += 2; } if (asianPhonetics) { if (offset + 4 > maxSize) { if (*error) *error = true; return QString(); } asianPhoneticsSize = readU32(data + offset); offset += 4; } // find out total bytes used in this string unsigned size = offset; if (richText) size += (formatRuns * 4); if (asianPhonetics) size += asianPhoneticsSize; if (size > maxSize) { if (*error) *error = true; return QString(); } QString str; for (unsigned k = 0; k < length; ++k) { unsigned uchar; if (unicode) { if (size + 2 > maxSize) { if (*error) *error = true; return QString(); } uchar = readU16(data + offset); offset += 2; size += 2; } else { if (size + 1 > maxSize) { if (*error) *error = true; return QString(); } uchar = data[offset++]; size++; } str.append(QChar(uchar)); if (offset == continuePosition && k < length - 1) { if (size + 1 > maxSize) { if (*error) *error = true; return QString(); } unicode = data[offset] & 1; size++; offset++; } } if (pSize) *pSize = size; return str; } QString readUnicodeString(const void* p, unsigned length, unsigned maxSize, bool* error, unsigned* pSize, unsigned continuePosition) { const unsigned char* data = reinterpret_cast(p); if (maxSize < 1) { if (*error) *error = true; return QString(); } unsigned char flags = data[0]; unsigned offset = 1; bool unicode = flags & 0x01; bool asianPhonetics = flags & 0x04; bool richText = flags & 0x08; return readUnicodeChars(p, length, maxSize, error, pSize, continuePosition, offset, unicode, asianPhonetics, richText); } QString readUnicodeCharArray(const void* p, unsigned length, unsigned maxSize, bool* error, unsigned* pSize, unsigned continuePosition) { if (length == unsigned(-1)) { // null terminated string return readTerminatedUnicodeChars(p, pSize, maxSize, error); } else { return readUnicodeChars(p, length, maxSize, error, pSize, continuePosition, 0, true, false, false); } } std::ostream& operator<<(std::ostream& s, const QString& ustring) { s << qPrintable(ustring); return s; } std::ostream& operator<<(std::ostream& s, const QByteArray& d) { s << std::hex << std::setfill('0'); for (int i = 0; i < d.size(); i++) s << " " << std::setw(2) << int((unsigned char)d[i]); return s << std::dec; } std::ostream& operator<<(std::ostream& s, const QUuid& uuid) { return s << uuid.toString().toLatin1().constData(); } Value errorAsValue(int errorCode) { Value result(Value::Error); switch (errorCode) { case 0x00: result = Value::errorNULL(); break; case 0x07: result = Value::errorDIV0(); break; case 0x0f: result = Value::errorVALUE(); break; case 0x17: result = Value::errorREF(); break; case 0x1d: result = Value::errorNAME(); break; case 0x24: result = Value::errorNUM(); break; case 0x2A: result = Value::errorNA(); break; default: break; } return result; } // ========== base record ========== const unsigned int Record::id = 0; // invalid of-course Record::Record(Workbook *book) { m_workbook = book; stream_position = 0; ver = Excel97; valid = true; m_size = 0; } Record::~Record() { } Record* Record::create(unsigned type, Workbook *book) { return RecordRegistry::createRecord(type, book); } void Record::setPosition(unsigned pos) { stream_position = pos; } unsigned Record::position() const { return stream_position; } void Record::setData(unsigned, const unsigned char*, const unsigned int*) { } void Record::writeData(XlsRecordOutputStream &out) const { Q_UNUSED(out); - fprintf(stderr, "ERROR! writeData not implemented for record type %u\n", rtti()); + qCWarning(lcSidewinder) << "ERROR! writeData not implemented for record type" << rtti(); } void Record::dump(std::ostream&) const { // nothing to dump } bool Record::isValid() const { return valid; } void Record::setIsValid(bool isValid) { valid = isValid; } void RecordRegistry::registerRecordClass(unsigned id, RecordFactory factory) { instance()->records[id] = factory; } void RecordRegistry::registerRecordClass(unsigned id, RecordFactoryWithArgs factory, void* args) { instance()->recordsWithArgs[id] = factory; instance()->recordArgs[id] = args; } void RecordRegistry::unregisterRecordClass(unsigned id) { instance()->records.erase(id); instance()->recordsWithArgs.erase(id); instance()->recordArgs.erase(id); } Record* RecordRegistry::createRecord(unsigned id, Workbook *book) { RecordRegistry* q = instance(); std::map::iterator it = q->records.find(id); if (it != q->records.end()) { return it->second(book); } std::map::iterator it2 = q->recordsWithArgs.find(id); if (it2 != q->recordsWithArgs.end()) { return it2->second(book, q->recordArgs[id]); } return 0; } RecordRegistry* RecordRegistry::instance() { static RecordRegistry* sinstance = 0; if (!sinstance) sinstance = new RecordRegistry(); return sinstance; } } // namespace Swinder diff --git a/filters/sheets/excel/sidewinder/utils.h b/filters/sheets/excel/sidewinder/utils.h index 0f0814dc143..6e012b26862 100644 --- a/filters/sheets/excel/sidewinder/utils.h +++ b/filters/sheets/excel/sidewinder/utils.h @@ -1,290 +1,294 @@ /* Swinder - Portable library for spreadsheet Copyright (C) 2003-2005 Ariya Hidayat Copyright (C) 2006,2009 Marijn Kruisselbrink Copyright (C) 2009,2010 Sebastian Sauer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #ifndef SWINDER_UTILS_H #define SWINDER_UTILS_H #include "value.h" #include #include #include #include #include +#include + +//"calligra.filter.sidewinder" +Q_DECLARE_LOGGING_CATEGORY(lcSidewinder) namespace Swinder { // The minimal number of rows and columns. This is used to fill remaining rows and columns with the // default style what is needed cause Excel does always define the default for all rows and columns // while ODF does only for those that are explicit defined. static const uint minimumColumnCount = 1024; static const uint minimumRowCount = 32768; // The maximal number of rows and columns. This allows us to cut rows and columns away that would // not be handled by the consumer application anyway cause they reached the applications limited. static const uint maximalColumnCount = 32768; static const uint maximalRowCount = 65536; // Returns A for 1, B for 2, C for 3, etc. QString columnName(uint column); // Returns the escaped sheet-name. QString encodeSheetName(const QString& name); // Returns an encoded cell-address like e.g. "Sheet1!A1". QString encodeAddress(const QString& sheetName, uint column, uint row); // Returns an encoded cell-range like e.g. "Sheet1!A1:B2". QString encodeAddress(const QString& sheetName, const QRect &rect); class Workbook; class XlsRecordOutputStream; Value errorAsValue(int errorCode); static inline unsigned long readU16(const void* p) { const unsigned char* ptr = (const unsigned char*) p; return ptr[0] + (ptr[1] << 8); } static inline unsigned readU8(const void* p) { const unsigned char* ptr = (const unsigned char*) p; return ptr[0]; } static inline long readS16(const void* p) { long val = readU16(p); if (val & 0x8000) val = val - 0x10000; return val; } static inline long readS8(const void* p) { const unsigned char* ptr = (const unsigned char*) p; long val = *ptr; if (val & 0x80) val = val - 0x100; return val; } static inline unsigned long readU32(const void* p) { const unsigned char* ptr = (const unsigned char*) p; return ptr[0] + (ptr[1] << 8) + (ptr[2] << 16) + (ptr[3] << 24); } static inline long readS32(const void* p) { long val = readU32(p); if (val & 0x800000) val = val - 0x1000000; return val; } static inline double readFixed32(const void* p) { const unsigned char* ptr = (const unsigned char*) p; unsigned a = readU16(ptr); unsigned b = readU16(ptr + 2); return a + (b / 65536.0); } static inline QUuid readUuid(const void* p) { const unsigned char* ptr = (const unsigned char*) p; return QUuid(readU32(ptr), readU16(ptr+4), readU16(ptr+6), ptr[9], ptr[9], ptr[10], ptr[11], ptr[12], ptr[13], ptr[14], ptr[15]); } // FIXME check that double is 64 bits static inline double readFloat64(const void*p) { quint64 val = qFromLittleEndian(reinterpret_cast(p)); double num = 0.0; memcpy(&num, &val, sizeof num); return num; } // RK value is special encoded integer or floating-point // see any documentation of Excel file format for detail description static inline void decodeRK(unsigned rkvalue, bool& isInteger, int& i, double& f) { double factor = (rkvalue & 0x01) ? 0.01 : 1; if (rkvalue & 0x02) { // FIXME check that int is 32 bits ? isInteger = true; i = *((int*) & rkvalue) >> 2; if (rkvalue & 0x01) { if (i % 100 == 0) { i /= 100; } else { isInteger = false; f = i * 0.01; } } } else { // TODO ensure double takes 8 bytes isInteger = false; rkvalue = qFromLittleEndian(rkvalue); unsigned char* s = (unsigned char*) & rkvalue; unsigned char* r = (unsigned char*) & f; r[0] = r[1] = r[2] = r[3] = 0; r[4] = s[0] & 0xfc; r[5] = s[1]; r[6] = s[2]; r[7] = s[3]; f *= factor; } } /** * Supported Excel document version. */ enum { UnknownExcel = 0, Excel95, Excel97, Excel2000 }; QString readByteString(const void* data, unsigned length, unsigned maxSize = -1, bool* error = 0, unsigned* size = 0); QString readTerminatedUnicodeChars(const void* data, unsigned* size = 0, unsigned maxSize = -1, bool* error = 0); QString readUnicodeChars(const void* data, unsigned length, unsigned maxSize = -1, bool* error = 0, unsigned* size = 0, unsigned continuePosition = -1, unsigned offset = 0, bool unicode = true, bool asianPhonetics = false, bool richText = false); QString readUnicodeString(const void* data, unsigned length, unsigned maxSize = -1, bool* error = 0, unsigned* size = 0, unsigned continuePosition = -1); QString readUnicodeCharArray(const void* p, unsigned length, unsigned maxSize = -1, bool* error = 0, unsigned* size = 0, unsigned continuePosition = -1); std::ostream& operator<<(std::ostream& s, const QString& ustring); std::ostream& operator<<(std::ostream& s, const QByteArray& data); std::ostream& operator<<(std::ostream& s, const QUuid& uuid); /** Class Record represents a base class for all other type record, hence do not use this class in real life. */ class Record { public: /** Static ID of the record. Subclasses should override this value with the id of the record they handle. */ static const unsigned int id; virtual unsigned int rtti() const { return this->id; } /** Creates a new generic record. */ explicit Record(Workbook*); /** Destroys the record. */ virtual ~Record(); /** * Record factory, create a new record of specified type. */ static Record* create(unsigned type, Workbook *book); void setVersion(unsigned v) { ver = v; } unsigned version() const { return ver; } void setRecordSize(unsigned size) { m_size = size; } unsigned recordSize() const { return m_size; } /** Sets the data for this record. */ virtual void setData(unsigned size, const unsigned char* data, const unsigned int* continuePositions); virtual void writeData(XlsRecordOutputStream& out) const; /** Sets the position of the record in the OLE stream. Somehow this is required to process BoundSheet and BOF(Worksheet) properly. */ void setPosition(unsigned pos); /** Gets the position of this record in the OLE stream. */ unsigned position() const; /** Returns the name of the record. For debugging only. */ virtual const char* name() const { return "Unknown"; } /** Dumps record information to output stream. For debugging only. */ virtual void dump(std::ostream& out) const; bool isValid() const; protected: void setIsValid(bool isValid); // the workbook Workbook *m_workbook; // position of this record in the OLE stream unsigned stream_position; // in which version does this record denote ? unsigned ver; // is the record valid? bool valid; // size of the record unsigned m_size; }; typedef Record*(*RecordFactory)(Workbook*); typedef Record*(*RecordFactoryWithArgs)(Workbook*, void*); class RecordRegistry { public: static void registerRecordClass(unsigned id, RecordFactory factory); static void registerRecordClass(unsigned id, RecordFactoryWithArgs factory, void* args); static void unregisterRecordClass(unsigned id); static Record* createRecord(unsigned id, Workbook *book); private: RecordRegistry() {}; static RecordRegistry* instance(); std::map records; std::map recordsWithArgs; std::map recordArgs; }; } // namespace Swinder #endif // SWINDER_UTILS_H diff --git a/filters/sheets/excel/sidewinder/worksheetsubstreamhandler.cpp b/filters/sheets/excel/sidewinder/worksheetsubstreamhandler.cpp index 487357fee01..1affc40e8b1 100644 --- a/filters/sheets/excel/sidewinder/worksheetsubstreamhandler.cpp +++ b/filters/sheets/excel/sidewinder/worksheetsubstreamhandler.cpp @@ -1,1203 +1,1203 @@ /* Swinder - Portable library for spreadsheet Copyright (C) 2003-2005 Ariya Hidayat Copyright (C) 2006,2009 Marijn Kruisselbrink Copyright (C) 2009,2010 Sebastian Sauer Copyright (C) 2010 Carlos Licea This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "worksheetsubstreamhandler.h" #include #include "globalssubstreamhandler.h" #include "excel.h" #include "cell.h" #include "objects.h" #include "sheet.h" #include "conditionals.h" #include #include "database/Filter.h" //#define SWINDER_XLS2RAW namespace Swinder { class WorksheetSubStreamHandler::Private { public: Sheet* sheet; const GlobalsSubStreamHandler* globals; // for FORMULA+SHAREDFMLA record pair Cell* lastFormulaCell; // for FORMULA+STRING record pair Cell* formulaStringCell; // mapping from cell position to data tables std::map, DataTableRecord*> dataTables; // mapping from cell position to shared formulas std::map, FormulaTokens> sharedFormulas; // mapping from object id's to object instances std::map sharedObjects; // maps object id's of NoteObject's to there continuous number std::map noteMap; // the number of NoteObject's in this worksheet int noteCount; // The last drawing object we got. MSO::OfficeArtDgContainer* lastDrawingObject; MSO::OfficeArtSpgrContainer* lastGroupObject; OfficeArtObject* lastOfficeArtObject; quint32 officeArtObjectCounter; // list of id's with ChartObject's. std::vector charts; // current ConditionalFormat ConditionalFormat* curConditionalFormat; }; WorksheetSubStreamHandler::WorksheetSubStreamHandler(Sheet* sheet, const GlobalsSubStreamHandler* globals) : SubStreamHandler(), FormulaDecoder(), d(new Private) { d->sheet = sheet; d->globals = globals; d->lastFormulaCell = 0; d->formulaStringCell = 0; d->noteCount = 0; d->lastDrawingObject = 0; d->lastGroupObject = 0; d->lastOfficeArtObject = 0; d->officeArtObjectCounter = 0; d->curConditionalFormat = 0; } WorksheetSubStreamHandler::~WorksheetSubStreamHandler() { for(std::map, DataTableRecord*>::iterator it = d->dataTables.begin(); it != d->dataTables.end(); ++it) delete (*it).second; //for(std::map::iterator it = d->sharedObjects.begin(); it != d->sharedObjects.end(); ++it) // delete (*it).second; //for(std::map, FormulaTokens*>::iterator it = d->sharedFormulas.begin(); it != d->sharedFormulas.end(); ++it) // delete it.second.second; delete d->lastDrawingObject; delete d->lastGroupObject; delete d; } Sheet* WorksheetSubStreamHandler::sheet() const { return d->sheet; } std::map& WorksheetSubStreamHandler::sharedObjects() const { return d->sharedObjects; } std::vector& WorksheetSubStreamHandler::charts() const { return d->charts; } const std::vector& WorksheetSubStreamHandler::externSheets() const { return d->globals->externSheets(); } QString WorksheetSubStreamHandler::nameFromIndex(unsigned index) const { return d->globals->nameFromIndex(index); } QString WorksheetSubStreamHandler::externNameFromIndex(unsigned index) const { return d->globals->externNameFromIndex(index); } FormulaTokens WorksheetSubStreamHandler::sharedFormulas(const std::pair& formulaCellPos) const { std::map, FormulaTokens>::iterator sharedFormula = d->sharedFormulas.find(formulaCellPos); return sharedFormula != d->sharedFormulas.end() ? sharedFormula->second : FormulaTokens(); } DataTableRecord* WorksheetSubStreamHandler::tableRecord(const std::pair& formulaCellPos) const { std::map, DataTableRecord*>::iterator datatable = d->dataTables.find(formulaCellPos); return datatable != d->dataTables.end() ? datatable->second : 0; } void WorksheetSubStreamHandler::handleRecord(Record* record) { if (!record) return; const unsigned type = record->rtti(); if (type == BottomMarginRecord::id) handleBottomMargin(static_cast(record)); else if (type == BoolErrRecord::id) handleBoolErr(static_cast(record)); else if (type == BlankRecord::id) handleBlank(static_cast(record)); else if (type == CalcModeRecord::id) handleCalcMode(static_cast(record)); else if (type == ColInfoRecord::id) handleColInfo(static_cast(record)); else if (type == DataTableRecord::id) handleDataTable(static_cast(record)); else if (type == FormulaRecord::id) handleFormula(static_cast(record)); else if (type == FooterRecord::id) handleFooter(static_cast(record)); else if (type == HeaderRecord::id) handleHeader(static_cast(record)); else if (type == LabelRecord::id) handleLabel(static_cast(record)); else if (type == LabelSSTRecord::id) handleLabelSST(static_cast(record)); else if (type == LeftMarginRecord::id) handleLeftMargin(static_cast(record)); else if (type == MergedCellsRecord::id) handleMergedCells(static_cast(record)); else if (type == MulBlankRecord::id) handleMulBlank(static_cast(record)); else if (type == MulRKRecord::id) handleMulRK(static_cast(record)); else if (type == NumberRecord::id) handleNumber(static_cast(record)); else if (type == RightMarginRecord::id) handleRightMargin(static_cast(record)); else if (type == RKRecord::id) handleRK(static_cast(record)); else if (type == RowRecord::id) handleRow(static_cast(record)); else if (type == RStringRecord::id) handleRString(static_cast(record)); else if (type == SharedFormulaRecord::id) handleSharedFormula(static_cast(record)); else if (type == StringRecord::id) handleString(static_cast(record)); else if (type == TopMarginRecord::id) handleTopMargin(static_cast(record)); else if (type == HLinkRecord::id) handleHLink(static_cast(record)); else if (type == NoteRecord::id) handleNote(static_cast(record)); else if (type == ObjRecord::id) handleObj(static_cast(record)); else if (type == TxORecord::id) handleTxO(static_cast(record)); else if (type == BOFRecord::id) handleBOF(static_cast(record)); else if (type == DefaultRowHeightRecord::id) handleDefaultRowHeight(static_cast(record)); else if (type == DefaultColWidthRecord::id) handleDefaultColWidth(static_cast(record)); else if (type == SetupRecord::id) handleSetup(static_cast(record)); else if (type == HCenterRecord::id) handleHCenter(static_cast(record)); else if (type == VCenterRecord::id) handleVCenter(static_cast(record)); else if (type == ZoomLevelRecord::id) handleZoomLevel(static_cast(record)); else if (type == 0xA) {} //EofRecord else if (type == DimensionRecord::id) handleDimension(static_cast(record)); else if (type == MsoDrawingRecord::id) handleMsoDrawing(static_cast(record)); else if (type == Window2Record::id) handleWindow2(static_cast(record)); else if (type == PasswordRecord::id) handlePassword(static_cast(record)); else if (type == BkHimRecord::id) handleBkHim(static_cast(record)); else if (type == VerticalPageBreaksRecord::id) handleVerticalPageBreaksRecord(static_cast(record)); else if (type == HorizontalPageBreaksRecord::id) handleHorizontalPageBreaksRecord(static_cast(record)); else if (type == CondFmtRecord::id) handleCondFmtRecord(static_cast(record)); else if (type == CFRecord::id) handleCFRecord(static_cast(record)); else if (type == AutoFilterRecord::id) handleAutoFilterRecord(static_cast(record)); else { - //std::cout << "Unhandled worksheet record with type=" << type << " name=" << record->name() << std::endl; + //qCDebug(lcSidewinder) << "Unhandled worksheet record with type=" << type << " name=" << record->name(); } } void WorksheetSubStreamHandler::handleBOF(BOFRecord* record) { if (!record) return; if (record->type() == BOFRecord::Worksheet) { // ... } else { - std::cout << "WorksheetSubStreamHandler::handleBOF Unhandled type=" << record->type() << std::endl; + qCDebug(lcSidewinder) << "WorksheetSubStreamHandler::handleBOF Unhandled type=" << record->type(); } } void WorksheetSubStreamHandler::handleBlank(BlankRecord* record) { if (!record) return; if (!d->sheet) return; unsigned column = record->column(); unsigned row = record->row(); unsigned xfIndex = record->xfIndex(); Cell* cell = d->sheet->cell(column, row, true); if (cell) { cell->setFormat(d->globals->convertedFormat(xfIndex)); } } void WorksheetSubStreamHandler::handleBoolErr(BoolErrRecord* record) { if (!record) return; if (!d->sheet) return; unsigned column = record->column(); unsigned row = record->row(); unsigned xfIndex = record->xfIndex(); Cell* cell = d->sheet->cell(column, row, true); if (cell) { cell->setValue(record->asValue()); cell->setFormat(d->globals->convertedFormat(xfIndex)); } } void WorksheetSubStreamHandler::handleBottomMargin(BottomMarginRecord* record) { if (!record) return; if (!d->sheet) return; // convert from inches to points double margin = record->bottomMargin() * 72.0; d->sheet->setBottomMargin(margin); } void WorksheetSubStreamHandler::handleCalcMode(CalcModeRecord* record) { if (!record) return; if (!d->sheet) return; d->sheet->setAutoCalc(record->calcMode() != CalcModeRecord::Manual); } void WorksheetSubStreamHandler::handleColInfo(ColInfoRecord* record) { if (!record) return; if (!d->sheet) return; unsigned firstColumn = record->firstColumn(); unsigned lastColumn = record->lastColumn(); unsigned xfIndex = record->xfIndex(); unsigned width = record->width(); bool hidden = record->isHidden(); for (unsigned i = firstColumn; i <= lastColumn; ++i) { Column* column = d->sheet->column(i, true); if (column) { column->setWidth( Column::columnUnitsToPts((double)width) ); column->setFormat(d->globals->convertedFormat(xfIndex)); column->setVisible(!hidden); column->setOutlineLevel(record->outlineLevel()); column->setCollapsed(record->isCollapsed()); } } } void WorksheetSubStreamHandler::handleDataTable(DataTableRecord* record) { if (!record) return; if (!d->lastFormulaCell) return; unsigned row = d->lastFormulaCell->row(); unsigned column = d->lastFormulaCell->column(); d->dataTables[std::make_pair(row, column)] = new DataTableRecord(*record); QString formula = dataTableFormula(row, column, record); d->lastFormulaCell->setFormula(formula); d->lastFormulaCell = 0; } void WorksheetSubStreamHandler::handleDimension(DimensionRecord* record) { if (!record) return; if (!d->sheet) return; // in the mean time we don't need to handle this because we don't care // about the used range of the sheet d->sheet->setMaxRow(record->lastRow()); d->sheet->setMaxColumn(record->lastColumn()); } void WorksheetSubStreamHandler::handleFormula(FormulaRecord* record) { if (!record) return; if (!d->sheet) return; unsigned column = record->column(); unsigned row = record->row(); unsigned xfIndex = record->xfIndex(); Value value = record->result(); QString formula = decodeFormula(row, column, record->isShared(), record->tokens()); Cell* cell = d->sheet->cell(column, row, true); if (cell) { cell->setValue(value); if (!formula.isEmpty()) cell->setFormula(formula); cell->setFormat(d->globals->convertedFormat(xfIndex)); // if value is string, real value is in subsequent String record if (value.isString()) d->formulaStringCell = cell; d->lastFormulaCell = cell; } } void WorksheetSubStreamHandler::handleFooter(FooterRecord* record) { if (!record) return; if (!d->sheet) return; QString footer = record->footer(); QString left, center, right; int pos = -1, len = 0; // left part pos = footer.indexOf("&L"); if (pos >= 0) { pos += 2; len = footer.indexOf("&C") - pos; if (len > 0) { left = footer.mid(pos, len); footer.remove(0, pos + len); } else { left = footer.mid(pos); } } // center part pos = footer.indexOf("&C"); if (pos >= 0) { pos += 2; len = footer.indexOf("&R") - pos; if (len > 0) { center = footer.mid(pos, len); footer.remove(0, pos + len); } else { center = footer.mid(pos); } } // right part pos = footer.indexOf("&R"); if (pos >= 0) { pos += 2; right = footer.mid(pos, footer.length() - pos); } d->sheet->setLeftFooter(left); d->sheet->setCenterFooter(center); d->sheet->setRightFooter(right); } void WorksheetSubStreamHandler::handleHeader(HeaderRecord* record) { if (!record) return; if (!d->sheet) return; QString header = record->header(); QString left, center, right; int pos = -1, len = 0; // left part of the header pos = header.indexOf("&L"); if (pos >= 0) { pos += 2; len = header.indexOf("&C") - pos; if (len > 0) { left = header.mid(pos, len); header = header.mid(pos + len, header.length()); } else { left = header.mid(pos); } } // center part of the header pos = header.indexOf("&C"); if (pos >= 0) { pos += 2; len = header.indexOf("&R") - pos; if (len > 0) { center = header.mid(pos, len); header = header.mid(pos + len, header.length()); } else { center = header.mid(pos); } } // right part of the header pos = header.indexOf("&R"); if (pos >= 0) { pos += 2; right = header.mid(pos, header.length() - pos); } d->sheet->setLeftHeader(left); d->sheet->setCenterHeader(center); d->sheet->setRightHeader(right); } void WorksheetSubStreamHandler::handleLabel(LabelRecord* record) { if (!record) return; if (!d->sheet) return; unsigned column = record->column(); unsigned row = record->row(); unsigned xfIndex = record->xfIndex(); QString label = record->label(); Cell* cell = d->sheet->cell(column, row, true); if (cell) { cell->setValue(Value(label)); cell->setFormat(d->globals->convertedFormat(xfIndex)); } } void WorksheetSubStreamHandler::handleLabelSST(LabelSSTRecord* record) { if (!record) return; if (!d->sheet) return; unsigned column = record->column(); unsigned row = record->row(); unsigned index = record->sstIndex(); unsigned xfIndex = record->xfIndex(); QString str = d->globals->stringFromSST(index); std::map formatRuns = d->globals->formatRunsFromSST(index); Cell* cell = d->sheet->cell(column, row, true); if (cell) { if (!formatRuns.empty()) cell->setValue(Value(str, formatRuns)); else cell->setValue(Value(str)); cell->setFormat(d->globals->convertedFormat(xfIndex)); } } void WorksheetSubStreamHandler::handleLeftMargin(LeftMarginRecord* record) { if (!record) return; if (!d->sheet) return; // convert from inches to points double margin = record->leftMargin() * 72.0; d->sheet->setLeftMargin(margin); } void WorksheetSubStreamHandler::handleMergedCells(MergedCellsRecord* record) { if (!record) return; if (!d->sheet) return; for (unsigned i = 0; i < record->count(); ++i) { unsigned firstRow = record->firstRow(i); unsigned lastRow = record->lastRow(i); unsigned firstColumn = record->firstColumn(i); unsigned lastColumn = record->lastColumn(i); Cell* cell = d->sheet->cell(firstColumn, firstRow, true); if (cell) { cell->setColumnSpan(lastColumn - firstColumn + 1); cell->setRowSpan(lastRow - firstRow + 1); } for (unsigned row = firstRow; row <= lastRow; ++row) for (unsigned col = firstColumn; col <= lastColumn; ++col) { if (row != firstRow || col != firstColumn) { d->sheet->cell(col, row, true)->setCovered(true); } } } } void WorksheetSubStreamHandler::handleMulBlank(MulBlankRecord* record) { if (!record) return; if (!d->sheet) return; unsigned firstColumn = record->firstColumn(); unsigned lastColumn = record->lastColumn(); unsigned row = record->row(); for (unsigned column = firstColumn; column <= lastColumn; ++column) { Cell* cell = d->sheet->cell(column, row, true); if (cell) { cell->setFormat(d->globals->convertedFormat(record->xfIndex(column - firstColumn))); } } } void WorksheetSubStreamHandler::handleMulRK(MulRKRecord* record) { if (!record) return; if (!d->sheet) return; const int firstColumn = record->firstColumn(); const int lastColumn = record->lastColumn(); const int row = record->row(); for (int column = firstColumn; column <= lastColumn; column++) { Cell* cell = d->sheet->cell(column, row, true); const int i = column - firstColumn; if (record->isInteger(i)) cell->setValue(Value(record->asInteger(i))); else cell->setValue(Value(record->asFloat(i))); cell->setFormat(d->globals->convertedFormat(record->xfIndex(column - firstColumn))); } } void WorksheetSubStreamHandler::handleNumber(NumberRecord* record) { if (!record) return; if (!d->sheet) return; unsigned column = record->column(); unsigned row = record->row(); unsigned xfIndex = record->xfIndex(); double number = record->number(); Cell* cell = d->sheet->cell(column, row, true); if (cell) { cell->setValue(Value(number)); cell->setFormat(d->globals->convertedFormat(xfIndex)); } } void WorksheetSubStreamHandler::handleRightMargin(RightMarginRecord* record) { if (!record) return; if (!d->sheet) return; // convert from inches to points double margin = record->rightMargin() * 72.0; d->sheet->setRightMargin(margin); } void WorksheetSubStreamHandler::handleRK(RKRecord* record) { if (!record) return; if (!d->sheet) return; unsigned column = record->column(); unsigned row = record->row(); unsigned xfIndex = record->xfIndex(); Value value; if (record->isInteger()) value.setValue(record->asInteger()); else value.setValue(record->asFloat()); Cell* cell = d->sheet->cell(column, row, true); if (cell) { cell->setValue(value); cell->setFormat(d->globals->convertedFormat(xfIndex)); } } void WorksheetSubStreamHandler::handleRow(RowRecord* record) { if (!record) return; if (!d->sheet) return; unsigned index = record->row(); unsigned xfIndex = record->xfIndex(); unsigned height = record->height(); bool hidden = record->isHidden(); Row* row = d->sheet->row(index, true); if (row) { row->setHeight(height / 20.0); row->setFormat(d->globals->convertedFormat(xfIndex)); row->setVisible(!hidden); row->setOutlineLevel(record->outlineLevel()); row->setCollapsed(record->isCollapsed()); } } void WorksheetSubStreamHandler::handleRString(RStringRecord* record) { if (!record) return; if (!d->sheet) return; unsigned column = record->column(); unsigned row = record->row(); unsigned xfIndex = record->xfIndex(); QString label = record->label(); Cell* cell = d->sheet->cell(column, row, true); if (cell) { cell->setValue(Value(label)); cell->setFormat(d->globals->convertedFormat(xfIndex)); } } void WorksheetSubStreamHandler::handleSharedFormula(SharedFormulaRecord* record) { if (!record) return; if (!d->lastFormulaCell) return; unsigned row = d->lastFormulaCell->row(); unsigned column = d->lastFormulaCell->column(); d->sharedFormulas[std::make_pair(row, column)] = record->tokens(); QString formula = decodeFormula(row, column, true, record->tokens()); d->lastFormulaCell->setFormula(formula); d->lastFormulaCell = 0; } void WorksheetSubStreamHandler::handleString(StringRecord* record) { if (!record) return; if (!d->formulaStringCell) return; d->formulaStringCell->setValue(record->value()); d->formulaStringCell = 0; } void WorksheetSubStreamHandler::handleTopMargin(TopMarginRecord* record) { if (!record) return; if (!d->sheet) return; // convert from inches to points double margin = record->topMargin() * 72.0; d->sheet->setTopMargin(margin); } static QString trimTrailingZero(const QString& s) { if (!s.isEmpty() && s[s.length()-1].unicode() == '\0') { return s.left(s.length()-1); } else { return s; } } void WorksheetSubStreamHandler::handleHLink(HLinkRecord* record) { if (!record) return; if (!d->sheet) return; //FIXME we ignore the m_lastRow and m_lastColumn values, does ODF have something similar? Cell *cell = d->sheet->cell(record->firstColumn(), record->firstRow()); if (cell) { QString url = trimTrailingZero(record->urlMonikerUrl()) + QString('#') + trimTrailingZero(record->location()); cell->setHyperlink(Hyperlink(trimTrailingZero(record->displayName()), url, trimTrailingZero(record->frameName()))); } } void WorksheetSubStreamHandler::handleTxO(TxORecord* record) { if (!record) return; if (d->sharedObjects.rbegin() != d->sharedObjects.rend()) { NoteObject* no = dynamic_cast(d->sharedObjects.rbegin()->second); if (no) { no->setNote(record->text()); } } if (d->lastOfficeArtObject) { d->lastOfficeArtObject->setText(*record); d->lastOfficeArtObject = 0; } } void WorksheetSubStreamHandler::handleNote(NoteRecord* record) { if (!record) return; if (!d->sheet) return; - std::cout << "WorksheetSubStreamHandler::handleNote column=" << record->column() << " row=" << record->row() << std::endl; + qCDebug(lcSidewinder) << "WorksheetSubStreamHandler::handleNote column=" << record->column() << "row=" << record->row(); Cell *cell = d->sheet->cell(record->column(), record->row()); if (cell) { const unsigned long id = record->idObj(); NoteObject *obj = dynamic_cast(d->sharedObjects[id]); if (obj) { cell->setNote(obj->note()); } } } void WorksheetSubStreamHandler::handleObj(ObjRecord* record) { if (!record) return; if (!d->sheet) return; const unsigned long id = record->m_object ? record->m_object->id() : -1; - std::cout << "WorksheetSubStreamHandler::handleObj id=" << id << " type=" << (record->m_object ? record->m_object->type() : -1) << std::endl; + qCDebug(lcSidewinder) << "WorksheetSubStreamHandler::handleObj id=" << id << " type=" << (record->m_object ? record->m_object->type() : -1); d->lastOfficeArtObject = 0; bool handled = false; if (record->m_object && d->lastDrawingObject && record->m_object->applyDrawing(*(d->lastDrawingObject))) { handled = true; switch (record->m_object->type()) { // Note: let's handle Pictures as OfficeArtObject, not as PictureObject case Object::Picture: { handled = false; } break; case Object::Chart: { d->charts.push_back(id); } break; case Object::Note: { // a NoteRecord will follow which picks that up. d->noteMap[id] = ++d->noteCount; } break; default: handled = false; } } if (!handled && d->lastDrawingObject) { //Q_ASSERT(!d->globals->drawing(record->m_object->id())); foreach (const MSO::OfficeArtSpgrContainerFileBlock& fb, d->lastDrawingObject->groupShape->rgfb) { if (fb.anon.is()) { delete d->lastGroupObject; d->lastGroupObject = new MSO::OfficeArtSpgrContainer(*fb.anon.get()); } else { const MSO::OfficeArtSpContainer& o = *fb.anon.get(); if (o.clientAnchor) { MSO::XlsOfficeArtClientAnchor* anchor = o.clientAnchor->anon.get(); if (!anchor) { qDebug() << "invalid client anchor"; } else { Cell *cell = d->sheet->cell(anchor->colL, anchor->rwT); OfficeArtObject* obj = new OfficeArtObject(o, d->officeArtObjectCounter++); cell->addDrawObject(obj); d->lastOfficeArtObject = obj; } } else { OfficeArtObject* obj = new OfficeArtObject(o, d->officeArtObjectCounter++); d->sheet->addDrawObject(obj, d->lastGroupObject); d->lastOfficeArtObject = obj; if (d->lastGroupObject) { if (!o.shapeProp.fChild) { delete d->lastGroupObject; d->lastGroupObject = 0; } } } } } } if (record->m_object) d->sharedObjects[id] = record->m_object; record->m_object = 0; // take over ownership delete d->lastDrawingObject; d->lastDrawingObject = 0; } void WorksheetSubStreamHandler::handleDefaultRowHeight(DefaultRowHeightRecord* record) { if (!record) return; if (!d->sheet) return; if (!record->isDyZero() && record->miyRw() != 0.0) d->sheet->setDefaultRowHeight(record->miyRw() / 20); } void WorksheetSubStreamHandler::handleDefaultColWidth(DefaultColWidthRecord* record) { if (!record) return; if (!d->sheet) return; d->sheet->setDefaultColWidth( Column::columnUnitsToPts(record->cchdefColWidth() * 256.0) ); } void WorksheetSubStreamHandler::handleSetup(SetupRecord*) { //TODO } void WorksheetSubStreamHandler::handleHCenter(HCenterRecord*) { //TODO } void WorksheetSubStreamHandler::handleVCenter(VCenterRecord*) { //TODO } void WorksheetSubStreamHandler::handleZoomLevel(ZoomLevelRecord *record) { if (!record) return; if (!d->sheet) return; if (record->denominator() == 0) return; d->sheet->setZoomLevel(record->numerator() / double(record->denominator())); } void WorksheetSubStreamHandler::handleMsoDrawing(MsoDrawingRecord* record) { if (!record || !record->isValid() || !d->sheet) return; // remember the MsoDrawingRecord for the ObjRecord that is expected to follow and to proper handle the drawing object. delete d->lastDrawingObject; d->lastDrawingObject = new MSO::OfficeArtDgContainer(record->dgContainer()); } void WorksheetSubStreamHandler::handleWindow2(Window2Record* record) { if (!record) return; if (!d->sheet) return; d->sheet->setShowGrid(record->isFDspGridRt()); d->sheet->setShowZeroValues(record->isFDspZerosRt()); d->sheet->setFirstVisibleCell(QPoint(record->colLeft(),record->rwTop())); d->sheet->setPageBreakViewEnabled(record->isFSLV()); d->sheet->setRightToLeft(record->isFRightToLeft()); } void WorksheetSubStreamHandler::handlePassword(PasswordRecord* record) { if (!record) return; if (!d->sheet) return; if (!record->wPassword()) return; - std::cout << "WorksheetSubStreamHandler::handlePassword passwordHash=" << record->wPassword() << std::endl; + qCDebug(lcSidewinder) << "WorksheetSubStreamHandler::handlePassword passwordHash=" << record->wPassword(); d->sheet->setPassword(record->wPassword()); #if 0 quint16 nHash = record->wPassword() ^ 0xCE4B; quint16 nDummy = nHash; quint16 nLen = 9; while( !(nDummy & 0x8000) && nLen ) { --nLen; nDummy <<= 1; } if( !nLen ) nLen = 2; if( (nLen ^ nHash) & 0x0001 ) nLen++; if( nLen == 9 ) { nLen = 10; nHash ^= 0x8001; } nHash ^= nLen; if( nLen < 9 ) nHash <<= (8 - nLen); quint16 nNewChar = 0; QByteArray sPasswd; for( quint16 iChar = nLen; iChar > 0; iChar-- ) { switch( iChar ) { case 10: nNewChar = (nHash & 0xC000) | 0x0400; nHash ^= nNewChar; nNewChar >>= 2; break; case 9: nNewChar = 0x4200; nHash ^= nNewChar; nNewChar >>= 1; break; case 1: nNewChar = nHash & 0xFF00; break; default: nNewChar = (nHash & 0xE000) ^ 0x2000; if( !nNewChar ) nNewChar = (nHash & 0xF000) ^ 0x1800; if( nNewChar == 0x6000 ) nNewChar = 0x6100; nHash ^= nNewChar; nHash <<= 1; break; } nNewChar >>= 8; nNewChar &= 0x00FF; //QByteArray sDummy = sPasswd; //typedef sal_Char STRING16[ 16 ]; //sPasswd = (sal_Char) nNewChar; //sPasswd += sDummy; sPasswd.prepend(QChar(nNewChar)); } - std::cout << ">>>> " << sPasswd.data() << std::endl; //0x218490a + qCDebug(lcSidewinder) << ">>>> " << sPasswd.data(); //0x218490a #endif } void WorksheetSubStreamHandler::handleBkHim(BkHimRecord* record) { d->sheet->setBackgroundImage(record->imagePath()); } void WorksheetSubStreamHandler::handleVerticalPageBreaksRecord(VerticalPageBreaksRecord* record) { const unsigned int count = record->count(); for(unsigned i = 0; i < count; ++i ) { VerticalPageBreak pageBreak; pageBreak.col = record->col(i); pageBreak.rowStart = record->rowStart(i); pageBreak.rowEnd = record->rowEnd(i); d->sheet->addVerticalPageBreak(pageBreak); } } void WorksheetSubStreamHandler::handleHorizontalPageBreaksRecord(HorizontalPageBreaksRecord* record) { const unsigned int count = record->count(); for(unsigned i = 0; i < count; ++i ) { HorizontalPageBreak pageBreak; pageBreak.row = record->row(i); pageBreak.colStart = record->colStart(i); pageBreak.colEnd = record->colEnd(i); d->sheet->addHorizontalPageBreak(pageBreak); } } void WorksheetSubStreamHandler::handleCondFmtRecord(Swinder::CondFmtRecord *record) { QRegion region; for (unsigned i = 0; i < record->refCount(); ++i) { QRect rect(QPoint(record->firstColumn(i), record->firstRow(i)), QPoint(record->lastColumn(i), record->lastRow(i))); region += rect; } d->curConditionalFormat = new ConditionalFormat; d->curConditionalFormat->setRegion(region); d->sheet->addConditionalFormat(d->curConditionalFormat); } void WorksheetSubStreamHandler::handleCFRecord(Swinder::CFRecord *record) { if (!d->curConditionalFormat) return; Conditional c; if (record->conditionType() == record->Formula) { c.cond = Conditional::Formula; FormulaDecoder dec; QByteArray rgce = record->rgce1(); unsigned size = rgce.size(); rgce.prepend((size >> 8) & 0xFF); rgce.prepend(size & 0xFF); FormulaTokens ts = dec.decodeFormula(rgce.size(), 0, reinterpret_cast(rgce.data()), record->version()); QString f = dec.decodeFormula(d->curConditionalFormat->region().boundingRect().top(), d->curConditionalFormat->region().boundingRect().left(), false, ts); c.value1 = Value(f); } else { int valcount = 1; c.cond = Conditional::None; switch (record->conditionFunction()) { case CFRecord::Between: c.cond = Conditional::Between; valcount = 2; break; case CFRecord::Outside: c.cond = Conditional::Outside; valcount = 2; break; case CFRecord::Equal: c.cond = Conditional::Equal; break; case CFRecord::NotEqual: c.cond = Conditional::NotEqual; break; case CFRecord::Greater: c.cond = Conditional::Greater; break; case CFRecord::Less: c.cond = Conditional::Less; break; case CFRecord::GreaterOrEqual: c.cond = Conditional::GreaterOrEqual; break; case CFRecord::LessOrEqual: c.cond = Conditional::LessOrEqual; break; } FormulaDecoder dec; QByteArray rgce = record->rgce1(); unsigned size = rgce.size(); rgce.prepend((size >> 8) & 0xFF); rgce.prepend(size & 0xFF); FormulaTokens ts = dec.decodeFormula(rgce.size(), 0, reinterpret_cast(rgce.data()), record->version()); if (ts.size() == 1 && (ts[0].id() == FormulaToken::ErrorCode || ts[0].id() == FormulaToken::Bool || ts[0].id() == FormulaToken::Integer || ts[0].id() == FormulaToken::Float || ts[0].id() == FormulaToken::String)) { c.value1 = ts[0].value(); } else { QString f = dec.decodeFormula(d->curConditionalFormat->region().boundingRect().top(), d->curConditionalFormat->region().boundingRect().left(), false, ts); c.value1 = Value(f); } if (valcount > 1) { rgce = record->rgce2(); size = rgce.size(); rgce.prepend((size >> 8) & 0xFF); rgce.prepend(size & 0xFF); ts = dec.decodeFormula(rgce.size(), 0, reinterpret_cast(rgce.data()), record->version()); if (ts.size() == 1 && (ts[0].id() == FormulaToken::ErrorCode || ts[0].id() == FormulaToken::Bool || ts[0].id() == FormulaToken::Integer || ts[0].id() == FormulaToken::Float || ts[0].id() == FormulaToken::String)) { c.value2 = ts[0].value(); } else { QString f = dec.decodeFormula(d->curConditionalFormat->region().boundingRect().top(), d->curConditionalFormat->region().boundingRect().left(), false, ts); c.value2 = Value(f); } } } if (record->isIbitAtrNum()) { if (record->isFIfmtUser()) { c.setValueFormat(record->formatString()); } else if (!record->isIfmtNinch()) { c.setValueFormat(d->globals->valueFormat(record->ifmt())); } } if (record->isIbitAtrFnt()) { // TODO: fontName // TODO: fontSize if (!record->isFontItalicNinch()) { c.setFontItalic(record->isFontItalic()); } if (!record->isFontStrikeoutNinch()) { c.setFontStrikeout(record->isFontStrikeout()); } if (!record->isFontWeightNinch()) { c.setFontBold(record->fontWeight() > 500); } if (!record->isSuperSubScriptNinch()) { switch (record->fontSuperSubScript()) { case CFRecord::SSS_Normal: c.setFontSubscript(false); c.setFontSuperscript(false); break; case CFRecord::SSS_Sub: c.setFontSubscript(true); c.setFontSuperscript(false); break; case CFRecord::SSS_Super: c.setFontSubscript(false); c.setFontSuperscript(true); break; default: break; } } if (!record->isUnderlineNinch() && record->underline() != CFRecord::UL_Ignore) { c.setFontUnderline(record->underline() != CFRecord::UL_None); } if (record->fontColor() >= 0) { c.setFontColor(d->globals->workbook()->color(record->fontColor())); } } if (record->isIbitAtrAlc()) { // TODO: alignment } if (record->isIbitAtrBdr()) { // TODO: borders } if (record->isIbitAtrPat()) { // TODO: background } if (record->isIbitAtrProt()) { // TODO: protection } d->curConditionalFormat->addConditional(c); } void WorksheetSubStreamHandler::handleAutoFilterRecord(Swinder::AutoFilterRecord *record) { Calligra::Sheets::Filter filter; int fieldNumber = record->entry(); if (record->isTopN()) { // TODO: top-N filters } else { Calligra::Sheets::Filter::Composition compos = record->join() == AutoFilterRecord::JoinAnd ? Calligra::Sheets::Filter::AndComposition : Calligra::Sheets::Filter::OrComposition; for (int i = 0; i < 2; i++) { Calligra::Sheets::Filter::Comparison compar = Calligra::Sheets::Filter::Match; switch (record->operation(i)) { case AutoFilterRecord::Less: compar = Calligra::Sheets::Filter::Less; break; case AutoFilterRecord::Equal: compar = Calligra::Sheets::Filter::Match; break; case AutoFilterRecord::LEqual: compar = Calligra::Sheets::Filter::LessOrEqual; break; case AutoFilterRecord::Greater: compar = Calligra::Sheets::Filter::Greater; break; case AutoFilterRecord::NotEqual: compar = Calligra::Sheets::Filter::NotMatch; break; case AutoFilterRecord::GEqual: compar = Calligra::Sheets::Filter::GreaterOrEqual; break; } switch (record->valueType(i)) { case AutoFilterRecord::RkNumber: { bool isInt; int iv; double dv; decodeRK(record->rkValue(i), isInt, iv, dv); if (isInt) dv = iv; filter.addCondition(compos, fieldNumber, compar, QString::number(dv), Qt::CaseInsensitive, Calligra::Sheets::Filter::Number); break; } case AutoFilterRecord::XNumber: filter.addCondition(compos, fieldNumber, compar, QString::number(record->floatValue(i)), Qt::CaseInsensitive, Calligra::Sheets::Filter::Number); break; case AutoFilterRecord::String: filter.addCondition(compos, fieldNumber, compar, record->string(i)); break; case AutoFilterRecord::BoolErr: // TODO break; case AutoFilterRecord::Blanks: filter.addCondition(compos, fieldNumber, Calligra::Sheets::Filter::Match, ""); break; case AutoFilterRecord::NonBlanks: filter.addCondition(compos, fieldNumber, Calligra::Sheets::Filter::NotMatch, ""); break; case AutoFilterRecord::UndefinedType: default: break; } } } Calligra::Sheets::Filter oldFilter = d->sheet->autoFilters(); oldFilter.addSubFilter(Calligra::Sheets::Filter::AndComposition, filter); d->sheet->setAutoFilters(oldFilter); } } // namespace Swinder diff --git a/filters/sheets/excel/sidewinder/xmltk.h b/filters/sheets/excel/sidewinder/xmltk.h index 3f899cdba10..2eaf774c4e2 100644 --- a/filters/sheets/excel/sidewinder/xmltk.h +++ b/filters/sheets/excel/sidewinder/xmltk.h @@ -1,249 +1,251 @@ /* Swinder - Portable library for spreadsheet Copyright (C) 2011 Sebastian Sauer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #ifndef SWINDER_XMLTK_H #define SWINDER_XMLTK_H #include #include +#include "utils.h" + namespace Swinder { enum XmlTkTags { XmlTkBackWallThickness = 0x0035, XmlTkBaseTimeUnitFrt = 0x005F, XmlTkColorMappingOverride = 0x0034, XmlTkDispBlanksAsFrt = 0x0066, XmlTkFloorThicknessFrt = 0x0036, XmlTkFormatCodeFrt = 0x0064, XmlTkHeightPercent = 0x0065, XmlTkLogBaseFrt = 0x0000, XmlTkMajorUnitFrt = 0x0053, XmlTkMajorUnitTypeFrt = 0x006A, XmlTkMaxFrt = 0x0055, XmlTkMinFrt = 0x0056, XmlTkMinorUnitFrt = 0x0054, XmlTkMinorUnitTypeFrt = 0x006B, XmlTkNoMultiLvlLbl = 0x002E, XmlTkOverlay = 0x002F, XmlTkPerspectiveFrt = 0x004D, XmlTkPieComboFrom12Frt = 0x005E, XmlTkRAngAxOffFrt = 0x0050, XmlTkRotXFrt = 0x004E, XmlTkRotYFrt = 0x004F, XmlTkShowDLblsOverMax = 0x005B, XmlTkSpb = 0x001E, XmlTkStartSurface = 0x0059, XmlTkStyle = 0x0003, XmlTkSymbolFrt = 0x0022, XmlTkThemeOverride = 0x0033, XmlTkTickLabelPositionFrt = 0x005C, XmlTkTickLabelSkipFrt = 0x0051, XmlTkTickMarkSkipFrt = 0x0052, XmlTkTpb = 0x0020 }; QByteArray xmlTkTagName(XmlTkTags tag) { switch (tag) { case XmlTkBackWallThickness: return "BackWallThickness"; case XmlTkBaseTimeUnitFrt: return "BaseTimeUnitFrt"; case XmlTkColorMappingOverride: return "ColorMappingOverride"; case XmlTkDispBlanksAsFrt: return "DispBlanksAsFrt"; case XmlTkFloorThicknessFrt: return "FloorThicknessFrt"; case XmlTkFormatCodeFrt: return "FormatCodeFrt"; case XmlTkHeightPercent: return "HeightPercent"; case XmlTkLogBaseFrt: return "LogBaseFrt"; case XmlTkMajorUnitFrt: return "MajorUnitFrt"; case XmlTkMajorUnitTypeFrt: return "MajorUnitTypeFrt"; case XmlTkMaxFrt: return "MaxFrt"; case XmlTkMinFrt: return "MinFrt"; case XmlTkMinorUnitFrt: return "MinorUnitFrt"; case XmlTkMinorUnitTypeFrt: return "MinorUnitTypeFrt"; case XmlTkNoMultiLvlLbl: return "NoMultiLvlLbl"; case XmlTkOverlay: return "Overlay"; case XmlTkPerspectiveFrt: return "PerspectiveFrt"; case XmlTkPieComboFrom12Frt: return "PieComboFrom12Frt"; case XmlTkRAngAxOffFrt: return "RAngAxOffFrt"; case XmlTkRotXFrt: return "RotXFrt"; case XmlTkRotYFrt: return "RotYFrt"; case XmlTkShowDLblsOverMax: return "ShowDLblsOverMax"; case XmlTkSpb: return "Spb"; case XmlTkStartSurface: return "StartSurface"; case XmlTkStyle: return "Style"; case XmlTkSymbolFrt: return "SymbolFrt"; case XmlTkThemeOverride: return "ThemeOverride"; case XmlTkTickLabelPositionFrt: return "TickLabelPositionFrt"; case XmlTkTickLabelSkipFrt: return "TickLabelSkipFrt"; case XmlTkTickMarkSkipFrt: return "TickMarkSkipFrt"; case XmlTkTpb: return "Tpb"; } return QByteArray(); } class XmlTk { public: unsigned m_xmlTkTag; virtual QString value() const = 0; virtual QString type() const = 0; virtual unsigned size() const = 0; explicit XmlTk(const unsigned char* data) { m_xmlTkTag = readU16(data + 2); } virtual ~XmlTk() {} }; class XmlTkBool : public XmlTk { public: bool m_value; virtual QString value() const { return QString::number(m_value); } virtual QString type() const { return "bool"; } virtual unsigned size() const { return 6; } explicit XmlTkBool(const unsigned char* data) : XmlTk(data) { m_value = readU8(data + 4); } }; class XmlTkDouble : public XmlTk { public: double m_value; virtual QString value() const { return QString::number(m_value); } virtual QString type() const { return "double"; } virtual unsigned size() const { return 12; } explicit XmlTkDouble(const unsigned char* data) : XmlTk(data) { m_value = readFloat64(data + 4); } }; class XmlTkDWord : public XmlTk { public: int m_value; virtual QString value() const { return QString::number(m_value); } virtual QString type() const { return "dword"; } virtual unsigned size() const { return 8; } explicit XmlTkDWord(const unsigned char* data) : XmlTk(data) { m_value = readS32(data + 4); } }; class XmlTkString : public XmlTk { public: QString m_value; virtual QString value() const { return m_value; } virtual QString type() const { return "string"; } virtual unsigned size() const { return 8 + m_size; } explicit XmlTkString(const unsigned char* data) : XmlTk(data) { m_cchValue = readU32(data + 4); m_value = readUnicodeChars(data + 8, m_cchValue, -1, 0, &m_size); } private: unsigned m_cchValue; unsigned m_size; }; class XmlTkToken : public XmlTk { public: unsigned m_value; virtual QString value() const { return QString::number(m_value); } virtual QString type() const { return "token"; } virtual unsigned size() const { return 6; } explicit XmlTkToken(const unsigned char* data) : XmlTk(data) { m_value = readU16(data + 4); } }; class XmlTkBlob : public XmlTk { public: QList m_tokens; virtual QString value() const { QStringList tokens; foreach(XmlTk* t, m_tokens) tokens.append( QString("%1 %2 %3").arg(t->m_xmlTkTag, 0, 16).arg(t->type()).arg(t->value()) ); return QString("[%1]").arg(tokens.join(", ")); } virtual QString type() const { return "blob"; } virtual unsigned size() const { return 8 + m_cbBlob; } explicit XmlTkBlob(const unsigned char* data); virtual ~XmlTkBlob() { qDeleteAll(m_tokens); } private: unsigned m_cbBlob; }; class XmlTkBegin : public XmlTk { public: virtual QString value() const { return QString(); } virtual QString type() const { return "begin"; } virtual unsigned size() const { return 4; } explicit XmlTkBegin(const unsigned char* data) : XmlTk(data) {} }; class XmlTkEnd : public XmlTk { public: virtual QString value() const { return QString(); } virtual QString type() const { return "end"; } virtual unsigned size() const { return 4; } explicit XmlTkEnd(const unsigned char* data) : XmlTk(data) {} }; XmlTk* parseXmlTk(const unsigned char* data) { unsigned drType = readU8(data); switch (drType) { case 0x00: return new XmlTkBegin(data); case 0x01: return new XmlTkEnd(data); case 0x02: return new XmlTkBool(data); case 0x03: return new XmlTkDouble(data); case 0x04: return new XmlTkDWord(data); case 0x05: return new XmlTkString(data); case 0x06: return new XmlTkToken(data); case 0x07: return new XmlTkBlob(data); default: - std::cout << "Error in " << __FUNCTION__ << ": Unhandled drType " << qPrintable(QString::number(drType, 16)) << std::endl; + qCDebug(lcSidewinder) << "Error in " << __FUNCTION__ << ": Unhandled drType " << QString::number(drType, 16); break; } return 0; } QList parseXmlTkChain(const unsigned char* data, int size) { QList tokens; for (int p = 0; p + 4 < size;) { XmlTk *t = parseXmlTk(data + p); if (!t) break; tokens.append(t); p += t->size(); } return tokens; } XmlTkBlob::XmlTkBlob(const unsigned char* data) : XmlTk(data) { m_cbBlob = readU32(data + 4); parseXmlTkChain(data + 8, m_cbBlob); } } // namespace Swinder #endif // SWINDER_CHARTSUBSTREAMHANDLER_H