/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace formula; using namespace ::com::sun::star; using ::std::vector; osl::Mutex ScCompiler::maMutex; const CharClass* ScCompiler::pCharClassEnglish = nullptr; const CharClass* ScCompiler::pCharClassLocalized = nullptr; const ScCompiler::Convention* ScCompiler::pConventions[ ] = { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr }; namespace { enum ScanState { ssGetChar, ssGetBool, ssGetValue, ssGetString, ssSkipString, ssGetIdent, ssGetReference, ssSkipReference, ssGetErrorConstant, ssGetTableRefItem, ssGetTableRefColumn, ssStop }; } static const char* pInternal[2] = { "TTT", "__DEBUG_VAR" }; using namespace ::com::sun::star::i18n; void ScCompiler::fillFromAddInMap( const NonConstOpCodeMapPtr& xMap,FormulaGrammar::Grammar _eGrammar ) const { size_t nSymbolOffset; switch( _eGrammar ) { // XFunctionAccess and XCell::setFormula()/getFormula() API always used // PODF grammar symbols, keep it. case FormulaGrammar::GRAM_API: case FormulaGrammar::GRAM_PODF: nSymbolOffset = offsetof( AddInMap, pUpper); break; default: case FormulaGrammar::GRAM_ODFF: nSymbolOffset = offsetof( AddInMap, pODFF); break; case FormulaGrammar::GRAM_ENGLISH: nSymbolOffset = offsetof( AddInMap, pEnglish); break; } const AddInMap* const pStop = g_aAddInMap + GetAddInMapCount(); for (const AddInMap* pMap = g_aAddInMap; pMap < pStop; ++pMap) { char const * const * ppSymbol = reinterpret_cast< char const * const * >( reinterpret_cast< char const * >(pMap) + nSymbolOffset); xMap->putExternal( OUString::createFromAscii( *ppSymbol), OUString::createFromAscii( pMap->pOriginal)); } if (_eGrammar == FormulaGrammar::GRAM_API) { // Add English names additionally to programmatic names, so they // can be used in XCell::setFormula() non-localized API calls. // Note the reverse map will still deliver programmatic names for // XCell::getFormula(). nSymbolOffset = offsetof( AddInMap, pEnglish); for (const AddInMap* pMap = g_aAddInMap; pMap < pStop; ++pMap) { char const * const * ppSymbol = reinterpret_cast< char const * const * >( reinterpret_cast< char const * >(pMap) + nSymbolOffset); xMap->putExternal( OUString::createFromAscii( *ppSymbol), OUString::createFromAscii( pMap->pOriginal)); } } } void ScCompiler::fillFromAddInCollectionUpperName( const NonConstOpCodeMapPtr& xMap ) const { ScUnoAddInCollection* pColl = ScGlobal::GetAddInCollection(); tools::Long nCount = pColl->GetFuncCount(); for (tools::Long i=0; i < nCount; ++i) { const ScUnoAddInFuncData* pFuncData = pColl->GetFuncData(i); if (pFuncData) xMap->putExternalSoftly( pFuncData->GetUpperName(), pFuncData->GetOriginalName()); } } void ScCompiler::fillFromAddInCollectionEnglishName( const NonConstOpCodeMapPtr& xMap ) const { ScUnoAddInCollection* pColl = ScGlobal::GetAddInCollection(); tools::Long nCount = pColl->GetFuncCount(); for (tools::Long i=0; i < nCount; ++i) { const ScUnoAddInFuncData* pFuncData = pColl->GetFuncData(i); if (pFuncData) { const OUString aName( pFuncData->GetUpperEnglish()); if (!aName.isEmpty()) xMap->putExternalSoftly( aName, pFuncData->GetOriginalName()); else xMap->putExternalSoftly( pFuncData->GetUpperName(), pFuncData->GetOriginalName()); } } } void ScCompiler::DeInit() { if (pCharClassEnglish) { delete pCharClassEnglish; pCharClassEnglish = nullptr; } if (pCharClassLocalized) { delete pCharClassLocalized; pCharClassLocalized = nullptr; } } bool ScCompiler::IsEnglishSymbol( const OUString& rName ) { // function names are always case-insensitive OUString aUpper = GetCharClassEnglish()->uppercase(rName); // 1. built-in function name formula::FormulaCompiler aCompiler; OpCode eOp = aCompiler.GetEnglishOpCode( aUpper ); if ( eOp != ocNone ) { return true; } // 2. old add in functions if (ScGlobal::GetLegacyFuncCollection()->findByName(aUpper)) { return true; } // 3. new (uno) add in functions OUString aIntName = ScGlobal::GetAddInCollection()->FindFunction(aUpper, false); return !aIntName.isEmpty(); // no valid function name } const CharClass* ScCompiler::GetCharClassEnglish() { if (!pCharClassEnglish) { osl::MutexGuard aGuard(maMutex); if (!pCharClassEnglish) { pCharClassEnglish = new CharClass( ::comphelper::getProcessComponentContext(), LanguageTag( LANGUAGE_ENGLISH_US)); } } return pCharClassEnglish; } const CharClass* ScCompiler::GetCharClassLocalized() { if (!pCharClassLocalized) { // Switching UI language requires restart; if not, we would have to // keep track of that. osl::MutexGuard aGuard(maMutex); if (!pCharClassLocalized) { pCharClassLocalized = new CharClass( ::comphelper::getProcessComponentContext(), Application::GetSettings().GetUILanguageTag()); } } return pCharClassLocalized; } void ScCompiler::SetGrammar( const FormulaGrammar::Grammar eGrammar ) { assert( eGrammar != FormulaGrammar::GRAM_UNSPECIFIED && "ScCompiler::SetGrammar: don't pass FormulaGrammar::GRAM_UNSPECIFIED"); if (eGrammar == GetGrammar()) return; // nothing to be done if( eGrammar == FormulaGrammar::GRAM_EXTERNAL ) { meGrammar = eGrammar; mxSymbols = GetFinalOpCodeMap( css::sheet::FormulaLanguage::NATIVE); } else { FormulaGrammar::Grammar eMyGrammar = eGrammar; const sal_Int32 nFormulaLanguage = FormulaGrammar::extractFormulaLanguage( eMyGrammar); OpCodeMapPtr xMap = GetFinalOpCodeMap( nFormulaLanguage); OSL_ENSURE( xMap, "ScCompiler::SetGrammar: unknown formula language"); if (!xMap) { xMap = GetFinalOpCodeMap( css::sheet::FormulaLanguage::NATIVE); eMyGrammar = xMap->getGrammar(); } // Save old grammar for call to SetGrammarAndRefConvention(). FormulaGrammar::Grammar eOldGrammar = GetGrammar(); // This also sets the grammar associated with the map! SetFormulaLanguage( xMap); // Override if necessary. if (eMyGrammar != GetGrammar()) SetGrammarAndRefConvention( eMyGrammar, eOldGrammar); } } // Unclear how this was intended to be refreshed when the // grammar or sheet count is changed ? Ideally this would be // a method on Document that would globally cache these. std::vector &ScCompiler::GetSetupTabNames() const { std::vector &rTabNames = const_cast(this)->maTabNames; if (rTabNames.empty()) { rTabNames = rDoc.GetAllTableNames(); for (auto& rTabName : rTabNames) ScCompiler::CheckTabQuotes(rTabName, formula::FormulaGrammar::extractRefConvention(meGrammar)); } return rTabNames; } void ScCompiler::SetNumberFormatter( SvNumberFormatter* pFormatter ) { mpFormatter = pFormatter; } void ScCompiler::SetFormulaLanguage( const ScCompiler::OpCodeMapPtr & xMap ) { if (!xMap) return; mxSymbols = xMap; if (mxSymbols->isEnglish()) pCharClass = GetCharClassEnglish(); else pCharClass = GetCharClassLocalized(); // The difference is needed for an uppercase() call that usually does not // result in different strings but for a few languages like Turkish; // though even de-DE and de-CH may differ in ß/SS handling.. // At least don't care if both are English. // The current locale is more likely to not be "en" so check first. const LanguageTag& rLT1 = ScGlobal::getCharClass().getLanguageTag(); const LanguageTag& rLT2 = pCharClass->getLanguageTag(); mbCharClassesDiffer = (rLT1 != rLT2 && (rLT1.getLanguage() != "en" || rLT2.getLanguage() != "en")); SetGrammarAndRefConvention( mxSymbols->getGrammar(), GetGrammar()); } void ScCompiler::SetGrammarAndRefConvention( const FormulaGrammar::Grammar eNewGrammar, const FormulaGrammar::Grammar eOldGrammar ) { meGrammar = eNewGrammar; // SetRefConvention needs the new grammar set! FormulaGrammar::AddressConvention eConv = FormulaGrammar::extractRefConvention( meGrammar); if (eConv == FormulaGrammar::CONV_UNSPECIFIED && eOldGrammar == FormulaGrammar::GRAM_UNSPECIFIED) SetRefConvention( rDoc.GetAddressConvention()); else SetRefConvention( eConv ); } OUString ScCompiler::FindAddInFunction( const OUString& rUpperName, bool bLocalFirst ) const { return ScGlobal::GetAddInCollection()->FindFunction(rUpperName, bLocalFirst); // bLocalFirst=false for english } ScCompiler::Convention::~Convention() { } ScCompiler::Convention::Convention( FormulaGrammar::AddressConvention eConv ) : meConv( eConv ) { int i; ScCharFlags *t= new ScCharFlags [128]; ScCompiler::pConventions[ meConv ] = this; mpCharTable.reset( t ); for (i = 0; i < 128; i++) t[i] = ScCharFlags::Illegal; // Allow tabs/newlines. // Allow saving whitespace as is (as per OpenFormula specification v.1.2, clause 5.14 "Whitespace"). /* tab */ t[ 9] = ScCharFlags::CharDontCare | ScCharFlags::WordSep | ScCharFlags::ValueSep; /* lf */ t[10] = ScCharFlags::CharDontCare | ScCharFlags::WordSep | ScCharFlags::ValueSep; /* cr */ t[13] = ScCharFlags::CharDontCare | ScCharFlags::WordSep | ScCharFlags::ValueSep; /* */ t[32] = ScCharFlags::CharDontCare | ScCharFlags::WordSep | ScCharFlags::ValueSep; /* ! */ t[33] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep; if (FormulaGrammar::CONV_ODF == meConv) /* ! */ t[33] |= ScCharFlags::OdfLabelOp; /* " */ t[34] = ScCharFlags::CharString | ScCharFlags::StringSep; /* # */ t[35] = ScCharFlags::WordSep | ScCharFlags::CharErrConst; /* $ */ t[36] = ScCharFlags::CharWord | ScCharFlags::Word | ScCharFlags::CharIdent | ScCharFlags::Ident; if (FormulaGrammar::CONV_ODF == meConv) /* $ */ t[36] |= ScCharFlags::OdfNameMarker; /* % */ t[37] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep; /* & */ t[38] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep; /* ' */ t[39] = ScCharFlags::NameSep; /* ( */ t[40] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep; /* ) */ t[41] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep; /* * */ t[42] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep; /* + */ t[43] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueExp | ScCharFlags::ValueSign; /* , */ t[44] = ScCharFlags::CharValue | ScCharFlags::Value; /* - */ t[45] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueExp | ScCharFlags::ValueSign; /* . */ t[46] = ScCharFlags::Word | ScCharFlags::CharValue | ScCharFlags::Value | ScCharFlags::Ident | ScCharFlags::Name; /* / */ t[47] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep; for (i = 48; i < 58; i++) /* 0-9 */ t[i] = ScCharFlags::CharValue | ScCharFlags::Word | ScCharFlags::Value | ScCharFlags::ValueExp | ScCharFlags::ValueValue | ScCharFlags::Ident | ScCharFlags::Name; /* : */ t[58] = ScCharFlags::Char | ScCharFlags::Word; /* ; */ t[59] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep; /* < */ t[60] = ScCharFlags::CharBool | ScCharFlags::WordSep | ScCharFlags::ValueSep; /* = */ t[61] = ScCharFlags::Char | ScCharFlags::Bool | ScCharFlags::WordSep | ScCharFlags::ValueSep; /* > */ t[62] = ScCharFlags::CharBool | ScCharFlags::Bool | ScCharFlags::WordSep | ScCharFlags::ValueSep; /* ? */ t[63] = ScCharFlags::CharWord | ScCharFlags::Word | ScCharFlags::Name; /* @ */ // FREE for (i = 65; i < 91; i++) /* A-Z */ t[i] = ScCharFlags::CharWord | ScCharFlags::Word | ScCharFlags::CharIdent | ScCharFlags::Ident | ScCharFlags::CharName | ScCharFlags::Name; if (FormulaGrammar::CONV_ODF == meConv) { /* [ */ t[91] = ScCharFlags::OdfLBracket; /* \ */ // FREE /* ] */ t[93] = ScCharFlags::OdfRBracket; } else if (FormulaGrammar::CONV_OOO == meConv) { /* [ */ t[91] = ScCharFlags::Char; /* \ */ // FREE /* ] */ t[93] = ScCharFlags::Char; } else if (FormulaGrammar::CONV_XL_OOX == meConv) { /* [ */ t[91] = ScCharFlags::Char | ScCharFlags::CharIdent; /* \ */ // FREE /* ] */ t[93] = ScCharFlags::Char | ScCharFlags::Ident; } else if (FormulaGrammar::CONV_XL_A1 == meConv) { /* [ */ t[91] = ScCharFlags::Char; /* \ */ // FREE /* ] */ t[93] = ScCharFlags::Char; } else if( FormulaGrammar::CONV_XL_R1C1 == meConv ) { /* [ */ t[91] = ScCharFlags::Ident; /* \ */ // FREE /* ] */ t[93] = ScCharFlags::Ident; } else { /* [ */ // FREE /* \ */ // FREE /* ] */ // FREE } /* ^ */ t[94] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep; /* _ */ t[95] = ScCharFlags::CharWord | ScCharFlags::Word | ScCharFlags::CharIdent | ScCharFlags::Ident | ScCharFlags::CharName | ScCharFlags::Name; /* ` */ // FREE for (i = 97; i < 123; i++) /* a-z */ t[i] = ScCharFlags::CharWord | ScCharFlags::Word | ScCharFlags::CharIdent | ScCharFlags::Ident | ScCharFlags::CharName | ScCharFlags::Name; /* { */ t[123] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep; // array open /* | */ t[124] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep; // array row sep (Should be OOo specific) /* } */ t[125] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep; // array close /* ~ */ t[126] = ScCharFlags::Char; // OOo specific /* 127 */ // FREE if( !(FormulaGrammar::CONV_XL_A1 == meConv || FormulaGrammar::CONV_XL_R1C1 == meConv || FormulaGrammar::CONV_XL_OOX == meConv) ) return; /* */ t[32] |= ScCharFlags::Word; /* ! */ t[33] |= ScCharFlags::Ident | ScCharFlags::Word; /* " */ t[34] |= ScCharFlags::Word; /* # */ t[35] &= ~ScCharFlags::WordSep; /* # */ t[35] |= ScCharFlags::Word; /* % */ t[37] |= ScCharFlags::Word; /* ' */ t[39] |= ScCharFlags::Word; /* % */ t[37] |= ScCharFlags::Word; /* & */ t[38] |= ScCharFlags::Word; /* ' */ t[39] |= ScCharFlags::Word; /* ( */ t[40] |= ScCharFlags::Word; /* ) */ t[41] |= ScCharFlags::Word; /* * */ t[42] |= ScCharFlags::Word; /* + */ t[43] |= ScCharFlags::Word; #if 0 /* this really needs to be locale specific. */ /* , */ t[44] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep; #else /* , */ t[44] |= ScCharFlags::Word; #endif /* - */ t[45] |= ScCharFlags::Word; /* ; */ t[59] |= ScCharFlags::Word; /* < */ t[60] |= ScCharFlags::Word; /* = */ t[61] |= ScCharFlags::Word; /* > */ t[62] |= ScCharFlags::Word; /* ? */ // question really is not permitted in sheet name /* @ */ t[64] |= ScCharFlags::Word; /* [ */ t[91] |= ScCharFlags::Word; /* ] */ t[93] |= ScCharFlags::Word; /* { */ t[123]|= ScCharFlags::Word; /* | */ t[124]|= ScCharFlags::Word; /* } */ t[125]|= ScCharFlags::Word; /* ~ */ t[126]|= ScCharFlags::Word; } static bool lcl_isValidQuotedText( const OUString& rFormula, sal_Int32 nSrcPos, ParseResult& rRes ) { // Tokens that start at ' can have anything in them until a final ' // but '' marks an escaped ' // We've earlier guaranteed that a string containing '' will be // surrounded by ' if (nSrcPos < rFormula.getLength() && rFormula[nSrcPos] == '\'') { sal_Int32 nPos = nSrcPos+1; while (nPos < rFormula.getLength()) { if (rFormula[nPos] == '\'') { if ( (nPos+1 == rFormula.getLength()) || (rFormula[nPos+1] != '\'') ) { rRes.TokenType = KParseType::SINGLE_QUOTE_NAME; rRes.EndPos = nPos+1; return true; } ++nPos; } ++nPos; } } return false; } static bool lcl_parseExternalName( const OUString& rSymbol, OUString& rFile, OUString& rName, const sal_Unicode cSep, const ScDocument& rDoc, const uno::Sequence* pExternalLinks ) { /* TODO: future versions will have to support sheet-local names too, thus * return a possible sheet name as well. */ const sal_Unicode* const pStart = rSymbol.getStr(); const sal_Unicode* p = pStart; sal_Int32 nLen = rSymbol.getLength(); OUString aTmpFile; OUStringBuffer aTmpName; sal_Int32 i = 0; bool bInName = false; if (cSep == '!') { // For XL use existing parser that resolves bracketed and quoted and // indexed external document names. ScRange aRange; OUString aStartTabName, aEndTabName; ScRefFlags nFlags = ScRefFlags::ZERO; p = aRange.Parse_XL_Header( p, rDoc, aTmpFile, aStartTabName, aEndTabName, nFlags, true, pExternalLinks ); if (!p || p == pStart) return false; i = sal_Int32(p - pStart); } for ( ; i < nLen; ++i, ++p) { sal_Unicode c = *p; if (i == 0) { if (c == '.' || c == cSep) return false; if (c == '\'') { // Move to the next char and loop until the second single // quote. sal_Unicode cPrev = c; ++i; ++p; for (sal_Int32 j = i; j < nLen; ++j, ++p) { c = *p; if (c == '\'') { if (j == i) { // empty quote e.g. (=''!Name) return false; } if (cPrev == '\'') { // two consecutive quotes equal a single quote in // the file name. aTmpFile += OUStringChar(c); cPrev = 'a'; } else cPrev = c; continue; } if (cPrev == '\'' && j != i) { // this is not a quote but the previous one is. This // ends the parsing of the quoted segment. At this // point, the current char must equal the separator // char. i = j; bInName = true; aTmpName.append(c); // Keep the separator as part of the name. break; } aTmpFile += OUStringChar(c); cPrev = c; } if (!bInName) { // premature ending of the quoted segment. return false; } if (c != cSep) { // only the separator is allowed after the closing quote. return false; } continue; } } if (bInName) { if (c == cSep) { // A second separator ? Not a valid external name. return false; } aTmpName.append(c); } else { if (c == cSep) { bInName = true; aTmpName.append(c); // Keep the separator as part of the name. } else { do { if (rtl::isAsciiAlphanumeric(c)) // allowed. break; if (c > 128) // non-ASCII character is allowed. break; bool bValid = false; switch (c) { case '_': case '-': case '.': // these special characters are allowed. bValid = true; break; } if (bValid) break; return false; } while (false); aTmpFile += OUStringChar(c); } } } if (!bInName) { // No name found - most likely the symbol has no '!'s. return false; } sal_Int32 nNameLen = aTmpName.getLength(); if (nNameLen < 2) { // Name must be at least 2-char long (separator plus name). return false; } if (aTmpName[0] != cSep) { // 1st char of the name must equal the separator. return false; } if (aTmpName[nNameLen-1] == '!') { // Check against #REF!. if (OUString::unacquired(aTmpName).equalsIgnoreAsciiCase("#REF!")) return false; } rFile = aTmpFile; rName = aTmpName.makeStringAndClear().copy(1); // Skip the first char as it is always the separator. return true; } static OUString lcl_makeExternalNameStr(const OUString& rFile, const OUString& rName, const sal_Unicode cSep, bool bODF ) { OUString aEscQuote("''"); OUString aFile(rFile.replaceAll("'", aEscQuote)); OUString aName(rName); if (bODF) aName = aName.replaceAll("'", aEscQuote); OUStringBuffer aBuf(aFile.getLength() + aName.getLength() + 9); if (bODF) aBuf.append( '['); aBuf.append( "'" + aFile + "'" + OUStringChar(cSep) ); if (bODF) aBuf.append( "$$'" ); aBuf.append( aName); if (bODF) aBuf.append( "']" ); return aBuf.makeStringAndClear(); } static bool lcl_getLastTabName( OUString& rTabName2, const OUString& rTabName1, const vector& rTabNames, const ScRange& rRef ) { SCTAB nTabSpan = rRef.aEnd.Tab() - rRef.aStart.Tab(); if (nTabSpan > 0) { size_t nCount = rTabNames.size(); vector::const_iterator itrBeg = rTabNames.begin(), itrEnd = rTabNames.end(); vector::const_iterator itr = ::std::find(itrBeg, itrEnd, rTabName1); if (itr == rTabNames.end()) { rTabName2 = ScResId(STR_NO_REF_TABLE); return false; } size_t nDist = ::std::distance(itrBeg, itr); if (nDist + static_cast(nTabSpan) >= nCount) { rTabName2 = ScResId(STR_NO_REF_TABLE); return false; } rTabName2 = rTabNames[nDist+nTabSpan]; } else rTabName2 = rTabName1; return true; } namespace { struct Convention_A1 : public ScCompiler::Convention { explicit Convention_A1( FormulaGrammar::AddressConvention eConv ) : ScCompiler::Convention( eConv ) { } static void MakeColStr( const ScSheetLimits& rLimits, OUStringBuffer& rBuffer, SCCOL nCol ); static void MakeRowStr( const ScSheetLimits& rLimits, OUStringBuffer& rBuffer, SCROW nRow ); ParseResult parseAnyToken( const OUString& rFormula, sal_Int32 nSrcPos, const CharClass* pCharClass, bool bGroupSeparator) const override { ParseResult aRet; if ( lcl_isValidQuotedText(rFormula, nSrcPos, aRet) ) return aRet; constexpr sal_Int32 nStartFlags = KParseTokens::ANY_LETTER_OR_NUMBER | KParseTokens::ASC_UNDERSCORE | KParseTokens::ASC_DOLLAR; constexpr sal_Int32 nContFlags = nStartFlags | KParseTokens::ASC_DOT; // '?' allowed in range names because of Xcl :-/ static constexpr OUStringLiteral aAddAllowed(u"?#"); return pCharClass->parseAnyToken( rFormula, nSrcPos, nStartFlags, aAddAllowed, (bGroupSeparator ? nContFlags | KParseTokens::GROUP_SEPARATOR_IN_NUMBER : nContFlags), aAddAllowed ); } virtual ScCharFlags getCharTableFlags( sal_Unicode c, sal_Unicode /*cLast*/ ) const override { return mpCharTable[static_cast(c)]; } }; } void Convention_A1::MakeColStr( const ScSheetLimits& rLimits, OUStringBuffer& rBuffer, SCCOL nCol ) { if ( !rLimits.ValidCol(nCol) ) rBuffer.append(ScResId(STR_NO_REF_TABLE)); else ::ScColToAlpha( rBuffer, nCol); } void Convention_A1::MakeRowStr( const ScSheetLimits& rLimits, OUStringBuffer& rBuffer, SCROW nRow ) { if ( !rLimits.ValidRow(nRow) ) rBuffer.append(ScResId(STR_NO_REF_TABLE)); else rBuffer.append(sal_Int32(nRow + 1)); } namespace { struct ConventionOOO_A1 : public Convention_A1 { ConventionOOO_A1() : Convention_A1 (FormulaGrammar::CONV_OOO) { } explicit ConventionOOO_A1( FormulaGrammar::AddressConvention eConv ) : Convention_A1 (eConv) { } static void MakeTabStr( OUStringBuffer &rBuf, const std::vector& rTabNames, SCTAB nTab ) { if (o3tl::make_unsigned(nTab) >= rTabNames.size()) rBuf.append(ScResId(STR_NO_REF_TABLE)); else rBuf.append(rTabNames[nTab]); rBuf.append('.'); } enum SingletonDisplay { SINGLETON_NONE, SINGLETON_COL, SINGLETON_ROW }; static void MakeOneRefStrImpl( const ScSheetLimits& rLimits, OUStringBuffer& rBuffer, std::u16string_view rErrRef, const std::vector& rTabNames, const ScSingleRefData& rRef, const ScAddress& rAbsRef, bool bForceTab, bool bODF, SingletonDisplay eSingletonDisplay ) { if( rRef.IsFlag3D() || bForceTab ) { if (!ValidTab(rAbsRef.Tab()) || rRef.IsTabDeleted()) { if (!rRef.IsTabRel()) rBuffer.append('$'); rBuffer.append(rErrRef); rBuffer.append('.'); } else { if (!rRef.IsTabRel()) rBuffer.append('$'); MakeTabStr(rBuffer, rTabNames, rAbsRef.Tab()); } } else if (bODF) rBuffer.append('.'); if (eSingletonDisplay != SINGLETON_ROW) { if (!rRef.IsColRel()) rBuffer.append('$'); if (!rLimits.ValidCol(rAbsRef.Col()) || rRef.IsColDeleted()) rBuffer.append(rErrRef); else MakeColStr(rLimits, rBuffer, rAbsRef.Col()); } if (eSingletonDisplay != SINGLETON_COL) { if (!rRef.IsRowRel()) rBuffer.append('$'); if (!rLimits.ValidRow(rAbsRef.Row()) || rRef.IsRowDeleted()) rBuffer.append(rErrRef); else MakeRowStr(rLimits, rBuffer, rAbsRef.Row()); } } static SingletonDisplay getSingletonDisplay( const ScSheetLimits& rLimits, const ScAddress& rAbs1, const ScAddress& rAbs2, const ScComplexRefData& rRef, bool bFromRangeName ) { // If any part is error, display as such. if (!rLimits.ValidCol(rAbs1.Col()) || rRef.Ref1.IsColDeleted() || !rLimits.ValidRow(rAbs1.Row()) || rRef.Ref1.IsRowDeleted() || !rLimits.ValidCol(rAbs2.Col()) || rRef.Ref2.IsColDeleted() || !rLimits.ValidRow(rAbs2.Row()) || rRef.Ref2.IsRowDeleted()) return SINGLETON_NONE; // A:A or $A:$A or A:$A or $A:A if (rRef.IsEntireCol(rLimits)) return SINGLETON_COL; // Same if not in named expression and both rows of entire columns are // relative references. if (!bFromRangeName && rAbs1.Row() == 0 && rAbs2.Row() == rLimits.mnMaxRow && rRef.Ref1.IsRowRel() && rRef.Ref2.IsRowRel()) return SINGLETON_COL; // 1:1 or $1:$1 or 1:$1 or $1:1 if (rRef.IsEntireRow(rLimits)) return SINGLETON_ROW; // Same if not in named expression and both columns of entire rows are // relative references. if (!bFromRangeName && rAbs1.Col() == 0 && rAbs2.Col() == rLimits.mnMaxCol && rRef.Ref1.IsColRel() && rRef.Ref2.IsColRel()) return SINGLETON_ROW; return SINGLETON_NONE; } virtual void makeRefStr( ScSheetLimits& rLimits, OUStringBuffer& rBuffer, formula::FormulaGrammar::Grammar /*eGram*/, const ScAddress& rPos, const OUString& rErrRef, const std::vector& rTabNames, const ScComplexRefData& rRef, bool bSingleRef, bool bFromRangeName ) const override { // In case absolute/relative positions weren't separately available: // transform relative to absolute! ScAddress aAbs1 = rRef.Ref1.toAbs(rLimits, rPos), aAbs2; if( !bSingleRef ) aAbs2 = rRef.Ref2.toAbs(rLimits, rPos); SingletonDisplay eSingleton = bSingleRef ? SINGLETON_NONE : getSingletonDisplay( rLimits, aAbs1, aAbs2, rRef, bFromRangeName); MakeOneRefStrImpl(rLimits, rBuffer, rErrRef, rTabNames, rRef.Ref1, aAbs1, false, false, eSingleton); if (!bSingleRef) { rBuffer.append(':'); MakeOneRefStrImpl(rLimits, rBuffer, rErrRef, rTabNames, rRef.Ref2, aAbs2, aAbs1.Tab() != aAbs2.Tab(), false, eSingleton); } } virtual sal_Unicode getSpecialSymbol( SpecialSymbolType eSymType ) const override { switch (eSymType) { case ScCompiler::Convention::ABS_SHEET_PREFIX: return '$'; case ScCompiler::Convention::SHEET_SEPARATOR: return '.'; } return u'\0'; } virtual bool parseExternalName( const OUString& rSymbol, OUString& rFile, OUString& rName, const ScDocument& rDoc, const uno::Sequence* pExternalLinks ) const override { return lcl_parseExternalName(rSymbol, rFile, rName, '#', rDoc, pExternalLinks); } virtual OUString makeExternalNameStr( sal_uInt16 /*nFileId*/, const OUString& rFile, const OUString& rName ) const override { return lcl_makeExternalNameStr( rFile, rName, '#', false); } static bool makeExternalSingleRefStr( const ScSheetLimits& rLimits, OUStringBuffer& rBuffer, const OUString& rFileName, const OUString& rTabName, const ScSingleRefData& rRef, const ScAddress& rPos, bool bDisplayTabName, bool bEncodeUrl ) { ScAddress aAbsRef = rRef.toAbs(rLimits, rPos); if (bDisplayTabName) { OUString aFile; if (bEncodeUrl) aFile = rFileName; else aFile = INetURLObject::decode(rFileName, INetURLObject::DecodeMechanism::Unambiguous); rBuffer.append("'" + aFile.replaceAll("'", "''") + "'#"); if (!rRef.IsTabRel()) rBuffer.append('$'); ScRangeStringConverter::AppendTableName(rBuffer, rTabName); rBuffer.append('.'); } if (!rRef.IsColRel()) rBuffer.append('$'); MakeColStr( rLimits, rBuffer, aAbsRef.Col()); if (!rRef.IsRowRel()) rBuffer.append('$'); MakeRowStr( rLimits, rBuffer, aAbsRef.Row()); return true; } static void makeExternalRefStrImpl( const ScSheetLimits& rLimits, OUStringBuffer& rBuffer, const ScAddress& rPos, const OUString& rFileName, const OUString& rTabName, const ScSingleRefData& rRef, bool bODF ) { if (bODF) rBuffer.append( '['); bool bEncodeUrl = bODF; makeExternalSingleRefStr(rLimits, rBuffer, rFileName, rTabName, rRef, rPos, true, bEncodeUrl); if (bODF) rBuffer.append( ']'); } virtual void makeExternalRefStr( ScSheetLimits& rLimits, OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName, const OUString& rTabName, const ScSingleRefData& rRef ) const override { makeExternalRefStrImpl(rLimits, rBuffer, rPos, rFileName, rTabName, rRef, false); } static void makeExternalRefStrImpl( const ScSheetLimits& rLimits, OUStringBuffer& rBuffer, const ScAddress& rPos, const OUString& rFileName, const std::vector& rTabNames, const OUString& rTabName, const ScComplexRefData& rRef, bool bODF ) { ScRange aAbsRange = rRef.toAbs(rLimits, rPos); if (bODF) rBuffer.append( '['); // Ensure that there's always a closing bracket, no premature returns. bool bEncodeUrl = bODF; do { if (!makeExternalSingleRefStr(rLimits, rBuffer, rFileName, rTabName, rRef.Ref1, rPos, true, bEncodeUrl)) break; rBuffer.append(':'); OUString aLastTabName; bool bDisplayTabName = (aAbsRange.aStart.Tab() != aAbsRange.aEnd.Tab()); if (bDisplayTabName) { // Get the name of the last table. if (!lcl_getLastTabName(aLastTabName, rTabName, rTabNames, aAbsRange)) { OSL_FAIL( "ConventionOOO_A1::makeExternalRefStrImpl: sheet name not found"); // aLastTabName contains #REF!, proceed. } } else if (bODF) rBuffer.append( '.'); // need at least the sheet separator in ODF makeExternalSingleRefStr(rLimits, rBuffer, rFileName, aLastTabName, rRef.Ref2, rPos, bDisplayTabName, bEncodeUrl); } while (false); if (bODF) rBuffer.append( ']'); } virtual void makeExternalRefStr( ScSheetLimits& rLimits, OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName, const std::vector& rTabNames, const OUString& rTabName, const ScComplexRefData& rRef ) const override { makeExternalRefStrImpl(rLimits, rBuffer, rPos, rFileName, rTabNames, rTabName, rRef, false); } }; struct ConventionOOO_A1_ODF : public ConventionOOO_A1 { ConventionOOO_A1_ODF() : ConventionOOO_A1 (FormulaGrammar::CONV_ODF) { } virtual void makeRefStr( ScSheetLimits& rLimits, OUStringBuffer& rBuffer, formula::FormulaGrammar::Grammar eGram, const ScAddress& rPos, const OUString& rErrRef, const std::vector& rTabNames, const ScComplexRefData& rRef, bool bSingleRef, bool bFromRangeName ) const override { rBuffer.append('['); // In case absolute/relative positions weren't separately available: // transform relative to absolute! ScAddress aAbs1 = rRef.Ref1.toAbs(rLimits, rPos), aAbs2; if( !bSingleRef ) aAbs2 = rRef.Ref2.toAbs(rLimits, rPos); if (FormulaGrammar::isODFF(eGram) && (rRef.Ref1.IsDeleted() || !rLimits.ValidAddress(aAbs1) || (!bSingleRef && (rRef.Ref2.IsDeleted() || !rLimits.ValidAddress(aAbs2))))) { rBuffer.append(rErrRef); // For ODFF write [#REF!], but not for PODF so apps reading ODF // 1.0/1.1 may have a better chance if they implemented the old // form. } else { SingletonDisplay eSingleton = bSingleRef ? SINGLETON_NONE : getSingletonDisplay( rLimits, aAbs1, aAbs2, rRef, bFromRangeName); MakeOneRefStrImpl(rLimits, rBuffer, rErrRef, rTabNames, rRef.Ref1, aAbs1, false, true, eSingleton); if (!bSingleRef) { rBuffer.append(':'); MakeOneRefStrImpl(rLimits, rBuffer, rErrRef, rTabNames, rRef.Ref2, aAbs2, aAbs1.Tab() != aAbs2.Tab(), true, eSingleton); } } rBuffer.append(']'); } virtual OUString makeExternalNameStr( sal_uInt16 /*nFileId*/, const OUString& rFile, const OUString& rName ) const override { return lcl_makeExternalNameStr( rFile, rName, '#', true); } virtual void makeExternalRefStr( ScSheetLimits& rLimits, OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName, const OUString& rTabName, const ScSingleRefData& rRef ) const override { makeExternalRefStrImpl(rLimits, rBuffer, rPos, rFileName, rTabName, rRef, true); } virtual void makeExternalRefStr( ScSheetLimits& rLimits, OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName, const std::vector& rTabNames, const OUString& rTabName, const ScComplexRefData& rRef ) const override { makeExternalRefStrImpl(rLimits, rBuffer, rPos, rFileName, rTabNames, rTabName, rRef, true); } }; struct ConventionXL { virtual ~ConventionXL() { } static void GetTab( const ScSheetLimits& rLimits, const ScAddress& rPos, const std::vector& rTabNames, const ScSingleRefData& rRef, OUString& rTabName ) { ScAddress aAbs = rRef.toAbs(rLimits, rPos); if (rRef.IsTabDeleted() || o3tl::make_unsigned(aAbs.Tab()) >= rTabNames.size()) { rTabName = ScResId( STR_NO_REF_TABLE ); return; } rTabName = rTabNames[aAbs.Tab()]; } static void MakeTabStr( const ScSheetLimits& rLimits, OUStringBuffer& rBuf, const ScAddress& rPos, const std::vector& rTabNames, const ScComplexRefData& rRef, bool bSingleRef ) { if( !rRef.Ref1.IsFlag3D() ) return; OUString aStartTabName, aEndTabName; GetTab(rLimits, rPos, rTabNames, rRef.Ref1, aStartTabName); if( !bSingleRef && rRef.Ref2.IsFlag3D() ) { GetTab(rLimits, rPos, rTabNames, rRef.Ref2, aEndTabName); } rBuf.append( aStartTabName ); if( !bSingleRef && rRef.Ref2.IsFlag3D() && aStartTabName != aEndTabName ) { rBuf.append( ':' ); rBuf.append( aEndTabName ); } rBuf.append( '!' ); } static sal_Unicode getSpecialSymbol( ScCompiler::Convention::SpecialSymbolType eSymType ) { switch (eSymType) { case ScCompiler::Convention::ABS_SHEET_PREFIX: return u'\0'; case ScCompiler::Convention::SHEET_SEPARATOR: return '!'; } return u'\0'; } static bool parseExternalName( const OUString& rSymbol, OUString& rFile, OUString& rName, const ScDocument& rDoc, const uno::Sequence* pExternalLinks ) { return lcl_parseExternalName( rSymbol, rFile, rName, '!', rDoc, pExternalLinks); } static OUString makeExternalNameStr( const OUString& rFile, const OUString& rName ) { return lcl_makeExternalNameStr( rFile, rName, '!', false); } static void makeExternalDocStr( OUStringBuffer& rBuffer, std::u16string_view rFullName ) { // Format that is easier to deal with inside OOo, because we use file // URL, and all characters are allowed. Check if it makes sense to do // it the way Gnumeric does it. Gnumeric doesn't use the URL form // and allows relative file path. // // ['file:///path/to/source/filename.xls'] rBuffer.append('['); rBuffer.append('\''); OUString aFullName = INetURLObject::decode(rFullName, INetURLObject::DecodeMechanism::Unambiguous); const sal_Unicode* pBuf = aFullName.getStr(); sal_Int32 nLen = aFullName.getLength(); for (sal_Int32 i = 0; i < nLen; ++i) { const sal_Unicode c = pBuf[i]; if (c == '\'') rBuffer.append(c); rBuffer.append(c); } rBuffer.append('\''); rBuffer.append(']'); } static void makeExternalTabNameRange( OUStringBuffer& rBuf, const OUString& rTabName, const vector& rTabNames, const ScRange& rRef ) { OUString aLastTabName; if (!lcl_getLastTabName(aLastTabName, rTabName, rTabNames, rRef)) { ScRangeStringConverter::AppendTableName(rBuf, aLastTabName); return; } ScRangeStringConverter::AppendTableName(rBuf, rTabName); if (rTabName != aLastTabName) { rBuf.append(':'); ScRangeStringConverter::AppendTableName(rBuf, aLastTabName); } } virtual void parseExternalDocName( const OUString& rFormula, sal_Int32& rSrcPos ) const { sal_Int32 nLen = rFormula.getLength(); const sal_Unicode* p = rFormula.getStr(); sal_Unicode cPrev = 0; for (sal_Int32 i = rSrcPos; i < nLen; ++i) { sal_Unicode c = p[i]; if (i == rSrcPos) { // first character must be '['. if (c != '[') return; } else if (i == rSrcPos + 1) { // second character must be a single quote. if (c != '\'') return; } else if (c == '\'') { if (cPrev == '\'') // two successive single quote is treated as a single // valid character. c = 'a'; } else if (c == ']') { if (cPrev == '\'') { // valid source document path found. Increment the // current position to skip the source path. rSrcPos = i + 1; if (rSrcPos >= nLen) rSrcPos = nLen - 1; return; } else return; } else { // any other character if (i > rSrcPos + 2 && cPrev == '\'') // unless it's the 3rd character, a normal character // following immediately a single quote is invalid. return; } cPrev = c; } } }; struct ConventionXL_A1 : public Convention_A1, public ConventionXL { ConventionXL_A1() : Convention_A1( FormulaGrammar::CONV_XL_A1 ) { } explicit ConventionXL_A1( FormulaGrammar::AddressConvention eConv ) : Convention_A1( eConv ) { } static void makeSingleCellStr( const ScSheetLimits& rLimits, OUStringBuffer& rBuf, const ScSingleRefData& rRef, const ScAddress& rAbs ) { if (!rRef.IsColRel()) rBuf.append('$'); MakeColStr(rLimits, rBuf, rAbs.Col()); if (!rRef.IsRowRel()) rBuf.append('$'); MakeRowStr(rLimits, rBuf, rAbs.Row()); } virtual void makeRefStr( ScSheetLimits& rLimits, OUStringBuffer& rBuf, formula::FormulaGrammar::Grammar /*eGram*/, const ScAddress& rPos, const OUString& rErrRef, const std::vector& rTabNames, const ScComplexRefData& rRef, bool bSingleRef, bool /*bFromRangeName*/ ) const override { ScComplexRefData aRef( rRef ); // Play fast and loose with invalid refs. There is not much point in producing // Foo!A1:#REF! versus #REF! at this point ScAddress aAbs1 = aRef.Ref1.toAbs(rLimits, rPos), aAbs2; MakeTabStr(rLimits, rBuf, rPos, rTabNames, aRef, bSingleRef); if (!rLimits.ValidAddress(aAbs1)) { rBuf.append(rErrRef); return; } if( !bSingleRef ) { aAbs2 = aRef.Ref2.toAbs(rLimits, rPos); if (!rLimits.ValidAddress(aAbs2)) { rBuf.append(rErrRef); return; } if (aAbs1.Col() == 0 && aAbs2.Col() >= rLimits.mnMaxCol) { if (!aRef.Ref1.IsRowRel()) rBuf.append( '$' ); MakeRowStr(rLimits, rBuf, aAbs1.Row()); rBuf.append( ':' ); if (!aRef.Ref2.IsRowRel()) rBuf.append( '$' ); MakeRowStr(rLimits, rBuf, aAbs2.Row()); return; } if (aAbs1.Row() == 0 && aAbs2.Row() >= rLimits.mnMaxRow) { if (!aRef.Ref1.IsColRel()) rBuf.append( '$' ); MakeColStr(rLimits, rBuf, aAbs1.Col()); rBuf.append( ':' ); if (!aRef.Ref2.IsColRel()) rBuf.append( '$' ); MakeColStr(rLimits, rBuf, aAbs2.Col()); return; } } makeSingleCellStr(rLimits, rBuf, aRef.Ref1, aAbs1); if (!bSingleRef) { rBuf.append( ':' ); makeSingleCellStr(rLimits, rBuf, aRef.Ref2, aAbs2); } } virtual ParseResult parseAnyToken( const OUString& rFormula, sal_Int32 nSrcPos, const CharClass* pCharClass, bool bGroupSeparator) const override { parseExternalDocName(rFormula, nSrcPos); ParseResult aRet; if ( lcl_isValidQuotedText(rFormula, nSrcPos, aRet) ) return aRet; constexpr sal_Int32 nStartFlags = KParseTokens::ANY_LETTER_OR_NUMBER | KParseTokens::ASC_UNDERSCORE | KParseTokens::ASC_DOLLAR; constexpr sal_Int32 nContFlags = nStartFlags | KParseTokens::ASC_DOT; // '?' allowed in range names static constexpr OUStringLiteral aAddAllowed(u"?!"); return pCharClass->parseAnyToken( rFormula, nSrcPos, nStartFlags, aAddAllowed, (bGroupSeparator ? nContFlags | KParseTokens::GROUP_SEPARATOR_IN_NUMBER : nContFlags), aAddAllowed ); } virtual sal_Unicode getSpecialSymbol( SpecialSymbolType eSymType ) const override { return ConventionXL::getSpecialSymbol(eSymType); } virtual bool parseExternalName( const OUString& rSymbol, OUString& rFile, OUString& rName, const ScDocument& rDoc, const uno::Sequence* pExternalLinks ) const override { return ConventionXL::parseExternalName( rSymbol, rFile, rName, rDoc, pExternalLinks); } virtual OUString makeExternalNameStr( sal_uInt16 /*nFileId*/, const OUString& rFile, const OUString& rName ) const override { return ConventionXL::makeExternalNameStr(rFile, rName); } virtual void makeExternalRefStr( ScSheetLimits& rLimits, OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName, const OUString& rTabName, const ScSingleRefData& rRef ) const override { // ['file:///path/to/file/filename.xls']'Sheet Name'!$A$1 // This is a little different from the format Excel uses, as Excel // puts [] only around the file name. But we need to enclose the // whole file path with [] because the file name can contain any // characters. ConventionXL::makeExternalDocStr(rBuffer, rFileName); ScRangeStringConverter::AppendTableName(rBuffer, rTabName); rBuffer.append('!'); makeSingleCellStr(rLimits, rBuffer, rRef, rRef.toAbs(rLimits, rPos)); } virtual void makeExternalRefStr( ScSheetLimits& rLimits, OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName, const std::vector& rTabNames, const OUString& rTabName, const ScComplexRefData& rRef ) const override { ScRange aAbsRef = rRef.toAbs(rLimits, rPos); ConventionXL::makeExternalDocStr(rBuffer, rFileName); ConventionXL::makeExternalTabNameRange(rBuffer, rTabName, rTabNames, aAbsRef); rBuffer.append('!'); makeSingleCellStr(rLimits, rBuffer, rRef.Ref1, aAbsRef.aStart); if (aAbsRef.aStart != aAbsRef.aEnd) { rBuffer.append(':'); makeSingleCellStr(rLimits, rBuffer, rRef.Ref2, aAbsRef.aEnd); } } }; struct ConventionXL_OOX : public ConventionXL_A1 { ConventionXL_OOX() : ConventionXL_A1( FormulaGrammar::CONV_XL_OOX ) { } virtual void makeRefStr( ScSheetLimits& rLimits, OUStringBuffer& rBuf, formula::FormulaGrammar::Grammar eGram, const ScAddress& rPos, const OUString& rErrRef, const std::vector& rTabNames, const ScComplexRefData& rRef, bool bSingleRef, bool bFromRangeName ) const override { // In OOXML relative references in named expressions are relative to // column 0 and row 0. Relative sheet references don't exist. ScAddress aPos( rPos ); if (bFromRangeName) { // XXX NOTE: by decrementing the reference position we may end up // with resolved references with negative values. There's no proper // way to solve that or wrap them around without sheet dimensions // that are stored along. That, or blindly assume fixed dimensions // here and in import. /* TODO: maybe do that blind fixed dimensions wrap? */ aPos.SetCol(0); aPos.SetRow(0); } if (rRef.Ref1.IsDeleted() || (!bSingleRef && rRef.Ref2.IsDeleted())) { // For OOXML write plain "#REF!" instead of detailed sheet/col/row // information. rBuf.append(rErrRef); return; } { ScAddress aAbs1 = rRef.Ref1.toAbs(rLimits, rPos); if (!rLimits.ValidAddress(aAbs1) || o3tl::make_unsigned(aAbs1.Tab()) >= rTabNames.size()) { rBuf.append(rErrRef); return; } } if (!bSingleRef) { ScAddress aAbs2 = rRef.Ref2.toAbs(rLimits, rPos); if (!rLimits.ValidAddress(aAbs2) || o3tl::make_unsigned(aAbs2.Tab()) >= rTabNames.size()) { rBuf.append(rErrRef); return; } } ConventionXL_A1::makeRefStr( rLimits, rBuf, eGram, aPos, rErrRef, rTabNames, rRef, bSingleRef, bFromRangeName); } virtual OUString makeExternalNameStr( sal_uInt16 nFileId, const OUString& /*rFile*/, const OUString& rName ) const override { // [N]!DefinedName is a workbook global name. return OUString( "[" + OUString::number(nFileId+1) + "]!" + rName ); /* TODO: add support for sheet local names, would be * [N]'Sheet Name'!DefinedName * Similar to makeExternalRefStr() but with DefinedName instead of * CellStr. */ } virtual void parseExternalDocName(const OUString& rFormula, sal_Int32& rSrcPos) const override { sal_Int32 nLen = rFormula.getLength(); const sal_Unicode* p = rFormula.getStr(); for (sal_Int32 i = rSrcPos; i < nLen; ++i) { sal_Unicode c = p[i]; if (i == rSrcPos) { // first character must be '['. if (c != '[') return; } else if (c == ']') { rSrcPos = i + 1; return; } } } virtual void makeExternalRefStr( ScSheetLimits& rLimits, OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 nFileId, const OUString& /*rFileName*/, const OUString& rTabName, const ScSingleRefData& rRef ) const override { // '[N]Sheet Name'!$A$1 or [N]SheetName!$A$1 // Where N is a 1-based positive integer number of a file name in OOXML // xl/externalLinks/externalLinkN.xml OUString aQuotedTab( rTabName); ScCompiler::CheckTabQuotes( aQuotedTab); if (!aQuotedTab.isEmpty() && aQuotedTab[0] == '\'') { rBuffer.append('\''); ConventionXL_OOX::makeExternalDocStr( rBuffer, nFileId); rBuffer.append( aQuotedTab.subView(1)); } else { ConventionXL_OOX::makeExternalDocStr( rBuffer, nFileId); rBuffer.append( aQuotedTab); } rBuffer.append('!'); makeSingleCellStr(rLimits, rBuffer, rRef, rRef.toAbs(rLimits, rPos)); } virtual void makeExternalRefStr( ScSheetLimits& rLimits, OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 nFileId, const OUString& /*rFileName*/, const std::vector& rTabNames, const OUString& rTabName, const ScComplexRefData& rRef ) const override { // '[N]Sheet One':'Sheet Two'!A1:B2 or [N]SheetOne!A1:B2 // Actually Excel writes '[N]Sheet One:Sheet Two'!A1:B2 but reads the // simpler to produce and more logical form with independently quoted // sheet names as well. The [N] having to be within the quoted sheet // name is ugly enough... ScRange aAbsRef = rRef.toAbs(rLimits, rPos); OUStringBuffer aBuf; ConventionXL::makeExternalTabNameRange( aBuf, rTabName, rTabNames, aAbsRef); if (!aBuf.isEmpty() && aBuf[0] == '\'') { rBuffer.append('\''); ConventionXL_OOX::makeExternalDocStr( rBuffer, nFileId); rBuffer.append( aBuf.subView(1)); } else { ConventionXL_OOX::makeExternalDocStr( rBuffer, nFileId); rBuffer.append( aBuf); } rBuffer.append('!'); makeSingleCellStr(rLimits, rBuffer, rRef.Ref1, aAbsRef.aStart); if (aAbsRef.aStart != aAbsRef.aEnd) { rBuffer.append(':'); makeSingleCellStr(rLimits, rBuffer, rRef.Ref2, aAbsRef.aEnd); } } static void makeExternalDocStr( OUStringBuffer& rBuffer, sal_uInt16 nFileId ) { rBuffer.append('[').append( static_cast(nFileId+1) ).append(']'); } }; } static void r1c1_add_col( OUStringBuffer &rBuf, const ScSingleRefData& rRef, const ScAddress& rAbsRef ) { rBuf.append( 'C' ); if( rRef.IsColRel() ) { SCCOL nCol = rRef.Col(); if (nCol != 0) rBuf.append("[" + OUString::number(nCol) + "]"); } else rBuf.append( static_cast(rAbsRef.Col() + 1) ); } static void r1c1_add_row( OUStringBuffer &rBuf, const ScSingleRefData& rRef, const ScAddress& rAbsRef ) { rBuf.append( 'R' ); if( rRef.IsRowRel() ) { if (rRef.Row() != 0) { rBuf.append("[" + OUString::number(rRef.Row()) + "]"); } } else rBuf.append( rAbsRef.Row() + 1 ); } namespace { struct ConventionXL_R1C1 : public ScCompiler::Convention, public ConventionXL { ConventionXL_R1C1() : ScCompiler::Convention( FormulaGrammar::CONV_XL_R1C1 ) { } virtual void makeRefStr( ScSheetLimits& rLimits, OUStringBuffer& rBuf, formula::FormulaGrammar::Grammar /*eGram*/, const ScAddress& rPos, const OUString& rErrRef, const std::vector& rTabNames, const ScComplexRefData& rRef, bool bSingleRef, bool /*bFromRangeName*/ ) const override { ScRange aAbsRef = rRef.toAbs(rLimits, rPos); ScComplexRefData aRef( rRef ); MakeTabStr(rLimits, rBuf, rPos, rTabNames, aRef, bSingleRef); // Play fast and loose with invalid refs. There is not much point in producing // Foo!A1:#REF! versus #REF! at this point if (!rLimits.ValidCol(aAbsRef.aStart.Col()) || !rLimits.ValidRow(aAbsRef.aStart.Row())) { rBuf.append(rErrRef); return; } if( !bSingleRef ) { if (!rLimits.ValidCol(aAbsRef.aEnd.Col()) || !rLimits.ValidRow(aAbsRef.aEnd.Row())) { rBuf.append(rErrRef); return; } if (aAbsRef.aStart.Col() == 0 && aAbsRef.aEnd.Col() >= rLimits.mnMaxCol) { r1c1_add_row(rBuf, rRef.Ref1, aAbsRef.aStart); if (aAbsRef.aStart.Row() != aAbsRef.aEnd.Row() || rRef.Ref1.IsRowRel() != rRef.Ref2.IsRowRel() ) { rBuf.append( ':' ); r1c1_add_row(rBuf, rRef.Ref2, aAbsRef.aEnd); } return; } if (aAbsRef.aStart.Row() == 0 && aAbsRef.aEnd.Row() >= rLimits.mnMaxRow) { r1c1_add_col(rBuf, rRef.Ref1, aAbsRef.aStart); if (aAbsRef.aStart.Col() != aAbsRef.aEnd.Col() || rRef.Ref1.IsColRel() != rRef.Ref2.IsColRel()) { rBuf.append( ':' ); r1c1_add_col(rBuf, rRef.Ref2, aAbsRef.aEnd); } return; } } r1c1_add_row(rBuf, rRef.Ref1, aAbsRef.aStart); r1c1_add_col(rBuf, rRef.Ref1, aAbsRef.aStart); if (!bSingleRef) { rBuf.append( ':' ); r1c1_add_row(rBuf, rRef.Ref2, aAbsRef.aEnd); r1c1_add_col(rBuf, rRef.Ref2, aAbsRef.aEnd); } } ParseResult parseAnyToken( const OUString& rFormula, sal_Int32 nSrcPos, const CharClass* pCharClass, bool bGroupSeparator) const override { parseExternalDocName(rFormula, nSrcPos); ParseResult aRet; if ( lcl_isValidQuotedText(rFormula, nSrcPos, aRet) ) return aRet; constexpr sal_Int32 nStartFlags = KParseTokens::ANY_LETTER_OR_NUMBER | KParseTokens::ASC_UNDERSCORE ; constexpr sal_Int32 nContFlags = nStartFlags | KParseTokens::ASC_DOT; // '?' allowed in range names static constexpr OUStringLiteral aAddAllowed(u"?-[]!"); return pCharClass->parseAnyToken( rFormula, nSrcPos, nStartFlags, aAddAllowed, (bGroupSeparator ? nContFlags | KParseTokens::GROUP_SEPARATOR_IN_NUMBER : nContFlags), aAddAllowed ); } virtual sal_Unicode getSpecialSymbol( SpecialSymbolType eSymType ) const override { return ConventionXL::getSpecialSymbol(eSymType); } virtual bool parseExternalName( const OUString& rSymbol, OUString& rFile, OUString& rName, const ScDocument& rDoc, const uno::Sequence* pExternalLinks ) const override { return ConventionXL::parseExternalName( rSymbol, rFile, rName, rDoc, pExternalLinks); } virtual OUString makeExternalNameStr( sal_uInt16 /*nFileId*/, const OUString& rFile, const OUString& rName ) const override { return ConventionXL::makeExternalNameStr(rFile, rName); } virtual void makeExternalRefStr( ScSheetLimits& rLimits, OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName, const OUString& rTabName, const ScSingleRefData& rRef ) const override { // ['file:///path/to/file/filename.xls']'Sheet Name'!$A$1 // This is a little different from the format Excel uses, as Excel // puts [] only around the file name. But we need to enclose the // whole file path with [] because the file name can contain any // characters. ScAddress aAbsRef = rRef.toAbs(rLimits, rPos); ConventionXL::makeExternalDocStr(rBuffer, rFileName); ScRangeStringConverter::AppendTableName(rBuffer, rTabName); rBuffer.append('!'); r1c1_add_row(rBuffer, rRef, aAbsRef); r1c1_add_col(rBuffer, rRef, aAbsRef); } virtual void makeExternalRefStr( ScSheetLimits& rLimits, OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName, const std::vector& rTabNames, const OUString& rTabName, const ScComplexRefData& rRef ) const override { ScRange aAbsRef = rRef.toAbs(rLimits, rPos); ConventionXL::makeExternalDocStr(rBuffer, rFileName); ConventionXL::makeExternalTabNameRange(rBuffer, rTabName, rTabNames, aAbsRef); rBuffer.append('!'); if (!rLimits.ValidCol(aAbsRef.aEnd.Col()) || !rLimits.ValidRow(aAbsRef.aEnd.Row())) { rBuffer.append(ScResId(STR_NO_REF_TABLE)); return; } if (aAbsRef.aStart.Col() == 0 && aAbsRef.aEnd.Col() >= rLimits.mnMaxCol) { r1c1_add_row(rBuffer, rRef.Ref1, aAbsRef.aStart); if (aAbsRef.aStart.Row() != aAbsRef.aEnd.Row() || rRef.Ref1.IsRowRel() != rRef.Ref2.IsRowRel()) { rBuffer.append(':'); r1c1_add_row(rBuffer, rRef.Ref2, aAbsRef.aEnd); } return; } if (aAbsRef.aStart.Row() == 0 && aAbsRef.aEnd.Row() >= rLimits.mnMaxRow) { r1c1_add_col(rBuffer, rRef.Ref1, aAbsRef.aStart); if (aAbsRef.aStart.Col() != aAbsRef.aEnd.Col() || rRef.Ref1.IsColRel() != rRef.Ref2.IsColRel()) { rBuffer.append(':'); r1c1_add_col(rBuffer, rRef.Ref2, aAbsRef.aEnd); } return; } r1c1_add_row(rBuffer, rRef.Ref1, aAbsRef.aStart); r1c1_add_col(rBuffer, rRef.Ref1, aAbsRef.aStart); rBuffer.append(':'); r1c1_add_row(rBuffer, rRef.Ref2, aAbsRef.aEnd); r1c1_add_col(rBuffer, rRef.Ref2, aAbsRef.aEnd); } virtual ScCharFlags getCharTableFlags( sal_Unicode c, sal_Unicode cLast ) const override { ScCharFlags nFlags = mpCharTable[static_cast(c)]; if (c == '-' && cLast == '[') // '-' can occur within a reference string only after '[' e.g. R[-1]C. nFlags |= ScCharFlags::Ident; return nFlags; } }; } ScCompiler::ScCompiler( sc::CompileFormulaContext& rCxt, const ScAddress& rPos, ScTokenArray& rArr, bool bComputeII, bool bMatrixFlag, const ScInterpreterContext* pContext ) : FormulaCompiler(rArr, bComputeII, bMatrixFlag), rDoc(rCxt.getDoc()), aPos(rPos), mpFormatter(pContext ? pContext->GetFormatTable() : rDoc.GetFormatTable()), mpInterpreterContext(pContext), mnCurrentSheetTab(-1), mnCurrentSheetEndPos(0), pCharClass(&ScGlobal::getCharClass()), mbCharClassesDiffer(false), mnPredetectedReference(0), mnRangeOpPosInSymbol(-1), pConv(GetRefConvention(FormulaGrammar::CONV_OOO)), meExtendedErrorDetection(EXTENDED_ERROR_DETECTION_NONE), mbCloseBrackets(true), mbRewind(false), mbRefConventionChartOOXML(false), maTabNames(rCxt.getTabNames()) { SetGrammar(rCxt.getGrammar()); } ScCompiler::ScCompiler( ScDocument& rDocument, const ScAddress& rPos, ScTokenArray& rArr, formula::FormulaGrammar::Grammar eGrammar, bool bComputeII, bool bMatrixFlag, const ScInterpreterContext* pContext ) : FormulaCompiler(rArr, bComputeII, bMatrixFlag), rDoc( rDocument ), aPos( rPos ), mpFormatter(pContext ? pContext->GetFormatTable() : rDoc.GetFormatTable()), mpInterpreterContext(pContext), mnCurrentSheetTab(-1), mnCurrentSheetEndPos(0), nSrcPos(0), pCharClass( &ScGlobal::getCharClass() ), mbCharClassesDiffer(false), mnPredetectedReference(0), mnRangeOpPosInSymbol(-1), pConv( GetRefConvention( FormulaGrammar::CONV_OOO ) ), meExtendedErrorDetection( EXTENDED_ERROR_DETECTION_NONE ), mbCloseBrackets( true ), mbRewind( false ), mbRefConventionChartOOXML( false ) { SetGrammar( (eGrammar == formula::FormulaGrammar::GRAM_UNSPECIFIED) ? rDocument.GetGrammar() : eGrammar ); } ScCompiler::ScCompiler( sc::CompileFormulaContext& rCxt, const ScAddress& rPos, bool bComputeII, bool bMatrixFlag, const ScInterpreterContext* pContext ) : FormulaCompiler(bComputeII, bMatrixFlag), rDoc(rCxt.getDoc()), aPos(rPos), mpFormatter(pContext ? pContext->GetFormatTable() : rDoc.GetFormatTable()), mpInterpreterContext(pContext), mnCurrentSheetTab(-1), mnCurrentSheetEndPos(0), pCharClass(&ScGlobal::getCharClass()), mbCharClassesDiffer(false), mnPredetectedReference(0), mnRangeOpPosInSymbol(-1), pConv(GetRefConvention(FormulaGrammar::CONV_OOO)), meExtendedErrorDetection(EXTENDED_ERROR_DETECTION_NONE), mbCloseBrackets(true), mbRewind(false), mbRefConventionChartOOXML(false), maTabNames(rCxt.getTabNames()) { SetGrammar(rCxt.getGrammar()); } ScCompiler::ScCompiler( ScDocument& rDocument, const ScAddress& rPos, formula::FormulaGrammar::Grammar eGrammar, bool bComputeII, bool bMatrixFlag, const ScInterpreterContext* pContext ) : FormulaCompiler(bComputeII, bMatrixFlag), rDoc( rDocument ), aPos( rPos ), mpFormatter(pContext ? pContext->GetFormatTable() : rDoc.GetFormatTable()), mpInterpreterContext(pContext), mnCurrentSheetTab(-1), mnCurrentSheetEndPos(0), nSrcPos(0), pCharClass( &ScGlobal::getCharClass() ), mbCharClassesDiffer(false), mnPredetectedReference(0), mnRangeOpPosInSymbol(-1), pConv( GetRefConvention( FormulaGrammar::CONV_OOO ) ), meExtendedErrorDetection( EXTENDED_ERROR_DETECTION_NONE ), mbCloseBrackets( true ), mbRewind( false ), mbRefConventionChartOOXML( false ) { SetGrammar( (eGrammar == formula::FormulaGrammar::GRAM_UNSPECIFIED) ? rDocument.GetGrammar() : eGrammar ); } ScCompiler::~ScCompiler() { } void ScCompiler::CheckTabQuotes( OUString& rString, const FormulaGrammar::AddressConvention eConv ) { sal_Int32 nStartFlags = KParseTokens::ANY_LETTER_OR_NUMBER | KParseTokens::ASC_UNDERSCORE; sal_Int32 nContFlags = nStartFlags; ParseResult aRes = ScGlobal::getCharClass().parsePredefinedToken( KParseType::IDENTNAME, rString, 0, nStartFlags, OUString(), nContFlags, OUString()); bool bNeedsQuote = !((aRes.TokenType & KParseType::IDENTNAME) && aRes.EndPos == rString.getLength()); switch ( eConv ) { default : case FormulaGrammar::CONV_UNSPECIFIED : break; case FormulaGrammar::CONV_OOO : case FormulaGrammar::CONV_XL_A1 : case FormulaGrammar::CONV_XL_R1C1 : case FormulaGrammar::CONV_XL_OOX : case FormulaGrammar::CONV_ODF : if( bNeedsQuote ) { // escape embedded quotes rString = rString.replaceAll( "'", "''" ); } break; } if ( !bNeedsQuote && CharClass::isAsciiNumeric( rString ) ) { // Prevent any possible confusion resulting from pure numeric sheet names. bNeedsQuote = true; } if( bNeedsQuote ) { rString = "'" + rString + "'"; } } sal_Int32 ScCompiler::GetDocTabPos( const OUString& rString ) { if (rString[0] != '\'') return -1; sal_Int32 nPos = ScGlobal::FindUnquoted( rString, SC_COMPILER_FILE_TAB_SEP); // it must be 'Doc'# if (nPos != -1 && rString[nPos-1] != '\'') nPos = -1; return nPos; } void ScCompiler::SetRefConvention( FormulaGrammar::AddressConvention eConv ) { const Convention* p = GetRefConvention(eConv); if (p) SetRefConvention(p); } const ScCompiler::Convention* ScCompiler::GetRefConvention( FormulaGrammar::AddressConvention eConv ) { switch (eConv) { case FormulaGrammar::CONV_OOO: { static const ConventionOOO_A1 ConvOOO_A1; return &ConvOOO_A1; } case FormulaGrammar::CONV_ODF: { static const ConventionOOO_A1_ODF ConvOOO_A1_ODF; return &ConvOOO_A1_ODF; } case FormulaGrammar::CONV_XL_A1: { static const ConventionXL_A1 ConvXL_A1; return &ConvXL_A1; } case FormulaGrammar::CONV_XL_R1C1: { static const ConventionXL_R1C1 ConvXL_R1C1; return &ConvXL_R1C1; } case FormulaGrammar::CONV_XL_OOX: { static const ConventionXL_OOX ConvXL_OOX; return &ConvXL_OOX; } case FormulaGrammar::CONV_UNSPECIFIED: default: ; } return nullptr; } void ScCompiler::SetRefConvention( const ScCompiler::Convention *pConvP ) { pConv = pConvP; meGrammar = FormulaGrammar::mergeToGrammar( meGrammar, pConv->meConv); assert( FormulaGrammar::isSupported( meGrammar)); } void ScCompiler::SetError(FormulaError nError) { if( pArr->GetCodeError() == FormulaError::NONE) pArr->SetCodeError( nError); } static sal_Unicode* lcl_UnicodeStrNCpy( sal_Unicode* pDst, const sal_Unicode* pSrc, sal_Int32 nMax ) { const sal_Unicode* const pStop = pDst + nMax; while ( pDst < pStop ) { *pDst++ = *pSrc++; } *pDst = 0; return pDst; } // p1 MUST contain at least n characters, or terminate with NIL. // p2 MUST pass upper case letters, if any. // n MUST not be greater than length of p2 static bool lcl_isUnicodeIgnoreAscii( const sal_Unicode* p1, const char* p2, size_t n ) { for (size_t i=0; i & rvSpaces, ScCompiler::Whitespace & rSpace, sal_Unicode c, sal_Int32 n ) { if (rSpace.cChar != c) { if (rSpace.cChar && rSpace.nCount > 0) rvSpaces.emplace_back(rSpace); rSpace.reset(c); } rSpace.nCount += n; } // NextSymbol // Parses the formula into separate symbols for further processing. // XXX NOTE: this is a rough sketch of the original idea, there are other // states that were added and didn't make it into this table and things are // more complicated. Use the source, Luke. // initial state = GetChar // old state | read character | action | new state //---------------+-------------------+-----------------------+--------------- // GetChar | ;()+-*/^=& | Symbol=char | Stop // | <> | Symbol=char | GetBool // | $ letter | Symbol=char | GetWord // | number | Symbol=char | GetValue // | " | none | GetString // | other | none | GetChar //---------------+-------------------+-----------------------+--------------- // GetBool | => | Symbol=Symbol+char | Stop // | other | Dec(CharPos) | Stop //---------------+-------------------+-----------------------+--------------- // GetWord | SepSymbol | Dec(CharPos) | Stop // | ()+-*/^=<>&~ | | // | space | Dec(CharPos) | Stop // | $_:. | | // | letter, number | Symbol=Symbol+char | GetWord // | other | error | Stop //---------------+-------------------+-----------------------+--------------- // GetValue | ;()*/^=<>& | | // | space | Dec(CharPos) | Stop // | number E+-%,. | Symbol=Symbol+char | GetValue // | other | error | Stop //---------------+-------------------+-----------------------+--------------- // GetString | " | none | Stop // | other | Symbol=Symbol+char | GetString //---------------+-------------------+-----------------------+--------------- std::vector ScCompiler::NextSymbol(bool bInArray) { std::vector vSpaces; cSymbol[MAXSTRLEN] = 0; // end sal_Unicode* pSym = cSymbol; const sal_Unicode* const pStart = aFormula.getStr(); const sal_Unicode* pSrc = pStart + nSrcPos; bool bi18n = false; sal_Unicode c = *pSrc; sal_Unicode cLast = 0; bool bQuote = false; mnRangeOpPosInSymbol = -1; ScanState eState = ssGetChar; Whitespace aSpace; sal_Unicode cSep = mxSymbols->getSymbolChar( ocSep); sal_Unicode cArrayColSep = mxSymbols->getSymbolChar( ocArrayColSep); sal_Unicode cArrayRowSep = mxSymbols->getSymbolChar( ocArrayRowSep); sal_Unicode cDecSep = (mxSymbols->isEnglishLocale() ? '.' : ScGlobal::getLocaleData().getNumDecimalSep()[0]); sal_Unicode cDecSepAlt = (mxSymbols->isEnglishLocale() ? 0 : ScGlobal::getLocaleData().getNumDecimalSepAlt().toChar()); // special symbols specific to address convention used sal_Unicode cSheetPrefix = pConv->getSpecialSymbol(ScCompiler::Convention::ABS_SHEET_PREFIX); sal_Unicode cSheetSep = pConv->getSpecialSymbol(ScCompiler::Convention::SHEET_SEPARATOR); int nDecSeps = 0; bool bAutoIntersection = false; size_t nAutoIntersectionSpacesPos = 0; int nRefInName = 0; bool bErrorConstantHadSlash = false; mnPredetectedReference = 0; // try to parse simple tokens before calling i18n parser while ((c != 0) && (eState != ssStop) ) { pSrc++; ScCharFlags nMask = GetCharTableFlags( c, cLast ); // The parameter separator and the array column and row separators end // things unconditionally if not in string or reference. if (c == cSep || (bInArray && (c == cArrayColSep || c == cArrayRowSep))) { switch (eState) { // these are to be continued case ssGetString: case ssSkipString: case ssGetReference: case ssSkipReference: case ssGetTableRefItem: case ssGetTableRefColumn: break; default: if (eState == ssGetChar) *pSym++ = c; else pSrc--; eState = ssStop; } } Label_MaskStateMachine: switch (eState) { case ssGetChar : { // Order is important! if (eLastOp == ocTableRefOpen && c != '[' && c != '#' && c != ']') { *pSym++ = c; eState = ssGetTableRefColumn; } else if( nMask & ScCharFlags::OdfLabelOp ) { // '!!' automatic intersection if (GetCharTableFlags( pSrc[0], 0 ) & ScCharFlags::OdfLabelOp) { /* TODO: For now the UI "space operator" is used, this * could be enhanced using a specialized OpCode to get * rid of the space ambiguity, which would need some * places to be adapted though. And we would still need * to support the ambiguous space operator for UI * purposes anyway. However, we then could check for * invalid usage of '!!', which currently isn't * possible. */ if (!bAutoIntersection) { ++pSrc; // Add 2 because it must match the character count // for bi18n. addWhitespace( vSpaces, aSpace, 0x20, 2); // Position of Whitespace where it will be added to // vector. nAutoIntersectionSpacesPos = vSpaces.size(); bAutoIntersection = true; } else { pSrc--; eState = ssStop; } } else { nMask &= ~ScCharFlags::OdfLabelOp; goto Label_MaskStateMachine; } } else if( nMask & ScCharFlags::OdfNameMarker ) { // '$$' defined name marker if (GetCharTableFlags( pSrc[0], 0 ) & ScCharFlags::OdfNameMarker) { // both eaten, not added to pSym ++pSrc; } else { nMask &= ~ScCharFlags::OdfNameMarker; goto Label_MaskStateMachine; } } else if( nMask & ScCharFlags::Char ) { // '[' is a special case in Excel syntax, it can start an // external reference, ID in OOXML like [1]Sheet1!A1 or // Excel_A1 [filename]Sheet!A1 or Excel_R1C1 // [filename]Sheet!R1C1 that needs to be scanned // entirely, or can be ocTableRefOpen, of which the first // transforms an ocDBArea into an ocTableRef. if (c == '[' && FormulaGrammar::isExcelSyntax( meGrammar) && eLastOp != ocDBArea && maTableRefs.empty()) { // [0]!Global_Range_Name, is a special case in OOXML // syntax, where the '0' is referencing to self and we // do not need it, so we should skip it, in order to // later it will be more recognisable for IsNamedRange. if (FormulaGrammar::isRefConventionOOXML(meGrammar) && pSrc[0] == '0' && pSrc[1] == ']' && pSrc[2] == '!') { pSrc += 3; c = *pSrc; continue; } nMask &= ~ScCharFlags::Char; goto Label_MaskStateMachine; } else { *pSym++ = c; eState = ssStop; } } else if( nMask & ScCharFlags::OdfLBracket ) { // eaten, not added to pSym eState = ssGetReference; mnPredetectedReference = 1; } else if( nMask & ScCharFlags::CharBool ) { *pSym++ = c; eState = ssGetBool; } else if( nMask & ScCharFlags::CharValue ) { *pSym++ = c; eState = ssGetValue; } else if( nMask & ScCharFlags::CharString ) { *pSym++ = c; eState = ssGetString; } else if( nMask & ScCharFlags::CharErrConst ) { *pSym++ = c; if (!maTableRefs.empty() && maTableRefs.back().mnLevel == 2) eState = ssGetTableRefItem; else eState = ssGetErrorConstant; } else if( nMask & ScCharFlags::CharDontCare ) { addWhitespace( vSpaces, aSpace, c); } else if( nMask & ScCharFlags::CharIdent ) { // try to get a simple ASCII identifier before calling // i18n, to gain performance during import *pSym++ = c; eState = ssGetIdent; } else { bi18n = true; eState = ssStop; } } break; case ssGetIdent: { if ( nMask & ScCharFlags::Ident ) { // This catches also $Sheet1.A$1, for example. if( pSym == &cSymbol[ MAXSTRLEN ] ) { SetError(FormulaError::StringOverflow); eState = ssStop; } else *pSym++ = c; } else if (c == '#' && lcl_isUnicodeIgnoreAscii( pSrc, "REF!", 4)) { // Completely ugly means to catch broken // [$]#REF!.[$]#REF![$]#REF! (one or multiple parts) // references that were written in ODF named ranges // (without embracing [] hence no predetected reference) // and to OOXML and handle them as one symbol. // Also catches these in UI, so we can process them // further. int i = 0; for ( ; i<5; ++i) { if( pSym == &cSymbol[ MAXSTRLEN ] ) { SetError(FormulaError::StringOverflow); eState = ssStop; break; // for } else { *pSym++ = c; c = *pSrc++; } } if (i == 5) c = *((--pSrc)-1); // position last/next character correctly } else if (c == ':' && mnRangeOpPosInSymbol < 0) { // One range operator may form Sheet1.A:A, which we need to // pass as one entity to IsReference(). if( pSym == &cSymbol[ MAXSTRLEN ] ) { SetError(FormulaError::StringOverflow); eState = ssStop; } else { mnRangeOpPosInSymbol = pSym - &cSymbol[0]; *pSym++ = c; } } else if ( 128 <= c || '\'' == c ) { // High values need reparsing with i18n, // single quoted $'sheet' names too (otherwise we'd had to // implement everything twice). bi18n = true; eState = ssStop; } else { pSrc--; eState = ssStop; } } break; case ssGetBool : { if( nMask & ScCharFlags::Bool ) { *pSym++ = c; eState = ssStop; } else { pSrc--; eState = ssStop; } } break; case ssGetValue : { if( pSym == &cSymbol[ MAXSTRLEN ] ) { SetError(FormulaError::StringOverflow); eState = ssStop; } else if (c == cDecSep || (cDecSepAlt && c == cDecSepAlt)) { if (++nDecSeps > 1) { // reparse with i18n, may be numeric sheet name as well bi18n = true; eState = ssStop; } else *pSym++ = c; } else if( nMask & ScCharFlags::Value ) *pSym++ = c; else if( nMask & ScCharFlags::ValueSep ) { pSrc--; eState = ssStop; } else if (c == 'E' || c == 'e') { if (GetCharTableFlags( pSrc[0], 0 ) & ScCharFlags::ValueExp) *pSym++ = c; else { // reparse with i18n bi18n = true; eState = ssStop; } } else if( nMask & ScCharFlags::ValueSign ) { if (((cLast == 'E') || (cLast == 'e')) && (GetCharTableFlags( pSrc[0], 0 ) & ScCharFlags::ValueValue)) { *pSym++ = c; } else { pSrc--; eState = ssStop; } } else { // reparse with i18n bi18n = true; eState = ssStop; } } break; case ssGetString : { if( nMask & ScCharFlags::StringSep ) { if ( !bQuote ) { if ( *pSrc == '"' ) bQuote = true; // "" => literal " else eState = ssStop; } else bQuote = false; } if ( !bQuote ) { if( pSym == &cSymbol[ MAXSTRLEN ] ) { SetError(FormulaError::StringOverflow); eState = ssSkipString; } else *pSym++ = c; } } break; case ssSkipString: if( nMask & ScCharFlags::StringSep ) eState = ssStop; break; case ssGetErrorConstant: { // ODFF Error ::= '#' [A-Z0-9]+ ([!?] | ('/' ([A-Z] | ([0-9] [!?])))) // BUT, in UI these may have been translated! So don't // check for ASCII alnum. Note that this construct can't be // parsed with i18n. /* TODO: be strict when reading ODFF, check for ASCII alnum * and proper continuation after '/'. However, even with * the lax parsing only the error constants we have defined * as opcode symbols will be recognized and others result * in ocBad, so the result is actually conformant. */ bool bAdd = true; if ('?' == c) eState = ssStop; else if ('!' == c) { // Check if this is #REF! that starts an invalid reference. // Note we have an implicit '!' here at the end. if (pSym - &cSymbol[0] == 4 && lcl_isUnicodeIgnoreAscii( cSymbol, "#REF", 4) && (GetCharTableFlags( *pSrc, c) & ScCharFlags::Ident)) eState = ssGetIdent; else eState = ssStop; } else if ('/' == c) { if (!bErrorConstantHadSlash) bErrorConstantHadSlash = true; else { bAdd = false; eState = ssStop; } } else if ((nMask & ScCharFlags::WordSep) || (c < 128 && !rtl::isAsciiAlphanumeric( c))) { bAdd = false; eState = ssStop; } if (!bAdd) --pSrc; else { if (pSym == &cSymbol[ MAXSTRLEN ]) { SetError( FormulaError::StringOverflow); eState = ssStop; } else *pSym++ = c; } } break; case ssGetTableRefItem: { // Scan whatever up to the next ']' closer. if (c != ']') { if( pSym == &cSymbol[ MAXSTRLEN ] ) { SetError( FormulaError::StringOverflow); eState = ssStop; } else *pSym++ = c; } else { --pSrc; eState = ssStop; } } break; case ssGetTableRefColumn: { // Scan whatever up to the next unescaped ']' closer. if (c != ']' || cLast == '\'') { if( pSym == &cSymbol[ MAXSTRLEN ] ) { SetError( FormulaError::StringOverflow); eState = ssStop; } else *pSym++ = c; } else { --pSrc; eState = ssStop; } } break; case ssGetReference: if( pSym == &cSymbol[ MAXSTRLEN ] ) { SetError( FormulaError::StringOverflow); eState = ssSkipReference; } [[fallthrough]]; case ssSkipReference: // ODF reference: ['External'#$'Sheet'.A1:.B2] with dots being // mandatory also if no sheet name. 'External'# is optional, // sheet name is optional, quotes around sheet name are // optional if no quote contained. [#REF!] is valid. // 2nd usage: ['Sheet'.$$'DefinedName'] // 3rd usage: ['External'#$$'DefinedName'] // 4th usage: ['External'#$'Sheet'.$$'DefinedName'] // Also for all these names quotes are optional if no quote // contained. { // nRefInName: 0 := not in sheet name yet. 'External' // is parsed as if it was a sheet name and nRefInName // is reset when # is encountered immediately after closing // quote. Same with 'DefinedName', nRefInName is cleared // when : is encountered. // Encountered leading $ before sheet name. constexpr int kDollar = (1 << 1); // Encountered ' opening quote, which may be after $ or // not. constexpr int kOpen = (1 << 2); // Somewhere in name. constexpr int kName = (1 << 3); // Encountered ' in name, will be cleared if double or // transformed to kClose if not, in which case kOpen is // cleared. constexpr int kQuote = (1 << 4); // Past ' closing quote. constexpr int kClose = (1 << 5); // Encountered # file/sheet separator. constexpr int kFileSep = (1 << 6); // Past . sheet name separator. constexpr int kPast = (1 << 7); // Marked name $$ follows sheet name separator, detected // while we're still on the separator. Will be cleared when // entering the name. constexpr int kMarkAhead = (1 << 8); // In marked defined name. constexpr int kDefName = (1 << 9); // Encountered # of #REF! constexpr int kRefErr = (1 << 10); bool bAddToSymbol = true; if ((nMask & ScCharFlags::OdfRBracket) && !(nRefInName & kOpen)) { OSL_ENSURE( nRefInName & (kPast | kDefName | kRefErr), "ScCompiler::NextSymbol: reference: " "closing bracket ']' without prior sheet name separator '.' violates ODF spec"); // eaten, not added to pSym bAddToSymbol = false; eState = ssStop; } else if (cSheetSep == c && nRefInName == 0) { // eat it, no sheet name [.A1] bAddToSymbol = false; nRefInName |= kPast; if ('$' == pSrc[0] && '$' == pSrc[1]) nRefInName |= kMarkAhead; } else if (!(nRefInName & kPast) || (nRefInName & (kMarkAhead | kDefName))) { // Not in col/row yet. if (SC_COMPILER_FILE_TAB_SEP == c && (nRefInName & kFileSep)) nRefInName = 0; else if ('$' == c && '$' == pSrc[0] && !(nRefInName & kOpen)) { nRefInName &= ~kMarkAhead; if (!(nRefInName & kDefName)) { // eaten, not added to pSym (2 chars) bAddToSymbol = false; ++pSrc; nRefInName &= kPast; nRefInName |= kDefName; } else { // ScAddress::Parse() will recognize this as // invalid later. if (eState != ssSkipReference) { *pSym++ = c; if( pSym == &cSymbol[ MAXSTRLEN ] ) { SetError( FormulaError::StringOverflow); eState = ssStop; } else *pSym++ = *pSrc++; } bAddToSymbol = false; } } else if (cSheetPrefix == c && nRefInName == 0) nRefInName |= kDollar; else if ('\'' == c) { // TODO: The conventions' parseExternalName() // should handle quoted names, but as long as they // don't remove non-embedded quotes here. if (!(nRefInName & kName)) { nRefInName |= (kOpen | kName); bAddToSymbol = !(nRefInName & kDefName); } else if (!(nRefInName & kOpen)) { OSL_FAIL("ScCompiler::NextSymbol: reference: " "a ''' without the name being enclosed in '...' violates ODF spec"); } else if (nRefInName & kQuote) { // escaped embedded quote nRefInName &= ~kQuote; } else { switch (pSrc[0]) { case '\'': // escapes embedded quote nRefInName |= kQuote; break; case SC_COMPILER_FILE_TAB_SEP: // sheet name should follow nRefInName |= kFileSep; [[fallthrough]]; default: // quote not followed by quote => close nRefInName |= kClose; nRefInName &= ~kOpen; } bAddToSymbol = !(nRefInName & kDefName); } } else if ('#' == c && nRefInName == 0) nRefInName |= kRefErr; else if (cSheetSep == c && !(nRefInName & kOpen)) { // unquoted sheet name separator nRefInName |= kPast; if ('$' == pSrc[0] && '$' == pSrc[1]) nRefInName |= kMarkAhead; } else if (':' == c && !(nRefInName & kOpen)) { OSL_FAIL("ScCompiler::NextSymbol: reference: " "range operator ':' without prior sheet name separator '.' violates ODF spec"); nRefInName = 0; ++mnPredetectedReference; } else if (!(nRefInName & kName)) { // start unquoted name nRefInName |= kName; } } else if (':' == c) { // range operator nRefInName = 0; ++mnPredetectedReference; } if (bAddToSymbol && eState != ssSkipReference) *pSym++ = c; // everything is part of reference } break; case ssStop: ; // nothing, prevent warning break; } cLast = c; c = *pSrc; } if (aSpace.nCount && aSpace.cChar) vSpaces.emplace_back(aSpace); if ( bi18n ) { const sal_Int32 nOldSrcPos = nSrcPos; for (const auto& r : vSpaces) nSrcPos += r.nCount; // If group separator is not a possible operator and not one of any // separators then it may be parsed away in numbers. This is // specifically the case with NO-BREAK SPACE, which actually triggers // the bi18n case (which we don't want to include as yet another // special case above as it is rare enough and doesn't generally occur // in formulas). const sal_Unicode cGroupSep = ScGlobal::getLocaleData().getNumThousandSep()[0]; const bool bGroupSeparator = (128 <= cGroupSep && cGroupSep != cSep && cGroupSep != cArrayColSep && cGroupSep != cArrayRowSep && cGroupSep != cDecSep && cGroupSep != cDecSepAlt && cGroupSep != cSheetPrefix && cGroupSep != cSheetSep); // If a numeric context triggered bi18n then use the default locale's // CharClass, this may accept group separator as well. const CharClass* pMyCharClass = (ScGlobal::getCharClass().isDigit( OUString(pStart[nSrcPos]), 0) ? &ScGlobal::getCharClass() : pCharClass); OUStringBuffer aSymbol; mnRangeOpPosInSymbol = -1; FormulaError nErr = FormulaError::NONE; do { bi18n = false; // special case (e.g. $'sheetname' in OOO A1) if ( pStart[nSrcPos] == cSheetPrefix && pStart[nSrcPos+1] == '\'' ) aSymbol.append(pStart[nSrcPos++]); ParseResult aRes = pConv->parseAnyToken( aFormula, nSrcPos, pMyCharClass, bGroupSeparator); if ( !aRes.TokenType ) { nErr = FormulaError::IllegalChar; SetError( nErr ); // parsed chars as string } if ( aRes.EndPos <= nSrcPos ) { // Could not parse anything meaningful. assert(!aRes.TokenType); nErr = FormulaError::IllegalChar; SetError( nErr ); // Caller has to act on an empty symbol for // nSrcPos < aFormula.getLength() nSrcPos = nOldSrcPos; aSymbol.setLength(0); } else { // When having parsed a second reference part, ensure that the // i18n parser did not mistakingly parse a number that included // a separator which happened to be meant as a parameter // separator instead. if (mnRangeOpPosInSymbol >= 0 && (aRes.TokenType & KParseType::ASC_NUMBER)) { for (sal_Int32 i = nSrcPos; i < aRes.EndPos; ++i) { if (pStart[i] == cSep) aRes.EndPos = i; // also ends for } } aSymbol.append( pStart + nSrcPos, aRes.EndPos - nSrcPos); nSrcPos = aRes.EndPos; c = pStart[nSrcPos]; if ( aRes.TokenType & KParseType::SINGLE_QUOTE_NAME ) { // special cases (e.g. 'sheetname'. or 'filename'# in OOO A1) bi18n = (c == cSheetSep || c == SC_COMPILER_FILE_TAB_SEP); } // One range operator restarts parsing for second reference. if (c == ':' && mnRangeOpPosInSymbol < 0) { mnRangeOpPosInSymbol = aSymbol.getLength(); bi18n = true; } if ( bi18n ) aSymbol.append(pStart[nSrcPos++]); } } while ( bi18n && nErr == FormulaError::NONE ); sal_Int32 nLen = aSymbol.getLength(); if ( nLen > MAXSTRLEN ) { SetError( FormulaError::StringOverflow ); nLen = MAXSTRLEN; } if (mnRangeOpPosInSymbol >= nLen) mnRangeOpPosInSymbol = -1; lcl_UnicodeStrNCpy( cSymbol, aSymbol.getStr(), nLen ); pSym = &cSymbol[nLen]; } else { nSrcPos = pSrc - pStart; *pSym = 0; } if (mnRangeOpPosInSymbol >= 0 && mnRangeOpPosInSymbol == (pSym-1) - &cSymbol[0]) { // This is a trailing range operator, which is nonsense. Will be caught // in next round. mnRangeOpPosInSymbol = -1; *--pSym = 0; --nSrcPos; } if ( bAutoCorrect ) aCorrectedSymbol = OUString(cSymbol, pSym - cSymbol); if (bAutoIntersection && vSpaces[nAutoIntersectionSpacesPos].nCount > 1) --vSpaces[nAutoIntersectionSpacesPos].nCount; // replace '!!' with only one space return vSpaces; } // Convert symbol to token bool ScCompiler::ParseOpCode( const OUString& rName, bool bInArray ) { OpCodeHashMap::const_iterator iLook( mxSymbols->getHashMap().find( rName)); bool bFound = (iLook != mxSymbols->getHashMap().end()); if (bFound) { OpCode eOp = iLook->second; if (bInArray) { if (rName == mxSymbols->getSymbol(ocArrayColSep)) eOp = ocArrayColSep; else if (rName == mxSymbols->getSymbol(ocArrayRowSep)) eOp = ocArrayRowSep; } else if (eOp == ocArrayColSep || eOp == ocArrayRowSep) { if (rName == mxSymbols->getSymbol(ocSep)) eOp = ocSep; else if (rName == ";") { switch (FormulaGrammar::extractFormulaLanguage( meGrammar)) { // Only for languages/grammars that actually use ';' // parameter separator. case css::sheet::FormulaLanguage::NATIVE: case css::sheet::FormulaLanguage::ENGLISH: case css::sheet::FormulaLanguage::ODFF: case css::sheet::FormulaLanguage::ODF_11: eOp = ocSep; } } } else if (eOp == ocCeil && mxSymbols->isOOXML()) { // Ensure that _xlfn.CEILING.MATH maps to ocCeil_Math. ocCeil is // unassigned for import. eOp = ocCeil_Math; } else if (eOp == ocFloor && mxSymbols->isOOXML()) { // Ensure that _xlfn.FLOOR.MATH maps to ocFloor_Math. ocFloor is // unassigned for import. eOp = ocFloor_Math; } maRawToken.SetOpCode(eOp); } else if (mxSymbols->isODFF()) { // ODFF names that are not written in the current mapping but to be // recognized. New names will be written in a future release, then // exchange (!) with the names in // formula/source/core/resource/core_resource.src to be able to still // read the old names as well. struct FunctionName { const char* pName; OpCode eOp; }; static const FunctionName aOdffAliases[] = { // Renamed old names, still accept them: { "B", ocB }, // B -> BINOM.DIST.RANGE { "TDIST", ocTDist }, // TDIST -> LEGACY.TDIST { "EASTERSUNDAY", ocEasterSunday }, // EASTERSUNDAY -> ORG.OPENOFFICE.EASTERSUNDAY { "ZGZ", ocRRI }, // ZGZ -> RRI { "COLOR", ocColor }, // COLOR -> ORG.LIBREOFFICE.COLOR { "GOALSEEK", ocBackSolver }, // GOALSEEK -> ORG.OPENOFFICE.GOALSEEK { "COM.MICROSOFT.F.DIST", ocFDist_LT }, // fdo#40835, -> FDIST -> COM.MICROSOFT.F.DIST { "COM.MICROSOFT.F.INV", ocFInv_LT } // tdf#94214, COM.MICROSOFT.F.INV -> FINV (ODF) // Renamed new names, prepare to read future names: //{ "ORG.OPENOFFICE.XXX", ocXXX } // XXX -> ORG.OPENOFFICE.XXX }; for (const FunctionName& rOdffAlias : aOdffAliases) { if (rName.equalsIgnoreAsciiCaseAscii( rOdffAlias.pName)) { maRawToken.SetOpCode( rOdffAlias.eOp); bFound = true; break; // for } } } else if (mxSymbols->isOOXML()) { // OOXML names that are not written in the current mapping but to be // recognized as old versions wrote them. struct FunctionName { const char* pName; OpCode eOp; }; static const FunctionName aOoxmlAliases[] = { { "EFFECTIVE", ocEffect }, // EFFECTIVE -> EFFECT { "ERRORTYPE", ocErrorType }, // ERRORTYPE -> _xlfn.ORG.OPENOFFICE.ERRORTYPE { "MULTIRANGE", ocMultiArea }, // MULTIRANGE -> _xlfn.ORG.OPENOFFICE.MULTIRANGE { "GOALSEEK", ocBackSolver }, // GOALSEEK -> _xlfn.ORG.OPENOFFICE.GOALSEEK { "EASTERSUNDAY", ocEasterSunday }, // EASTERSUNDAY -> _xlfn.ORG.OPENOFFICE.EASTERSUNDAY { "CURRENT", ocCurrent }, // CURRENT -> _xlfn.ORG.OPENOFFICE.CURRENT { "STYLE", ocStyle } // STYLE -> _xlfn.ORG.OPENOFFICE.STYLE }; for (const FunctionName& rOoxmlAlias : aOoxmlAliases) { if (rName.equalsIgnoreAsciiCaseAscii( rOoxmlAlias.pName)) { maRawToken.SetOpCode( rOoxmlAlias.eOp); bFound = true; break; // for } } } else if (mxSymbols->isPODF()) { // PODF names are ODF 1.0/1.1 and also used in API XFunctionAccess. // We can't rename them in // formula/source/core/resource/core_resource.src but can add // additional names to be recognized here so they match the UI names if // those are renamed. struct FunctionName { const char* pName; OpCode eOp; }; static const FunctionName aPodfAliases[] = { { "EFFECT", ocEffect } // EFFECTIVE -> EFFECT }; for (const FunctionName& rPodfAlias : aPodfAliases) { if (rName.equalsIgnoreAsciiCaseAscii( rPodfAlias.pName)) { maRawToken.SetOpCode( rPodfAlias.eOp); bFound = true; break; // for } } } if (!bFound) { OUString aIntName; if (mxSymbols->hasExternals()) { // If symbols are set by filters get mapping to exact name. ExternalHashMap::const_iterator iExt( mxSymbols->getExternalHashMap().find( rName)); if (iExt != mxSymbols->getExternalHashMap().end()) { if (ScGlobal::GetAddInCollection()->GetFuncData( (*iExt).second)) aIntName = (*iExt).second; } } else { // Old (deprecated) addins first for legacy. if (ScGlobal::GetLegacyFuncCollection()->findByName(OUString(cSymbol))) { aIntName = cSymbol; } else // bLocalFirst=false for (English) upper full original name // (service.function) aIntName = ScGlobal::GetAddInCollection()->FindFunction( rName, !mxSymbols->isEnglish()); } if (!aIntName.isEmpty()) { maRawToken.SetExternal( aIntName ); // international name bFound = true; } } if (!bFound) return false; OpCode eOp = maRawToken.GetOpCode(); if (eOp == ocSub || eOp == ocNegSub) { bool bShouldBeNegSub = (eLastOp == ocOpen || eLastOp == ocSep || eLastOp == ocNegSub || (SC_OPCODE_START_BIN_OP <= eLastOp && eLastOp < SC_OPCODE_STOP_BIN_OP) || eLastOp == ocArrayOpen || eLastOp == ocArrayColSep || eLastOp == ocArrayRowSep); if (bShouldBeNegSub && eOp == ocSub) maRawToken.NewOpCode( ocNegSub ); //TODO: if ocNegSub had ForceArray we'd have to set it here else if (!bShouldBeNegSub && eOp == ocNegSub) maRawToken.NewOpCode( ocSub ); } return bFound; } bool ScCompiler::ParseOpCode2( std::u16string_view rName ) { bool bFound = false; sal_uInt16 i; for( i = ocInternalBegin; i <= ocInternalEnd && !bFound; i++ ) bFound = o3tl::equalsAscii( rName, pInternal[ i-ocInternalBegin ] ); if (bFound) { maRawToken.SetOpCode( static_cast(--i) ); } return bFound; } static bool lcl_ParenthesisFollows( const sal_Unicode* p ) { while (*p == ' ') p++; return *p == '('; } bool ScCompiler::ParseValue( const OUString& rSym ) { const sal_Int32 nFormulaLanguage = FormulaGrammar::extractFormulaLanguage( GetGrammar()); if (nFormulaLanguage == css::sheet::FormulaLanguage::ODFF || nFormulaLanguage == css::sheet::FormulaLanguage::OOXML) { // Speedup things for ODFF, only well-formed numbers, not locale // dependent nor user input. rtl_math_ConversionStatus eStatus; sal_Int32 nParseEnd; double fVal = rtl::math::stringToDouble( rSym, '.', 0, &eStatus, &nParseEnd); if (nParseEnd != rSym.getLength()) { // Not (only) a number. if (nParseEnd > 0) return false; // partially a number => no such thing if (lcl_ParenthesisFollows( aFormula.getStr() + nSrcPos)) return false; // some function name, not a constant // Could be TRUE or FALSE constant. OpCode eOpFunc = ocNone; if (rSym.equalsIgnoreAsciiCase("TRUE")) eOpFunc = ocTrue; else if (rSym.equalsIgnoreAsciiCase("FALSE")) eOpFunc = ocFalse; if (eOpFunc != ocNone) { maRawToken.SetOpCode(eOpFunc); // add missing trailing parentheses maPendingOpCodes.push(ocOpen); maPendingOpCodes.push(ocClose); return true; } return false; } if (eStatus == rtl_math_ConversionStatus_OutOfRange) { // rtl::math::stringToDouble() recognizes XMLSchema-2 "INF" and // "NaN" (case sensitive) that could be named expressions or DB // areas as well. // rSym is already upper so "NaN" is not possible here. if (!std::isfinite(fVal) && rSym == "INF") { SCTAB nSheet = -1; if (GetRangeData( nSheet, rSym)) return false; if (rDoc.GetDBCollection()->getNamedDBs().findByUpperName(rSym)) return false; } /* TODO: is there a specific reason why we don't accept an infinity * value that would raise an error in the interpreter, instead of * setting the hard error at the token array already? */ SetError( FormulaError::IllegalArgument ); } maRawToken.SetDouble( fVal ); return true; } double fVal; sal_uInt32 nIndex = mxSymbols->isEnglishLocale() ? mpFormatter->GetStandardIndex(LANGUAGE_ENGLISH_US) : 0; if (!mpFormatter->IsNumberFormat(rSym, nIndex, fVal)) return false; SvNumFormatType nType = mpFormatter->GetType(nIndex); // Don't accept 3:3 as time, it is a reference to entire row 3 instead. // Dates should never be entered directly and automatically converted // to serial, because the serial would be wrong if null-date changed. // Usually it wouldn't be accepted anyway because the date separator // clashed with other separators or operators. if (nType & (SvNumFormatType::TIME | SvNumFormatType::DATE)) return false; if (nType == SvNumFormatType::LOGICAL) { if (lcl_ParenthesisFollows( aFormula.getStr() + nSrcPos)) return false; // Boolean function instead. } if( nType == SvNumFormatType::TEXT ) // HACK: number too big! SetError( FormulaError::IllegalArgument ); maRawToken.SetDouble( fVal ); return true; } bool ScCompiler::ParseString() { if ( cSymbol[0] != '"' ) return false; const sal_Unicode* p = cSymbol+1; while ( *p ) p++; sal_Int32 nLen = sal::static_int_cast( p - cSymbol - 1 ); if (!nLen || cSymbol[nLen] != '"') return false; svl::SharedString aSS = rDoc.GetSharedStringPool().intern(OUString(cSymbol+1, nLen-1)); maRawToken.SetString(aSS.getData(), aSS.getDataIgnoreCase()); return true; } bool ScCompiler::ParsePredetectedErrRefReference( const OUString& rName, const OUString* pErrRef ) { switch (mnPredetectedReference) { case 1: return ParseSingleReference( rName, pErrRef); case 2: return ParseDoubleReference( rName, pErrRef); default: return false; } } bool ScCompiler::ParsePredetectedReference( const OUString& rName ) { // Speedup documents with lots of broken references, e.g. sheet deleted. // It could also be a broken invalidated reference that contains #REF! // (but is not equal to), which we wrote prior to ODFF and also to ODFF // between 2013 and 2016 until 5.1.4 const OUString aErrRef("#REF!"); // not localized in ODFF sal_Int32 nPos = rName.indexOf( aErrRef); if (nPos != -1) { /* TODO: this may be enhanced by reusing scan information from * NextSymbol(), the positions of quotes and special characters found * there for $'sheet'.A1:... could be stored in a vector. We don't * fully rescan here whether found positions are within single quotes * for performance reasons. This code does not check for possible * occurrences of insane "valid" sheet names like * 'haha.#REF!1fooledyou' and will generate an error on such. */ if (nPos == 0) { // Per ODFF the correct string for a reference error is just #REF!, // so pass it on. if (rName.getLength() == 5) return ParseErrorConstant( rName); // #REF!.AB42 or #REF!42 or #REF!#REF! return ParsePredetectedErrRefReference( rName, &aErrRef); } sal_Unicode c = rName[nPos-1]; // before #REF! if ('$' == c) { if (nPos == 1) { // $#REF!.AB42 or $#REF!42 or $#REF!#REF! return ParsePredetectedErrRefReference( rName, &aErrRef); } c = rName[nPos-2]; // before $#REF! } sal_Unicode c2 = nPos+5 < rName.getLength() ? rName[nPos+5] : 0; // after #REF! switch (c) { case '.': if ('$' == c2 || '#' == c2 || ('0' <= c2 && c2 <= '9')) { // sheet.#REF!42 or sheet.#REF!#REF! return ParsePredetectedErrRefReference( rName, &aErrRef); } break; case ':': if (mnPredetectedReference > 1 && ('.' == c2 || '$' == c2 || '#' == c2 || ('0' <= c2 && c2 <= '9'))) { // :#REF!.AB42 or :#REF!42 or :#REF!#REF! return ParsePredetectedErrRefReference( rName, &aErrRef); } break; default: if (rtl::isAsciiAlpha(c) && ((mnPredetectedReference > 1 && ':' == c2) || 0 == c2)) { // AB#REF!: or AB#REF! return ParsePredetectedErrRefReference( rName, &aErrRef); } } } switch (mnPredetectedReference) { case 1: return ParseSingleReference( rName); case 2: return ParseDoubleReference( rName); } return false; } bool ScCompiler::ParseDoubleReference( const OUString& rName, const OUString* pErrRef ) { ScRange aRange( aPos, aPos ); const ScAddress::Details aDetails( pConv->meConv, aPos ); ScAddress::ExternalInfo aExtInfo; ScRefFlags nFlags = aRange.Parse( rName, rDoc, aDetails, &aExtInfo, &maExternalLinks, pErrRef ); if( nFlags & ScRefFlags::VALID ) { ScComplexRefData aRef; aRef.InitRange( aRange ); aRef.Ref1.SetColRel( (nFlags & ScRefFlags::COL_ABS) == ScRefFlags::ZERO ); aRef.Ref1.SetRowRel( (nFlags & ScRefFlags::ROW_ABS) == ScRefFlags::ZERO ); aRef.Ref1.SetTabRel( (nFlags & ScRefFlags::TAB_ABS) == ScRefFlags::ZERO ); if ( !(nFlags & ScRefFlags::TAB_VALID) ) aRef.Ref1.SetTabDeleted( true ); // #REF! aRef.Ref1.SetFlag3D( ( nFlags & ScRefFlags::TAB_3D ) != ScRefFlags::ZERO ); aRef.Ref2.SetColRel( (nFlags & ScRefFlags::COL2_ABS) == ScRefFlags::ZERO ); aRef.Ref2.SetRowRel( (nFlags & ScRefFlags::ROW2_ABS) == ScRefFlags::ZERO ); aRef.Ref2.SetTabRel( (nFlags & ScRefFlags::TAB2_ABS) == ScRefFlags::ZERO ); if ( !(nFlags & ScRefFlags::TAB2_VALID) ) aRef.Ref2.SetTabDeleted( true ); // #REF! aRef.Ref2.SetFlag3D( ( nFlags & ScRefFlags::TAB2_3D ) != ScRefFlags::ZERO ); aRef.SetRange(rDoc.GetSheetLimits(), aRange, aPos); if (aExtInfo.mbExternal) { ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager(); const OUString* pRealTab = pRefMgr->getRealTableName(aExtInfo.mnFileId, aExtInfo.maTabName); maRawToken.SetExternalDoubleRef( aExtInfo.mnFileId, pRealTab ? *pRealTab : aExtInfo.maTabName, aRef); maExternalFiles.push_back(aExtInfo.mnFileId); } else { maRawToken.SetDoubleReference(aRef); } } return ( nFlags & ScRefFlags::VALID ) != ScRefFlags::ZERO; } bool ScCompiler::ParseSingleReference( const OUString& rName, const OUString* pErrRef ) { mnCurrentSheetEndPos = 0; mnCurrentSheetTab = -1; ScAddress aAddr( aPos ); const ScAddress::Details aDetails( pConv->meConv, aPos ); ScAddress::ExternalInfo aExtInfo; ScRefFlags nFlags = aAddr.Parse( rName, rDoc, aDetails, &aExtInfo, &maExternalLinks, &mnCurrentSheetEndPos, pErrRef); // Something must be valid in order to recognize Sheet1.blah or blah.a1 // as a (wrong) reference. if( nFlags & ( ScRefFlags::COL_VALID|ScRefFlags::ROW_VALID|ScRefFlags::TAB_VALID ) ) { // Valid given tab and invalid col or row may indicate a sheet-local // named expression, bail out early and don't create a reference token. if (!(nFlags & ScRefFlags::VALID) && mnCurrentSheetEndPos > 0 && (nFlags & ScRefFlags::TAB_VALID) && (nFlags & ScRefFlags::TAB_3D)) { if (aExtInfo.mbExternal) { // External names are handled separately. mnCurrentSheetEndPos = 0; mnCurrentSheetTab = -1; } else { mnCurrentSheetTab = aAddr.Tab(); } return false; } if( HasPossibleNamedRangeConflict( aAddr.Tab())) { // A named range named e.g. 'num1' is valid with 1k columns, but would become a reference // when the document is opened later with 16k columns. Resolve the conflict by not // considering it a reference. OUString aUpper( ScGlobal::getCharClass().uppercase( rName )); mnCurrentSheetTab = aAddr.Tab(); // temporarily set for ParseNamedRange() if(ParseNamedRange( aUpper, true )) // only check return false; mnCurrentSheetTab = -1; } ScSingleRefData aRef; aRef.InitAddress( aAddr ); aRef.SetColRel( (nFlags & ScRefFlags::COL_ABS) == ScRefFlags::ZERO ); aRef.SetRowRel( (nFlags & ScRefFlags::ROW_ABS) == ScRefFlags::ZERO ); aRef.SetTabRel( (nFlags & ScRefFlags::TAB_ABS) == ScRefFlags::ZERO ); aRef.SetFlag3D( ( nFlags & ScRefFlags::TAB_3D ) != ScRefFlags::ZERO ); // the reference is really invalid if( !( nFlags & ScRefFlags::VALID ) ) { if( !( nFlags & ScRefFlags::COL_VALID ) ) aRef.SetColDeleted(true); if( !( nFlags & ScRefFlags::ROW_VALID ) ) aRef.SetRowDeleted(true); if( !( nFlags & ScRefFlags::TAB_VALID ) ) aRef.SetTabDeleted(true); nFlags |= ScRefFlags::VALID; } aRef.SetAddress(rDoc.GetSheetLimits(), aAddr, aPos); if (aExtInfo.mbExternal) { ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager(); const OUString* pRealTab = pRefMgr->getRealTableName(aExtInfo.mnFileId, aExtInfo.maTabName); maRawToken.SetExternalSingleRef( aExtInfo.mnFileId, pRealTab ? *pRealTab : aExtInfo.maTabName, aRef); maExternalFiles.push_back(aExtInfo.mnFileId); } else maRawToken.SetSingleReference(aRef); } return ( nFlags & ScRefFlags::VALID ) != ScRefFlags::ZERO; } bool ScCompiler::ParseReference( const OUString& rName, const OUString* pErrRef ) { // Has to be called before ParseValue // A later ParseNamedRange() relies on these, being set in ParseSingleReference() // if so, reset in all cases. mnCurrentSheetEndPos = 0; mnCurrentSheetTab = -1; sal_Unicode ch1 = rName[0]; sal_Unicode cDecSep = ( mxSymbols->isEnglishLocale() ? '.' : ScGlobal::getLocaleData().getNumDecimalSep()[0] ); if ( ch1 == cDecSep ) return false; // Code further down checks only if cDecSep=='.' so simply obtaining the // alternative decimal separator if it's not is sufficient. if (cDecSep != '.') { cDecSep = ScGlobal::getLocaleData().getNumDecimalSepAlt().toChar(); if ( ch1 == cDecSep ) return false; } // Who was that imbecile introducing '.' as the sheet name separator!?! if ( rtl::isAsciiDigit( ch1 ) && pConv->getSpecialSymbol( Convention::SHEET_SEPARATOR) == '.' ) { // Numerical sheet name is valid. // But English 1.E2 or 1.E+2 is value 100, 1.E-2 is 0.01 // Don't create a #REF! of values. But also do not bail out on // something like 3:3, meaning entire row 3. do { const sal_Int32 nPos = ScGlobal::FindUnquoted( rName, '.'); if ( nPos == -1 ) { if (ScGlobal::FindUnquoted( rName, ':') != -1) break; // may be 3:3, continue as usual return false; } sal_Unicode const * const pTabSep = rName.getStr() + nPos; sal_Unicode ch2 = pTabSep[1]; // maybe a column identifier if ( !(ch2 == '$' || rtl::isAsciiAlpha( ch2 )) ) return false; if ( cDecSep == '.' && (ch2 == 'E' || ch2 == 'e') // E + - digit && (GetCharTableFlags( pTabSep[2], pTabSep[1] ) & ScCharFlags::ValueExp) ) { // If it is an 1.E2 expression check if "1" is an existent sheet // name. If so, a desired value 1.E2 would have to be entered as // 1E2 or 1.0E2 or 1.E+2, sorry. Another possibility would be to // require numerical sheet names always being entered quoted, which // is not desirable (too many 1999, 2000, 2001 sheets in use). // Furthermore, XML files created with versions prior to SRC640e // wouldn't contain the quotes added by MakeTabStr()/CheckTabQuotes() // and would produce wrong formulas if the conditions here are met. // If you can live with these restrictions you may remove the // check and return an unconditional FALSE. OUString aTabName( rName.copy( 0, nPos ) ); SCTAB nTab; if ( !rDoc.GetTable( aTabName, nTab ) ) return false; // If sheet "1" exists and the expression is 1.E+2 continue as // usual, the ScRange/ScAddress parser will take care of it. } } while(false); } if (ParseSingleReference( rName, pErrRef)) return true; // Though the range operator is handled explicitly, when encountering // something like Sheet1.A:A we will have to treat it as one entity if it // doesn't pass as single cell reference. if (mnRangeOpPosInSymbol > 0) // ":foo" would be nonsense { if (ParseDoubleReference( rName, pErrRef)) return true; // Now try with a symbol up to the range operator, rewind source // position. assert(mnRangeOpPosInSymbol < MAXSTRLEN); // We should have caught the maldoers. if (mnRangeOpPosInSymbol >= MAXSTRLEN) // TODO: this check and return return false; // can be removed when sure. sal_Int32 nLen = mnRangeOpPosInSymbol; while (cSymbol[++nLen]) ; cSymbol[mnRangeOpPosInSymbol] = 0; nSrcPos -= (nLen - mnRangeOpPosInSymbol); mnRangeOpPosInSymbol = -1; mbRewind = true; return true; // end all checks } else { switch (pConv->meConv) { case FormulaGrammar::CONV_XL_A1: case FormulaGrammar::CONV_XL_OOX: // Special treatment for the 'E:\[doc]Sheet1:Sheet3'!D5 Excel // sickness, mnRangeOpPosInSymbol did not catch the range // operator as it is within a quoted name. if (rName[0] != '\'') return false; // Document name has to be single quoted. [[fallthrough]]; case FormulaGrammar::CONV_XL_R1C1: // C2 or C[1] are valid entire column references. if (ParseDoubleReference( rName, pErrRef)) return true; break; default: ; // nothing } } return false; } bool ScCompiler::ParseMacro( const OUString& rName ) { #if !HAVE_FEATURE_SCRIPTING (void) rName; return false; #else // Calling SfxObjectShell::GetBasic() may result in all sort of things // including obtaining the model and deep down in // SfxBaseModel::getDocumentStorage() acquiring the SolarMutex, which when // formulas are compiled from a threaded import may result in a deadlock. // Check first if we actually could acquire it and if not bail out. /* FIXME: yes, but how ... */ vcl::SolarMutexTryAndBuyGuard g; if (!g.isAcquired()) { SAL_WARN( "sc.core", "ScCompiler::ParseMacro - SolarMutex would deadlock, not obtaining Basic"); return false; // bad luck } OUString aName( rName); StarBASIC* pObj = nullptr; SfxObjectShell* pDocSh = rDoc.GetDocumentShell(); try { if( pDocSh )//XXX pObj = pDocSh->GetBasic(); else pObj = SfxApplication::GetBasic(); } catch (...) { return false; } if (!pObj) return false; // ODFF recommends to store user-defined functions prefixed with "USER.", // use only unprefixed name if encountered. BASIC doesn't allow '.' in a // function name so a function "USER.FOO" could not exist, and macro check // is assigned the lowest priority in function name check. if (FormulaGrammar::isODFF( GetGrammar()) && aName.startsWithIgnoreAsciiCase("USER.")) aName = aName.copy(5); SbxMethod* pMeth = static_cast(pObj->Find( aName, SbxClassType::Method )); if( !pMeth ) { return false; } // It really should be a BASIC function! if( pMeth->GetType() == SbxVOID || ( pMeth->IsFixed() && pMeth->GetType() == SbxEMPTY ) || dynamic_cast( pMeth) == nullptr ) { return false; } maRawToken.SetExternal( aName ); maRawToken.eOp = ocMacro; return true; #endif } const ScRangeData* ScCompiler::GetRangeData( SCTAB& rSheet, const OUString& rUpperName ) const { // try local names first rSheet = aPos.Tab(); const ScRangeName* pRangeName = rDoc.GetRangeName(rSheet); const ScRangeData* pData = nullptr; if (pRangeName) pData = pRangeName->findByUpperName(rUpperName); if (!pData) { pRangeName = rDoc.GetRangeName(); if (pRangeName) pData = pRangeName->findByUpperName(rUpperName); if (pData) rSheet = -1; } return pData; } bool ScCompiler::HasPossibleNamedRangeConflict( SCTAB nTab ) const { const ScRangeName* pRangeName = rDoc.GetRangeName(); if (pRangeName && pRangeName->hasPossibleAddressConflict()) return true; pRangeName = rDoc.GetRangeName(nTab); if (pRangeName && pRangeName->hasPossibleAddressConflict()) return true; return false; } bool ScCompiler::ParseNamedRange( const OUString& rUpperName, bool onlyCheck ) { // ParseNamedRange is called only from NextNewToken, with an upper-case string SCTAB nSheet = -1; const ScRangeData* pData = GetRangeData( nSheet, rUpperName); if (pData) { if (!onlyCheck) maRawToken.SetName( nSheet, pData->GetIndex()); return true; } // Sheet-local name with sheet specified. if (mnCurrentSheetEndPos > 0 && mnCurrentSheetTab >= 0) { OUString aName( rUpperName.copy( mnCurrentSheetEndPos)); const ScRangeName* pRangeName = rDoc.GetRangeName( mnCurrentSheetTab); if (pRangeName) { pData = pRangeName->findByUpperName(aName); if (pData) { if (!onlyCheck) maRawToken.SetName( mnCurrentSheetTab, pData->GetIndex()); return true; } } } return false; } bool ScCompiler::ParseExternalNamedRange( const OUString& rSymbol, bool& rbInvalidExternalNameRange ) { /* FIXME: This code currently (2008-12-02T15:41+0100 in CWS mooxlsc) * correctly parses external named references in OOo, as required per RFE * #i3740#, just that we can't store them in ODF yet. We will need an OASIS * spec first. Until then don't pretend to support external names that * wouldn't survive a save and reload cycle, return false instead. */ rbInvalidExternalNameRange = false; if (!pConv) return false; OUString aFile, aName; if (!pConv->parseExternalName( rSymbol, aFile, aName, rDoc, &maExternalLinks)) return false; if (aFile.getLength() > MAXSTRLEN || aName.getLength() > MAXSTRLEN) return false; ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager(); OUString aTmp = aFile; pRefMgr->convertToAbsName(aTmp); aFile = aTmp; sal_uInt16 nFileId = pRefMgr->getExternalFileId(aFile); if (!pRefMgr->isValidRangeName(nFileId, aName)) { rbInvalidExternalNameRange = true; // range name doesn't exist in the source document. return false; } const OUString* pRealName = pRefMgr->getRealRangeName(nFileId, aName); maRawToken.SetExternalName(nFileId, pRealName ? *pRealName : aTmp); maExternalFiles.push_back(nFileId); return true; } bool ScCompiler::ParseDBRange( const OUString& rName ) { ScDBCollection::NamedDBs& rDBs = rDoc.GetDBCollection()->getNamedDBs(); const ScDBData* p = rDBs.findByUpperName(rName); if (!p) return false; maRawToken.SetName( -1, p->GetIndex()); // DB range is always global. maRawToken.eOp = ocDBArea; return true; } bool ScCompiler::ParseColRowName( const OUString& rName ) { bool bInList = false; bool bFound = false; ScSingleRefData aRef; OUString aName( rName ); DeQuote( aName ); SCTAB nThisTab = aPos.Tab(); for ( short jThisTab = 1; jThisTab >= 0 && !bInList; jThisTab-- ) { // first check ranges on this sheet, in case of duplicated names for ( short jRow=0; jRow<2 && !bInList; jRow++ ) { ScRangePairList* pRL; if ( !jRow ) pRL = rDoc.GetColNameRanges(); else pRL = rDoc.GetRowNameRanges(); for ( size_t iPair = 0, nPairs = pRL->size(); iPair < nPairs && !bInList; ++iPair ) { const ScRangePair & rR = (*pRL)[iPair]; const ScRange& rNameRange = rR.GetRange(0); if ( jThisTab && (rNameRange.aStart.Tab() > nThisTab || nThisTab > rNameRange.aEnd.Tab()) ) continue; // for ScCellIterator aIter( rDoc, rNameRange ); for (bool bHas = aIter.first(); bHas && !bInList; bHas = aIter.next()) { // Don't crash if cell (via CompileNameFormula) encounters // a formula cell without code and // HasStringData/Interpret/Compile is executed and all that // recursively... // Furthermore, *this* cell won't be touched, since no RPN exists yet. CellType eType = aIter.getType(); bool bOk = false; if (eType == CELLTYPE_FORMULA) { ScFormulaCell* pFC = aIter.getFormulaCell(); bOk = (pFC->GetCode()->GetCodeLen() > 0) && (pFC->aPos != aPos); } else bOk = true; if (bOk && aIter.hasString()) { OUString aStr = aIter.getString(); if ( ScGlobal::GetTransliteration().isEqual( aStr, aName ) ) { aRef.InitFlags(); if ( !jRow ) aRef.SetColRel( true ); // ColName else aRef.SetRowRel( true ); // RowName aRef.SetAddress(rDoc.GetSheetLimits(), aIter.GetPos(), aPos); bInList = bFound = true; } } } } } } if ( !bInList && rDoc.GetDocOptions().IsLookUpColRowNames() ) { // search in current sheet tools::Long nDistance = 0, nMax = 0; tools::Long nMyCol = static_cast(aPos.Col()); tools::Long nMyRow = static_cast(aPos.Row()); bool bTwo = false; ScAddress aOne( 0, 0, aPos.Tab() ); ScAddress aTwo( rDoc.MaxCol(), rDoc.MaxRow(), aPos.Tab() ); ScAutoNameCache* pNameCache = rDoc.GetAutoNameCache(); if ( pNameCache ) { // use GetNameOccurrences to collect all positions of aName on the sheet // (only once), similar to the outer part of the loop in the "else" branch. const ScAutoNameAddresses& rAddresses = pNameCache->GetNameOccurrences( aName, aPos.Tab() ); // Loop through the found positions, similar to the inner part of the loop in the "else" branch. // The order of addresses in the vector is the same as from ScCellIterator. for ( const ScAddress& aAddress : rAddresses ) { if ( bFound ) { // stop if everything else is further away if ( nMax < static_cast(aAddress.Col()) ) break; // aIter } if ( aAddress != aPos ) { // same treatment as in isEqual case below SCCOL nCol = aAddress.Col(); SCROW nRow = aAddress.Row(); tools::Long nC = nMyCol - nCol; tools::Long nR = nMyRow - nRow; if ( bFound ) { tools::Long nD = nC * nC + nR * nR; if ( nD < nDistance ) { if ( nC < 0 || nR < 0 ) { // right or below bTwo = true; aTwo.Set( nCol, nRow, aAddress.Tab() ); nMax = std::max( nMyCol + std::abs( nC ), nMyRow + std::abs( nR ) ); nDistance = nD; } else if ( nRow >= aOne.Row() || nMyRow < static_cast(aOne.Row()) ) { // upper left, only if not further up than the // current entry and nMyRow is below (CellIter // runs column-wise) bTwo = false; aOne.Set( nCol, nRow, aAddress.Tab() ); nMax = std::max( nMyCol + nC, nMyRow + nR ); nDistance = nD; } } } else { aOne.Set( nCol, nRow, aAddress.Tab() ); nDistance = nC * nC + nR * nR; nMax = std::max( nMyCol + std::abs( nC ), nMyRow + std::abs( nR ) ); } bFound = true; } } } else { ScCellIterator aIter( rDoc, ScRange( aOne, aTwo ) ); for (bool bHas = aIter.first(); bHas; bHas = aIter.next()) { if ( bFound ) { // stop if everything else is further away if ( nMax < static_cast(aIter.GetPos().Col()) ) break; // aIter } CellType eType = aIter.getType(); bool bOk = false; if (eType == CELLTYPE_FORMULA) { ScFormulaCell* pFC = aIter.getFormulaCell(); bOk = (pFC->GetCode()->GetCodeLen() > 0) && (pFC->aPos != aPos); } else bOk = true; if (bOk && aIter.hasString()) { OUString aStr = aIter.getString(); if ( ScGlobal::GetTransliteration().isEqual( aStr, aName ) ) { SCCOL nCol = aIter.GetPos().Col(); SCROW nRow = aIter.GetPos().Row(); tools::Long nC = nMyCol - nCol; tools::Long nR = nMyRow - nRow; if ( bFound ) { tools::Long nD = nC * nC + nR * nR; if ( nD < nDistance ) { if ( nC < 0 || nR < 0 ) { // right or below bTwo = true; aTwo.Set( nCol, nRow, aIter.GetPos().Tab() ); nMax = std::max( nMyCol + std::abs( nC ), nMyRow + std::abs( nR ) ); nDistance = nD; } else if ( nRow >= aOne.Row() || nMyRow < static_cast(aOne.Row()) ) { // upper left, only if not further up than the // current entry and nMyRow is below (CellIter // runs column-wise) bTwo = false; aOne.Set( nCol, nRow, aIter.GetPos().Tab() ); nMax = std::max( nMyCol + nC, nMyRow + nR ); nDistance = nD; } } } else { aOne.Set( nCol, nRow, aIter.GetPos().Tab() ); nDistance = nC * nC + nR * nR; nMax = std::max( nMyCol + std::abs( nC ), nMyRow + std::abs( nR ) ); } bFound = true; } } } } if ( bFound ) { ScAddress aAdr; if ( bTwo ) { if ( nMyCol >= static_cast(aOne.Col()) && nMyRow >= static_cast(aOne.Row()) ) aAdr = aOne; // upper left takes precedence else { if ( nMyCol < static_cast(aOne.Col()) ) { // two to the right if ( nMyRow >= static_cast(aTwo.Row()) ) aAdr = aTwo; // directly right else aAdr = aOne; } else { // two below or below and right, take the nearest tools::Long nC1 = nMyCol - aOne.Col(); tools::Long nR1 = nMyRow - aOne.Row(); tools::Long nC2 = nMyCol - aTwo.Col(); tools::Long nR2 = nMyRow - aTwo.Row(); if ( nC1 * nC1 + nR1 * nR1 <= nC2 * nC2 + nR2 * nR2 ) aAdr = aOne; else aAdr = aTwo; } } } else aAdr = aOne; aRef.InitAddress( aAdr ); // Prioritize on column label; row label only if the next cell // above/below the found label cell is text, or if both are not and // the cell below is empty and the next cell to the right is // numeric. if ((aAdr.Row() < rDoc.MaxRow() && rDoc.HasStringData( aAdr.Col(), aAdr.Row() + 1, aAdr.Tab())) || (aAdr.Row() > 0 && rDoc.HasStringData( aAdr.Col(), aAdr.Row() - 1, aAdr.Tab())) || (aAdr.Row() < rDoc.MaxRow() && rDoc.GetRefCellValue( ScAddress( aAdr.Col(), aAdr.Row() + 1, aAdr.Tab())).isEmpty() && aAdr.Col() < rDoc.MaxCol() && rDoc.GetRefCellValue( ScAddress( aAdr.Col() + 1, aAdr.Row(), aAdr.Tab())).hasNumeric())) aRef.SetRowRel( true ); // RowName else aRef.SetColRel( true ); // ColName aRef.SetAddress(rDoc.GetSheetLimits(), aAdr, aPos); } } if ( bFound ) { maRawToken.SetSingleReference( aRef ); maRawToken.eOp = ocColRowName; return true; } else return false; } bool ScCompiler::ParseBoolean( const OUString& rName ) { OpCodeHashMap::const_iterator iLook( mxSymbols->getHashMap().find( rName ) ); if( iLook != mxSymbols->getHashMap().end() && ((*iLook).second == ocTrue || (*iLook).second == ocFalse) ) { maRawToken.SetOpCode( (*iLook).second ); return true; } else return false; } bool ScCompiler::ParseErrorConstant( const OUString& rName ) { FormulaError nError = GetErrorConstant( rName); if (nError != FormulaError::NONE) { maRawToken.SetErrorConstant( nError); return true; } else return false; } bool ScCompiler::ParseTableRefItem( const OUString& rName ) { bool bItem = false; OpCodeHashMap::const_iterator iLook( mxSymbols->getHashMap().find( rName)); if (iLook != mxSymbols->getHashMap().end()) { // Only called when there actually is a current TableRef, hence // accessing maTableRefs.back() is safe. ScTableRefToken* p = dynamic_cast(maTableRefs.back().mxToken.get()); assert(p); // not a ScTableRefToken can't be switch ((*iLook).second) { case ocTableRefItemAll: bItem = true; p->AddItem( ScTableRefToken::ALL); break; case ocTableRefItemHeaders: bItem = true; p->AddItem( ScTableRefToken::HEADERS); break; case ocTableRefItemData: bItem = true; p->AddItem( ScTableRefToken::DATA); break; case ocTableRefItemTotals: bItem = true; p->AddItem( ScTableRefToken::TOTALS); break; case ocTableRefItemThisRow: bItem = true; p->AddItem( ScTableRefToken::THIS_ROW); break; default: ; } if (bItem) maRawToken.SetOpCode( (*iLook).second ); } return bItem; } namespace { OUString unescapeTableRefColumnSpecifier( const OUString& rStr ) { // '#', '[', ']' and '\'' are escaped with '\'' if (rStr.indexOf( '\'' ) < 0) return rStr; const sal_Int32 n = rStr.getLength(); OUStringBuffer aBuf( n ); const sal_Unicode* p = rStr.getStr(); const sal_Unicode* const pStop = p + n; bool bEscaped = false; for ( ; p < pStop; ++p) { const sal_Unicode c = *p; if (bEscaped) { aBuf.append( c ); bEscaped = false; } else if (c == '\'') bEscaped = true; // unescaped escaping '\'' else aBuf.append( c ); } return aBuf.makeStringAndClear(); } } bool ScCompiler::ParseTableRefColumn( const OUString& rName ) { // Only called when there actually is a current TableRef, hence // accessing maTableRefs.back() is safe. ScTableRefToken* p = dynamic_cast(maTableRefs.back().mxToken.get()); assert(p); // not a ScTableRefToken can't be ScDBData* pDBData = rDoc.GetDBCollection()->getNamedDBs().findByIndex( p->GetIndex()); if (!pDBData) return false; OUString aName( unescapeTableRefColumnSpecifier( rName)); ScRange aRange; pDBData->GetArea( aRange); aRange.aEnd.SetTab( aRange.aStart.Tab()); aRange.aEnd.SetRow( aRange.aStart.Row()); // Prefer the stored internal table column name, which is also needed for // named expressions during document load time when cell content isn't // available yet. Also, avoiding a possible calculation step in case the // header cell is a formula cell is "a good thing". sal_Int32 nOffset = pDBData->GetColumnNameOffset( aName); if (nOffset >= 0) { // This is sneaky... we always use the top row of the database range, // regardless of whether it is a header row or not. Code evaluating // this reference must take that into account and may have to act // differently if it is a header-less table. Which are two places, // HandleTableRef() (no change necessary there) and // CreateStringFromSingleRef() (must not fallback to cell lookup). ScSingleRefData aRef; ScAddress aAdr( aRange.aStart); aAdr.IncCol( nOffset); aRef.InitAddress( aAdr); maRawToken.SetSingleReference( aRef ); return true; } if (pDBData->HasHeader()) { // Quite similar to IsColRowName() but limited to one row of headers. ScCellIterator aIter( rDoc, aRange); for (bool bHas = aIter.first(); bHas; bHas = aIter.next()) { CellType eType = aIter.getType(); bool bOk = false; if (eType == CELLTYPE_FORMULA) { ScFormulaCell* pFC = aIter.getFormulaCell(); bOk = (pFC->GetCode()->GetCodeLen() > 0) && (pFC->aPos != aPos); } else bOk = true; if (bOk && aIter.hasString()) { OUString aStr = aIter.getString(); if (ScGlobal::GetTransliteration().isEqual( aStr, aName)) { // If this is successful and the internal column name // lookup was not, it may be worth a warning. SAL_WARN("sc.core", "ScCompiler::IsTableRefColumn - falling back to cell lookup"); /* XXX NOTE: we could init the column as relative so copying a * formula across columns would point to the relative column, * but do it absolute because: * a) it makes the reference work in named expressions without * having to distinguish * b) Excel does it the same. */ ScSingleRefData aRef; aRef.InitAddress( aIter.GetPos()); maRawToken.SetSingleReference( aRef ); return true; } } } } return false; } void ScCompiler::SetAutoCorrection( bool bVal ) { assert(mbJumpCommandReorder); bAutoCorrect = bVal; mbStopOnError = !bVal; } void ScCompiler::AutoCorrectParsedSymbol() { sal_Int32 nPos = aCorrectedSymbol.getLength(); if ( !nPos ) return; nPos--; const sal_Unicode cQuote = '\"'; const sal_Unicode cx = 'x'; const sal_Unicode cX = 'X'; sal_Unicode c1 = aCorrectedSymbol[0]; sal_Unicode c2 = aCorrectedSymbol[nPos]; sal_Unicode c2p = nPos > 0 ? aCorrectedSymbol[nPos-1] : 0; if ( c1 == cQuote && c2 != cQuote ) { // "... // What's not a word doesn't belong to it. // Don't be pedantic: c < 128 should be sufficient here. while ( nPos && ((aCorrectedSymbol[nPos] < 128) && ((GetCharTableFlags(aCorrectedSymbol[nPos], aCorrectedSymbol[nPos-1]) & (ScCharFlags::Word | ScCharFlags::CharDontCare)) == ScCharFlags::NONE)) ) nPos--; if ( nPos == MAXSTRLEN - 1 ) aCorrectedSymbol = aCorrectedSymbol.replaceAt( nPos, 1, rtl::OUStringChar(cQuote) ); // '"' the MAXSTRLENth character else aCorrectedSymbol = aCorrectedSymbol.replaceAt( nPos + 1, 0, rtl::OUStringChar(cQuote) ); bCorrected = true; } else if ( c1 != cQuote && c2 == cQuote ) { // ..." aCorrectedSymbol = OUStringChar(cQuote) + aCorrectedSymbol; bCorrected = true; } else if ( nPos == 0 && (c1 == cx || c1 == cX) ) { // x => * aCorrectedSymbol = mxSymbols->getSymbol(ocMul); bCorrected = true; } else if ( (GetCharTableFlags( c1, 0 ) & ScCharFlags::CharValue) && (GetCharTableFlags( c2, c2p ) & ScCharFlags::CharValue) ) { if ( aCorrectedSymbol.indexOf(cx) >= 0 ) // At least two tokens separated by cx { // x => * sal_Unicode c = mxSymbols->getSymbolChar(ocMul); aCorrectedSymbol = aCorrectedSymbol.replaceAll(OUStringChar(cx), OUStringChar(c)); bCorrected = true; } if ( aCorrectedSymbol.indexOf(cX) >= 0 ) // At least two tokens separated by cX { // X => * sal_Unicode c = mxSymbols->getSymbolChar(ocMul); aCorrectedSymbol = aCorrectedSymbol.replaceAll(OUStringChar(cX), OUStringChar(c)); bCorrected = true; } } else { OUString aSymbol( aCorrectedSymbol ); OUString aDoc; if ( aSymbol[0] == '\'' ) { sal_Int32 nPosition = aSymbol.indexOf( "'#" ); if (nPosition != -1) { // Split off 'Doc'#, may be d:\... or whatever aDoc = aSymbol.copy(0, nPosition + 2); aSymbol = aSymbol.copy(nPosition + 2); } } sal_Int32 nRefs = comphelper::string::getTokenCount(aSymbol, ':'); bool bColons; if ( nRefs > 2 ) { // duplicated or too many ':'? B:2::C10 => B2:C10 bColons = true; sal_Int32 nIndex = 0; OUString aTmp1( aSymbol.getToken( 0, ':', nIndex ) ); sal_Int32 nLen1 = aTmp1.getLength(); OUStringBuffer aSym; OUString aTmp2; bool bLastAlp = true; sal_Int32 nStrip = 0; sal_Int32 nCount = nRefs; for ( sal_Int32 j=1; j A4 etc. OUString aTab[2], aRef[2]; const ScAddress::Details aDetails( pConv->meConv, aPos ); if ( nRefs == 2 ) { sal_Int32 nIdx{ 0 }; aRef[0] = aSymbol.getToken( 0, ':', nIdx ); aRef[1] = aSymbol.getToken( 0, ':', nIdx ); } else aRef[0] = aSymbol; bool bChanged = false; bool bOk = true; ScRefFlags nMask = ScRefFlags::VALID | ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID; for ( int j=0; juppercase(rOrg); return false; } } bool ScCompiler::NextNewToken( bool bInArray ) { if (!maPendingOpCodes.empty()) { maRawToken.SetOpCode(maPendingOpCodes.front()); maPendingOpCodes.pop(); return true; } bool bAllowBooleans = bInArray; const std::vector & vSpaces = NextSymbol(bInArray); if (!cSymbol[0]) { if (nSrcPos < aFormula.getLength()) { // Nothing could be parsed, remainder as bad string. // NextSymbol() must had set an error for this. assert( pArr->GetCodeError() != FormulaError::NONE); const OUString aBad( aFormula.copy( nSrcPos)); svl::SharedString aSS = rDoc.GetSharedStringPool().intern( aBad); maRawToken.SetString( aSS.getData(), aSS.getDataIgnoreCase()); maRawToken.NewOpCode( ocBad); nSrcPos = aFormula.getLength(); // Add bad string as last token. return true; } return false; } if (!vSpaces.empty()) { ScRawToken aToken; for (const auto& rSpace : vSpaces) { if (rSpace.cChar == 0x20) { // For now keep this a FormulaByteToken for the nasty // significant whitespace intersection. This probably can be // changed to a FormulaSpaceToken but then other places may // need to be adapted. aToken.SetOpCode( ocSpaces ); aToken.sbyte.cByte = static_cast( std::min(rSpace.nCount, 255) ); } else { aToken.SetOpCode( ocWhitespace ); aToken.whitespace.nCount = static_cast( std::min(rSpace.nCount, 255) ); aToken.whitespace.cChar = rSpace.cChar; } if (!static_cast(pArr)->AddRawToken( aToken )) { SetError(FormulaError::CodeOverflow); return false; } } } // Short cut for references when reading ODF to speedup things. if (mnPredetectedReference) { OUString aStr( cSymbol); bool bInvalidExternalNameRange; if (!ParsePredetectedReference( aStr) && !ParseExternalNamedRange( aStr, bInvalidExternalNameRange )) { svl::SharedString aSS = rDoc.GetSharedStringPool().intern(aStr); maRawToken.SetString(aSS.getData(), aSS.getDataIgnoreCase()); maRawToken.NewOpCode( ocBad ); } return true; } if ( (cSymbol[0] == '#' || cSymbol[0] == '$') && cSymbol[1] == 0 && !bAutoCorrect ) { // special case to speed up broken [$]#REF documents /* FIXME: ISERROR(#REF!) would be valid and true and the formula to * be processed as usual. That would need some special treatment, * also in NextSymbol() because of possible combinations of * #REF!.#REF!#REF! parts. In case of reading ODF that is all * handled by IsPredetectedReference(), this case here remains for * manual/API input. */ OUString aBad( aFormula.copy( nSrcPos-1 ) ); const FormulaToken* pBadToken = pArr->AddBad(aBad); eLastOp = pBadToken ? pBadToken->GetOpCode() : ocNone; return false; } if( ParseString() ) return true; bool bMayBeFuncName; bool bAsciiNonAlnum; // operators, separators, ... if ( cSymbol[0] < 128 ) { bMayBeFuncName = rtl::isAsciiAlpha( cSymbol[0] ); if (!bMayBeFuncName && (cSymbol[0] == '_' && cSymbol[1] == '_') && !utl::ConfigManager::IsFuzzing()) { bMayBeFuncName = officecfg::Office::Common::Misc::ExperimentalMode::get(); } bAsciiNonAlnum = !bMayBeFuncName && !rtl::isAsciiDigit( cSymbol[0] ); } else { OUString aTmpStr( cSymbol[0] ); bMayBeFuncName = pCharClass->isLetter( aTmpStr, 0 ); bAsciiNonAlnum = false; } // Within a TableRef anything except an unescaped '[' or ']' is an item // or a column specifier, do not attempt to recognize any other single // operator there so even [,] or [+] for a single character column // specifier works. Note that space between two ocTableRefOpen is not // supported (Table[ [ColumnSpec]]), not only here. Note also that Table[] // without any item or column specifier is valid. if (bAsciiNonAlnum && cSymbol[1] == 0 && (eLastOp != ocTableRefOpen || cSymbol[0] == '[' || cSymbol[0] == ']')) { // Shortcut for operators and separators that need no further checks or upper. if (ParseOpCode( OUString( cSymbol), bInArray )) return true; } if ( bMayBeFuncName ) { // a function name must be followed by a parenthesis const sal_Unicode* p = aFormula.getStr() + nSrcPos; while( *p == ' ' ) p++; bMayBeFuncName = ( *p == '(' ); } // Italian ARCTAN.2 resulted in #REF! => ParseOpcode() before // ParseReference(). OUString aUpper; bool bAsciiUpper = false; Label_Rewind: do { const OUString aOrg( cSymbol ); // Check for TableRef column specifier first, it may be anything. if (cSymbol[0] != '#' && !maTableRefs.empty() && maTableRefs.back().mnLevel) { if (ParseTableRefColumn( aOrg )) return true; // Do not attempt to resolve as any other name. aUpper = aOrg; // for ocBad break; // do; create ocBad token or set error. } mbRewind = false; aUpper.clear(); bAsciiUpper = false; if (bAsciiNonAlnum) { bAsciiUpper = ToUpperAsciiOrI18nIsAscii( aUpper, aOrg); if (cSymbol[0] == '#') { // Check for TableRef item specifiers first. if (!maTableRefs.empty() && maTableRefs.back().mnLevel == 2) { if (ParseTableRefItem( aUpper )) return true; } // This can be either an error constant ... if (ParseErrorConstant( aUpper)) return true; // ... or some invalidated reference starting with #REF! // which is handled after the do loop. break; // do; create ocBad token or set error. } if (ParseOpCode( aUpper, bInArray )) return true; } if (bMayBeFuncName) { if (aUpper.isEmpty()) bAsciiUpper = ToUpperAsciiOrI18nIsAscii( aUpper, aOrg); if (ParseOpCode( aUpper, bInArray )) return true; } // Column 'DM' ("Deutsche Mark", German currency) couldn't be // referred => ParseReference() before ParseValue(). // Preserve case of file names in external references. if (ParseReference( aOrg )) { if (mbRewind) // Range operator, but no direct reference. continue; // do; up to range operator. // If a syntactically correct reference was recognized but invalid // e.g. because of non-existing sheet name => entire reference // ocBad to preserve input instead of #REF!.A1 if (!maRawToken.IsValidReference(rDoc)) { aUpper = aOrg; // ensure for ocBad break; // do; create ocBad token or set error. } return true; } if (aUpper.isEmpty()) bAsciiUpper = ToUpperAsciiOrI18nIsAscii( aUpper, aOrg); // ParseBoolean() before ParseValue() to catch inline bools without the kludge // for inline arrays. if (bAllowBooleans && ParseBoolean( aUpper )) return true; if (ParseValue( aUpper )) return true; // User defined names and such do need i18n upper also in ODF. if (bAsciiUpper || mbCharClassesDiffer) { // Use current system locale here because user defined symbols are // more likely in that localized language than in the formula // language. This in corner cases needs to continue to work for // existing documents and environments. // Do not change bAsciiUpper from here on for the lowercase() call // below in the ocBad case to use the correct CharClass. aUpper = ScGlobal::getCharClass().uppercase( aOrg ); } if (ParseNamedRange( aUpper )) return true; // Compiling a named expression during collecting them in import shall // not match arbitrary names that otherwise if all named expressions // were present would be recognized as named expression. Such name will // flag an error below and will be recompiled in a second step later // with ScRangeData::CompileUnresolvedXML() if (meExtendedErrorDetection == EXTENDED_ERROR_DETECTION_NAME_NO_BREAK && rDoc.IsImportingXML()) break; // while // Preserve case of file names in external references. bool bInvalidExternalNameRange; if (ParseExternalNamedRange( aOrg, bInvalidExternalNameRange )) return true; // Preserve case of file names in external references even when range // is not valid and previous check failed tdf#89330 if (bInvalidExternalNameRange) { // add ocBad but do not lowercase svl::SharedString aSS = rDoc.GetSharedStringPool().intern(aOrg); maRawToken.SetString(aSS.getData(), aSS.getDataIgnoreCase()); maRawToken.NewOpCode( ocBad ); return true; } if (ParseDBRange( aUpper )) return true; // If followed by '(' (with or without space inbetween) it can not be a // column/row label. Prevent arbitrary content detection. if (!bMayBeFuncName && ParseColRowName( aUpper )) return true; if (bMayBeFuncName && ParseMacro( aUpper )) return true; if (bMayBeFuncName && ParseOpCode2( aUpper )) return true; } while (mbRewind); // Last chance: it could be a broken invalidated reference that contains // #REF! (but is not equal to), which we also wrote to ODFF between 2013 // and 2016 until 5.1.4 OUString aErrRef( mxSymbols->getSymbol( ocErrRef)); if (aUpper.indexOf( aErrRef) >= 0 && ParseReference( aUpper, &aErrRef)) { if (mbRewind) goto Label_Rewind; return true; } if ( meExtendedErrorDetection != EXTENDED_ERROR_DETECTION_NONE ) { // set an error SetError( FormulaError::NoName ); if (meExtendedErrorDetection == EXTENDED_ERROR_DETECTION_NAME_BREAK) return false; // end compilation } // Provide single token information and continue. Do not set an error, that // would prematurely end compilation. Simple unknown names are handled by // the interpreter. // Use the same CharClass that was used for uppercase. aUpper = ((bAsciiUpper || mbCharClassesDiffer) ? ScGlobal::getCharClass() : *pCharClass).lowercase( aUpper ); svl::SharedString aSS = rDoc.GetSharedStringPool().intern(aUpper); maRawToken.SetString(aSS.getData(), aSS.getDataIgnoreCase()); maRawToken.NewOpCode( ocBad ); if ( bAutoCorrect ) AutoCorrectParsedSymbol(); return true; } void ScCompiler::CreateStringFromXMLTokenArray( OUString& rFormula, OUString& rFormulaNmsp ) { bool bExternal = GetGrammar() == FormulaGrammar::GRAM_EXTERNAL; sal_uInt16 nExpectedCount = bExternal ? 2 : 1; OSL_ENSURE( pArr->GetLen() == nExpectedCount, "ScCompiler::CreateStringFromXMLTokenArray - wrong number of tokens" ); if( pArr->GetLen() == nExpectedCount ) { FormulaToken** ppTokens = pArr->GetArray(); // string tokens expected, GetString() will assert if token type is wrong rFormula = ppTokens[0]->GetString().getString(); if( bExternal ) rFormulaNmsp = ppTokens[1]->GetString().getString(); } } namespace { class ExternalFileInserter { ScAddress maPos; ScExternalRefManager& mrRefMgr; public: ExternalFileInserter(const ScAddress& rPos, ScExternalRefManager& rRefMgr) : maPos(rPos), mrRefMgr(rRefMgr) {} void operator() (sal_uInt16 nFileId) const { mrRefMgr.insertRefCell(nFileId, maPos); } }; } std::unique_ptr ScCompiler::CompileString( const OUString& rFormula ) { OSL_ENSURE( meGrammar != FormulaGrammar::GRAM_EXTERNAL, "ScCompiler::CompileString - unexpected grammar GRAM_EXTERNAL" ); if( meGrammar == FormulaGrammar::GRAM_EXTERNAL ) SetGrammar( FormulaGrammar::GRAM_PODF ); ScTokenArray aArr(rDoc); pArr = &aArr; maArrIterator = FormulaTokenArrayPlainIterator(*pArr); aFormula = comphelper::string::strip(rFormula, ' '); nSrcPos = 0; bCorrected = false; if ( bAutoCorrect ) { aCorrectedFormula.clear(); aCorrectedSymbol.clear(); } sal_uInt8 nForced = 0; // ==formula forces recalc even if cell is not visible if( nSrcPos < aFormula.getLength() && aFormula[nSrcPos] == '=' ) { nSrcPos++; nForced++; if ( bAutoCorrect ) aCorrectedFormula += "="; } if( nSrcPos < aFormula.getLength() && aFormula[nSrcPos] == '=' ) { nSrcPos++; nForced++; if ( bAutoCorrect ) aCorrectedFormula += "="; } struct FunctionStack { OpCode eOp; short nSep; }; // FunctionStack only used if PODF or OOXML! bool bPODF = FormulaGrammar::isPODF( meGrammar); bool bOOXML = FormulaGrammar::isOOXML( meGrammar); bool bUseFunctionStack = (bPODF || bOOXML); const size_t nAlloc = 512; FunctionStack aFuncs[ nAlloc ]; FunctionStack* pFunctionStack = (bUseFunctionStack && o3tl::make_unsigned(rFormula.getLength()) > nAlloc ? new FunctionStack[rFormula.getLength()] : &aFuncs[0]); pFunctionStack[0].eOp = ocNone; pFunctionStack[0].nSep = 0; size_t nFunction = 0; short nBrackets = 0; bool bInArray = false; eLastOp = ocOpen; while( NextNewToken( bInArray ) ) { const OpCode eOp = maRawToken.GetOpCode(); if (eOp == ocSkip) continue; switch (eOp) { case ocOpen: { ++nBrackets; if (bUseFunctionStack) { ++nFunction; pFunctionStack[ nFunction ].eOp = eLastOp; pFunctionStack[ nFunction ].nSep = 0; } } break; case ocClose: { if( !nBrackets ) { SetError( FormulaError::PairExpected ); if ( bAutoCorrect ) { bCorrected = true; aCorrectedSymbol.clear(); } } else nBrackets--; if (bUseFunctionStack && nFunction) --nFunction; } break; case ocSep: { if (bUseFunctionStack) ++pFunctionStack[ nFunction ].nSep; } break; case ocArrayOpen: { if( bInArray ) SetError( FormulaError::NestedArray ); else bInArray = true; // Don't count following column separator as parameter separator. if (bUseFunctionStack) { ++nFunction; pFunctionStack[ nFunction ].eOp = eOp; pFunctionStack[ nFunction ].nSep = 0; } } break; case ocArrayClose: { if( bInArray ) { bInArray = false; } else { SetError( FormulaError::PairExpected ); if ( bAutoCorrect ) { bCorrected = true; aCorrectedSymbol.clear(); } } if (bUseFunctionStack && nFunction) --nFunction; } break; case ocTableRefOpen: { // Don't count following item separator as parameter separator. if (bUseFunctionStack) { ++nFunction; pFunctionStack[ nFunction ].eOp = eOp; pFunctionStack[ nFunction ].nSep = 0; } } break; case ocTableRefClose: { if (bUseFunctionStack && nFunction) --nFunction; } break; case ocColRowName: case ocColRowNameAuto: // The current implementation of column / row labels doesn't // function correctly in grouped cells. aArr.SetShareable(false); break; default: break; } if ((eLastOp != ocOpen || eOp != ocClose) && (eLastOp == ocOpen || eLastOp == ocSep || eLastOp == ocArrayRowSep || eLastOp == ocArrayColSep || eLastOp == ocArrayOpen) && (eOp == ocSep || eOp == ocClose || eOp == ocArrayRowSep || eOp == ocArrayColSep || eOp == ocArrayClose)) { // TODO: should we check for known functions with optional empty // args so the correction dialog can do better? if ( !static_cast(pArr)->Add( new FormulaMissingToken ) ) { SetError(FormulaError::CodeOverflow); break; } } if (bOOXML) { // Append a parameter for WEEKNUM, all 1.0 // Function is already closed, parameter count is nSep+1 size_t nFunc = nFunction + 1; if (eOp == ocClose && (pFunctionStack[ nFunc ].eOp == ocWeek && // 2nd week start pFunctionStack[ nFunc ].nSep == 0)) { if ( !static_cast(pArr)->Add( new FormulaToken( svSep, ocSep)) || !static_cast(pArr)->Add( new FormulaDoubleToken( 1.0))) { SetError(FormulaError::CodeOverflow); break; } } } else if (bPODF) { /* TODO: for now this is the only PODF adapter. If there were more, * factor this out. */ // Insert ADDRESS() new empty parameter 4 if there is a 4th, now to be 5th. if (eOp == ocSep && pFunctionStack[ nFunction ].eOp == ocAddress && pFunctionStack[ nFunction ].nSep == 3) { if ( !static_cast(pArr)->Add( new FormulaToken( svSep, ocSep)) || !static_cast(pArr)->Add( new FormulaDoubleToken( 1.0))) { SetError(FormulaError::CodeOverflow); break; } ++pFunctionStack[ nFunction ].nSep; } } FormulaToken* pNewToken = static_cast(pArr)->Add( maRawToken.CreateToken(rDoc.GetSheetLimits())); if (!pNewToken && eOp == ocArrayClose && pArr->OpCodeBefore( pArr->GetLen()) == ocArrayClose) { // Nested inline array or non-value/non-string in array. The // original tokens are still in the ScTokenArray and not merged // into an ScMatrixToken. Set error but keep on tokenizing. SetError( FormulaError::BadArrayContent); } else if (!pNewToken) { SetError(FormulaError::CodeOverflow); break; } else if (eLastOp == ocRange && pNewToken->GetOpCode() == ocPush && pNewToken->GetType() == svSingleRef) { static_cast(pArr)->MergeRangeReference( aPos); } else if (eLastOp == ocDBArea && pNewToken->GetOpCode() == ocTableRefOpen) { sal_uInt16 nIdx = pArr->GetLen() - 1; const FormulaToken* pPrev = pArr->PeekPrev( nIdx); if (pPrev && pPrev->GetOpCode() == ocDBArea) { FormulaToken* pTableRefToken = new ScTableRefToken( pPrev->GetIndex(), ScTableRefToken::TABLE); maTableRefs.emplace_back( pTableRefToken); // pPrev may be dead hereafter. static_cast(pArr)->ReplaceToken( nIdx, pTableRefToken, FormulaTokenArray::ReplaceMode::CODE_ONLY); } } switch (eOp) { case ocTableRefOpen: SAL_WARN_IF( maTableRefs.empty(), "sc.core", "ocTableRefOpen without TableRefEntry"); if (maTableRefs.empty()) SetError(FormulaError::Pair); else ++maTableRefs.back().mnLevel; break; case ocTableRefClose: SAL_WARN_IF( maTableRefs.empty(), "sc.core", "ocTableRefClose without TableRefEntry"); if (maTableRefs.empty()) SetError(FormulaError::Pair); else { if (--maTableRefs.back().mnLevel == 0) maTableRefs.pop_back(); } break; default: break; } eLastOp = maRawToken.GetOpCode(); if ( bAutoCorrect ) aCorrectedFormula += aCorrectedSymbol; } if ( mbCloseBrackets ) { if( bInArray ) { FormulaByteToken aToken( ocArrayClose ); if( !pArr->AddToken( aToken ) ) { SetError(FormulaError::CodeOverflow); } else if ( bAutoCorrect ) aCorrectedFormula += mxSymbols->getSymbol(ocArrayClose); } if (nBrackets) { FormulaToken aToken( svSep, ocClose ); while( nBrackets-- ) { if( !pArr->AddToken( aToken ) ) { SetError(FormulaError::CodeOverflow); break; // while } if ( bAutoCorrect ) aCorrectedFormula += mxSymbols->getSymbol(ocClose); } } } if ( nForced >= 2 ) pArr->SetRecalcModeForced(); if (pFunctionStack != &aFuncs[0]) delete [] pFunctionStack; // remember pArr, in case a subsequent CompileTokenArray() is executed. std::unique_ptr pNew(new ScTokenArray( aArr )); pNew->GenHash(); // coverity[escape : FALSE] - ownership of pNew is retained by caller, so pArr remains valid pArr = pNew.get(); maArrIterator = FormulaTokenArrayPlainIterator(*pArr); if (!maExternalFiles.empty()) { // Remove duplicates, and register all external files found in this cell. std::sort(maExternalFiles.begin(), maExternalFiles.end()); std::vector::iterator itEnd = std::unique(maExternalFiles.begin(), maExternalFiles.end()); std::for_each(maExternalFiles.begin(), itEnd, ExternalFileInserter(aPos, *rDoc.GetExternalRefManager())); maExternalFiles.erase(itEnd, maExternalFiles.end()); } return pNew; } std::unique_ptr ScCompiler::CompileString( const OUString& rFormula, const OUString& rFormulaNmsp ) { OSL_ENSURE( (GetGrammar() == FormulaGrammar::GRAM_EXTERNAL) || rFormulaNmsp.isEmpty(), "ScCompiler::CompileString - unexpected formula namespace for internal grammar" ); if( GetGrammar() == FormulaGrammar::GRAM_EXTERNAL ) try { ScFormulaParserPool& rParserPool = rDoc.GetFormulaParserPool(); uno::Reference< sheet::XFormulaParser > xParser( rParserPool.getFormulaParser( rFormulaNmsp ), uno::UNO_SET_THROW ); table::CellAddress aReferencePos; ScUnoConversion::FillApiAddress( aReferencePos, aPos ); uno::Sequence< sheet::FormulaToken > aTokenSeq = xParser->parseFormula( rFormula, aReferencePos ); ScTokenArray aTokenArray(rDoc); if( ScTokenConversion::ConvertToTokenArray( rDoc, aTokenArray, aTokenSeq ) ) { // remember pArr, in case a subsequent CompileTokenArray() is executed. std::unique_ptr pNew(new ScTokenArray( aTokenArray )); // coverity[escape : FALSE] - ownership of pNew is retained by caller, so pArr remains valid pArr = pNew.get(); maArrIterator = FormulaTokenArrayPlainIterator(*pArr); return pNew; } } catch( uno::Exception& ) { } // no success - fallback to some internal grammar and hope the best return CompileString( rFormula ); } ScRangeData* ScCompiler::GetRangeData( const FormulaToken& rToken ) const { return rDoc.FindRangeNameBySheetAndIndex( rToken.GetSheet(), rToken.GetIndex()); } bool ScCompiler::HandleRange() { ScTokenArray* pNew; const ScRangeData* pRangeData = GetRangeData( *mpToken); if (pRangeData) { FormulaError nErr = pRangeData->GetErrCode(); if( nErr != FormulaError::NONE ) SetError( nErr ); else if (mbJumpCommandReorder) { // put named formula into parentheses. // But only if there aren't any yet, parenthetical // ocSep doesn't work, e.g. SUM((...;...)) // or if not directly between ocSep/parenthesis, // e.g. SUM(...;(...;...)) no, SUM(...;(...)*3) yes, // in short: if it isn't a self-contained expression. FormulaToken* p1 = maArrIterator.PeekPrevNoSpaces(); FormulaToken* p2 = maArrIterator.PeekNextNoSpaces(); OpCode eOp1 = (p1 ? p1->GetOpCode() : ocSep); OpCode eOp2 = (p2 ? p2->GetOpCode() : ocSep); bool bBorder1 = (eOp1 == ocSep || eOp1 == ocOpen); bool bBorder2 = (eOp2 == ocSep || eOp2 == ocClose); bool bAddPair = !(bBorder1 && bBorder2); if ( bAddPair ) { pNew = new ScTokenArray(rDoc); pNew->AddOpCode( ocClose ); PushTokenArray( pNew, true ); } pNew = pRangeData->GetCode()->Clone().release(); pNew->SetFromRangeName( true ); PushTokenArray( pNew, true ); if( pRangeData->HasReferences() ) { // Relative sheet references in sheet-local named expressions // shall still point to the same sheet as if used on the // original sheet, not shifted to the current position where // they are used. SCTAB nSheetTab = mpToken->GetSheet(); if (nSheetTab >= 0 && nSheetTab != aPos.Tab()) AdjustSheetLocalNameRelReferences( nSheetTab - aPos.Tab()); SetRelNameReference(); MoveRelWrap(); } maArrIterator.Reset(); if ( bAddPair ) { pNew = new ScTokenArray(rDoc); pNew->AddOpCode( ocOpen ); PushTokenArray( pNew, true ); } return GetToken(); } } else { // No ScRangeData for an already compiled token can happen in BIFF .xls // import if the original range is not present in the document. pNew = new ScTokenArray(rDoc); pNew->Add( new FormulaErrorToken( FormulaError::NoName)); PushTokenArray( pNew, true ); return GetToken(); } return true; } bool ScCompiler::HandleExternalReference(const FormulaToken& _aToken) { // Handle external range names. switch (_aToken.GetType()) { case svExternalSingleRef: case svExternalDoubleRef: break; case svExternalName: { ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager(); const OUString* pFile = pRefMgr->getExternalFileName(_aToken.GetIndex()); if (!pFile) { SetError(FormulaError::NoName); return true; } OUString aName = _aToken.GetString().getString(); ScExternalRefCache::TokenArrayRef xNew = pRefMgr->getRangeNameTokens( _aToken.GetIndex(), aName, &aPos); if (!xNew) { SetError(FormulaError::NoName); return true; } ScTokenArray* pNew = xNew->Clone().release(); PushTokenArray( pNew, true); if (FormulaTokenArrayPlainIterator(*pNew).GetNextReference() != nullptr) { SetRelNameReference(); MoveRelWrap(); } maArrIterator.Reset(); return GetToken(); } default: OSL_FAIL("Wrong type for external reference!"); return false; } return true; } void ScCompiler::AdjustSheetLocalNameRelReferences( SCTAB nDelta ) { for ( auto t: pArr->References() ) { ScSingleRefData& rRef1 = *t->GetSingleRef(); if (rRef1.IsTabRel()) rRef1.IncTab( nDelta); if ( t->GetType() == svDoubleRef ) { ScSingleRefData& rRef2 = t->GetDoubleRef()->Ref2; if (rRef2.IsTabRel()) rRef2.IncTab( nDelta); } } } // reference of named range with relative references void ScCompiler::SetRelNameReference() { for ( auto t: pArr->References() ) { ScSingleRefData& rRef1 = *t->GetSingleRef(); if ( rRef1.IsColRel() || rRef1.IsRowRel() || rRef1.IsTabRel() ) rRef1.SetRelName( true ); if ( t->GetType() == svDoubleRef ) { ScSingleRefData& rRef2 = t->GetDoubleRef()->Ref2; if ( rRef2.IsColRel() || rRef2.IsRowRel() || rRef2.IsTabRel() ) rRef2.SetRelName( true ); } } } // Wrap-adjust relative references of a RangeName to current position, // don't call for other token arrays! void ScCompiler::MoveRelWrap() { for ( auto t: pArr->References() ) { if ( t->GetType() == svSingleRef || t->GetType() == svExternalSingleRef ) ScRefUpdate::MoveRelWrap( rDoc, aPos, rDoc.MaxCol(), rDoc.MaxRow(), SingleDoubleRefModifier( *t->GetSingleRef() ).Ref() ); else ScRefUpdate::MoveRelWrap( rDoc, aPos, rDoc.MaxCol(), rDoc.MaxRow(), *t->GetDoubleRef() ); } } // Wrap-adjust relative references of a RangeName to current position, // don't call for other token arrays! void ScCompiler::MoveRelWrap( const ScTokenArray& rArr, const ScDocument& rDoc, const ScAddress& rPos, SCCOL nMaxCol, SCROW nMaxRow ) { for ( auto t: rArr.References() ) { if ( t->GetType() == svSingleRef || t->GetType() == svExternalSingleRef ) ScRefUpdate::MoveRelWrap( rDoc, rPos, nMaxCol, nMaxRow, SingleDoubleRefModifier( *t->GetSingleRef() ).Ref() ); else ScRefUpdate::MoveRelWrap( rDoc, rPos, nMaxCol, nMaxRow, *t->GetDoubleRef() ); } } bool ScCompiler::IsCharFlagAllConventions( OUString const & rStr, sal_Int32 nPos, ScCharFlags nFlags ) { sal_Unicode c = rStr[ nPos ]; sal_Unicode cLast = nPos > 0 ? rStr[ nPos-1 ] : 0; if (c < 128) { for ( int nConv = formula::FormulaGrammar::CONV_UNSPECIFIED; ++nConv < formula::FormulaGrammar::CONV_LAST; ) { if (pConventions[nConv] && ((pConventions[nConv]->getCharTableFlags(c, cLast) & nFlags) != nFlags)) return false; // convention not known => assume valid } return true; } else return ScGlobal::getCharClass().isLetterNumeric( rStr, nPos ); } void ScCompiler::CreateStringFromExternal( OUStringBuffer& rBuffer, const FormulaToken* pTokenP ) const { const FormulaToken* t = pTokenP; sal_uInt16 nFileId = t->GetIndex(); ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager(); sal_uInt16 nUsedFileId = pRefMgr->convertFileIdToUsedFileId(nFileId); const OUString* pFileName = pRefMgr->getExternalFileName(nFileId); if (!pFileName) return; switch (t->GetType()) { case svExternalName: rBuffer.append(pConv->makeExternalNameStr( nFileId, *pFileName, t->GetString().getString())); break; case svExternalSingleRef: pConv->makeExternalRefStr(rDoc.GetSheetLimits(), rBuffer, GetPos(), nUsedFileId, *pFileName, t->GetString().getString(), *t->GetSingleRef()); break; case svExternalDoubleRef: { vector aTabNames; pRefMgr->getAllCachedTableNames(nFileId, aTabNames); // No sheet names is a valid case if external sheets were not // cached in this document and external document is not reachable, // else not and worth to be investigated. SAL_WARN_IF( aTabNames.empty(), "sc.core", "wrecked cache of external document? '" << *pFileName << "' '" << t->GetString().getString() << "'"); pConv->makeExternalRefStr( rDoc.GetSheetLimits(), rBuffer, GetPos(), nUsedFileId, *pFileName, aTabNames, t->GetString().getString(), *t->GetDoubleRef()); } break; default: // warning, not error, otherwise we may end up with a never // ending message box loop if this was the cursor cell to be redrawn. OSL_FAIL("ScCompiler::CreateStringFromToken: unknown type of ocExternalRef"); } } void ScCompiler::CreateStringFromMatrix( OUStringBuffer& rBuffer, const FormulaToken* pTokenP ) const { const ScMatrix* pMatrix = pTokenP->GetMatrix(); SCSIZE nC, nMaxC, nR, nMaxR; pMatrix->GetDimensions( nMaxC, nMaxR); rBuffer.append( mxSymbols->getSymbol(ocArrayOpen) ); for( nR = 0 ; nR < nMaxR ; nR++) { if( nR > 0) { rBuffer.append( mxSymbols->getSymbol(ocArrayRowSep) ); } for( nC = 0 ; nC < nMaxC ; nC++) { if( nC > 0) { rBuffer.append( mxSymbols->getSymbol(ocArrayColSep) ); } if( pMatrix->IsValue( nC, nR ) ) { if (pMatrix->IsBoolean(nC, nR)) AppendBoolean(rBuffer, pMatrix->GetDouble(nC, nR) != 0.0); else { FormulaError nErr = pMatrix->GetError(nC, nR); if (nErr != FormulaError::NONE) rBuffer.append(ScGlobal::GetErrorString(nErr)); else AppendDouble(rBuffer, pMatrix->GetDouble(nC, nR)); } } else if( pMatrix->IsEmpty( nC, nR ) ) ; else if( pMatrix->IsStringOrEmpty( nC, nR ) ) AppendString( rBuffer, pMatrix->GetString(nC, nR).getString() ); } } rBuffer.append( mxSymbols->getSymbol(ocArrayClose) ); } namespace { void escapeTableRefColumnSpecifier( OUString& rStr ) { const sal_Int32 n = rStr.getLength(); OUStringBuffer aBuf( n * 2 ); const sal_Unicode* p = rStr.getStr(); const sal_Unicode* const pStop = p + n; for ( ; p < pStop; ++p) { const sal_Unicode c = *p; switch (c) { case '\'': case '[': case '#': case ']': aBuf.append( '\'' ); break; default: ; // nothing } aBuf.append( c ); } rStr = aBuf.makeStringAndClear(); } } void ScCompiler::CreateStringFromSingleRef( OUStringBuffer& rBuffer, const FormulaToken* _pTokenP ) const { const FormulaToken* p; OUString aErrRef = GetCurrentOpCodeMap()->getSymbol(ocErrRef); const OpCode eOp = _pTokenP->GetOpCode(); const ScSingleRefData& rRef = *_pTokenP->GetSingleRef(); ScComplexRefData aRef; aRef.Ref1 = aRef.Ref2 = rRef; if ( eOp == ocColRowName ) { ScAddress aAbs = rRef.toAbs(rDoc, aPos); if (rDoc.HasStringData(aAbs.Col(), aAbs.Row(), aAbs.Tab())) { OUString aStr = rDoc.GetString(aAbs, mpInterpreterContext); EnQuote( aStr ); rBuffer.append(aStr); } else { rBuffer.append(ScCompiler::GetNativeSymbol(ocErrName)); pConv->makeRefStr(rDoc.GetSheetLimits(), rBuffer, meGrammar, aPos, aErrRef, GetSetupTabNames(), aRef, true, (pArr && pArr->IsFromRangeName())); } } else if (pArr && (p = maArrIterator.PeekPrevNoSpaces()) && p->GetOpCode() == ocTableRefOpen) { OUString aStr; ScAddress aAbs = rRef.toAbs(rDoc, aPos); const ScDBData* pData = rDoc.GetDBAtCursor( aAbs.Col(), aAbs.Row(), aAbs.Tab(), ScDBDataPortion::AREA); SAL_WARN_IF( !pData, "sc.core", "ScCompiler::CreateStringFromSingleRef - TableRef without ScDBData: " << aAbs.Format( ScRefFlags::VALID | ScRefFlags::TAB_3D, &rDoc)); if (pData) aStr = pData->GetTableColumnName( aAbs.Col()); if (aStr.isEmpty()) { if (pData && pData->HasHeader()) { SAL_WARN("sc.core", "ScCompiler::CreateStringFromSingleRef - TableRef falling back to cell: " << aAbs.Format( ScRefFlags::VALID | ScRefFlags::TAB_3D, &rDoc)); aStr = rDoc.GetString(aAbs, mpInterpreterContext); } else { SAL_WARN("sc.core", "ScCompiler::CreateStringFromSingleRef - TableRef of empty header-less: " << aAbs.Format( ScRefFlags::VALID | ScRefFlags::TAB_3D, &rDoc)); aStr = aErrRef; } } escapeTableRefColumnSpecifier( aStr); rBuffer.append(aStr); } else pConv->makeRefStr(rDoc.GetSheetLimits(), rBuffer, meGrammar, aPos, aErrRef, GetSetupTabNames(), aRef, true, (pArr && pArr->IsFromRangeName())); } void ScCompiler::CreateStringFromDoubleRef( OUStringBuffer& rBuffer, const FormulaToken* _pTokenP ) const { OUString aErrRef = GetCurrentOpCodeMap()->getSymbol(ocErrRef); pConv->makeRefStr(rDoc.GetSheetLimits(), rBuffer, meGrammar, aPos, aErrRef, GetSetupTabNames(), *_pTokenP->GetDoubleRef(), false, (pArr && pArr->IsFromRangeName())); } void ScCompiler::CreateStringFromIndex( OUStringBuffer& rBuffer, const FormulaToken* _pTokenP ) const { const OpCode eOp = _pTokenP->GetOpCode(); OUStringBuffer aBuffer; switch ( eOp ) { case ocName: { const ScRangeData* pData = GetRangeData( *_pTokenP); if (pData) { SCTAB nTab = _pTokenP->GetSheet(); if (nTab >= 0 && (nTab != aPos.Tab() || mbRefConventionChartOOXML)) { // Sheet-local on other sheet. OUString aName; if (rDoc.GetName( nTab, aName)) { ScCompiler::CheckTabQuotes( aName, pConv->meConv); aBuffer.append( aName); } else aBuffer.append(ScCompiler::GetNativeSymbol(ocErrName)); aBuffer.append( pConv->getSpecialSymbol( ScCompiler::Convention::SHEET_SEPARATOR)); } else if (mbRefConventionChartOOXML) { aBuffer.append("[0]"); aBuffer.append(pConv->getSpecialSymbol(ScCompiler::Convention::SHEET_SEPARATOR)); } aBuffer.append(pData->GetName()); } } break; case ocDBArea: { const ScDBData* pDBData = rDoc.GetDBCollection()->getNamedDBs().findByIndex(_pTokenP->GetIndex()); if (pDBData) aBuffer.append(pDBData->GetName()); } break; case ocTableRef: { if (NeedsTableRefTransformation()) { // Write the resulting reference if TableRef is not supported. const ScTableRefToken* pTR = dynamic_cast(_pTokenP); if (!pTR) AppendErrorConstant( aBuffer, FormulaError::NoCode); else { const FormulaToken* pRef = pTR->GetAreaRefRPN(); if (!pRef) AppendErrorConstant( aBuffer, FormulaError::NoCode); else { switch (pRef->GetType()) { case svSingleRef: CreateStringFromSingleRef( aBuffer, pRef); break; case svDoubleRef: CreateStringFromDoubleRef( aBuffer, pRef); break; case svError: AppendErrorConstant( aBuffer, pRef->GetError()); break; default: AppendErrorConstant( aBuffer, FormulaError::NoCode); } } } } else { const ScDBData* pDBData = rDoc.GetDBCollection()->getNamedDBs().findByIndex(_pTokenP->GetIndex()); if (pDBData) aBuffer.append(pDBData->GetName()); } } break; default: ; // nothing } if ( !aBuffer.isEmpty() ) rBuffer.append(aBuffer); else rBuffer.append(ScCompiler::GetNativeSymbol(ocErrName)); } void ScCompiler::LocalizeString( OUString& rName ) const { ScGlobal::GetAddInCollection()->LocalizeString( rName ); } // Put quotes around string if non-alphanumeric characters are contained, // quote characters contained within are escaped by '\\'. bool ScCompiler::EnQuote( OUString& rStr ) { sal_Int32 nPos = 0; while ( (nPos = rStr.indexOf( '\'', nPos)) != -1 ) { rStr = rStr.replaceAt( nPos, 0, u"'" ); nPos += 2; } rStr = "'" + rStr + "'"; return true; } FormulaTokenRef ScCompiler::ExtendRangeReference( FormulaToken & rTok1, FormulaToken & rTok2 ) { return extendRangeReference( rDoc.GetSheetLimits(), rTok1, rTok2, aPos, true/*bReuseDoubleRef*/ ); } void ScCompiler::fillAddInToken(::std::vector< css::sheet::FormulaOpCodeMapEntry >& _rVec,bool _bIsEnglish) const { // All known AddIn functions. sheet::FormulaOpCodeMapEntry aEntry; aEntry.Token.OpCode = ocExternal; const LanguageTag aEnglishLanguageTag(LANGUAGE_ENGLISH_US); ScUnoAddInCollection* pColl = ScGlobal::GetAddInCollection(); const tools::Long nCount = pColl->GetFuncCount(); for (tools::Long i=0; i < nCount; ++i) { const ScUnoAddInFuncData* pFuncData = pColl->GetFuncData(i); if (pFuncData) { if ( _bIsEnglish ) { // This is used with OOXML import, so GetExcelName() is really // wanted here until we'll have a parameter to differentiate // from the general css::sheet::XFormulaOpCodeMapper case and // use pFuncData->GetUpperEnglish(). OUString aName; if (pFuncData->GetExcelName( aEnglishLanguageTag, aName)) aEntry.Name = aName; else aEntry.Name = pFuncData->GetUpperName(); } else aEntry.Name = pFuncData->GetUpperLocal(); aEntry.Token.Data <<= pFuncData->GetOriginalName(); _rVec.push_back( aEntry); } } // FIXME: what about those old non-UNO AddIns? } bool ScCompiler::HandleColRowName() { ScSingleRefData& rRef = *mpToken->GetSingleRef(); const ScAddress aAbs = rRef.toAbs(rDoc, aPos); if (!rDoc.ValidAddress(aAbs)) { SetError( FormulaError::NoRef ); return true; } SCCOL nCol = aAbs.Col(); SCROW nRow = aAbs.Row(); SCTAB nTab = aAbs.Tab(); bool bColName = rRef.IsColRel(); SCCOL nMyCol = aPos.Col(); SCROW nMyRow = aPos.Row(); bool bInList = false; bool bValidName = false; ScRangePairList* pRL = (bColName ? rDoc.GetColNameRanges() : rDoc.GetRowNameRanges()); ScRange aRange; for ( size_t i = 0, nPairs = pRL->size(); i < nPairs; ++i ) { const ScRangePair & rR = (*pRL)[i]; if ( rR.GetRange(0).Contains( aAbs ) ) { bInList = bValidName = true; aRange = rR.GetRange(1); if ( bColName ) { aRange.aStart.SetCol( nCol ); aRange.aEnd.SetCol( nCol ); } else { aRange.aStart.SetRow( nRow ); aRange.aEnd.SetRow( nRow ); } break; // for } } if ( !bInList && rDoc.GetDocOptions().IsLookUpColRowNames() ) { // automagically or created by copying and NamePos isn't in list ScRefCellValue aCell(rDoc, aAbs); bool bString = aCell.hasString(); if (!bString && aCell.isEmpty()) bString = true; // empty cell is ok if ( bString ) { // corresponds with ScInterpreter::ScColRowNameAuto() bValidName = true; if ( bColName ) { // ColName SCROW nStartRow = nRow + 1; if ( nStartRow > rDoc.MaxRow() ) nStartRow = rDoc.MaxRow(); SCROW nMaxRow = rDoc.MaxRow(); if ( nMyCol == nCol ) { // formula cell in same column if ( nMyRow == nStartRow ) { // take remainder under name cell nStartRow++; if ( nStartRow > rDoc.MaxRow() ) nStartRow = rDoc.MaxRow(); } else if ( nMyRow > nStartRow ) { // from name cell down to formula cell nMaxRow = nMyRow - 1; } } for ( size_t i = 0, nPairs = pRL->size(); i < nPairs; ++i ) { // next defined ColNameRange below limits row const ScRangePair & rR = (*pRL)[i]; const ScRange& rRange = rR.GetRange(1); if ( rRange.aStart.Col() <= nCol && nCol <= rRange.aEnd.Col() ) { // identical column range SCROW nTmp = rRange.aStart.Row(); if ( nStartRow < nTmp && nTmp <= nMaxRow ) nMaxRow = nTmp - 1; } } aRange.aStart.Set( nCol, nStartRow, nTab ); aRange.aEnd.Set( nCol, nMaxRow, nTab ); } else { // RowName SCCOL nStartCol = nCol + 1; if ( nStartCol > rDoc.MaxCol() ) nStartCol = rDoc.MaxCol(); SCCOL nMaxCol = rDoc.MaxCol(); if ( nMyRow == nRow ) { // formula cell in same row if ( nMyCol == nStartCol ) { // take remainder right from name cell nStartCol++; if ( nStartCol > rDoc.MaxCol() ) nStartCol = rDoc.MaxCol(); } else if ( nMyCol > nStartCol ) { // from name cell right to formula cell nMaxCol = nMyCol - 1; } } for ( size_t i = 0, nPairs = pRL->size(); i < nPairs; ++i ) { // next defined RowNameRange to the right limits column const ScRangePair & rR = (*pRL)[i]; const ScRange& rRange = rR.GetRange(1); if ( rRange.aStart.Row() <= nRow && nRow <= rRange.aEnd.Row() ) { // identical row range SCCOL nTmp = rRange.aStart.Col(); if ( nStartCol < nTmp && nTmp <= nMaxCol ) nMaxCol = nTmp - 1; } } aRange.aStart.Set( nStartCol, nRow, nTab ); aRange.aEnd.Set( nMaxCol, nRow, nTab ); } } } if ( bValidName ) { // And now the magic to distinguish between a range and a single // cell thereof, which is picked position-dependent of the formula // cell. If a direct neighbor is a binary operator (ocAdd, ...) a // SingleRef matching the column/row of the formula cell is // generated. A ocColRowName or ocIntersect as a neighbor results // in a range. Special case: if label is valid for a single cell, a // position independent SingleRef is generated. bool bSingle = (aRange.aStart == aRange.aEnd); bool bFound; if ( bSingle ) bFound = true; else { FormulaToken* p1 = maArrIterator.PeekPrevNoSpaces(); FormulaToken* p2 = maArrIterator.PeekNextNoSpaces(); // begin/end of a formula => single OpCode eOp1 = p1 ? p1->GetOpCode() : ocAdd; OpCode eOp2 = p2 ? p2->GetOpCode() : ocAdd; if ( eOp1 != ocColRowName && eOp1 != ocIntersect && eOp2 != ocColRowName && eOp2 != ocIntersect ) { if ( (SC_OPCODE_START_BIN_OP <= eOp1 && eOp1 < SC_OPCODE_STOP_BIN_OP) || (SC_OPCODE_START_BIN_OP <= eOp2 && eOp2 < SC_OPCODE_STOP_BIN_OP)) bSingle = true; } if ( bSingle ) { // column and/or row must match range if ( bColName ) { bFound = (aRange.aStart.Row() <= nMyRow && nMyRow <= aRange.aEnd.Row()); if ( bFound ) aRange.aStart.SetRow( nMyRow ); } else { bFound = (aRange.aStart.Col() <= nMyCol && nMyCol <= aRange.aEnd.Col()); if ( bFound ) aRange.aStart.SetCol( nMyCol ); } } else bFound = true; } if ( !bFound ) SetError(FormulaError::NoRef); else if (mbJumpCommandReorder) { ScTokenArray* pNew = new ScTokenArray(rDoc); if ( bSingle ) { ScSingleRefData aRefData; aRefData.InitAddress( aRange.aStart ); if ( bColName ) aRefData.SetColRel( true ); else aRefData.SetRowRel( true ); aRefData.SetAddress(rDoc.GetSheetLimits(), aRange.aStart, aPos); pNew->AddSingleReference( aRefData ); } else { ScComplexRefData aRefData; aRefData.InitRange( aRange ); if ( bColName ) { aRefData.Ref1.SetColRel( true ); aRefData.Ref2.SetColRel( true ); } else { aRefData.Ref1.SetRowRel( true ); aRefData.Ref2.SetRowRel( true ); } aRefData.SetRange(rDoc.GetSheetLimits(), aRange, aPos); if ( bInList ) pNew->AddDoubleReference( aRefData ); else { // automagically pNew->Add( new ScDoubleRefToken( rDoc.GetSheetLimits(), aRefData, ocColRowNameAuto ) ); } } PushTokenArray( pNew, true ); return GetToken(); } } else SetError(FormulaError::NoName); return true; } bool ScCompiler::HandleDbData() { ScDBData* pDBData = rDoc.GetDBCollection()->getNamedDBs().findByIndex(mpToken->GetIndex()); if ( !pDBData ) SetError(FormulaError::NoName); else if (mbJumpCommandReorder) { ScComplexRefData aRefData; aRefData.InitFlags(); ScRange aRange; pDBData->GetArea(aRange); aRange.aEnd.SetTab(aRange.aStart.Tab()); aRefData.SetRange(rDoc.GetSheetLimits(), aRange, aPos); ScTokenArray* pNew = new ScTokenArray(rDoc); pNew->AddDoubleReference( aRefData ); PushTokenArray( pNew, true ); return GetToken(); } return true; } bool ScCompiler::GetTokenIfOpCode( OpCode eOp ) { const formula::FormulaToken* p = maArrIterator.PeekNextNoSpaces(); if (p && p->GetOpCode() == eOp) return GetToken(); return false; } /* Documentation on MS-Excel Table structured references: * https://support.office.com/en-us/article/Use-structured-references-in-Excel-table-formulas-75fb07d3-826a-449c-b76f-363057e3d16f * * as of Excel 2013 * [MS-XLSX]: Formulas https://msdn.microsoft.com/en-us/library/dd906358.aspx * * look for structure-reference */ bool ScCompiler::HandleTableRef() { ScTableRefToken* pTR = dynamic_cast(mpToken.get()); if (!pTR) { SetError(FormulaError::UnknownToken); return true; } ScDBData* pDBData = rDoc.GetDBCollection()->getNamedDBs().findByIndex( pTR->GetIndex()); if ( !pDBData ) SetError(FormulaError::NoName); else if (mbJumpCommandReorder) { ScRange aDBRange; pDBData->GetArea(aDBRange); aDBRange.aEnd.SetTab(aDBRange.aStart.Tab()); ScRange aRange( aDBRange); FormulaError nError = FormulaError::NONE; bool bForwardToClose = false; ScTableRefToken::Item eItem = pTR->GetItem(); switch (eItem) { case ScTableRefToken::TABLE: { // The table name without items references the table data, // without headers or totals. if (pDBData->HasHeader()) aRange.aStart.IncRow(); if (pDBData->HasTotals()) aRange.aEnd.IncRow(-1); if (aRange.aEnd.Row() < aRange.aStart.Row()) nError = FormulaError::NoRef; bForwardToClose = true; } break; case ScTableRefToken::ALL: { bForwardToClose = true; } break; case ScTableRefToken::HEADERS: { if (pDBData->HasHeader()) aRange.aEnd.SetRow( aRange.aStart.Row()); else nError = FormulaError::NoRef; bForwardToClose = true; } break; case ScTableRefToken::DATA: { if (pDBData->HasHeader()) aRange.aStart.IncRow(); } [[fallthrough]]; case ScTableRefToken::HEADERS_DATA: { if (pDBData->HasTotals()) aRange.aEnd.IncRow(-1); if (aRange.aEnd.Row() < aRange.aStart.Row()) nError = FormulaError::NoRef; bForwardToClose = true; } break; case ScTableRefToken::TOTALS: { if (pDBData->HasTotals()) aRange.aStart.SetRow( aRange.aEnd.Row()); else nError = FormulaError::NoRef; bForwardToClose = true; } break; case ScTableRefToken::DATA_TOTALS: { if (pDBData->HasHeader()) aRange.aStart.IncRow(); if (aRange.aEnd.Row() < aRange.aStart.Row()) nError = FormulaError::NoRef; bForwardToClose = true; } break; case ScTableRefToken::THIS_ROW: { if (aRange.aStart.Row() <= aPos.Row() && aPos.Row() <= aRange.aEnd.Row()) { aRange.aStart.SetRow( aPos.Row()); aRange.aEnd.SetRow( aPos.Row()); } else { nError = FormulaError::NoValue; // For *some* relative row reference in named // expressions' thisrow special handling below. aRange.aEnd.SetRow( aRange.aStart.Row()); } bForwardToClose = true; } break; } bool bColumnRange = false; bool bCol1Rel = false; bool bCol1RelName = false; int nLevel = 0; if (bForwardToClose && GetTokenIfOpCode( ocTableRefOpen)) { ++nLevel; enum { sOpen, sItem, sClose, sSep, sLast, sStop } eState = sOpen; do { const formula::FormulaToken* p = maArrIterator.PeekNextNoSpaces(); if (!p) eState = sStop; else { switch (p->GetOpCode()) { case ocTableRefOpen: eState = ((eState == sOpen || eState == sSep) ? sOpen : sStop); if (++nLevel > 2) { SetError( FormulaError::Pair); eState = sStop; } break; case ocTableRefItemAll: case ocTableRefItemHeaders: case ocTableRefItemData: case ocTableRefItemTotals: case ocTableRefItemThisRow: eState = ((eState == sOpen) ? sItem : sStop); break; case ocTableRefClose: eState = ((eState == sItem || eState == sClose) ? sClose : sStop); if (eState != sStop && --nLevel == 0) eState = sLast; break; case ocSep: eState = ((eState == sClose) ? sSep : sStop); break; case ocPush: if (eState == sOpen && p->GetType() == svSingleRef) { bColumnRange = true; bCol1Rel = p->GetSingleRef()->IsColRel(); bCol1RelName = p->GetSingleRef()->IsRelName(); eState = sLast; } else { eState = sStop; } break; case ocBad: eState = sLast; if (nError == FormulaError::NONE) nError = FormulaError::NoName; break; default: eState = sStop; } if (eState != sStop) GetToken(); if (eState == sLast) eState = sStop; } } while (eState != sStop); } ScTokenArray* pNew = new ScTokenArray(rDoc); if (nError == FormulaError::NONE || nError == FormulaError::NoValue) { bool bCol2Rel = false; bool bCol2RelName = false; // The FormulaError::NoValue case generates a thisrow reference that can be // used to save named expressions in A1 syntax notation. if (bColumnRange) { // Limit range to specified columns. ScRange aColRange( ScAddress::INITIALIZE_INVALID ); switch (mpToken->GetType()) { case svSingleRef: { aColRange.aStart = aColRange.aEnd = mpToken->GetSingleRef()->toAbs(rDoc, aPos); if ( GetTokenIfOpCode( ocTableRefClose) && (nLevel--) && GetTokenIfOpCode( ocRange) && GetTokenIfOpCode( ocTableRefOpen) && (++nLevel) && GetTokenIfOpCode( ocPush)) { if (mpToken->GetType() != svSingleRef) aColRange = ScRange( ScAddress::INITIALIZE_INVALID); else { aColRange.aEnd = mpToken->GetSingleRef()->toAbs(rDoc, aPos); aColRange.PutInOrder(); bCol2Rel = mpToken->GetSingleRef()->IsColRel(); bCol2RelName = mpToken->GetSingleRef()->IsRelName(); } } } break; default: ; // nothing } // coverity[copy_paste_error : FALSE] - this is correct, aStart in both aDBRange uses if (aColRange.aStart.Row() != aDBRange.aStart.Row() || aColRange.aEnd.Row() != aDBRange.aStart.Row()) aRange = ScRange( ScAddress::INITIALIZE_INVALID); else { aColRange.aEnd.SetRow( aRange.aEnd.Row()); aRange = aRange.Intersection( aColRange); } } if (aRange.IsValid()) { if (aRange.aStart == aRange.aEnd) { ScSingleRefData aRefData; aRefData.InitFlags(); aRefData.SetColRel( bCol1Rel); if (eItem == ScTableRefToken::THIS_ROW) { aRefData.SetRowRel( true); if (!bCol1RelName) bCol1RelName = pArr->IsFromRangeName(); } aRefData.SetRelName( bCol1RelName); aRefData.SetFlag3D( true); if (nError != FormulaError::NONE) { aRefData.SetAddress( rDoc.GetSheetLimits(), aRange.aStart, aRange.aStart); pTR->SetAreaRefRPN( new ScSingleRefToken(rDoc.GetSheetLimits(), aRefData)); // set reference at TableRef pNew->Add( new FormulaErrorToken( nError)); // set error in RPN } else { aRefData.SetAddress( rDoc.GetSheetLimits(), aRange.aStart, aPos); pTR->SetAreaRefRPN( pNew->AddSingleReference( aRefData)); } } else { ScComplexRefData aRefData; aRefData.InitFlags(); aRefData.Ref1.SetColRel( bCol1Rel); aRefData.Ref2.SetColRel( bCol2Rel); bool bRelName = bCol1RelName || bCol2RelName; if (eItem == ScTableRefToken::THIS_ROW) { aRefData.Ref1.SetRowRel( true); aRefData.Ref2.SetRowRel( true); if (!bRelName) bRelName = pArr->IsFromRangeName(); } aRefData.Ref1.SetRelName( bRelName); aRefData.Ref2.SetRelName( bRelName); aRefData.Ref1.SetFlag3D( true); if (nError != FormulaError::NONE) { aRefData.SetRange( rDoc.GetSheetLimits(), aRange, aRange.aStart); pTR->SetAreaRefRPN( new ScDoubleRefToken(rDoc.GetSheetLimits(), aRefData)); // set reference at TableRef pNew->Add( new FormulaErrorToken( nError)); // set error in RPN } else { aRefData.SetRange( rDoc.GetSheetLimits(), aRange, aPos); pTR->SetAreaRefRPN( pNew->AddDoubleReference( aRefData)); } } } else { pTR->SetAreaRefRPN( pNew->Add( new FormulaErrorToken( FormulaError::NoRef))); } } else { pTR->SetAreaRefRPN( pNew->Add( new FormulaErrorToken( nError))); } while (nLevel-- > 0) { if (!GetTokenIfOpCode( ocTableRefClose)) SetError( FormulaError::Pair); } PushTokenArray( pNew, true ); return GetToken(); } return true; } formula::ParamClass ScCompiler::GetForceArrayParameter( const formula::FormulaToken* pToken, sal_uInt16 nParam ) const { return ScParameterClassification::GetParameterType( pToken, nParam); } bool ScCompiler::ParameterMayBeImplicitIntersection(const FormulaToken* token, int parameter) { formula::ParamClass param = ScParameterClassification::GetParameterType( token, parameter ); return param == Value || param == Array; } bool ScCompiler::SkipImplicitIntersectionOptimization(const FormulaToken* token) const { if (mbMatrixFlag) return true; formula::ParamClass paramClass = token->GetInForceArray(); if (paramClass == formula::ForceArray || paramClass == formula::ReferenceOrForceArray || paramClass == formula::SuppressedReferenceOrForceArray || paramClass == formula::ReferenceOrRefArray) { return true; } formula::ParamClass returnType = ScParameterClassification::GetParameterType( token, SAL_MAX_UINT16 ); return returnType == formula::Reference; } void ScCompiler::HandleIIOpCode(FormulaToken* token, FormulaToken*** pppToken, sal_uInt8 nNumParams) { if (!mbComputeII) return; #ifdef DBG_UTIL if(!HandleIIOpCodeInternal(token, pppToken, nNumParams)) mUnhandledPossibleImplicitIntersectionsOpCodes.insert(token->GetOpCode()); #else HandleIIOpCodeInternal(token, pppToken, nNumParams); #endif } // return true if opcode is handled bool ScCompiler::HandleIIOpCodeInternal(FormulaToken* token, FormulaToken*** pppToken, sal_uInt8 nNumParams) { if (nNumParams > 0 && *pppToken[0] == nullptr) return false; // Bad expression (see the dummy creation in FormulaCompiler::CompileTokenArray()) const OpCode nOpCode = token->GetOpCode(); if (nOpCode == ocPush) { if(token->GetType() == svDoubleRef) mUnhandledPossibleImplicitIntersections.insert( token ); return true; } else if (nOpCode == ocSumIf || nOpCode == ocAverageIf) { if (nNumParams != 3) return false; if (!(pppToken[0] && pppToken[2] && *pppToken[0] && *pppToken[2])) return false; if ((*pppToken[0])->GetType() != svDoubleRef) return false; const StackVar eSumRangeType = (*pppToken[2])->GetType(); if ( eSumRangeType != svSingleRef && eSumRangeType != svDoubleRef ) return false; const ScComplexRefData& rBaseRange = *(*pppToken[0])->GetDoubleRef(); ScComplexRefData aSumRange; if (eSumRangeType == svSingleRef) { aSumRange.Ref1 = *(*pppToken[2])->GetSingleRef(); aSumRange.Ref2 = aSumRange.Ref1; } else aSumRange = *(*pppToken[2])->GetDoubleRef(); CorrectSumRange(rBaseRange, aSumRange, pppToken[2]); // TODO mark parameters as handled return true; } else if (nOpCode >= SC_OPCODE_START_1_PAR && nOpCode < SC_OPCODE_STOP_1_PAR) { if (nNumParams != 1) return false; if( !ParameterMayBeImplicitIntersection( token, 0 )) return false; if (SkipImplicitIntersectionOptimization(token)) return false; if ((*pppToken[0])->GetType() != svDoubleRef) return false; mPendingImplicitIntersectionOptimizations.emplace_back( pppToken[0], token ); return true; } else if ((nOpCode >= SC_OPCODE_START_BIN_OP && nOpCode < SC_OPCODE_STOP_BIN_OP) || nOpCode == ocRound || nOpCode == ocRoundUp || nOpCode == ocRoundDown) { if (nNumParams != 2) return false; if( !ParameterMayBeImplicitIntersection( token, 0 ) || !ParameterMayBeImplicitIntersection( token, 1 )) return false; if (SkipImplicitIntersectionOptimization(token)) return false; // Convert only if the other parameter is not a matrix (which would force the result to be a matrix). if ((*pppToken[0])->GetType() == svDoubleRef && (*pppToken[1])->GetType() != svMatrix) mPendingImplicitIntersectionOptimizations.emplace_back( pppToken[0], token ); if ((*pppToken[1])->GetType() == svDoubleRef && (*pppToken[0])->GetType() != svMatrix) mPendingImplicitIntersectionOptimizations.emplace_back( pppToken[1], token ); return true; } else if ((nOpCode >= SC_OPCODE_START_UN_OP && nOpCode < SC_OPCODE_STOP_UN_OP) || nOpCode == ocPercentSign) { if (nNumParams != 1) return false; if( !ParameterMayBeImplicitIntersection( token, 0 )) return false; if (SkipImplicitIntersectionOptimization(token)) return false; if ((*pppToken[0])->GetType() == svDoubleRef) mPendingImplicitIntersectionOptimizations.emplace_back( pppToken[0], token ); return true; } else if (nOpCode == ocVLookup) { if (nNumParams != 3 && nNumParams != 4) return false; if (SkipImplicitIntersectionOptimization(token)) return false; assert( ParameterMayBeImplicitIntersection( token, 0 )); assert( !ParameterMayBeImplicitIntersection( token, 1 )); assert( ParameterMayBeImplicitIntersection( token, 2 )); assert( ParameterMayBeImplicitIntersection( token, 3 )); if ((*pppToken[2])->GetType() == svDoubleRef) mPendingImplicitIntersectionOptimizations.emplace_back( pppToken[2], token ); if ((*pppToken[0])->GetType() == svDoubleRef) mPendingImplicitIntersectionOptimizations.emplace_back( pppToken[0], token ); if (nNumParams == 4 && (*pppToken[3])->GetType() == svDoubleRef) mPendingImplicitIntersectionOptimizations.emplace_back( pppToken[3], token ); // a range for the second parameters is not an implicit intersection mUnhandledPossibleImplicitIntersections.erase( *pppToken[ 1 ] ); return true; } else { bool possibleII = false; for( int i = 0; i < nNumParams; ++i ) { if( ParameterMayBeImplicitIntersection( token, i )) { possibleII = true; break; } } if( !possibleII ) { // all parameters have been handled, they are not implicit intersections for( int i = 0; i < nNumParams; ++i ) mUnhandledPossibleImplicitIntersections.erase( *pppToken[ i ] ); return true; } } return false; } void ScCompiler::PostProcessCode() { for( const PendingImplicitIntersectionOptimization& item : mPendingImplicitIntersectionOptimizations ) { if( *item.parameterLocation != item.parameter ) // the parameter has been changed somehow continue; if( item.parameterLocation >= pCode ) // the location is not inside the code (pCode points after the end) continue; // E.g. "SUMPRODUCT(I5:I6+1)" shouldn't do implicit intersection. if( item.operation->IsInForceArray()) continue; ReplaceDoubleRefII( item.parameterLocation ); } mPendingImplicitIntersectionOptimizations.clear(); } void ScCompiler::AnnotateOperands() { AnnotateTrimOnDoubleRefs(); } void ScCompiler::ReplaceDoubleRefII(FormulaToken** ppDoubleRefTok) { const ScComplexRefData* pRange = (*ppDoubleRefTok)->GetDoubleRef(); if (!pRange) return; const ScComplexRefData& rRange = *pRange; // Can't do optimization reliably in this case (when row references are absolute). // Example : =SIN(A$1:A$10) filled in a formula group starting at B5 and of length 100. // If we just optimize the argument $A$1:$A$10 to singleref "A5" for the top cell in the fg, then // the results in cells B11:B104 will be incorrect (sin(0) = 0, assuming empty cells in A11:A104) // instead of the #VALUE! errors we would expect. We need to know the formula-group length to // fix this, but that is unknown at this stage, so skip such cases. if (!rRange.Ref1.IsRowRel() && !rRange.Ref2.IsRowRel()) return; ScRange aAbsRange = rRange.toAbs(rDoc, aPos); if (aAbsRange.aStart == aAbsRange.aEnd) return; // Nothing to do (trivial case). ScAddress aAddr; if (!DoubleRefToPosSingleRefScalarCase(aAbsRange, aAddr, aPos)) return; ScSingleRefData aSingleRef; aSingleRef.InitFlags(); aSingleRef.SetColRel(rRange.Ref1.IsColRel()); aSingleRef.SetRowRel(true); aSingleRef.SetTabRel(rRange.Ref1.IsTabRel()); aSingleRef.SetAddress(rDoc.GetSheetLimits(), aAddr, aPos); // Replace the original doubleref token with computed singleref token FormulaToken* pNewSingleRefTok = new ScSingleRefToken(rDoc.GetSheetLimits(), aSingleRef); (*ppDoubleRefTok)->DecRef(); *ppDoubleRefTok = pNewSingleRefTok; pNewSingleRefTok->IncRef(); } bool ScCompiler::DoubleRefToPosSingleRefScalarCase(const ScRange& rRange, ScAddress& rAdr, const ScAddress& rFormulaPos) { assert(rRange.aStart != rRange.aEnd); bool bOk = false; SCCOL nMyCol = rFormulaPos.Col(); SCROW nMyRow = rFormulaPos.Row(); SCTAB nMyTab = rFormulaPos.Tab(); SCCOL nCol = 0; SCROW nRow = 0; SCTAB nTab; nTab = rRange.aStart.Tab(); if ( rRange.aStart.Col() <= nMyCol && nMyCol <= rRange.aEnd.Col() ) { nRow = rRange.aStart.Row(); if ( nRow == rRange.aEnd.Row() ) { bOk = true; nCol = nMyCol; } else if ( nTab != nMyTab && nTab == rRange.aEnd.Tab() && rRange.aStart.Row() <= nMyRow && nMyRow <= rRange.aEnd.Row() ) { bOk = true; nCol = nMyCol; nRow = nMyRow; } } else if ( rRange.aStart.Row() <= nMyRow && nMyRow <= rRange.aEnd.Row() ) { nCol = rRange.aStart.Col(); if ( nCol == rRange.aEnd.Col() ) { bOk = true; nRow = nMyRow; } else if ( nTab != nMyTab && nTab == rRange.aEnd.Tab() && rRange.aStart.Col() <= nMyCol && nMyCol <= rRange.aEnd.Col() ) { bOk = true; nCol = nMyCol; nRow = nMyRow; } } if ( bOk ) { if ( nTab == rRange.aEnd.Tab() ) ; // all done else if ( nTab <= nMyTab && nMyTab <= rRange.aEnd.Tab() ) nTab = nMyTab; else bOk = false; if ( bOk ) rAdr.Set( nCol, nRow, nTab ); } return bOk; } static void lcl_GetColRowDeltas(const ScRange& rRange, SCCOL& rXDelta, SCROW& rYDelta) { rXDelta = rRange.aEnd.Col() - rRange.aStart.Col(); rYDelta = rRange.aEnd.Row() - rRange.aStart.Row(); } bool ScCompiler::AdjustSumRangeShape(const ScComplexRefData& rBaseRange, ScComplexRefData& rSumRange) { ScRange aAbs = rSumRange.toAbs(rDoc, aPos); // Current sum-range end col/row SCCOL nEndCol = aAbs.aEnd.Col(); SCROW nEndRow = aAbs.aEnd.Row(); // Current behaviour is, we will get a #NAME? for the below case, so bail out. // Note that sum-range's End[Col,Row] are same as Start[Col,Row] if the original formula // has a single-ref as the sum-range. if (!rDoc.ValidCol(nEndCol) || !rDoc.ValidRow(nEndRow)) return false; SCCOL nXDeltaSum = 0; SCROW nYDeltaSum = 0; lcl_GetColRowDeltas(aAbs, nXDeltaSum, nYDeltaSum); aAbs = rBaseRange.toAbs(rDoc, aPos); SCCOL nXDelta = 0; SCROW nYDelta = 0; lcl_GetColRowDeltas(aAbs, nXDelta, nYDelta); if (nXDelta == nXDeltaSum && nYDelta == nYDeltaSum) return false; // shapes of base-range match current sum-range // Try to make the sum-range to take the same shape as base-range, // by adjusting Ref2 member of rSumRange if the resultant sum-range don't // go out-of-bounds. SCCOL nXInc = nXDelta - nXDeltaSum; SCROW nYInc = nYDelta - nYDeltaSum; // Don't let a valid End[Col,Row] go beyond (rDoc.MaxCol(),rDoc.MaxRow()) to match // what happens in ScInterpreter::IterateParametersIf(), but there it also shrinks // the base-range by the (out-of-bound)amount clipped off the sum-range. // TODO: Probably we can optimize (from threading perspective) rBaseRange // by shrinking it here correspondingly (?) if (nEndCol + nXInc > rDoc.MaxCol()) nXInc = rDoc.MaxCol() - nEndCol; if (nEndRow + nYInc > rDoc.MaxRow()) nYInc = rDoc.MaxRow() - nEndRow; rSumRange.Ref2.IncCol(nXInc); rSumRange.Ref2.IncRow(nYInc); return true; } void ScCompiler::CorrectSumRange(const ScComplexRefData& rBaseRange, ScComplexRefData& rSumRange, FormulaToken** ppSumRangeToken) { if (!AdjustSumRangeShape(rBaseRange, rSumRange)) return; // Replace sum-range token FormulaToken* pNewSumRangeTok = new ScDoubleRefToken(rDoc.GetSheetLimits(), rSumRange); (*ppSumRangeToken)->DecRef(); *ppSumRangeToken = pNewSumRangeTok; pNewSumRangeTok->IncRef(); } void ScCompiler::AnnotateTrimOnDoubleRefs() { if (!pCode || !(*(pCode - 1))) return; // OpCode of the "root" operator (which is already in RPN array). OpCode eOpCode = (*(pCode - 1))->GetOpCode(); // eOpCode can be some operator which does not change with operands with or contains zero values. if (eOpCode != ocSum) return; FormulaToken** ppTok = pCode - 2; // exclude the root operator. // The following loop runs till a "pattern" is found or there is a mismatch // and marks the push DoubleRef arguments as trimmable when there is a match. // The pattern is // SUM(IF(=, )) // such that one of the operands of ocEqual is a double-ref. // Examples of formula that matches this are: // SUM(IF(D:D=$A$1,F:F)*$H$1*2.3/$G$2) // SUM((IF(D:D=$A$1,F:F)*$H$1*2.3/$G$2)*$H$2*5/$G$3) // SUM(IF(E:E=16,F:F)*$H$1*100) bool bTillClose = true; bool bCloseTillIf = false; sal_Int16 nToksTillIf = 0; constexpr sal_Int16 MAXDIST_IF = 15; while (*ppTok) { FormulaToken* pTok = *ppTok; OpCode eCurrOp = pTok->GetOpCode(); ++nToksTillIf; // TODO : Is there a better way to handle this ? // ocIf is too far off from the sum opcode. if (nToksTillIf > MAXDIST_IF) return; switch (eCurrOp) { case ocDiv: case ocMul: if (!bTillClose) return; break; case ocPush: break; case ocClose: if (bTillClose) { bTillClose = false; bCloseTillIf = true; } else return; break; case ocIf: { if (!bCloseTillIf) return; if (!pTok->IsInForceArray()) return; const short nJumpCount = pTok->GetJump()[0]; if (nJumpCount != 2) // Should have THEN but no ELSE. return; OpCode eCompOp = (*(ppTok - 1))->GetOpCode(); if (eCompOp != ocEqual) return; FormulaToken* pLHS = *(ppTok - 2); FormulaToken* pRHS = *(ppTok - 3); if (((pLHS->GetType() == svSingleRef || pLHS->GetType() == svDouble) && pRHS->GetType() == svDoubleRef) || ((pRHS->GetType() == svSingleRef || pRHS->GetType() == svDouble) && pLHS->GetType() == svDoubleRef)) { if (pLHS->GetType() == svDoubleRef) pLHS->GetDoubleRef()->SetTrimToData(true); else pRHS->GetDoubleRef()->SetTrimToData(true); return; } } break; default: return; } --ppTok; } return; } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */