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