/* -*- 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 <sheetdatacontext.hxx>
#include <oox/core/xmlfilterbase.hxx>
#include <oox/helper/attributelist.hxx>
#include <oox/helper/binaryinputstream.hxx>
#include <oox/token/namespaces.hxx>
#include <oox/token/tokens.hxx>
#include <addressconverter.hxx>
#include <biffhelper.hxx>
#include <formulaparser.hxx>
#include <richstringcontext.hxx>
#include <sal/log.hxx>
#include <o3tl/string_view.hxx>
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<RichString>();
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, 0); // default style index is 0
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 (nSepPos == std::u16string_view::npos)
continue;
if (nSepPos > 0 && (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 = false;
std::string_view p = rAttribs.getView(XML_r);
if (!p.empty())
{
bValid = mrAddressConv.convertToCellAddress(maCellData.maCellAddr, OUString::fromUtf8(p),
mnSheet, true);
if (bValid)
mnCol = maCellData.maCellAddr.Col();
}
if (!bValid)
{
++mnCol;
ScAddress aAddress( mnCol, mnRow, mnSheet );
bValid = mrAddressConv.checkCellAddress( aAddress, true );
maCellData.maCellAddr = aAddress;
}
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<RichString>();
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<RichString>();
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: */
↑ V1083 Signed integer overflow is possible in 'o3tl::toInt32(aColSpanToken.substr(0, nSepPos)) - 1' arithmetic expression. This leads to undefined behavior. Left operand is in range [-2147483648..2147483647], right operand is '1'.
↑ V1083 Signed integer overflow is possible in 'o3tl::toInt32(aColSpanToken.substr(nSepPos + 1)) - 1' arithmetic expression. This leads to undefined behavior. Left operand is in range [-2147483648..2147483647], right operand is '1'.