/* -*- 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 .
*/
void ScCompiler::fillFromAddInCollectionExcelName( const NonConstOpCodeMapPtr& xMap ) const
{ const LanguageTag aDestLang(LANGUAGE_ENGLISH_US);
ScUnoAddInCollection* pColl = ScGlobal::GetAddInCollection();
tools::Long nCount = pColl->GetFuncCount(); for (tools::Long i=0; i < nCount; ++i)
{
OUString aExcelName; const ScUnoAddInFuncData* pFuncData = pColl->GetFuncData(i); if (pFuncData && pFuncData->GetExcelName( aDestLang, aExcelName, true))
{ // Note that function names not defined in OOXML but implemented by // Excel should have the "_xlfn." prefix. We have no way to check // though what an Add-In actually implements.
xMap->putExternalSoftly( GetCharClassEnglish()->uppercase(aExcelName), pFuncData->GetOriginalName());
}
}
}
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 )
{ returntrue;
} // 2. old add in functions if (ScGlobal::GetLegacyFuncCollection()->findByName(aUpper))
{ returntrue;
}
// 3. new (uno) add in functions
OUString aIntName = ScGlobal::GetAddInCollection()->FindFunction(aUpper, false); return !aIntName.isEmpty(); // no valid function name
}
const CharClass* ScCompiler::GetCharClassEnglish()
{
std::scoped_lock aGuard(gCharClassMutex); if (!pCharClassEnglish)
{
pCharClassEnglish = new CharClass( ::comphelper::getProcessComponentContext(),
LanguageTag( LANGUAGE_ENGLISH_US));
} return pCharClassEnglish;
}
const CharClass* ScCompiler::GetCharClassLocalized()
{ // Switching UI language requires restart; if not, we would have to // keep track of that.
std::scoped_lock aGuard(gCharClassMutex); 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
// 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<OUString> &ScCompiler::GetSetupTabNames() const
{
std::vector<OUString> &rTabNames = const_cast<ScCompiler *>(this)->maTabNames;
if (rTabNames.empty())
{
rTabNames = rDoc.GetAllTableNames(); for (auto& rTabName : rTabNames)
ScCompiler::CheckTabQuotes(rTabName, formula::FormulaGrammar::extractRefConvention(meGrammar));
}
// 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"));
staticbool lcl_isValidQuotedText( std::u16string_view rFormula, size_t 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.size() && rFormula[nSrcPos] == '\'')
{
size_t nPos = nSrcPos+1; while (nPos < rFormula.size())
{ if (rFormula[nPos] == '\'')
{ if ( (nPos+1 == rFormula.size()) || (rFormula[nPos+1] != '\'') )
{
rRes.TokenType = KParseType::SINGLE_QUOTE_NAME;
rRes.EndPos = nPos+1; returntrue;
}
++nPos;
}
++nPos;
}
}
returnfalse;
}
staticbool lcl_parseExternalName( const OUString& rSymbol,
OUString& rFile,
OUString& rName, const sal_Unicode cSep, const ScDocument& rDoc, const uno::Sequence<sheet::ExternalLinkInfo>* 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) returnfalse;
i = sal_Int32(p - pStart);
} for ( ; i < nLen; ++i, ++p)
{
sal_Unicode c = *p; if (i == 0)
{ if (c == '.' || c == cSep) returnfalse;
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) returnfalse;
}
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. returnfalse;
}
if (c != cSep)
{ // only the separator is allowed after the closing quote. returnfalse;
}
continue;
}
}
if (bInName)
{ if (c == cSep)
{ // A second separator ? Not a valid external name. returnfalse;
}
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;
returnfalse;
} while (false);
aTmpFile += OUStringChar(c);
}
}
}
if (!bInName)
{ // No name found - most likely the symbol has no '!'s. returnfalse;
}
sal_Int32 nNameLen = aTmpName.getLength(); if (nNameLen < 2)
{ // Name must be at least 2-char long (separator plus name). returnfalse;
}
if (aTmpName[0] != cSep)
{ // 1st char of the name must equal the separator. returnfalse;
}
if (aTmpName[nNameLen-1] == '!')
{ // Check against #REF!. if (OUString::unacquired(aTmpName).equalsIgnoreAsciiCase("#REF!")) returnfalse;
}
rFile = aTmpFile;
rName = aTmpName.makeStringAndClear().copy(1); // Skip the first char as it is always the separator. returntrue;
}
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;
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.
}
} elseif (bODF)
rBuffer.append( '.'); // need at least the sheet separator in ODF
makeExternalSingleRefStr(rLimits,
rBuffer, rFileName, aLastTabName, rRef.Ref2, rPos, bDisplayTabName, bEncodeUrl);
} while (false);
staticvoid 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']
virtualvoid 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;
} elseif (i == rSrcPos + 1)
{ // second character must be a single quote. if (c != '\'') return;
} elseif (c == '\'')
{ if (cPrev == '\'') // two successive single quote is treated as a single // valid character.
c = 'a';
} elseif (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;
}
}
};
// 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;
virtualvoid 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.
virtualvoid makeRefStr( ScSheetLimits& rLimits,
OUStringBuffer& rBuf,
formula::FormulaGrammar::Grammar eGram, const ScAddress& rPos, const OUString& rErrRef, const std::vector<OUString>& 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;
}
/* TODO: add support for sheet local names, would be * [N]'Sheet Name'!DefinedName * Similar to makeExternalRefStr() but with DefinedName instead of
* CellStr. */
}
virtualvoid 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;
} elseif (c == ']')
{
rSrcPos = i + 1; return;
}
}
}
virtualvoid 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
virtualvoid makeExternalRefStr(
ScSheetLimits& rLimits,
OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 nFileId, const OUString& /*rFileName*/, const std::vector<OUString>& 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...
// 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;
}
r1c1_add_row(rBuf, rRef.Ref1, aAbsRef.aStart);
r1c1_add_col(rBuf, rRef.Ref1, aAbsRef.aStart); // We can't parse a single col/row reference in the context of a R1C1 // 3D reference back yet, otherwise (if Excel understands it) an // additional condition similar to ConventionXL_A1::makeRefStr() could // be // // && (aAbsRef.aStart.Row() != aAbsRef.aEnd.Row() || rRef.Ref1.IsRowRel() != rRef.Ref2.IsRowRel() || // aAbsRef.aStart.Col() != aAbsRef.aEnd.Col() || rRef.Ref1.IsColRel() != rRef.Ref2.IsColRel()) if (!bSingleRef)
{
rBuf.append( ':' );
r1c1_add_row(rBuf, rRef.Ref2, aAbsRef.aEnd);
r1c1_add_col(rBuf, rRef.Ref2, aAbsRef.aEnd);
}
}
virtualvoid 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.
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;
}
// 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 staticbool lcl_isUnicodeIgnoreAscii( const sal_Unicode* p1, constchar* p2, size_t n )
{ for (size_t i=0; i<n; ++i)
{ if (!p1[i]) returnfalse; if (p1[i] != p2[i])
{ if (p1[i] < 'a' || 'z' < p1[i]) returnfalse; // not a lower case letter if (p2[i] < 'A' || 'Z' < p2[i]) returnfalse; // not a letter to match if (p1[i] != p2[i] + 0x20) returnfalse; // lower case doesn't match either
}
} returntrue;
}
// static void ScCompiler::addWhitespace( std::vector<ScCompiler::Whitespace> & 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 //---------------+-------------------+-----------------------+---------------
// 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);
// 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;
} elseif( 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;
}
} elseif( 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;
}
} elseif( 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;
}
} elseif( nMask & ScCharFlags::OdfLBracket )
{ // eaten, not added to pSym
eState = ssGetReference;
mnPredetectedReference = 1;
} elseif( nMask & ScCharFlags::CharBool )
{
*pSym++ = c;
eState = ssGetBool;
} elseif( nMask & ScCharFlags::CharValue )
{
*pSym++ = c;
eState = ssGetValue;
} elseif( nMask & ScCharFlags::CharString )
{
*pSym++ = c;
eState = ssGetString;
} elseif( nMask & ScCharFlags::CharErrConst )
{
*pSym++ = c;
sal_uInt16 nLevel; if (!maTableRefs.empty() && ((nLevel = maTableRefs.back().mnLevel) == 2 || nLevel == 1))
eState = ssGetTableRefItem; else
eState = ssGetErrorConstant;
} elseif( nMask & ScCharFlags::CharDontCare )
{
addWhitespace( vSpaces, aSpace, c);
} elseif( 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;
} elseif (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
} elseif (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;
}
} elseif ( 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;
} elseif (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;
} elseif( nMask & ScCharFlags::Value )
*pSym++ = c; elseif( nMask & ScCharFlags::ValueSep )
{
pSrc--;
eState = ssStop;
} elseif (c == 'E' || c == 'e')
{ if (GetCharTableFlags( pSrc[0], 0 ) & ScCharFlags::ValueExp)
*pSym++ = c; else
{ // reparse with i18n
bi18n = true;
eState = ssStop;
}
} elseif( 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; elseif ('!' == 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;
} elseif ('/' == c)
{ if (!bErrorConstantHadSlash)
bErrorConstantHadSlash = true; else
{
bAdd = false;
eState = ssStop;
}
} elseif ((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;
} elseif (cSheetSep == c && nRefInName == 0)
{ // eat it, no sheet name [.A1]
bAddToSymbol = false;
nRefInName |= kPast; if ('$' == pSrc[0] && '$' == pSrc[1])
nRefInName |= kMarkAhead;
} elseif (!(nRefInName & kPast) || (nRefInName & (kMarkAhead | kDefName)))
{ // Not in col/row yet.
if (SC_COMPILER_FILE_TAB_SEP == c && (nRefInName & kFileSep))
nRefInName = 0; elseif ('$' == 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;
}
} elseif (cSheetPrefix == c && nRefInName == 0)
nRefInName |= kDollar; elseif ('\'' == 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);
} elseif (!(nRefInName & kOpen))
{
OSL_FAIL("ScCompiler::NextSymbol: reference: " "a ''' without the name being enclosed in '...' violates ODF spec");
} elseif (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);
}
} elseif ('#' == c && nRefInName == 0)
nRefInName |= kRefErr; elseif (cSheetSep == c && !(nRefInName & kOpen))
{ // unquoted sheet name separator
nRefInName |= kPast; if ('$' == pSrc[0] && '$' == pSrc[1])
nRefInName |= kMarkAhead;
} elseif (':' == c && !(nRefInName & kOpen))
{
OSL_FAIL("ScCompiler::NextSymbol: reference: " "range operator ':' without prior sheet name separator '.' violates ODF spec");
nRefInName = 0;
++mnPredetectedReference;
} elseif (!(nRefInName & kName))
{ // start unquoted name
nRefInName |= kName;
}
} elseif (':' == 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 (constauto& 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]; constbool 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++]);
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 mistakenly 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; elseif (rName == mxSymbols->getSymbol(ocArrayRowSep))
eOp = ocArrayRowSep;
} elseif (eOp == ocArrayColSep || eOp == ocArrayRowSep)
{ if (rName == mxSymbols->getSymbol(ocSep))
eOp = ocSep; elseif (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;
}
}
} elseif (eOp == ocCeil && mxSymbols->isOOXML())
{ // Ensure that _xlfn.CEILING.MATH maps to ocCeil_Math. ocCeil is // unassigned for import.
eOp = ocCeil_Math;
} elseif (eOp == ocFloor && mxSymbols->isOOXML())
{ // Ensure that _xlfn.FLOOR.MATH maps to ocFloor_Math. ocFloor is // unassigned for import.
eOp = ocFloor_Math;
}
maRawToken.SetOpCode(eOp);
} elseif (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
{ constchar* pName;
OpCode eOp;
}; staticconst FunctionName aOdffAliases[] = { // Renamed old names, still accept them:
{ "B", ocB }, // B -> BINOM.DIST.RANGE
{ "TDIST", ocTDist }, // TDIST -> LEGACY.TDIST
{ "ORG.OPENOFFICE.EASTERSUNDAY", ocEasterSunday }, // ORG.OPENOFFICE.EASTERSUNDAY -> 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
}
}
} elseif (mxSymbols->isOOXML())
{ // OOXML names that are not written in the current mapping but to be // recognized as old versions wrote them. struct FunctionName
{ constchar* pName;
OpCode eOp;
}; staticconst 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
}
}
} elseif (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
{ constchar* pName;
OpCode eOp;
}; staticconst 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) returnfalse;
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 elseif (!bShouldBeNegSub && eOp == ocNegSub)
maRawToken.NewOpCode( ocSub );
} return bFound;
}
staticbool 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) returnfalse; // partially a number => no such thing
if (lcl_ParenthesisFollows( aFormula.getStr() + nSrcPos)) returnfalse; // some function name, not a constant
// Could be TRUE or FALSE constant.
OpCode eOpFunc = ocNone; if (rSym.equalsIgnoreAsciiCase("TRUE"))
eOpFunc = ocTrue; elseif (rSym.equalsIgnoreAsciiCase("FALSE"))
eOpFunc = ocFalse; if (eOpFunc != ocNone)
{
maRawToken.SetOpCode(eOpFunc); // add missing trailing parentheses
maPendingOpCodes.push(ocOpen);
maPendingOpCodes.push(ocClose); returntrue;
} returnfalse;
} 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)) returnfalse; if (rDoc.GetDBCollection()->getNamedDBs().findByUpperName(rSym)) returnfalse;
} /* 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 ); returntrue;
}
// 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)) returnfalse;
if (nType == SvNumFormatType::LOGICAL)
{ if (lcl_ParenthesisFollows( aFormula.getStr() + nSrcPos)) returnfalse; // Boolean function instead.
}
if( nType == SvNumFormatType::TEXT ) // HACK: number too big!
SetError( FormulaError::IllegalArgument );
maRawToken.SetDouble( fVal ); returntrue;
}
bool ScCompiler::ParseString()
{ if ( cSymbol[0] != '"' ) returnfalse; const sal_Unicode* p = cSymbol+1; while ( *p )
p++;
sal_Int32 nLen = sal::static_int_cast<sal_Int32>( p - cSymbol - 1 ); if (!nLen || cSymbol[nLen] != '"') returnfalse;
svl::SharedString aSS = rDoc.GetSharedStringPool().intern(OUString(cSymbol+1, nLen-1));
maRawToken.SetString(aSS.getData(), aSS.getDataIgnoreCase()); returntrue;
}
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 static constexpr OUString aErrRef(u"#REF!"_ustr); // 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);
} returnfalse;
}
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();
} returnfalse;
}
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 returnfalse;
mnCurrentSheetTab = -1;
}
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 ) returnfalse; // 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 ) returnfalse;
} // 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 returnfalse;
}
sal_Unicode const * const pTabSep = rName.getStr() + nPos;
sal_Unicode ch2 = pTabSep[1]; // maybe a column identifier if ( !(ch2 == '$' || rtl::isAsciiAlpha( ch2 )) ) returnfalse; 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 ) ) returnfalse; // 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)) returntrue;
// 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)) returntrue; // 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 returnfalse; // can be removed when sure.
sal_Int32 nLen = mnRangeOpPosInSymbol; while (cSymbol[++nLen])
;
cSymbol[mnRangeOpPosInSymbol] = 0;
nSrcPos -= (nLen - mnRangeOpPosInSymbol);
mnRangeOpPosInSymbol = -1;
mbRewind = true; returntrue; // 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] != '\'') returnfalse; // 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)) returntrue; break; default:
; // nothing
}
} returnfalse;
}
// 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"); returnfalse; // bad luck
}
// 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);
bool ScCompiler::ParseNamedRange( const OUString& rUpperName, bool onlyCheck )
{ // ParseNamedRange is called only from NextNewToken, with an upper-case string
if (m_aLambda.nParaPos % 2 == 1 && m_aLambda.nParaCount > m_aLambda.nParaPos)
m_aLambda.aNameSet.insert(aName); else
{ // should already exist the name if (m_aLambda.aNameSet.find(aName) == m_aLambda.aNameSet.end()) returnfalse;
}
svl::SharedString aSS = rDoc.GetSharedStringPool().intern(aName);
maRawToken.SetStringName(aSS.getData(), aSS.getDataIgnoreCase()); returntrue;
} returnfalse;
}
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. */
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.
// 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<tools::Long>(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;
} elseif ( nRow >= aOne.Row() || nMyRow < static_cast<tools::Long>(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<tools::Long>(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;
} elseif ( nRow >= aOne.Row() || nMyRow < static_cast<tools::Long>(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<tools::Long>(aOne.Col()) && nMyRow >= static_cast<tools::Long>(aOne.Row()) )
aAdr = aOne; // upper left takes precedence else
{ if ( nMyCol < static_cast<tools::Long>(aOne.Col()) )
{ // two to the right if ( nMyRow >= static_cast<tools::Long>(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; returntrue;
} else returnfalse;
}
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 = 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;
} elseif (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 = maTableRefs.back().mxToken.get();
assert(p); // not a ScTableRefToken can't be
ScDBData* pDBData = rDoc.GetDBCollection()->getNamedDBs().findByIndex( p->GetIndex()); if (!pDBData) returnfalse;
// 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 ); returntrue;
}
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 ); returntrue;
}
}
}
}
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. returntrue;
} returnfalse;
}
if (!vSpaces.empty())
{
ScRawToken aToken; for (constauto& 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<sal_uInt8>( std::min<sal_Int32>(rSpace.nCount, 255) );
} else
{
aToken.SetOpCode( ocWhitespace );
aToken.whitespace.nCount = static_cast<sal_uInt8>( std::min<sal_Int32>(rSpace.nCount, 255) );
aToken.whitespace.cChar = rSpace.cChar;
} if (!static_cast<ScTokenArray*>(pArr)->AddRawToken( aToken ))
{
SetError(FormulaError::CodeOverflow); returnfalse;
}
}
}
// 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 );
} returntrue;
}
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; returnfalse;
}
// 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 )) returntrue;
}
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 )) returntrue; // Do not attempt to resolve as any other name.
aUpper = aOrg; // for ocBad break; // do; create ocBad token or set error.
}
if (bAsciiNonAlnum)
{
bAsciiUpper = ToUpperAsciiOrI18nIsAscii( aUpper, aOrg); if (cSymbol[0] == '#')
{ // Check for TableRef item specifiers first.
sal_uInt16 nLevel; if (!maTableRefs.empty() && ((nLevel = maTableRefs.back().mnLevel) == 2 || nLevel == 1))
{ if (ParseTableRefItem( aUpper )) returntrue;
}
// This can be either an error constant ... if (ParseErrorConstant( aUpper)) returntrue;
// ... 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 )) returntrue;
}
if (bMayBeFuncName)
{ if (aUpper.isEmpty())
bAsciiUpper = ToUpperAsciiOrI18nIsAscii( aUpper, aOrg); if (ParseOpCode( aUpper, bInArray )) returntrue;
}
// 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.
} returntrue;
}
if (aUpper.isEmpty())
bAsciiUpper = ToUpperAsciiOrI18nIsAscii( aUpper, aOrg);
// ParseBoolean() before ParseValue() to catch inline bools without the kludge // for inline arrays. if (bAllowBooleans && ParseBoolean( aUpper )) returntrue;
if (ParseValue( aUpper )) returntrue;
// 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 )) returntrue;
// 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 )) returntrue; // 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 ); returntrue;
} if (ParseDBRange( aUpper )) returntrue; // If followed by '(' (with or without space inbetween) it can not be a // column/row label. Prevent arbitrary content detection. if (!bMayBeFuncName && ParseColRowName( aUpper )) returntrue; if (bMayBeFuncName && ParseMacro( aUpper )) returntrue; if (bMayBeFuncName && ParseOpCode2( aUpper )) returntrue;
if (ParseLambdaFuncName( aOrg )) returntrue;
} 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; returntrue;
}
if ( meExtendedErrorDetection != EXTENDED_ERROR_DETECTION_NONE )
{ // set an error
SetError( FormulaError::NoName ); if (meExtendedErrorDetection == EXTENDED_ERROR_DETECTION_NAME_BREAK) returnfalse; // 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(); returntrue;
}
if (m_aLambda.bInLambdaFunction && m_aLambda.nBracketPos + 1 == nBrackets)
m_aLambda.nParaPos++;
} 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;
nHighWatermark = nFunction;
}
} 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;
nHighWatermark = nFunction;
}
} 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<ScTokenArray*>(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 && nFunc <= nHighWatermark &&
pFunctionStack[ nFunc ].nSep == 0 &&
pFunctionStack[ nFunc ].eOp == ocWeek) // 2nd week start
{ if ( !static_cast<ScTokenArray*>(pArr)->Add( new FormulaToken( svSep, ocSep)) ||
!static_cast<ScTokenArray*>(pArr)->Add( new FormulaDoubleToken( 1.0)))
{
SetError(FormulaError::CodeOverflow); break;
}
}
} elseif (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<ScTokenArray*>(pArr)->Add( new FormulaToken( svSep, ocSep)) ||
!static_cast<ScTokenArray*>(pArr)->Add( new FormulaDoubleToken( 1.0)))
{
SetError(FormulaError::CodeOverflow); break;
}
++pFunctionStack[ nFunction ].nSep;
}
}
FormulaToken* pNewToken = static_cast<ScTokenArray*>(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);
} elseif (!pNewToken)
{
SetError(FormulaError::CodeOverflow); break;
} elseif (eLastOp == ocRange && pNewToken->GetOpCode() == ocPush && pNewToken->GetType() == svSingleRef)
{ static_cast<ScTokenArray*>(pArr)->MergeRangeReference( aPos);
} elseif (eLastOp == ocDBArea && pNewToken->GetOpCode() == ocTableRefOpen)
{
sal_uInt16 nIdx = pArr->GetLen() - 1; const FormulaToken* pPrev = pArr->PeekPrev( nIdx); if (pPrev && pPrev->GetOpCode() == ocDBArea)
{
ScTableRefToken* pTableRefToken = new ScTableRefToken( pPrev->GetIndex(), ScTableRefToken::TABLE);
maTableRefs.emplace_back( pTableRefToken); // pPrev may be dead hereafter. static_cast<ScTokenArray*>(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);
} elseif ( 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<ScTokenArray> pNew(new ScTokenArray( std::move(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<sal_uInt16>::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<ScTokenArray> 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<ScTokenArray> pNew(new ScTokenArray( std::move(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 );
}
bool ScCompiler::HandleRange()
{
ScTokenArray* pNew; const ScRangeData* pRangeData = GetRangeData( *mpToken); if (pRangeData)
{
FormulaError nErr = pRangeData->GetErrCode(); if( nErr != FormulaError::NONE )
SetError( nErr ); elseif (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();
} returntrue;
}
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); returntrue;
}
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<OUString> 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::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 ); returntrue;
}
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();
} elseif ( 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();
} elseif ( 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); elseif (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); returntrue;
}
// return true if opcode is handled bool ScCompiler::HandleIIOpCodeInternal(FormulaToken* token, FormulaToken*** pppToken, sal_uInt8 nNumParams)
{ if (nNumParams > 0 && *pppToken[0] == nullptr) returnfalse; // Bad expression (see the dummy creation in FormulaCompiler::CompileTokenArray())
// 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 ); returntrue;
} elseif ((nOpCode >= SC_OPCODE_START_UN_OP && nOpCode < SC_OPCODE_STOP_UN_OP)
|| nOpCode == ocPercentSign)
{ if (nNumParams != 1) returnfalse;
if( !ParameterMayBeImplicitIntersection( token, 0 )) returnfalse; if (SkipImplicitIntersectionOptimization(token)) returnfalse;
// 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;
// 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)) returnfalse;
if (nXDelta == nXDeltaSum &&
nYDelta == nYDeltaSum) returnfalse; // 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.
// 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;
// If we are loading, we might be loading each sheet in a separate thread at the same time // It is unsafe to use ShrinkToDataArea on a range that refers to a different sheet whose // columns and rows are still being added to. Even if it is the same sheet, it probably // doesn't make sense to ShrinkToDataArea during document load. staticbool IsSafeToShrinkToDataArea(const ScDocument& rDoc)
{ constbool bIsLoading = !rDoc.GetDocumentShell() || rDoc.GetDocumentShell()->IsLoading(); return !bIsLoading;
}
void ScCompiler::AnnotateTrimOnDoubleRefs()
{ if (!pCode || !(*(pCode - 1))) return;
// OpCode of the "root" operator (which is already in RPN array).
OpCode eOpCode = (*(pCode - 1))->GetOpCode(); // Param number of the "root" operator (which is already in RPN array).
sal_uInt8 nRootParam = (*(pCode - 1))->GetByte(); // eOpCode can be some operator which does not change with operands with or contains zero values. if (eOpCode == ocSum)
{
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(<reference|double>=<reference|double>, <then-clause>)<a some operands with operators / or *>) // 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;
constshort nJumpCount = pTok->GetJump()[0]; if (nJumpCount != 2) // Should have THEN but no ELSE. 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;
}
} elseif (eOpCode == ocSumProduct)
{
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 // SUMPRODUCT(IF(<reference|double>=<reference|double>, <then-clause>)<a some operands with operators / or *>) // such that one of the operands of ocEqual is a double-ref. // Examples of formula that matches this are: // SUMPRODUCT(IF($A:$A=$L12;$D:$D*G:G)) // Also in case of DoubleRef arguments around other Binary operators can be trimmable inside one parameter // of the root operator: // SUMPRODUCT(($D:$D>M47:M47)*($D:$D<M48:M48)*($I:$I=N$41)) 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 (!pTok->IsInForceArray() || nRootParam > 1) break;
FormulaToken* pLHS = *(ppTok - 1);
FormulaToken* pRHS = *(ppTok - 2); if (pLHS && pRHS)
{
StackVar lhsType = pLHS->GetType();
StackVar rhsType = pRHS->GetType(); if (lhsType == svDoubleRef && rhsType == svDoubleRef)
{
pLHS->GetDoubleRef()->SetTrimToData(true);
pRHS->GetDoubleRef()->SetTrimToData(true);
}
}
} break; case ocEqual: case ocAdd: case ocSub: case ocAmpersand: case ocPow: case ocNotEqual: case ocLess: case ocGreater: case ocLessEqual: case ocGreaterEqual: case ocAnd: case ocOr: case ocXor: case ocIntersect:
{ // tdf#160616: Double refs with these operators only // trimmable in case of one parameter if (!pTok->IsInForceArray() || nRootParam > 1) break;
FormulaToken* pLHS = *(ppTok - 1);
FormulaToken* pRHS = *(ppTok - 2); if (pLHS && pRHS)
{
StackVar lhsType = pLHS->GetType();
StackVar rhsType = pRHS->GetType(); if (lhsType == svDoubleRef && (rhsType == svSingleRef || rhsType == svDoubleRef))
{
pLHS->GetDoubleRef()->SetTrimToData(true);
} if (rhsType == svDoubleRef && (lhsType == svSingleRef || lhsType == svDoubleRef))
{
pRHS->GetDoubleRef()->SetTrimToData(true);
}
}
} break; case ocPush: break; case ocClose: if (bTillClose)
{
bTillClose = false;
bCloseTillIf = true;
} else return; break; case ocIf:
{ if (!bCloseTillIf) return;
if (!pTok->IsInForceArray()) return;
constshort nJumpCount = pTok->GetJump()[0]; if (nJumpCount != 2) // Should have THEN but no ELSE. return;
FormulaToken* pLHS = *(ppTok - 2);
FormulaToken* pRHS = *(ppTok - 3);
StackVar lhsType = pLHS->GetType();
StackVar rhsType = pRHS->GetType(); if (lhsType == svDoubleRef && (rhsType == svSingleRef || rhsType == svDouble))
{
pLHS->GetDoubleRef()->SetTrimToData(true);
} if ((lhsType == svSingleRef || lhsType == svDouble) && rhsType == svDoubleRef)
{
pRHS->GetDoubleRef()->SetTrimToData(true);
} return;
} break; default: return;
}
--ppTok;
}
} elseif (eOpCode == ocSubTotal)
{ // tdf#164843: Double references from relative named ranges can point to large // ranges (MAXCOL/MAXROW) and because of that some function evaluation // like SubTotal can be extremely slow when we call ScTable::CompileHybridFormula // with these large ranges. Since all the SubTotal functions ignore empty cells // its worth to optimize and trim the double references in SubTotal functions.
FormulaToken** ppTok = pCode - 2; while (*ppTok)
{
FormulaToken* pTok = *ppTok; if (pTok->GetType() == svDoubleRef)
{
ScComplexRefData* pRefData = pTok->GetDoubleRef(); // do no set pRefData->SetTrimToData(true); because we need to trim here if possible
ScRange rRange = pRefData->toAbs(rDoc, aPos);
SCCOL nTempStartCol = rRange.aStart.Col();
SCROW nTempStartRow = rRange.aStart.Row();
SCCOL nTempEndCol = rRange.aEnd.Col();
SCROW nTempEndRow = rRange.aEnd.Row(); if (IsSafeToShrinkToDataArea(rDoc))
rDoc.ShrinkToDataArea(rRange.aStart.Tab(), nTempStartCol, nTempStartRow, nTempEndCol, nTempEndRow); // check if range is still valid if (nTempStartRow <= nTempEndRow && nTempStartCol <= nTempEndCol)
{
rRange.aStart.Set(nTempStartCol, nTempStartRow, rRange.aStart.Tab());
rRange.aEnd.Set(nTempEndCol, nTempEndRow, rRange.aEnd.Tab()); if (rRange.IsValid())
pRefData->SetRange(rDoc.GetSheetLimits(), rRange, aPos);
}
}
--ppTok;
}
}
}
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.228Bemerkung:
(vorverarbeitet am 2026-05-02)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.