/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include #include #include #include namespace oox::xls { using ::oox::core::ContextHandlerRef; namespace { // record constants ----------------------------------------------------------- const sal_uInt32 BIFF12_CELL_SHOWPHONETIC = 0x01000000; const sal_uInt8 BIFF12_DATATABLE_ROW = 0x01; const sal_uInt8 BIFF12_DATATABLE_2D = 0x02; const sal_uInt8 BIFF12_DATATABLE_REF1DEL = 0x04; const sal_uInt8 BIFF12_DATATABLE_REF2DEL = 0x08; const sal_uInt16 BIFF12_ROW_THICKTOP = 0x0001; const sal_uInt16 BIFF12_ROW_THICKBOTTOM = 0x0002; const sal_uInt16 BIFF12_ROW_COLLAPSED = 0x0800; const sal_uInt16 BIFF12_ROW_HIDDEN = 0x1000; const sal_uInt16 BIFF12_ROW_CUSTOMHEIGHT = 0x2000; const sal_uInt16 BIFF12_ROW_CUSTOMFORMAT = 0x4000; const sal_uInt8 BIFF12_ROW_SHOWPHONETIC = 0x01; } // namespace SheetDataContext::SheetDataContext( WorksheetFragmentBase& rFragment ) : WorksheetContextBase( rFragment ), mrAddressConv( rFragment.getAddressConverter() ), mrSheetData( rFragment.getSheetData() ), mnSheet( rFragment.getSheetIndex() ), mbHasFormula( false ), mbValidRange( false ), mnRow( -1 ), mnCol( -1 ) { SAL_INFO( "sc.filter", "start safe sheet data context - unlock" ); mxFormulaParser.reset(rFragment.createFormulaParser()); } SheetDataContext::~SheetDataContext() { SAL_INFO( "sc.filter", "end safe sheet data context - relock" ); } ContextHandlerRef SheetDataContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) { switch( getCurrentElement() ) { case XLS_TOKEN( sheetData ): if( nElement == XLS_TOKEN( row ) ) { importRow( rAttribs ); return this; } break; case XLS_TOKEN( row ): // do not process cell elements with invalid (out-of-range) address if( nElement == XLS_TOKEN( c ) && importCell( rAttribs ) ) return this; break; case XLS_TOKEN( c ): switch( nElement ) { case XLS_TOKEN( is ): mxInlineStr = std::make_shared(); return new RichStringContext( *this, mxInlineStr ); case XLS_TOKEN( v ): return this; // characters contain cell value case XLS_TOKEN( f ): importFormula( rAttribs ); return this; // characters contain formula string } break; } return nullptr; } void SheetDataContext::onCharacters( const OUString& rChars ) { switch( getCurrentElement() ) { case XLS_TOKEN( v ): maCellValue = rChars; break; case XLS_TOKEN( f ): if( maFmlaData.mnFormulaType != XML_TOKEN_INVALID ) { maFormulaStr = rChars; } break; } } void SheetDataContext::onEndElement() { if( getCurrentElement() != XLS_TOKEN( c ) ) return; // try to create a formula cell if( mbHasFormula ) switch( maFmlaData.mnFormulaType ) { // will buffer formulas but need to // a) need to set format first // :/ case XML_normal: setCellFormula( maCellData.maCellAddr, maFormulaStr ); mrSheetData.setCellFormat( maCellData ); // If a number cell has some preloaded value, stick it into the buffer // but do this only for real cell formulas (not array, shared etc.) if (!maCellValue.isEmpty()) setCellFormulaValue(maCellData.maCellAddr, maCellValue, maCellData.mnCellType); break; case XML_shared: if( maFmlaData.mnSharedId >= 0 ) { if( mbValidRange && maFmlaData.isValidSharedRef( maCellData.maCellAddr ) ) createSharedFormulaMapEntry(maCellData.maCellAddr, maFmlaData.mnSharedId, maFormulaStr); setCellFormula(maCellData.maCellAddr, maFmlaData.mnSharedId, maCellValue, maCellData.mnCellType); mrSheetData.setCellFormat( maCellData ); } else // no success, set plain cell value and formatting below mbHasFormula = false; break; case XML_array: if( mbValidRange && maFmlaData.isValidArrayRef( maCellData.maCellAddr ) ) { setCellArrayFormula( maFmlaData.maFormulaRef, maCellData.maCellAddr, maFormulaStr ); } // set cell formatting, but do not set result as cell value mrSheetData.setBlankCell( maCellData ); break; case XML_dataTable: if( mbValidRange ) mrSheetData.createTableOperation( maFmlaData.maFormulaRef, maTableData ); // set cell formatting, but do not set result as cell value mrSheetData.setBlankCell( maCellData ); break; default: OSL_ENSURE( maFmlaData.mnFormulaType == XML_TOKEN_INVALID, "SheetDataContext::onEndElement - unknown formula type" ); mbHasFormula = false; } if( mbHasFormula ) return; // no formula created: try to set the cell value if( !maCellValue.isEmpty() ) switch( maCellData.mnCellType ) { case XML_n: mrSheetData.setValueCell( maCellData, maCellValue.toDouble() ); break; case XML_b: { // Some generators may write true or false instead of 1 or 0. /* XXX NOTE: PivotCacheItem::readBool() may suffer from this as * well, but for now let's assume that software writing this * here wrong won't write pivot caches at all.. */ bool bValue = (maCellValue.toDouble() != 0.0); if (!bValue && maCellValue.equalsIgnoreAsciiCase(u"true")) bValue = true; mrSheetData.setBooleanCell( maCellData, bValue ); } break; case XML_e: mrSheetData.setErrorCell( maCellData, maCellValue ); break; case XML_str: mrSheetData.setStringCell( maCellData, maCellValue ); break; case XML_s: mrSheetData.setStringCell( maCellData, maCellValue.toInt32() ); break; case XML_d: mrSheetData.setDateCell( maCellData, maCellValue ); break; } else if( (maCellData.mnCellType == XML_inlineStr) && mxInlineStr ) { mxInlineStr->finalizeImport(*this); mrSheetData.setStringCell( maCellData, mxInlineStr ); } else { // empty cell, update cell type maCellData.mnCellType = XML_TOKEN_INVALID; mrSheetData.setBlankCell( maCellData ); } } ContextHandlerRef SheetDataContext::onCreateRecordContext( sal_Int32 nRecId, SequenceInputStream& rStrm ) { switch( getCurrentElement() ) { case BIFF12_ID_SHEETDATA: if( nRecId == BIFF12_ID_ROW ) { importRow( rStrm ); return this; } break; case BIFF12_ID_ROW: switch( nRecId ) { case BIFF12_ID_ARRAY: importArray( rStrm ); break; case BIFF12_ID_CELL_BOOL: importCellBool( rStrm, CELLTYPE_VALUE ); break; case BIFF12_ID_CELL_BLANK: importCellBlank( rStrm, CELLTYPE_VALUE ); break; case BIFF12_ID_CELL_DOUBLE: importCellDouble( rStrm, CELLTYPE_VALUE ); break; case BIFF12_ID_CELL_ERROR: importCellError( rStrm, CELLTYPE_VALUE ); break; case BIFF12_ID_CELL_RK: importCellRk( rStrm, CELLTYPE_VALUE ); break; case BIFF12_ID_CELL_RSTRING: importCellRString( rStrm, CELLTYPE_VALUE ); break; case BIFF12_ID_CELL_SI: importCellSi( rStrm, CELLTYPE_VALUE ); break; case BIFF12_ID_CELL_STRING: importCellString( rStrm, CELLTYPE_VALUE ); break; case BIFF12_ID_DATATABLE: importDataTable( rStrm ); break; case BIFF12_ID_FORMULA_BOOL: importCellBool( rStrm, CELLTYPE_FORMULA ); break; case BIFF12_ID_FORMULA_DOUBLE: importCellDouble( rStrm, CELLTYPE_FORMULA ); break; case BIFF12_ID_FORMULA_ERROR: importCellError( rStrm, CELLTYPE_FORMULA ); break; case BIFF12_ID_FORMULA_STRING: importCellString( rStrm, CELLTYPE_FORMULA ); break; case BIFF12_ID_MULTCELL_BOOL: importCellBool( rStrm, CELLTYPE_MULTI ); break; case BIFF12_ID_MULTCELL_BLANK: importCellBlank( rStrm, CELLTYPE_MULTI ); break; case BIFF12_ID_MULTCELL_DOUBLE: importCellDouble( rStrm, CELLTYPE_MULTI ); break; case BIFF12_ID_MULTCELL_ERROR: importCellError( rStrm, CELLTYPE_MULTI ); break; case BIFF12_ID_MULTCELL_RK: importCellRk( rStrm, CELLTYPE_MULTI ); break; case BIFF12_ID_MULTCELL_RSTRING:importCellRString( rStrm, CELLTYPE_MULTI ); break; case BIFF12_ID_MULTCELL_SI: importCellSi( rStrm, CELLTYPE_MULTI ); break; case BIFF12_ID_MULTCELL_STRING: importCellString( rStrm, CELLTYPE_MULTI ); break; case BIFF12_ID_SHAREDFMLA: importSharedFmla( rStrm ); break; } break; } return nullptr; } // private -------------------------------------------------------------------- void SheetDataContext::importRow( const AttributeList& rAttribs ) { RowModel aModel; sal_Int32 nRow = rAttribs.getInteger( XML_r, -1 ); // 1-based row index if(nRow != -1) { aModel.mnRow = nRow; mnRow = nRow-1; // to 0-based row index. } else aModel.mnRow = (++mnRow + 1); // increment 0-based row index, to 1-based model row mrAddressConv.checkRow( mnRow, true); mnCol = -1; aModel.mfHeight = rAttribs.getDouble( XML_ht, -1.0 ); aModel.mnXfId = rAttribs.getInteger( XML_s, -1 ); aModel.mnLevel = rAttribs.getInteger( XML_outlineLevel, 0 ); aModel.mbCustomHeight = rAttribs.getBool( XML_customHeight, false ); aModel.mbCustomFormat = rAttribs.getBool( XML_customFormat, false ); aModel.mbShowPhonetic = rAttribs.getBool( XML_ph, false ); aModel.mbHidden = rAttribs.getBool( XML_hidden, false ); aModel.mbCollapsed = rAttribs.getBool( XML_collapsed, false ); aModel.mbThickTop = rAttribs.getBool( XML_thickTop, false ); aModel.mbThickBottom = rAttribs.getBool( XML_thickBot, false ); if (aModel.mfHeight > 0 && getFilter().isMSODocument()) { aModel.mfHeight -= fmod(aModel.mfHeight, 0.75); //round down to 0.75pt } // decode the column spans (space-separated list of colon-separated integer pairs) OUString aColSpansText = rAttribs.getString( XML_spans, OUString() ); sal_Int32 nIndex = 0; while( nIndex >= 0 ) { std::u16string_view aColSpanToken = o3tl::getToken(aColSpansText, 0, ' ', nIndex ); size_t nSepPos = aColSpanToken.find( ':' ); if( (0 < nSepPos) && (nSepPos + 1 < aColSpanToken.size()) ) { // OOXML uses 1-based integer column indexes, row model expects 0-based colspans const sal_Int32 nCol1 = o3tl::toInt32(aColSpanToken.substr( 0, nSepPos )) - 1; const bool bValid1 = mrAddressConv.checkCol( nCol1, true); if (bValid1) { const sal_Int32 nCol2 = o3tl::toInt32(aColSpanToken.substr( nSepPos + 1 )) - 1; mrAddressConv.checkCol( nCol2, true); } } } // set row properties in the current sheet setRowModel( aModel ); } bool SheetDataContext::importCell( const AttributeList& rAttribs ) { bool bValid = true; std::string_view p = rAttribs.getView(XML_r); if (p.empty()) { ++mnCol; ScAddress aAddress( mnCol, mnRow, mnSheet ); bValid = mrAddressConv.checkCellAddress( aAddress, true ); maCellData.maCellAddr = aAddress; } else { bValid = mrAddressConv.convertToCellAddress(maCellData.maCellAddr, p, mnSheet, true); mnCol = maCellData.maCellAddr.Col(); } if( bValid ) { maCellData.mnCellType = rAttribs.getToken( XML_t, XML_n ); maCellData.mnXfId = rAttribs.getInteger( XML_s, -1 ); maCellData.mbShowPhonetic = rAttribs.getBool( XML_ph, false ); // reset cell value, formula settings, and inline string maCellValue.clear(); mxInlineStr.reset(); mbHasFormula = false; // update used area of the sheet extendUsedArea( maCellData.maCellAddr ); } return bValid; } void SheetDataContext::importFormula( const AttributeList& rAttribs ) { mbHasFormula = true; mbValidRange = mrAddressConv.convertToCellRange( maFmlaData.maFormulaRef, rAttribs.getString( XML_ref, OUString() ), mnSheet, true, true ); maFmlaData.mnFormulaType = rAttribs.getToken( XML_t, XML_normal ); maFmlaData.mnSharedId = rAttribs.getInteger( XML_si, -1 ); if( maFmlaData.mnFormulaType == XML_dataTable ) { maTableData.maRef1 = rAttribs.getString( XML_r1, OUString() ); maTableData.maRef2 = rAttribs.getString( XML_r2, OUString() ); maTableData.mb2dTable = rAttribs.getBool( XML_dt2D, false ); maTableData.mbRowTable = rAttribs.getBool( XML_dtr, false ); maTableData.mbRef1Deleted = rAttribs.getBool( XML_del1, false ); maTableData.mbRef2Deleted = rAttribs.getBool( XML_del2, false ); } maFormulaStr.clear(); } void SheetDataContext::importRow( SequenceInputStream& rStrm ) { RowModel aModel; sal_Int32 nSpanCount; sal_uInt16 nHeight, nFlags1; sal_uInt8 nFlags2; maCurrPos.mnRow = rStrm.readInt32(); aModel.mnXfId = rStrm.readInt32(); nHeight = rStrm.readuInt16(); nFlags1 = rStrm.readuInt16(); nFlags2 = rStrm.readuChar(); nSpanCount = rStrm.readInt32(); maCurrPos.mnCol = 0; mrAddressConv.checkRow( maCurrPos.mnRow, true); // row index is 0-based in BIFF12, but RowModel expects 1-based aModel.mnRow = maCurrPos.mnRow + 1; // row height is in twips in BIFF12, convert to points aModel.mfHeight = nHeight / 20.0; aModel.mnLevel = extractValue< sal_Int32 >( nFlags1, 8, 3 ); aModel.mbCustomHeight = getFlag( nFlags1, BIFF12_ROW_CUSTOMHEIGHT ); aModel.mbCustomFormat = getFlag( nFlags1, BIFF12_ROW_CUSTOMFORMAT ); aModel.mbShowPhonetic = getFlag( nFlags2, BIFF12_ROW_SHOWPHONETIC ); aModel.mbHidden = getFlag( nFlags1, BIFF12_ROW_HIDDEN ); aModel.mbCollapsed = getFlag( nFlags1, BIFF12_ROW_COLLAPSED ); aModel.mbThickTop = getFlag( nFlags1, BIFF12_ROW_THICKTOP ); aModel.mbThickBottom = getFlag( nFlags1, BIFF12_ROW_THICKBOTTOM ); // read the column spans for( sal_Int32 nSpanIdx = 0; (nSpanIdx < nSpanCount) && !rStrm.isEof(); ++nSpanIdx ) { sal_Int32 nFirstCol, nLastCol; nFirstCol = rStrm.readInt32(); mrAddressConv.checkCol( nFirstCol, true); nLastCol = rStrm.readInt32(); mrAddressConv.checkCol( nLastCol, true); } // set row properties in the current sheet setRowModel( aModel ); } bool SheetDataContext::readCellHeader( SequenceInputStream& rStrm, CellType eCellType ) { switch( eCellType ) { case CELLTYPE_VALUE: case CELLTYPE_FORMULA: maCurrPos.mnCol = rStrm.readInt32(); break; case CELLTYPE_MULTI: ++maCurrPos.mnCol; break; } sal_uInt32 nXfId = rStrm.readuInt32(); bool bValidAddr = mrAddressConv.convertToCellAddress( maCellData.maCellAddr, maCurrPos, mnSheet, true ); maCellData.mnXfId = extractValue< sal_Int32 >( nXfId, 0, 24 ); maCellData.mbShowPhonetic = getFlag( nXfId, BIFF12_CELL_SHOWPHONETIC ); // update used area of the sheet if( bValidAddr ) extendUsedArea( maCellData.maCellAddr ); return bValidAddr; } ApiTokenSequence SheetDataContext::readCellFormula( SequenceInputStream& rStrm ) { rStrm.skip( 2 ); return mxFormulaParser->importFormula( maCellData.maCellAddr, FormulaType::Cell, rStrm ); } bool SheetDataContext::readFormulaRef( SequenceInputStream& rStrm ) { BinRange aRange; rStrm >> aRange; return mrAddressConv.convertToCellRange( maFmlaData.maFormulaRef, aRange, mnSheet, true, true ); } void SheetDataContext::importCellBool( SequenceInputStream& rStrm, CellType eCellType ) { if( readCellHeader( rStrm, eCellType ) ) { maCellData.mnCellType = XML_b; bool bValue = rStrm.readuInt8() != 0; if( eCellType == CELLTYPE_FORMULA ) mrSheetData.setFormulaCell( maCellData, readCellFormula( rStrm ) ); else mrSheetData.setBooleanCell( maCellData, bValue ); } } void SheetDataContext::importCellBlank( SequenceInputStream& rStrm, CellType eCellType ) { OSL_ENSURE( eCellType != CELLTYPE_FORMULA, "SheetDataContext::importCellBlank - no formula cells supported" ); if( readCellHeader( rStrm, eCellType ) ) mrSheetData.setBlankCell( maCellData ); } void SheetDataContext::importCellDouble( SequenceInputStream& rStrm, CellType eCellType ) { if( readCellHeader( rStrm, eCellType ) ) { maCellData.mnCellType = XML_n; double fValue = rStrm.readDouble(); if( eCellType == CELLTYPE_FORMULA ) mrSheetData.setFormulaCell( maCellData, readCellFormula( rStrm ) ); else mrSheetData.setValueCell( maCellData, fValue ); } } void SheetDataContext::importCellError( SequenceInputStream& rStrm, CellType eCellType ) { if( readCellHeader( rStrm, eCellType ) ) { maCellData.mnCellType = XML_e; sal_uInt8 nErrorCode = rStrm.readuInt8(); if( eCellType == CELLTYPE_FORMULA ) mrSheetData.setFormulaCell( maCellData, readCellFormula( rStrm ) ); else mrSheetData.setErrorCell( maCellData, nErrorCode ); } } void SheetDataContext::importCellRk( SequenceInputStream& rStrm, CellType eCellType ) { OSL_ENSURE( eCellType != CELLTYPE_FORMULA, "SheetDataContext::importCellRk - no formula cells supported" ); if( readCellHeader( rStrm, eCellType ) ) { maCellData.mnCellType = XML_n; mrSheetData.setValueCell( maCellData, BiffHelper::calcDoubleFromRk( rStrm.readInt32() ) ); } } void SheetDataContext::importCellRString( SequenceInputStream& rStrm, CellType eCellType ) { OSL_ENSURE( eCellType != CELLTYPE_FORMULA, "SheetDataContext::importCellRString - no formula cells supported" ); if( readCellHeader( rStrm, eCellType ) ) { maCellData.mnCellType = XML_inlineStr; RichStringRef xString = std::make_shared(); xString->importString( rStrm, true, *this ); xString->finalizeImport( *this ); mrSheetData.setStringCell( maCellData, xString ); } } void SheetDataContext::importCellSi( SequenceInputStream& rStrm, CellType eCellType ) { OSL_ENSURE( eCellType != CELLTYPE_FORMULA, "SheetDataContext::importCellSi - no formula cells supported" ); if( readCellHeader( rStrm, eCellType ) ) { maCellData.mnCellType = XML_s; mrSheetData.setStringCell( maCellData, rStrm.readInt32() ); } } void SheetDataContext::importCellString( SequenceInputStream& rStrm, CellType eCellType ) { if( readCellHeader( rStrm, eCellType ) ) { maCellData.mnCellType = XML_inlineStr; // always import the string, stream will point to formula afterwards, if existing RichStringRef xString = std::make_shared(); xString->importString( rStrm, false, *this ); xString->finalizeImport( *this ); if( eCellType == CELLTYPE_FORMULA ) mrSheetData.setFormulaCell( maCellData, readCellFormula( rStrm ) ); else mrSheetData.setStringCell( maCellData, xString ); } } void SheetDataContext::importArray( SequenceInputStream& rStrm ) { if( readFormulaRef( rStrm ) && maFmlaData.isValidArrayRef( maCellData.maCellAddr ) ) { rStrm.skip( 1 ); ApiTokenSequence aTokens = mxFormulaParser->importFormula( maCellData.maCellAddr, FormulaType::Array, rStrm ); mrSheetData.createArrayFormula( maFmlaData.maFormulaRef, aTokens ); } } void SheetDataContext::importDataTable( SequenceInputStream& rStrm ) { if( !readFormulaRef( rStrm ) ) return; BinAddress aRef1, aRef2; sal_uInt8 nFlags; rStrm >> aRef1 >> aRef2; nFlags = rStrm.readuChar(); maTableData.maRef1 = FormulaProcessorBase::generateAddress2dString( aRef1, false ); maTableData.maRef2 = FormulaProcessorBase::generateAddress2dString( aRef2, false ); maTableData.mbRowTable = getFlag( nFlags, BIFF12_DATATABLE_ROW ); maTableData.mb2dTable = getFlag( nFlags, BIFF12_DATATABLE_2D ); maTableData.mbRef1Deleted = getFlag( nFlags, BIFF12_DATATABLE_REF1DEL ); maTableData.mbRef2Deleted = getFlag( nFlags, BIFF12_DATATABLE_REF2DEL ); mrSheetData.createTableOperation( maFmlaData.maFormulaRef, maTableData ); } void SheetDataContext::importSharedFmla( SequenceInputStream& rStrm ) { if( readFormulaRef( rStrm ) && maFmlaData.isValidSharedRef( maCellData.maCellAddr ) ) { ApiTokenSequence aTokens = mxFormulaParser->importFormula( maCellData.maCellAddr, FormulaType::SharedFormula, rStrm ); mrSheetData.createSharedFormula( maCellData.maCellAddr, aTokens ); } } } // namespace oox /* vim:set shiftwidth=4 softtabstop=4 expandtab: */