Resolves: tdf#162093 TableRef item specifier may occur standalone
[LibreOffice.git] / sc / source / core / tool / compiler.cxx
blobd26f07f5c664a27e92af70d8c29582a98007f62a
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <config_features.h>
22 #include <compiler.hxx>
24 #include <mutex>
25 #include <vcl/svapp.hxx>
26 #include <vcl/settings.hxx>
27 #include <sfx2/app.hxx>
28 #include <sfx2/objsh.hxx>
29 #include <basic/sbmeth.hxx>
30 #include <basic/sbstar.hxx>
31 #include <svl/numformat.hxx>
32 #include <svl/zforlist.hxx>
33 #include <svl/sharedstringpool.hxx>
34 #include <sal/log.hxx>
35 #include <o3tl/safeint.hxx>
36 #include <o3tl/string_view.hxx>
37 #include <osl/diagnose.h>
38 #include <rtl/character.hxx>
39 #include <unotools/charclass.hxx>
40 #include <unotools/configmgr.hxx>
41 #include <com/sun/star/lang/Locale.hpp>
42 #include <com/sun/star/sheet/FormulaOpCodeMapEntry.hpp>
43 #include <com/sun/star/sheet/FormulaLanguage.hpp>
44 #include <com/sun/star/i18n/KParseTokens.hpp>
45 #include <com/sun/star/i18n/KParseType.hpp>
46 #include <comphelper/processfactory.hxx>
47 #include <comphelper/string.hxx>
48 #include <unotools/transliterationwrapper.hxx>
49 #include <tools/urlobj.hxx>
50 #include <rtl/math.hxx>
51 #include <rtl/ustring.hxx>
52 #include <stdlib.h>
53 #include <rangenam.hxx>
54 #include <dbdata.hxx>
55 #include <document.hxx>
56 #include <docsh.hxx>
57 #include <callform.hxx>
58 #include <addincol.hxx>
59 #include <refupdat.hxx>
60 #include <globstr.hrc>
61 #include <scresid.hxx>
62 #include <formulacell.hxx>
63 #include <dociter.hxx>
64 #include <docoptio.hxx>
65 #include <formula/errorcodes.hxx>
66 #include <parclass.hxx>
67 #include <autonamecache.hxx>
68 #include <externalrefmgr.hxx>
69 #include <rangeutl.hxx>
70 #include <convuno.hxx>
71 #include <tokenuno.hxx>
72 #include <formulaparserpool.hxx>
73 #include <tokenarray.hxx>
74 #include <scmatrix.hxx>
75 #include <tokenstringcontext.hxx>
76 #include <officecfg/Office/Common.hxx>
77 #include <sfx2/linkmgr.hxx>
78 #include <interpre.hxx>
80 using namespace formula;
81 using namespace ::com::sun::star;
82 using ::std::vector;
84 const CharClass* ScCompiler::pCharClassEnglish = nullptr;
85 const CharClass* ScCompiler::pCharClassLocalized = nullptr;
86 const ScCompiler::Convention* ScCompiler::pConventions[ ] = { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr };
88 namespace {
90 enum ScanState
92 ssGetChar,
93 ssGetBool,
94 ssGetValue,
95 ssGetString,
96 ssSkipString,
97 ssGetIdent,
98 ssGetReference,
99 ssSkipReference,
100 ssGetErrorConstant,
101 ssGetTableRefItem,
102 ssGetTableRefColumn,
103 ssStop
108 static const char* pInternal[2] = { "TTT", "__DEBUG_VAR" };
110 using namespace ::com::sun::star::i18n;
112 void ScCompiler::fillFromAddInMap( const NonConstOpCodeMapPtr& xMap,FormulaGrammar::Grammar _eGrammar ) const
114 size_t nSymbolOffset;
115 switch( _eGrammar )
117 // XFunctionAccess and XCell::setFormula()/getFormula() API always used
118 // PODF grammar symbols, keep it.
119 case FormulaGrammar::GRAM_API:
120 case FormulaGrammar::GRAM_PODF:
121 nSymbolOffset = offsetof( AddInMap, pUpper);
122 break;
123 default:
124 case FormulaGrammar::GRAM_ODFF:
125 nSymbolOffset = offsetof( AddInMap, pODFF);
126 break;
127 case FormulaGrammar::GRAM_ENGLISH:
128 nSymbolOffset = offsetof( AddInMap, pEnglish);
129 break;
131 const AddInMap* const pStop = g_aAddInMap + GetAddInMapCount();
132 for (const AddInMap* pMap = g_aAddInMap; pMap < pStop; ++pMap)
134 char const * const * ppSymbol =
135 reinterpret_cast< char const * const * >(
136 reinterpret_cast< char const * >(pMap) + nSymbolOffset);
137 xMap->putExternal( OUString::createFromAscii( *ppSymbol),
138 OUString::createFromAscii( pMap->pOriginal));
140 if (_eGrammar == FormulaGrammar::GRAM_API)
142 // Add English names additionally to programmatic names, so they
143 // can be used in XCell::setFormula() non-localized API calls.
144 // Note the reverse map will still deliver programmatic names for
145 // XCell::getFormula().
146 nSymbolOffset = offsetof( AddInMap, pEnglish);
147 for (const AddInMap* pMap = g_aAddInMap; pMap < pStop; ++pMap)
149 char const * const * ppSymbol =
150 reinterpret_cast< char const * const * >(
151 reinterpret_cast< char const * >(pMap) + nSymbolOffset);
152 xMap->putExternal( OUString::createFromAscii( *ppSymbol),
153 OUString::createFromAscii( pMap->pOriginal));
158 void ScCompiler::fillFromAddInCollectionUpperName( const NonConstOpCodeMapPtr& xMap ) const
160 ScUnoAddInCollection* pColl = ScGlobal::GetAddInCollection();
161 tools::Long nCount = pColl->GetFuncCount();
162 for (tools::Long i=0; i < nCount; ++i)
164 const ScUnoAddInFuncData* pFuncData = pColl->GetFuncData(i);
165 if (pFuncData)
166 xMap->putExternalSoftly( pFuncData->GetUpperName(),
167 pFuncData->GetOriginalName());
171 void ScCompiler::fillFromAddInCollectionEnglishName( const NonConstOpCodeMapPtr& xMap ) const
173 ScUnoAddInCollection* pColl = ScGlobal::GetAddInCollection();
174 tools::Long nCount = pColl->GetFuncCount();
175 for (tools::Long i=0; i < nCount; ++i)
177 const ScUnoAddInFuncData* pFuncData = pColl->GetFuncData(i);
178 if (pFuncData)
180 const OUString aName( pFuncData->GetUpperEnglish());
181 if (!aName.isEmpty())
182 xMap->putExternalSoftly( aName, pFuncData->GetOriginalName());
183 else
184 xMap->putExternalSoftly( pFuncData->GetUpperName(),
185 pFuncData->GetOriginalName());
190 void ScCompiler::DeInit()
192 if (pCharClassEnglish)
194 delete pCharClassEnglish;
195 pCharClassEnglish = nullptr;
197 if (pCharClassLocalized)
199 delete pCharClassLocalized;
200 pCharClassLocalized = nullptr;
204 bool ScCompiler::IsEnglishSymbol( const OUString& rName )
206 // function names are always case-insensitive
207 OUString aUpper = GetCharClassEnglish()->uppercase(rName);
209 // 1. built-in function name
210 formula::FormulaCompiler aCompiler;
211 OpCode eOp = aCompiler.GetEnglishOpCode( aUpper );
212 if ( eOp != ocNone )
214 return true;
216 // 2. old add in functions
217 if (ScGlobal::GetLegacyFuncCollection()->findByName(aUpper))
219 return true;
222 // 3. new (uno) add in functions
223 OUString aIntName = ScGlobal::GetAddInCollection()->FindFunction(aUpper, false);
224 return !aIntName.isEmpty(); // no valid function name
227 static std::mutex& getCharClassMutex()
229 static std::mutex aMutex;
230 return aMutex;
233 const CharClass* ScCompiler::GetCharClassEnglish()
235 if (!pCharClassEnglish)
237 std::scoped_lock aGuard(getCharClassMutex());
238 if (!pCharClassEnglish)
240 pCharClassEnglish = new CharClass( ::comphelper::getProcessComponentContext(),
241 LanguageTag( LANGUAGE_ENGLISH_US));
244 return pCharClassEnglish;
247 const CharClass* ScCompiler::GetCharClassLocalized()
249 if (!pCharClassLocalized)
251 // Switching UI language requires restart; if not, we would have to
252 // keep track of that.
253 std::scoped_lock aGuard(getCharClassMutex());
254 if (!pCharClassLocalized)
256 pCharClassLocalized = new CharClass( ::comphelper::getProcessComponentContext(),
257 Application::GetSettings().GetUILanguageTag());
260 return pCharClassLocalized;
263 void ScCompiler::SetGrammar( const FormulaGrammar::Grammar eGrammar )
265 assert( eGrammar != FormulaGrammar::GRAM_UNSPECIFIED && "ScCompiler::SetGrammar: don't pass FormulaGrammar::GRAM_UNSPECIFIED");
266 if (eGrammar == GetGrammar())
267 return; // nothing to be done
269 if( eGrammar == FormulaGrammar::GRAM_EXTERNAL )
271 meGrammar = eGrammar;
272 mxSymbols = GetFinalOpCodeMap( css::sheet::FormulaLanguage::NATIVE);
274 else
276 FormulaGrammar::Grammar eMyGrammar = eGrammar;
277 const sal_Int32 nFormulaLanguage = FormulaGrammar::extractFormulaLanguage( eMyGrammar);
278 OpCodeMapPtr xMap = GetFinalOpCodeMap( nFormulaLanguage);
279 OSL_ENSURE( xMap, "ScCompiler::SetGrammar: unknown formula language");
280 if (!xMap)
282 xMap = GetFinalOpCodeMap( css::sheet::FormulaLanguage::NATIVE);
283 eMyGrammar = xMap->getGrammar();
286 // Save old grammar for call to SetGrammarAndRefConvention().
287 FormulaGrammar::Grammar eOldGrammar = GetGrammar();
288 // This also sets the grammar associated with the map!
289 SetFormulaLanguage( xMap);
291 // Override if necessary.
292 if (eMyGrammar != GetGrammar())
293 SetGrammarAndRefConvention( eMyGrammar, eOldGrammar);
297 // Unclear how this was intended to be refreshed when the
298 // grammar or sheet count is changed ? Ideally this would be
299 // a method on Document that would globally cache these.
300 std::vector<OUString> &ScCompiler::GetSetupTabNames() const
302 std::vector<OUString> &rTabNames = const_cast<ScCompiler *>(this)->maTabNames;
304 if (rTabNames.empty())
306 rTabNames = rDoc.GetAllTableNames();
307 for (auto& rTabName : rTabNames)
308 ScCompiler::CheckTabQuotes(rTabName, formula::FormulaGrammar::extractRefConvention(meGrammar));
311 return rTabNames;
314 void ScCompiler::SetFormulaLanguage( const ScCompiler::OpCodeMapPtr & xMap )
316 if (!xMap)
317 return;
319 mxSymbols = xMap;
320 if (mxSymbols->isEnglish())
321 pCharClass = GetCharClassEnglish();
322 else
323 pCharClass = GetCharClassLocalized();
325 // The difference is needed for an uppercase() call that usually does not
326 // result in different strings but for a few languages like Turkish;
327 // though even de-DE and de-CH may differ in ß/SS handling..
328 // At least don't care if both are English.
329 // The current locale is more likely to not be "en" so check first.
330 const LanguageTag& rLT1 = ScGlobal::getCharClass().getLanguageTag();
331 const LanguageTag& rLT2 = pCharClass->getLanguageTag();
332 mbCharClassesDiffer = (rLT1 != rLT2 && (rLT1.getLanguage() != "en" || rLT2.getLanguage() != "en"));
334 SetGrammarAndRefConvention( mxSymbols->getGrammar(), GetGrammar());
337 void ScCompiler::SetGrammarAndRefConvention(
338 const FormulaGrammar::Grammar eNewGrammar, const FormulaGrammar::Grammar eOldGrammar )
340 meGrammar = eNewGrammar; // SetRefConvention needs the new grammar set!
341 FormulaGrammar::AddressConvention eConv = FormulaGrammar::extractRefConvention( meGrammar);
342 if (eConv == FormulaGrammar::CONV_UNSPECIFIED && eOldGrammar == FormulaGrammar::GRAM_UNSPECIFIED)
343 SetRefConvention( rDoc.GetAddressConvention());
344 else
345 SetRefConvention( eConv );
348 OUString ScCompiler::FindAddInFunction( const OUString& rUpperName, bool bLocalFirst ) const
350 return ScGlobal::GetAddInCollection()->FindFunction(rUpperName, bLocalFirst); // bLocalFirst=false for english
353 ScCompiler::Convention::~Convention()
357 ScCompiler::Convention::Convention( FormulaGrammar::AddressConvention eConv )
359 meConv( eConv )
361 int i;
362 ScCharFlags *t= new ScCharFlags [128];
364 ScCompiler::pConventions[ meConv ] = this;
365 mpCharTable.reset( t );
367 for (i = 0; i < 128; i++)
368 t[i] = ScCharFlags::Illegal;
370 // Allow tabs/newlines.
371 // Allow saving whitespace as is (as per OpenFormula specification v.1.2, clause 5.14 "Whitespace").
372 /* tab */ t[ 9] = ScCharFlags::CharDontCare | ScCharFlags::WordSep | ScCharFlags::ValueSep;
373 /* lf */ t[10] = ScCharFlags::CharDontCare | ScCharFlags::WordSep | ScCharFlags::ValueSep;
374 /* cr */ t[13] = ScCharFlags::CharDontCare | ScCharFlags::WordSep | ScCharFlags::ValueSep;
376 /* */ t[32] = ScCharFlags::CharDontCare | ScCharFlags::WordSep | ScCharFlags::ValueSep;
377 /* ! */ t[33] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep;
378 if (FormulaGrammar::CONV_ODF == meConv)
379 /* ! */ t[33] |= ScCharFlags::OdfLabelOp;
380 /* " */ t[34] = ScCharFlags::CharString | ScCharFlags::StringSep;
381 /* # */ t[35] = ScCharFlags::WordSep | ScCharFlags::CharErrConst;
382 /* $ */ t[36] = ScCharFlags::CharWord | ScCharFlags::Word | ScCharFlags::CharIdent | ScCharFlags::Ident;
383 if (FormulaGrammar::CONV_ODF == meConv)
384 /* $ */ t[36] |= ScCharFlags::OdfNameMarker;
385 /* % */ t[37] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep;
386 /* & */ t[38] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep;
387 /* ' */ t[39] = ScCharFlags::NameSep;
388 /* ( */ t[40] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep;
389 /* ) */ t[41] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep;
390 /* * */ t[42] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep;
391 /* + */ t[43] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueExp | ScCharFlags::ValueSign;
392 /* , */ t[44] = ScCharFlags::CharValue | ScCharFlags::Value;
393 /* - */ t[45] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueExp | ScCharFlags::ValueSign;
394 /* . */ t[46] = ScCharFlags::Word | ScCharFlags::CharValue | ScCharFlags::Value | ScCharFlags::Ident | ScCharFlags::Name;
395 /* / */ t[47] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep;
397 for (i = 48; i < 58; i++)
398 /* 0-9 */ t[i] = ScCharFlags::CharValue | ScCharFlags::Word | ScCharFlags::Value | ScCharFlags::ValueExp | ScCharFlags::ValueValue | ScCharFlags::Ident | ScCharFlags::Name;
400 /* : */ t[58] = ScCharFlags::Char | ScCharFlags::Word;
401 /* ; */ t[59] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep;
402 /* < */ t[60] = ScCharFlags::CharBool | ScCharFlags::WordSep | ScCharFlags::ValueSep;
403 /* = */ t[61] = ScCharFlags::Char | ScCharFlags::Bool | ScCharFlags::WordSep | ScCharFlags::ValueSep;
404 /* > */ t[62] = ScCharFlags::CharBool | ScCharFlags::Bool | ScCharFlags::WordSep | ScCharFlags::ValueSep;
405 /* ? */ t[63] = ScCharFlags::CharWord | ScCharFlags::Word | ScCharFlags::Name;
406 /* @ */ // FREE
408 for (i = 65; i < 91; i++)
409 /* A-Z */ t[i] = ScCharFlags::CharWord | ScCharFlags::Word | ScCharFlags::CharIdent | ScCharFlags::Ident | ScCharFlags::CharName | ScCharFlags::Name;
411 if (FormulaGrammar::CONV_ODF == meConv)
413 /* [ */ t[91] = ScCharFlags::OdfLBracket;
414 /* \ */ // FREE
415 /* ] */ t[93] = ScCharFlags::OdfRBracket;
417 else if (FormulaGrammar::CONV_OOO == meConv)
419 /* [ */ t[91] = ScCharFlags::Char;
420 /* \ */ // FREE
421 /* ] */ t[93] = ScCharFlags::Char;
423 else if (FormulaGrammar::CONV_XL_OOX == meConv)
425 /* [ */ t[91] = ScCharFlags::Char | ScCharFlags::CharIdent;
426 /* \ */ // FREE
427 /* ] */ t[93] = ScCharFlags::Char | ScCharFlags::Ident;
429 else if (FormulaGrammar::CONV_XL_A1 == meConv)
431 /* [ */ t[91] = ScCharFlags::Char;
432 /* \ */ // FREE
433 /* ] */ t[93] = ScCharFlags::Char;
435 else if( FormulaGrammar::CONV_XL_R1C1 == meConv )
437 /* [ */ t[91] = ScCharFlags::Ident;
438 /* \ */ // FREE
439 /* ] */ t[93] = ScCharFlags::Ident;
441 else
443 /* [ */ // FREE
444 /* \ */ // FREE
445 /* ] */ // FREE
448 /* ^ */ t[94] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep;
449 /* _ */ t[95] = ScCharFlags::CharWord | ScCharFlags::Word | ScCharFlags::CharIdent | ScCharFlags::Ident | ScCharFlags::CharName | ScCharFlags::Name;
450 /* ` */ // FREE
452 for (i = 97; i < 123; i++)
453 /* a-z */ t[i] = ScCharFlags::CharWord | ScCharFlags::Word | ScCharFlags::CharIdent | ScCharFlags::Ident | ScCharFlags::CharName | ScCharFlags::Name;
455 /* { */ t[123] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep; // array open
456 /* | */ t[124] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep; // array row sep (Should be OOo specific)
457 /* } */ t[125] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep; // array close
458 /* ~ */ t[126] = ScCharFlags::Char; // OOo specific
459 /* 127 */ // FREE
461 if( !(FormulaGrammar::CONV_XL_A1 == meConv || FormulaGrammar::CONV_XL_R1C1 == meConv || FormulaGrammar::CONV_XL_OOX == meConv) )
462 return;
464 /* */ t[32] |= ScCharFlags::Word;
465 /* ! */ t[33] |= ScCharFlags::Ident | ScCharFlags::Word;
466 /* " */ t[34] |= ScCharFlags::Word;
467 /* # */ t[35] &= ~ScCharFlags::WordSep;
468 /* # */ t[35] |= ScCharFlags::Word;
469 /* % */ t[37] |= ScCharFlags::Word;
470 /* & */ t[38] |= ScCharFlags::Word;
471 /* ' */ t[39] |= ScCharFlags::Word;
472 /* ( */ t[40] |= ScCharFlags::Word;
473 /* ) */ t[41] |= ScCharFlags::Word;
474 /* * */ t[42] |= ScCharFlags::Word;
475 /* + */ t[43] |= ScCharFlags::Word;
476 #if 0 /* this really needs to be locale specific. */
477 /* , */ t[44] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep;
478 #else
479 /* , */ t[44] |= ScCharFlags::Word;
480 #endif
481 /* - */ t[45] |= ScCharFlags::Word;
483 /* ; */ t[59] |= ScCharFlags::Word;
484 /* < */ t[60] |= ScCharFlags::Word;
485 /* = */ t[61] |= ScCharFlags::Word;
486 /* > */ t[62] |= ScCharFlags::Word;
487 /* ? */ // question really is not permitted in sheet name
488 /* @ */ t[64] |= ScCharFlags::Word;
489 /* [ */ t[91] |= ScCharFlags::Word;
490 /* ] */ t[93] |= ScCharFlags::Word;
491 /* { */ t[123]|= ScCharFlags::Word;
492 /* | */ t[124]|= ScCharFlags::Word;
493 /* } */ t[125]|= ScCharFlags::Word;
494 /* ~ */ t[126]|= ScCharFlags::Word;
497 static bool lcl_isValidQuotedText( std::u16string_view rFormula, size_t nSrcPos, ParseResult& rRes )
499 // Tokens that start at ' can have anything in them until a final '
500 // but '' marks an escaped '
501 // We've earlier guaranteed that a string containing '' will be
502 // surrounded by '
503 if (nSrcPos < rFormula.size() && rFormula[nSrcPos] == '\'')
505 size_t nPos = nSrcPos+1;
506 while (nPos < rFormula.size())
508 if (rFormula[nPos] == '\'')
510 if ( (nPos+1 == rFormula.size()) || (rFormula[nPos+1] != '\'') )
512 rRes.TokenType = KParseType::SINGLE_QUOTE_NAME;
513 rRes.EndPos = nPos+1;
514 return true;
516 ++nPos;
518 ++nPos;
522 return false;
525 static bool lcl_parseExternalName(
526 const OUString& rSymbol,
527 OUString& rFile,
528 OUString& rName,
529 const sal_Unicode cSep,
530 const ScDocument& rDoc,
531 const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks )
533 /* TODO: future versions will have to support sheet-local names too, thus
534 * return a possible sheet name as well. */
535 const sal_Unicode* const pStart = rSymbol.getStr();
536 const sal_Unicode* p = pStart;
537 sal_Int32 nLen = rSymbol.getLength();
538 OUString aTmpFile;
539 OUStringBuffer aTmpName;
540 sal_Int32 i = 0;
541 bool bInName = false;
542 if (cSep == '!')
544 // For XL use existing parser that resolves bracketed and quoted and
545 // indexed external document names.
546 ScRange aRange;
547 OUString aStartTabName, aEndTabName;
548 ScRefFlags nFlags = ScRefFlags::ZERO;
549 p = aRange.Parse_XL_Header( p, rDoc, aTmpFile, aStartTabName,
550 aEndTabName, nFlags, true, pExternalLinks );
551 if (!p || p == pStart)
552 return false;
553 i = sal_Int32(p - pStart);
555 for ( ; i < nLen; ++i, ++p)
557 sal_Unicode c = *p;
558 if (i == 0)
560 if (c == '.' || c == cSep)
561 return false;
563 if (c == '\'')
565 // Move to the next char and loop until the second single
566 // quote.
567 sal_Unicode cPrev = c;
568 ++i; ++p;
569 for (sal_Int32 j = i; j < nLen; ++j, ++p)
571 c = *p;
572 if (c == '\'')
574 if (j == i)
576 // empty quote e.g. (=''!Name)
577 return false;
580 if (cPrev == '\'')
582 // two consecutive quotes equal a single quote in
583 // the file name.
584 aTmpFile += OUStringChar(c);
585 cPrev = 'a';
587 else
588 cPrev = c;
590 continue;
593 if (cPrev == '\'' && j != i)
595 // this is not a quote but the previous one is. This
596 // ends the parsing of the quoted segment. At this
597 // point, the current char must equal the separator
598 // char.
600 i = j;
601 bInName = true;
602 aTmpName.append(c); // Keep the separator as part of the name.
603 break;
605 aTmpFile += OUStringChar(c);
606 cPrev = c;
609 if (!bInName)
611 // premature ending of the quoted segment.
612 return false;
615 if (c != cSep)
617 // only the separator is allowed after the closing quote.
618 return false;
621 continue;
625 if (bInName)
627 if (c == cSep)
629 // A second separator ? Not a valid external name.
630 return false;
632 aTmpName.append(c);
634 else
636 if (c == cSep)
638 bInName = true;
639 aTmpName.append(c); // Keep the separator as part of the name.
641 else
645 if (rtl::isAsciiAlphanumeric(c))
646 // allowed.
647 break;
649 if (c > 128)
650 // non-ASCII character is allowed.
651 break;
653 bool bValid = false;
654 switch (c)
656 case '_':
657 case '-':
658 case '.':
659 // these special characters are allowed.
660 bValid = true;
661 break;
663 if (bValid)
664 break;
666 return false;
668 while (false);
669 aTmpFile += OUStringChar(c);
674 if (!bInName)
676 // No name found - most likely the symbol has no '!'s.
677 return false;
680 sal_Int32 nNameLen = aTmpName.getLength();
681 if (nNameLen < 2)
683 // Name must be at least 2-char long (separator plus name).
684 return false;
687 if (aTmpName[0] != cSep)
689 // 1st char of the name must equal the separator.
690 return false;
693 if (aTmpName[nNameLen-1] == '!')
695 // Check against #REF!.
696 if (OUString::unacquired(aTmpName).equalsIgnoreAsciiCase("#REF!"))
697 return false;
700 rFile = aTmpFile;
701 rName = aTmpName.makeStringAndClear().copy(1); // Skip the first char as it is always the separator.
702 return true;
705 static OUString lcl_makeExternalNameStr(const OUString& rFile, const OUString& rName,
706 const sal_Unicode cSep, bool bODF )
708 OUString aEscQuote(u"''"_ustr);
709 OUString aFile(rFile.replaceAll("'", aEscQuote));
710 OUString aName(rName);
711 if (bODF)
712 aName = aName.replaceAll("'", aEscQuote);
713 OUStringBuffer aBuf(aFile.getLength() + aName.getLength() + 9);
714 if (bODF)
715 aBuf.append( '[');
716 aBuf.append( "'" + aFile + "'" + OUStringChar(cSep) );
717 if (bODF)
718 aBuf.append( "$$'" );
719 aBuf.append( aName);
720 if (bODF)
721 aBuf.append( "']" );
722 return aBuf.makeStringAndClear();
725 static bool lcl_getLastTabName( OUString& rTabName2, const OUString& rTabName1,
726 const vector<OUString>& rTabNames, const ScRange& rRef )
728 SCTAB nTabSpan = rRef.aEnd.Tab() - rRef.aStart.Tab();
729 if (nTabSpan > 0)
731 size_t nCount = rTabNames.size();
732 vector<OUString>::const_iterator itrBeg = rTabNames.begin(), itrEnd = rTabNames.end();
733 vector<OUString>::const_iterator itr = ::std::find(itrBeg, itrEnd, rTabName1);
734 if (itr == rTabNames.end())
736 rTabName2 = ScResId(STR_NO_REF_TABLE);
737 return false;
740 size_t nDist = ::std::distance(itrBeg, itr);
741 if (nDist + static_cast<size_t>(nTabSpan) >= nCount)
743 rTabName2 = ScResId(STR_NO_REF_TABLE);
744 return false;
747 rTabName2 = rTabNames[nDist+nTabSpan];
749 else
750 rTabName2 = rTabName1;
752 return true;
755 namespace {
757 struct Convention_A1 : public ScCompiler::Convention
759 explicit Convention_A1( FormulaGrammar::AddressConvention eConv ) : ScCompiler::Convention( eConv ) { }
760 static void MakeColStr( const ScSheetLimits& rLimits, OUStringBuffer& rBuffer, SCCOL nCol );
761 static void MakeRowStr( const ScSheetLimits& rLimits, OUStringBuffer& rBuffer, SCROW nRow );
763 ParseResult parseAnyToken( const OUString& rFormula,
764 sal_Int32 nSrcPos,
765 const CharClass* pCharClass,
766 bool bGroupSeparator) const override
768 ParseResult aRet;
769 if ( lcl_isValidQuotedText(rFormula, nSrcPos, aRet) )
770 return aRet;
772 constexpr sal_Int32 nStartFlags = KParseTokens::ANY_LETTER_OR_NUMBER |
773 KParseTokens::ASC_UNDERSCORE | KParseTokens::ASC_DOLLAR;
774 constexpr sal_Int32 nContFlags = nStartFlags | KParseTokens::ASC_DOT;
775 // '?' allowed in range names because of Xcl :-/
776 static constexpr OUString aAddAllowed(u"?#"_ustr);
777 return pCharClass->parseAnyToken( rFormula,
778 nSrcPos, nStartFlags, aAddAllowed,
779 (bGroupSeparator ? nContFlags | KParseTokens::GROUP_SEPARATOR_IN_NUMBER : nContFlags),
780 aAddAllowed );
783 virtual ScCharFlags getCharTableFlags( sal_Unicode c, sal_Unicode /*cLast*/ ) const override
785 return mpCharTable[static_cast<sal_uInt8>(c)];
791 void Convention_A1::MakeColStr( const ScSheetLimits& rLimits, OUStringBuffer& rBuffer, SCCOL nCol )
793 if ( !rLimits.ValidCol(nCol) )
794 rBuffer.append(ScResId(STR_NO_REF_TABLE));
795 else
796 ::ScColToAlpha( rBuffer, nCol);
799 void Convention_A1::MakeRowStr( const ScSheetLimits& rLimits, OUStringBuffer& rBuffer, SCROW nRow )
801 if ( !rLimits.ValidRow(nRow) )
802 rBuffer.append(ScResId(STR_NO_REF_TABLE));
803 else
804 rBuffer.append(sal_Int32(nRow + 1));
807 namespace {
809 struct ConventionOOO_A1 : public Convention_A1
811 ConventionOOO_A1() : Convention_A1 (FormulaGrammar::CONV_OOO) { }
812 explicit ConventionOOO_A1( FormulaGrammar::AddressConvention eConv ) : Convention_A1 (eConv) { }
814 static void MakeTabStr( OUStringBuffer &rBuf, const std::vector<OUString>& rTabNames, SCTAB nTab )
816 if (o3tl::make_unsigned(nTab) >= rTabNames.size())
817 rBuf.append(ScResId(STR_NO_REF_TABLE));
818 else
819 rBuf.append(rTabNames[nTab]);
820 rBuf.append('.');
823 enum SingletonDisplay
825 SINGLETON_NONE,
826 SINGLETON_COL,
827 SINGLETON_ROW
830 static void MakeOneRefStrImpl(
831 const ScSheetLimits& rLimits, OUStringBuffer& rBuffer,
832 std::u16string_view rErrRef, const std::vector<OUString>& rTabNames,
833 const ScSingleRefData& rRef, const ScAddress& rAbsRef,
834 bool bForceTab, bool bODF, SingletonDisplay eSingletonDisplay )
836 if( rRef.IsFlag3D() || bForceTab )
838 if (!ValidTab(rAbsRef.Tab()) || rRef.IsTabDeleted())
840 if (!rRef.IsTabRel())
841 rBuffer.append('$');
842 rBuffer.append(rErrRef);
843 rBuffer.append('.');
845 else
847 if (!rRef.IsTabRel())
848 rBuffer.append('$');
849 MakeTabStr(rBuffer, rTabNames, rAbsRef.Tab());
852 else if (bODF)
853 rBuffer.append('.');
855 if (eSingletonDisplay != SINGLETON_ROW)
857 if (!rRef.IsColRel())
858 rBuffer.append('$');
859 if (!rLimits.ValidCol(rAbsRef.Col()) || rRef.IsColDeleted())
860 rBuffer.append(rErrRef);
861 else
862 MakeColStr(rLimits, rBuffer, rAbsRef.Col());
865 if (eSingletonDisplay != SINGLETON_COL)
867 if (!rRef.IsRowRel())
868 rBuffer.append('$');
869 if (!rLimits.ValidRow(rAbsRef.Row()) || rRef.IsRowDeleted())
870 rBuffer.append(rErrRef);
871 else
872 MakeRowStr(rLimits, rBuffer, rAbsRef.Row());
876 static SingletonDisplay getSingletonDisplay( const ScSheetLimits& rLimits, const ScAddress& rAbs1, const ScAddress& rAbs2,
877 const ScComplexRefData& rRef, bool bFromRangeName )
879 // If any part is error, display as such.
880 if (!rLimits.ValidCol(rAbs1.Col()) || rRef.Ref1.IsColDeleted() || !rLimits.ValidRow(rAbs1.Row()) || rRef.Ref1.IsRowDeleted() ||
881 !rLimits.ValidCol(rAbs2.Col()) || rRef.Ref2.IsColDeleted() || !rLimits.ValidRow(rAbs2.Row()) || rRef.Ref2.IsRowDeleted())
882 return SINGLETON_NONE;
884 // A:A or $A:$A or A:$A or $A:A
885 if (rRef.IsEntireCol(rLimits))
886 return SINGLETON_COL;
888 // Same if not in named expression and both rows of entire columns are
889 // relative references.
890 if (!bFromRangeName && rAbs1.Row() == 0 && rAbs2.Row() == rLimits.mnMaxRow &&
891 rRef.Ref1.IsRowRel() && rRef.Ref2.IsRowRel())
892 return SINGLETON_COL;
894 // 1:1 or $1:$1 or 1:$1 or $1:1
895 if (rRef.IsEntireRow(rLimits))
896 return SINGLETON_ROW;
898 // Same if not in named expression and both columns of entire rows are
899 // relative references.
900 if (!bFromRangeName && rAbs1.Col() == 0 && rAbs2.Col() == rLimits.mnMaxCol &&
901 rRef.Ref1.IsColRel() && rRef.Ref2.IsColRel())
902 return SINGLETON_ROW;
904 return SINGLETON_NONE;
907 virtual void makeRefStr(
908 ScSheetLimits& rLimits,
909 OUStringBuffer& rBuffer,
910 formula::FormulaGrammar::Grammar /*eGram*/,
911 const ScAddress& rPos,
912 const OUString& rErrRef, const std::vector<OUString>& rTabNames,
913 const ScComplexRefData& rRef,
914 bool bSingleRef,
915 bool bFromRangeName ) const override
917 // In case absolute/relative positions weren't separately available:
918 // transform relative to absolute!
919 ScAddress aAbs1 = rRef.Ref1.toAbs(rLimits, rPos), aAbs2;
920 if( !bSingleRef )
921 aAbs2 = rRef.Ref2.toAbs(rLimits, rPos);
923 SingletonDisplay eSingleton = bSingleRef ? SINGLETON_NONE :
924 getSingletonDisplay( rLimits, aAbs1, aAbs2, rRef, bFromRangeName);
925 MakeOneRefStrImpl(rLimits, rBuffer, rErrRef, rTabNames, rRef.Ref1, aAbs1, false, false, eSingleton);
926 if (!bSingleRef)
928 rBuffer.append(':');
929 MakeOneRefStrImpl(rLimits, rBuffer, rErrRef, rTabNames, rRef.Ref2, aAbs2, aAbs1.Tab() != aAbs2.Tab(), false,
930 eSingleton);
934 virtual sal_Unicode getSpecialSymbol( SpecialSymbolType eSymType ) const override
936 switch (eSymType)
938 case ScCompiler::Convention::ABS_SHEET_PREFIX:
939 return '$';
940 case ScCompiler::Convention::SHEET_SEPARATOR:
941 return '.';
944 return u'\0';
947 virtual bool parseExternalName( const OUString& rSymbol, OUString& rFile, OUString& rName,
948 const ScDocument& rDoc,
949 const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks ) const override
951 return lcl_parseExternalName(rSymbol, rFile, rName, '#', rDoc, pExternalLinks);
954 virtual OUString makeExternalNameStr( sal_uInt16 /*nFileId*/, const OUString& rFile,
955 const OUString& rName ) const override
957 return lcl_makeExternalNameStr( rFile, rName, '#', false);
960 static bool makeExternalSingleRefStr(
961 const ScSheetLimits& rLimits,
962 OUStringBuffer& rBuffer, const OUString& rFileName, const OUString& rTabName,
963 const ScSingleRefData& rRef, const ScAddress& rPos, bool bDisplayTabName, bool bEncodeUrl )
965 ScAddress aAbsRef = rRef.toAbs(rLimits, rPos);
966 if (bDisplayTabName)
968 OUString aFile;
969 if (bEncodeUrl)
970 aFile = rFileName;
971 else
972 aFile = INetURLObject::decode(rFileName, INetURLObject::DecodeMechanism::Unambiguous);
974 rBuffer.append("'" + aFile.replaceAll("'", "''") + "'#");
976 if (!rRef.IsTabRel())
977 rBuffer.append('$');
978 ScRangeStringConverter::AppendTableName(rBuffer, rTabName);
980 rBuffer.append('.');
983 if (!rRef.IsColRel())
984 rBuffer.append('$');
985 MakeColStr( rLimits, rBuffer, aAbsRef.Col());
986 if (!rRef.IsRowRel())
987 rBuffer.append('$');
988 MakeRowStr( rLimits, rBuffer, aAbsRef.Row());
990 return true;
993 static void makeExternalRefStrImpl(
994 const ScSheetLimits& rLimits,
995 OUStringBuffer& rBuffer, const ScAddress& rPos, const OUString& rFileName,
996 const OUString& rTabName, const ScSingleRefData& rRef, bool bODF )
998 if (bODF)
999 rBuffer.append( '[');
1001 bool bEncodeUrl = bODF;
1002 makeExternalSingleRefStr(rLimits, rBuffer, rFileName, rTabName, rRef, rPos, true, bEncodeUrl);
1003 if (bODF)
1004 rBuffer.append( ']');
1007 virtual void makeExternalRefStr(
1008 ScSheetLimits& rLimits,
1009 OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName,
1010 const OUString& rTabName, const ScSingleRefData& rRef ) const override
1012 makeExternalRefStrImpl(rLimits, rBuffer, rPos, rFileName, rTabName, rRef, false);
1015 static void makeExternalRefStrImpl(
1016 const ScSheetLimits& rLimits,
1017 OUStringBuffer& rBuffer, const ScAddress& rPos, const OUString& rFileName,
1018 const std::vector<OUString>& rTabNames, const OUString& rTabName,
1019 const ScComplexRefData& rRef, bool bODF )
1021 ScRange aAbsRange = rRef.toAbs(rLimits, rPos);
1023 if (bODF)
1024 rBuffer.append( '[');
1025 // Ensure that there's always a closing bracket, no premature returns.
1026 bool bEncodeUrl = bODF;
1030 if (!makeExternalSingleRefStr(rLimits, rBuffer, rFileName, rTabName, rRef.Ref1, rPos, true, bEncodeUrl))
1031 break;
1033 rBuffer.append(':');
1035 OUString aLastTabName;
1036 bool bDisplayTabName = (aAbsRange.aStart.Tab() != aAbsRange.aEnd.Tab());
1037 if (bDisplayTabName)
1039 // Get the name of the last table.
1040 if (!lcl_getLastTabName(aLastTabName, rTabName, rTabNames, aAbsRange))
1042 OSL_FAIL( "ConventionOOO_A1::makeExternalRefStrImpl: sheet name not found");
1043 // aLastTabName contains #REF!, proceed.
1046 else if (bODF)
1047 rBuffer.append( '.'); // need at least the sheet separator in ODF
1048 makeExternalSingleRefStr(rLimits,
1049 rBuffer, rFileName, aLastTabName, rRef.Ref2, rPos, bDisplayTabName, bEncodeUrl);
1050 } while (false);
1052 if (bODF)
1053 rBuffer.append( ']');
1056 virtual void makeExternalRefStr(
1057 ScSheetLimits& rLimits,
1058 OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName,
1059 const std::vector<OUString>& rTabNames, const OUString& rTabName,
1060 const ScComplexRefData& rRef ) const override
1062 makeExternalRefStrImpl(rLimits, rBuffer, rPos, rFileName, rTabNames, rTabName, rRef, false);
1066 struct ConventionOOO_A1_ODF : public ConventionOOO_A1
1068 ConventionOOO_A1_ODF() : ConventionOOO_A1 (FormulaGrammar::CONV_ODF) { }
1070 virtual void makeRefStr(
1071 ScSheetLimits& rLimits,
1072 OUStringBuffer& rBuffer,
1073 formula::FormulaGrammar::Grammar eGram,
1074 const ScAddress& rPos,
1075 const OUString& rErrRef, const std::vector<OUString>& rTabNames,
1076 const ScComplexRefData& rRef,
1077 bool bSingleRef,
1078 bool bFromRangeName ) const override
1080 rBuffer.append('[');
1081 // In case absolute/relative positions weren't separately available:
1082 // transform relative to absolute!
1083 ScAddress aAbs1 = rRef.Ref1.toAbs(rLimits, rPos), aAbs2;
1084 if( !bSingleRef )
1085 aAbs2 = rRef.Ref2.toAbs(rLimits, rPos);
1087 if (FormulaGrammar::isODFF(eGram) && (rRef.Ref1.IsDeleted() || !rLimits.ValidAddress(aAbs1) ||
1088 (!bSingleRef && (rRef.Ref2.IsDeleted() || !rLimits.ValidAddress(aAbs2)))))
1090 rBuffer.append(rErrRef);
1091 // For ODFF write [#REF!], but not for PODF so apps reading ODF
1092 // 1.0/1.1 may have a better chance if they implemented the old
1093 // form.
1095 else
1097 SingletonDisplay eSingleton = bSingleRef ? SINGLETON_NONE :
1098 getSingletonDisplay( rLimits, aAbs1, aAbs2, rRef, bFromRangeName);
1099 MakeOneRefStrImpl(rLimits, rBuffer, rErrRef, rTabNames, rRef.Ref1, aAbs1, false, true, eSingleton);
1100 if (!bSingleRef)
1102 rBuffer.append(':');
1103 MakeOneRefStrImpl(rLimits, rBuffer, rErrRef, rTabNames, rRef.Ref2, aAbs2, aAbs1.Tab() != aAbs2.Tab(), true,
1104 eSingleton);
1107 rBuffer.append(']');
1110 virtual OUString makeExternalNameStr( sal_uInt16 /*nFileId*/, const OUString& rFile,
1111 const OUString& rName ) const override
1113 return lcl_makeExternalNameStr( rFile, rName, '#', true);
1116 virtual void makeExternalRefStr(
1117 ScSheetLimits& rLimits,
1118 OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName,
1119 const OUString& rTabName, const ScSingleRefData& rRef ) const override
1121 makeExternalRefStrImpl(rLimits, rBuffer, rPos, rFileName, rTabName, rRef, true);
1124 virtual void makeExternalRefStr(
1125 ScSheetLimits& rLimits,
1126 OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName,
1127 const std::vector<OUString>& rTabNames,
1128 const OUString& rTabName, const ScComplexRefData& rRef ) const override
1130 makeExternalRefStrImpl(rLimits, rBuffer, rPos, rFileName, rTabNames, rTabName, rRef, true);
1134 struct ConventionXL
1136 virtual ~ConventionXL()
1140 static void GetTab(
1141 const ScSheetLimits& rLimits,
1142 const ScAddress& rPos, const std::vector<OUString>& rTabNames,
1143 const ScSingleRefData& rRef, OUString& rTabName )
1145 ScAddress aAbs = rRef.toAbs(rLimits, rPos);
1146 if (rRef.IsTabDeleted() || o3tl::make_unsigned(aAbs.Tab()) >= rTabNames.size())
1148 rTabName = ScResId( STR_NO_REF_TABLE );
1149 return;
1151 rTabName = rTabNames[aAbs.Tab()];
1154 static void MakeTabStr( const ScSheetLimits& rLimits, OUStringBuffer& rBuf,
1155 const ScAddress& rPos,
1156 const std::vector<OUString>& rTabNames,
1157 const ScComplexRefData& rRef,
1158 bool bSingleRef )
1160 if( !rRef.Ref1.IsFlag3D() )
1161 return;
1163 OUString aStartTabName, aEndTabName;
1165 GetTab(rLimits, rPos, rTabNames, rRef.Ref1, aStartTabName);
1167 if( !bSingleRef && rRef.Ref2.IsFlag3D() )
1169 GetTab(rLimits, rPos, rTabNames, rRef.Ref2, aEndTabName);
1172 rBuf.append( aStartTabName );
1173 if( !bSingleRef && rRef.Ref2.IsFlag3D() && aStartTabName != aEndTabName )
1175 rBuf.append( ':' );
1176 rBuf.append( aEndTabName );
1179 rBuf.append( '!' );
1182 static sal_Unicode getSpecialSymbol( ScCompiler::Convention::SpecialSymbolType eSymType )
1184 switch (eSymType)
1186 case ScCompiler::Convention::ABS_SHEET_PREFIX:
1187 return u'\0';
1188 case ScCompiler::Convention::SHEET_SEPARATOR:
1189 return '!';
1191 return u'\0';
1194 static bool parseExternalName( const OUString& rSymbol, OUString& rFile, OUString& rName,
1195 const ScDocument& rDoc,
1196 const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks )
1198 return lcl_parseExternalName( rSymbol, rFile, rName, '!', rDoc, pExternalLinks);
1201 static OUString makeExternalNameStr( const OUString& rFile, const OUString& rName )
1203 return lcl_makeExternalNameStr( rFile, rName, '!', false);
1206 static void makeExternalDocStr( OUStringBuffer& rBuffer, std::u16string_view rFullName )
1208 // Format that is easier to deal with inside OOo, because we use file
1209 // URL, and all characters are allowed. Check if it makes sense to do
1210 // it the way Gnumeric does it. Gnumeric doesn't use the URL form
1211 // and allows relative file path.
1213 // ['file:///path/to/source/filename.xls']
1215 rBuffer.append('[');
1216 rBuffer.append('\'');
1217 OUString aFullName = INetURLObject::decode(rFullName, INetURLObject::DecodeMechanism::Unambiguous);
1219 const sal_Unicode* pBuf = aFullName.getStr();
1220 sal_Int32 nLen = aFullName.getLength();
1221 for (sal_Int32 i = 0; i < nLen; ++i)
1223 const sal_Unicode c = pBuf[i];
1224 if (c == '\'')
1225 rBuffer.append(c);
1226 rBuffer.append(c);
1228 rBuffer.append('\'');
1229 rBuffer.append(']');
1232 static void makeExternalTabNameRange( OUStringBuffer& rBuf, const OUString& rTabName,
1233 const vector<OUString>& rTabNames,
1234 const ScRange& rRef )
1236 OUString aLastTabName;
1237 if (!lcl_getLastTabName(aLastTabName, rTabName, rTabNames, rRef))
1239 ScRangeStringConverter::AppendTableName(rBuf, aLastTabName);
1240 return;
1243 ScRangeStringConverter::AppendTableName(rBuf, rTabName);
1244 if (rTabName != aLastTabName)
1246 rBuf.append(':');
1247 ScRangeStringConverter::AppendTableName(rBuf, aLastTabName);
1251 virtual void parseExternalDocName( const OUString& rFormula, sal_Int32& rSrcPos ) const
1253 sal_Int32 nLen = rFormula.getLength();
1254 const sal_Unicode* p = rFormula.getStr();
1255 sal_Unicode cPrev = 0;
1256 for (sal_Int32 i = rSrcPos; i < nLen; ++i)
1258 sal_Unicode c = p[i];
1259 if (i == rSrcPos)
1261 // first character must be '['.
1262 if (c != '[')
1263 return;
1265 else if (i == rSrcPos + 1)
1267 // second character must be a single quote.
1268 if (c != '\'')
1269 return;
1271 else if (c == '\'')
1273 if (cPrev == '\'')
1274 // two successive single quote is treated as a single
1275 // valid character.
1276 c = 'a';
1278 else if (c == ']')
1280 if (cPrev == '\'')
1282 // valid source document path found. Increment the
1283 // current position to skip the source path.
1284 rSrcPos = i + 1;
1285 if (rSrcPos >= nLen)
1286 rSrcPos = nLen - 1;
1287 return;
1289 else
1290 return;
1292 else
1294 // any other character
1295 if (i > rSrcPos + 2 && cPrev == '\'')
1296 // unless it's the 3rd character, a normal character
1297 // following immediately a single quote is invalid.
1298 return;
1300 cPrev = c;
1305 struct ConventionXL_A1 : public Convention_A1, public ConventionXL
1307 ConventionXL_A1() : Convention_A1( FormulaGrammar::CONV_XL_A1 ) { }
1308 explicit ConventionXL_A1( FormulaGrammar::AddressConvention eConv ) : Convention_A1( eConv ) { }
1310 static void makeSingleCellStr( const ScSheetLimits& rLimits, OUStringBuffer& rBuf, const ScSingleRefData& rRef, const ScAddress& rAbs )
1312 if (!rRef.IsColRel())
1313 rBuf.append('$');
1314 MakeColStr(rLimits, rBuf, rAbs.Col());
1315 if (!rRef.IsRowRel())
1316 rBuf.append('$');
1317 MakeRowStr(rLimits, rBuf, rAbs.Row());
1320 virtual void makeRefStr(
1321 ScSheetLimits& rLimits,
1322 OUStringBuffer& rBuf,
1323 formula::FormulaGrammar::Grammar /*eGram*/,
1324 const ScAddress& rPos,
1325 const OUString& rErrRef, const std::vector<OUString>& rTabNames,
1326 const ScComplexRefData& rRef,
1327 bool bSingleRef,
1328 bool /*bFromRangeName*/ ) const override
1330 ScComplexRefData aRef( rRef );
1332 // Play fast and loose with invalid refs. There is not much point in producing
1333 // Foo!A1:#REF! versus #REF! at this point
1334 ScAddress aAbs1 = aRef.Ref1.toAbs(rLimits, rPos), aAbs2;
1336 MakeTabStr(rLimits, rBuf, rPos, rTabNames, aRef, bSingleRef);
1338 if (!rLimits.ValidAddress(aAbs1))
1340 rBuf.append(rErrRef);
1341 return;
1344 if( !bSingleRef )
1346 aAbs2 = aRef.Ref2.toAbs(rLimits, rPos);
1347 if (!rLimits.ValidAddress(aAbs2))
1349 rBuf.append(rErrRef);
1350 return;
1353 if (aAbs1.Col() == 0 && aAbs2.Col() >= rLimits.mnMaxCol)
1355 if (!aRef.Ref1.IsRowRel())
1356 rBuf.append( '$' );
1357 MakeRowStr(rLimits, rBuf, aAbs1.Row());
1358 rBuf.append( ':' );
1359 if (!aRef.Ref2.IsRowRel())
1360 rBuf.append( '$' );
1361 MakeRowStr(rLimits, rBuf, aAbs2.Row());
1362 return;
1365 if (aAbs1.Row() == 0 && aAbs2.Row() >= rLimits.mnMaxRow)
1367 if (!aRef.Ref1.IsColRel())
1368 rBuf.append( '$' );
1369 MakeColStr(rLimits, rBuf, aAbs1.Col());
1370 rBuf.append( ':' );
1371 if (!aRef.Ref2.IsColRel())
1372 rBuf.append( '$' );
1373 MakeColStr(rLimits, rBuf, aAbs2.Col());
1374 return;
1378 makeSingleCellStr(rLimits, rBuf, aRef.Ref1, aAbs1);
1379 if (!bSingleRef)
1381 rBuf.append( ':' );
1382 makeSingleCellStr(rLimits, rBuf, aRef.Ref2, aAbs2);
1386 virtual ParseResult parseAnyToken( const OUString& rFormula,
1387 sal_Int32 nSrcPos,
1388 const CharClass* pCharClass,
1389 bool bGroupSeparator) const override
1391 parseExternalDocName(rFormula, nSrcPos);
1393 ParseResult aRet;
1394 if ( lcl_isValidQuotedText(rFormula, nSrcPos, aRet) )
1395 return aRet;
1397 constexpr sal_Int32 nStartFlags = KParseTokens::ANY_LETTER_OR_NUMBER |
1398 KParseTokens::ASC_UNDERSCORE | KParseTokens::ASC_DOLLAR;
1399 constexpr sal_Int32 nContFlags = nStartFlags | KParseTokens::ASC_DOT;
1400 // '?' allowed in range names
1401 static constexpr OUString aAddAllowed(u"?!"_ustr);
1402 return pCharClass->parseAnyToken( rFormula,
1403 nSrcPos, nStartFlags, aAddAllowed,
1404 (bGroupSeparator ? nContFlags | KParseTokens::GROUP_SEPARATOR_IN_NUMBER : nContFlags),
1405 aAddAllowed );
1408 virtual sal_Unicode getSpecialSymbol( SpecialSymbolType eSymType ) const override
1410 return ConventionXL::getSpecialSymbol(eSymType);
1413 virtual bool parseExternalName( const OUString& rSymbol, OUString& rFile, OUString& rName,
1414 const ScDocument& rDoc,
1415 const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks ) const override
1417 return ConventionXL::parseExternalName( rSymbol, rFile, rName, rDoc, pExternalLinks);
1420 virtual OUString makeExternalNameStr( sal_uInt16 /*nFileId*/, const OUString& rFile,
1421 const OUString& rName ) const override
1423 return ConventionXL::makeExternalNameStr(rFile, rName);
1426 virtual void makeExternalRefStr(
1427 ScSheetLimits& rLimits,
1428 OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName,
1429 const OUString& rTabName, const ScSingleRefData& rRef ) const override
1431 // ['file:///path/to/file/filename.xls']'Sheet Name'!$A$1
1432 // This is a little different from the format Excel uses, as Excel
1433 // puts [] only around the file name. But we need to enclose the
1434 // whole file path with [] because the file name can contain any
1435 // characters.
1437 ConventionXL::makeExternalDocStr(rBuffer, rFileName);
1438 ScRangeStringConverter::AppendTableName(rBuffer, rTabName);
1439 rBuffer.append('!');
1441 makeSingleCellStr(rLimits, rBuffer, rRef, rRef.toAbs(rLimits, rPos));
1444 virtual void makeExternalRefStr(
1445 ScSheetLimits& rLimits,
1446 OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName,
1447 const std::vector<OUString>& rTabNames, const OUString& rTabName,
1448 const ScComplexRefData& rRef ) const override
1450 ScRange aAbsRef = rRef.toAbs(rLimits, rPos);
1452 ConventionXL::makeExternalDocStr(rBuffer, rFileName);
1453 ConventionXL::makeExternalTabNameRange(rBuffer, rTabName, rTabNames, aAbsRef);
1454 rBuffer.append('!');
1456 makeSingleCellStr(rLimits, rBuffer, rRef.Ref1, aAbsRef.aStart);
1457 if (aAbsRef.aStart != aAbsRef.aEnd)
1459 rBuffer.append(':');
1460 makeSingleCellStr(rLimits, rBuffer, rRef.Ref2, aAbsRef.aEnd);
1465 struct ConventionXL_OOX : public ConventionXL_A1
1467 ConventionXL_OOX() : ConventionXL_A1( FormulaGrammar::CONV_XL_OOX ) { }
1469 virtual void makeRefStr( ScSheetLimits& rLimits,
1470 OUStringBuffer& rBuf,
1471 formula::FormulaGrammar::Grammar eGram,
1472 const ScAddress& rPos,
1473 const OUString& rErrRef, const std::vector<OUString>& rTabNames,
1474 const ScComplexRefData& rRef,
1475 bool bSingleRef,
1476 bool bFromRangeName ) const override
1478 // In OOXML relative references in named expressions are relative to
1479 // column 0 and row 0. Relative sheet references don't exist.
1480 ScAddress aPos( rPos );
1481 if (bFromRangeName)
1483 // XXX NOTE: by decrementing the reference position we may end up
1484 // with resolved references with negative values. There's no proper
1485 // way to solve that or wrap them around without sheet dimensions
1486 // that are stored along. That, or blindly assume fixed dimensions
1487 // here and in import.
1488 /* TODO: maybe do that blind fixed dimensions wrap? */
1489 aPos.SetCol(0);
1490 aPos.SetRow(0);
1493 if (rRef.Ref1.IsDeleted() || (!bSingleRef && rRef.Ref2.IsDeleted()))
1495 // For OOXML write plain "#REF!" instead of detailed sheet/col/row
1496 // information.
1497 rBuf.append(rErrRef);
1498 return;
1502 ScAddress aAbs1 = rRef.Ref1.toAbs(rLimits, rPos);
1503 if (!rLimits.ValidAddress(aAbs1)
1504 || o3tl::make_unsigned(aAbs1.Tab()) >= rTabNames.size())
1506 rBuf.append(rErrRef);
1507 return;
1511 if (!bSingleRef)
1513 ScAddress aAbs2 = rRef.Ref2.toAbs(rLimits, rPos);
1514 if (!rLimits.ValidAddress(aAbs2)
1515 || o3tl::make_unsigned(aAbs2.Tab()) >= rTabNames.size())
1517 rBuf.append(rErrRef);
1518 return;
1522 ConventionXL_A1::makeRefStr( rLimits, rBuf, eGram, aPos, rErrRef, rTabNames, rRef, bSingleRef, bFromRangeName);
1525 virtual OUString makeExternalNameStr( sal_uInt16 nFileId, const OUString& /*rFile*/,
1526 const OUString& rName ) const override
1528 // [N]!DefinedName is a workbook global name.
1529 return OUString( "[" + OUString::number(nFileId+1) + "]!" + rName );
1531 /* TODO: add support for sheet local names, would be
1532 * [N]'Sheet Name'!DefinedName
1533 * Similar to makeExternalRefStr() but with DefinedName instead of
1534 * CellStr. */
1537 virtual void parseExternalDocName(const OUString& rFormula, sal_Int32& rSrcPos) const override
1539 sal_Int32 nLen = rFormula.getLength();
1540 const sal_Unicode* p = rFormula.getStr();
1541 for (sal_Int32 i = rSrcPos; i < nLen; ++i)
1543 sal_Unicode c = p[i];
1544 if (i == rSrcPos)
1546 // first character must be '['.
1547 if (c != '[')
1548 return;
1550 else if (c == ']')
1552 rSrcPos = i + 1;
1553 return;
1558 virtual void makeExternalRefStr(
1559 ScSheetLimits& rLimits,
1560 OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 nFileId, const OUString& /*rFileName*/,
1561 const OUString& rTabName, const ScSingleRefData& rRef ) const override
1563 // '[N]Sheet Name'!$A$1 or [N]SheetName!$A$1
1564 // Where N is a 1-based positive integer number of a file name in OOXML
1565 // xl/externalLinks/externalLinkN.xml
1567 OUString aQuotedTab( rTabName);
1568 ScCompiler::CheckTabQuotes( aQuotedTab);
1569 if (!aQuotedTab.isEmpty() && aQuotedTab[0] == '\'')
1571 rBuffer.append('\'');
1572 ConventionXL_OOX::makeExternalDocStr( rBuffer, nFileId);
1573 rBuffer.append( aQuotedTab.subView(1));
1575 else
1577 ConventionXL_OOX::makeExternalDocStr( rBuffer, nFileId);
1578 rBuffer.append( aQuotedTab);
1580 rBuffer.append('!');
1582 makeSingleCellStr(rLimits, rBuffer, rRef, rRef.toAbs(rLimits, rPos));
1585 virtual void makeExternalRefStr(
1586 ScSheetLimits& rLimits,
1587 OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 nFileId, const OUString& /*rFileName*/,
1588 const std::vector<OUString>& rTabNames, const OUString& rTabName,
1589 const ScComplexRefData& rRef ) const override
1591 // '[N]Sheet One':'Sheet Two'!A1:B2 or [N]SheetOne!A1:B2
1592 // Actually Excel writes '[N]Sheet One:Sheet Two'!A1:B2 but reads the
1593 // simpler to produce and more logical form with independently quoted
1594 // sheet names as well. The [N] having to be within the quoted sheet
1595 // name is ugly enough...
1597 ScRange aAbsRef = rRef.toAbs(rLimits, rPos);
1599 OUStringBuffer aBuf;
1600 ConventionXL::makeExternalTabNameRange( aBuf, rTabName, rTabNames, aAbsRef);
1601 if (!aBuf.isEmpty() && aBuf[0] == '\'')
1603 rBuffer.append('\'');
1604 ConventionXL_OOX::makeExternalDocStr( rBuffer, nFileId);
1605 rBuffer.append( aBuf.subView(1));
1607 else
1609 ConventionXL_OOX::makeExternalDocStr( rBuffer, nFileId);
1610 rBuffer.append( aBuf);
1612 rBuffer.append('!');
1614 makeSingleCellStr(rLimits, rBuffer, rRef.Ref1, aAbsRef.aStart);
1615 if (aAbsRef.aStart != aAbsRef.aEnd)
1617 rBuffer.append(':');
1618 makeSingleCellStr(rLimits, rBuffer, rRef.Ref2, aAbsRef.aEnd);
1622 static void makeExternalDocStr( OUStringBuffer& rBuffer, sal_uInt16 nFileId )
1624 rBuffer.append("[" + OUString::number( static_cast<sal_Int32>(nFileId+1) ) + "]");
1630 static void
1631 r1c1_add_col( OUStringBuffer &rBuf, const ScSingleRefData& rRef, const ScAddress& rAbsRef )
1633 rBuf.append( 'C' );
1634 if( rRef.IsColRel() )
1636 SCCOL nCol = rRef.Col();
1637 if (nCol != 0)
1638 rBuf.append("[" + OUString::number(nCol) + "]");
1640 else
1641 rBuf.append( static_cast<sal_Int32>(rAbsRef.Col() + 1) );
1643 static void
1644 r1c1_add_row( OUStringBuffer &rBuf, const ScSingleRefData& rRef, const ScAddress& rAbsRef )
1646 rBuf.append( 'R' );
1647 if( rRef.IsRowRel() )
1649 if (rRef.Row() != 0)
1651 rBuf.append("[" + OUString::number(rRef.Row()) + "]");
1654 else
1655 rBuf.append( rAbsRef.Row() + 1 );
1658 namespace {
1660 struct ConventionXL_R1C1 : public ScCompiler::Convention, public ConventionXL
1662 ConventionXL_R1C1() : ScCompiler::Convention( FormulaGrammar::CONV_XL_R1C1 ) { }
1664 virtual void makeRefStr( ScSheetLimits& rLimits,
1665 OUStringBuffer& rBuf,
1666 formula::FormulaGrammar::Grammar /*eGram*/,
1667 const ScAddress& rPos,
1668 const OUString& rErrRef, const std::vector<OUString>& rTabNames,
1669 const ScComplexRefData& rRef,
1670 bool bSingleRef,
1671 bool /*bFromRangeName*/ ) const override
1673 ScRange aAbsRef = rRef.toAbs(rLimits, rPos);
1674 ScComplexRefData aRef( rRef );
1676 MakeTabStr(rLimits, rBuf, rPos, rTabNames, aRef, bSingleRef);
1678 // Play fast and loose with invalid refs. There is not much point in producing
1679 // Foo!A1:#REF! versus #REF! at this point
1680 if (!rLimits.ValidCol(aAbsRef.aStart.Col()) || !rLimits.ValidRow(aAbsRef.aStart.Row()))
1682 rBuf.append(rErrRef);
1683 return;
1686 if( !bSingleRef )
1688 if (!rLimits.ValidCol(aAbsRef.aEnd.Col()) || !rLimits.ValidRow(aAbsRef.aEnd.Row()))
1690 rBuf.append(rErrRef);
1691 return;
1694 if (aAbsRef.aStart.Col() == 0 && aAbsRef.aEnd.Col() >= rLimits.mnMaxCol)
1696 r1c1_add_row(rBuf, rRef.Ref1, aAbsRef.aStart);
1697 if (aAbsRef.aStart.Row() != aAbsRef.aEnd.Row() ||
1698 rRef.Ref1.IsRowRel() != rRef.Ref2.IsRowRel() )
1700 rBuf.append( ':' );
1701 r1c1_add_row(rBuf, rRef.Ref2, aAbsRef.aEnd);
1703 return;
1707 if (aAbsRef.aStart.Row() == 0 && aAbsRef.aEnd.Row() >= rLimits.mnMaxRow)
1709 r1c1_add_col(rBuf, rRef.Ref1, aAbsRef.aStart);
1710 if (aAbsRef.aStart.Col() != aAbsRef.aEnd.Col() ||
1711 rRef.Ref1.IsColRel() != rRef.Ref2.IsColRel())
1713 rBuf.append( ':' );
1714 r1c1_add_col(rBuf, rRef.Ref2, aAbsRef.aEnd);
1716 return;
1720 r1c1_add_row(rBuf, rRef.Ref1, aAbsRef.aStart);
1721 r1c1_add_col(rBuf, rRef.Ref1, aAbsRef.aStart);
1722 if (!bSingleRef)
1724 rBuf.append( ':' );
1725 r1c1_add_row(rBuf, rRef.Ref2, aAbsRef.aEnd);
1726 r1c1_add_col(rBuf, rRef.Ref2, aAbsRef.aEnd);
1730 ParseResult parseAnyToken( const OUString& rFormula,
1731 sal_Int32 nSrcPos,
1732 const CharClass* pCharClass,
1733 bool bGroupSeparator) const override
1735 parseExternalDocName(rFormula, nSrcPos);
1737 ParseResult aRet;
1738 if ( lcl_isValidQuotedText(rFormula, nSrcPos, aRet) )
1739 return aRet;
1741 constexpr sal_Int32 nStartFlags = KParseTokens::ANY_LETTER_OR_NUMBER |
1742 KParseTokens::ASC_UNDERSCORE ;
1743 constexpr sal_Int32 nContFlags = nStartFlags | KParseTokens::ASC_DOT;
1744 // '?' allowed in range names
1745 static constexpr OUString aAddAllowed(u"?-[]!"_ustr);
1747 return pCharClass->parseAnyToken( rFormula,
1748 nSrcPos, nStartFlags, aAddAllowed,
1749 (bGroupSeparator ? nContFlags | KParseTokens::GROUP_SEPARATOR_IN_NUMBER : nContFlags),
1750 aAddAllowed );
1753 virtual sal_Unicode getSpecialSymbol( SpecialSymbolType eSymType ) const override
1755 return ConventionXL::getSpecialSymbol(eSymType);
1758 virtual bool parseExternalName( const OUString& rSymbol, OUString& rFile, OUString& rName,
1759 const ScDocument& rDoc,
1760 const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks ) const override
1762 return ConventionXL::parseExternalName( rSymbol, rFile, rName, rDoc, pExternalLinks);
1765 virtual OUString makeExternalNameStr( sal_uInt16 /*nFileId*/, const OUString& rFile,
1766 const OUString& rName ) const override
1768 return ConventionXL::makeExternalNameStr(rFile, rName);
1771 virtual void makeExternalRefStr(
1772 ScSheetLimits& rLimits,
1773 OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName,
1774 const OUString& rTabName, const ScSingleRefData& rRef ) const override
1776 // ['file:///path/to/file/filename.xls']'Sheet Name'!$A$1
1777 // This is a little different from the format Excel uses, as Excel
1778 // puts [] only around the file name. But we need to enclose the
1779 // whole file path with [] because the file name can contain any
1780 // characters.
1782 ScAddress aAbsRef = rRef.toAbs(rLimits, rPos);
1783 ConventionXL::makeExternalDocStr(rBuffer, rFileName);
1784 ScRangeStringConverter::AppendTableName(rBuffer, rTabName);
1785 rBuffer.append('!');
1787 r1c1_add_row(rBuffer, rRef, aAbsRef);
1788 r1c1_add_col(rBuffer, rRef, aAbsRef);
1791 virtual void makeExternalRefStr(
1792 ScSheetLimits& rLimits,
1793 OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName,
1794 const std::vector<OUString>& rTabNames, const OUString& rTabName,
1795 const ScComplexRefData& rRef ) const override
1797 ScRange aAbsRef = rRef.toAbs(rLimits, rPos);
1799 ConventionXL::makeExternalDocStr(rBuffer, rFileName);
1800 ConventionXL::makeExternalTabNameRange(rBuffer, rTabName, rTabNames, aAbsRef);
1801 rBuffer.append('!');
1803 if (!rLimits.ValidCol(aAbsRef.aEnd.Col()) || !rLimits.ValidRow(aAbsRef.aEnd.Row()))
1805 rBuffer.append(ScResId(STR_NO_REF_TABLE));
1806 return;
1809 if (aAbsRef.aStart.Col() == 0 && aAbsRef.aEnd.Col() >= rLimits.mnMaxCol)
1811 r1c1_add_row(rBuffer, rRef.Ref1, aAbsRef.aStart);
1812 if (aAbsRef.aStart.Row() != aAbsRef.aEnd.Row() || rRef.Ref1.IsRowRel() != rRef.Ref2.IsRowRel())
1814 rBuffer.append(':');
1815 r1c1_add_row(rBuffer, rRef.Ref2, aAbsRef.aEnd);
1817 return;
1820 if (aAbsRef.aStart.Row() == 0 && aAbsRef.aEnd.Row() >= rLimits.mnMaxRow)
1822 r1c1_add_col(rBuffer, rRef.Ref1, aAbsRef.aStart);
1823 if (aAbsRef.aStart.Col() != aAbsRef.aEnd.Col() || rRef.Ref1.IsColRel() != rRef.Ref2.IsColRel())
1825 rBuffer.append(':');
1826 r1c1_add_col(rBuffer, rRef.Ref2, aAbsRef.aEnd);
1828 return;
1831 r1c1_add_row(rBuffer, rRef.Ref1, aAbsRef.aStart);
1832 r1c1_add_col(rBuffer, rRef.Ref1, aAbsRef.aStart);
1833 rBuffer.append(':');
1834 r1c1_add_row(rBuffer, rRef.Ref2, aAbsRef.aEnd);
1835 r1c1_add_col(rBuffer, rRef.Ref2, aAbsRef.aEnd);
1838 virtual ScCharFlags getCharTableFlags( sal_Unicode c, sal_Unicode cLast ) const override
1840 ScCharFlags nFlags = mpCharTable[static_cast<sal_uInt8>(c)];
1841 if (c == '-' && cLast == '[')
1842 // '-' can occur within a reference string only after '[' e.g. R[-1]C.
1843 nFlags |= ScCharFlags::Ident;
1844 return nFlags;
1850 ScCompiler::ScCompiler( sc::CompileFormulaContext& rCxt, const ScAddress& rPos, ScTokenArray& rArr,
1851 bool bComputeII, bool bMatrixFlag, ScInterpreterContext* pContext )
1852 : FormulaCompiler(rArr, bComputeII, bMatrixFlag),
1853 rDoc(rCxt.getDoc()),
1854 aPos(rPos),
1855 mrInterpreterContext(pContext ? *pContext : rDoc.GetNonThreadedContext()),
1856 mnCurrentSheetTab(-1),
1857 mnCurrentSheetEndPos(0),
1858 pCharClass(&ScGlobal::getCharClass()),
1859 mbCharClassesDiffer(false),
1860 mnPredetectedReference(0),
1861 mnRangeOpPosInSymbol(-1),
1862 pConv(GetRefConvention(FormulaGrammar::CONV_OOO)),
1863 meExtendedErrorDetection(EXTENDED_ERROR_DETECTION_NONE),
1864 mbCloseBrackets(true),
1865 mbRewind(false),
1866 mbRefConventionChartOOXML(false),
1867 maTabNames(rCxt.getTabNames())
1869 SetGrammar(rCxt.getGrammar());
1872 ScCompiler::ScCompiler( ScDocument& rDocument, const ScAddress& rPos, ScTokenArray& rArr,
1873 formula::FormulaGrammar::Grammar eGrammar,
1874 bool bComputeII, bool bMatrixFlag, ScInterpreterContext* pContext )
1875 : FormulaCompiler(rArr, bComputeII, bMatrixFlag),
1876 rDoc( rDocument ),
1877 aPos( rPos ),
1878 mrInterpreterContext(pContext ? *pContext : rDoc.GetNonThreadedContext()),
1879 mnCurrentSheetTab(-1),
1880 mnCurrentSheetEndPos(0),
1881 nSrcPos(0),
1882 pCharClass( &ScGlobal::getCharClass() ),
1883 mbCharClassesDiffer(false),
1884 mnPredetectedReference(0),
1885 mnRangeOpPosInSymbol(-1),
1886 pConv( GetRefConvention( FormulaGrammar::CONV_OOO ) ),
1887 meExtendedErrorDetection( EXTENDED_ERROR_DETECTION_NONE ),
1888 mbCloseBrackets( true ),
1889 mbRewind( false ),
1890 mbRefConventionChartOOXML( false )
1892 SetGrammar( (eGrammar == formula::FormulaGrammar::GRAM_UNSPECIFIED) ?
1893 rDocument.GetGrammar() :
1894 eGrammar );
1897 ScCompiler::ScCompiler( sc::CompileFormulaContext& rCxt, const ScAddress& rPos,
1898 bool bComputeII, bool bMatrixFlag, ScInterpreterContext* pContext )
1899 : FormulaCompiler(bComputeII, bMatrixFlag),
1900 rDoc(rCxt.getDoc()),
1901 aPos(rPos),
1902 mrInterpreterContext(pContext ? *pContext : rDoc.GetNonThreadedContext()),
1903 mnCurrentSheetTab(-1),
1904 mnCurrentSheetEndPos(0),
1905 pCharClass(&ScGlobal::getCharClass()),
1906 mbCharClassesDiffer(false),
1907 mnPredetectedReference(0),
1908 mnRangeOpPosInSymbol(-1),
1909 pConv(GetRefConvention(FormulaGrammar::CONV_OOO)),
1910 meExtendedErrorDetection(EXTENDED_ERROR_DETECTION_NONE),
1911 mbCloseBrackets(true),
1912 mbRewind(false),
1913 mbRefConventionChartOOXML(false),
1914 maTabNames(rCxt.getTabNames())
1916 SetGrammar(rCxt.getGrammar());
1919 ScCompiler::ScCompiler( ScDocument& rDocument, const ScAddress& rPos,
1920 formula::FormulaGrammar::Grammar eGrammar,
1921 bool bComputeII, bool bMatrixFlag, ScInterpreterContext* pContext )
1922 : FormulaCompiler(bComputeII, bMatrixFlag),
1923 rDoc( rDocument ),
1924 aPos( rPos ),
1925 mrInterpreterContext(pContext ? *pContext : rDoc.GetNonThreadedContext()),
1926 mnCurrentSheetTab(-1),
1927 mnCurrentSheetEndPos(0),
1928 nSrcPos(0),
1929 pCharClass( &ScGlobal::getCharClass() ),
1930 mbCharClassesDiffer(false),
1931 mnPredetectedReference(0),
1932 mnRangeOpPosInSymbol(-1),
1933 pConv( GetRefConvention( FormulaGrammar::CONV_OOO ) ),
1934 meExtendedErrorDetection( EXTENDED_ERROR_DETECTION_NONE ),
1935 mbCloseBrackets( true ),
1936 mbRewind( false ),
1937 mbRefConventionChartOOXML( false )
1939 SetGrammar( (eGrammar == formula::FormulaGrammar::GRAM_UNSPECIFIED) ?
1940 rDocument.GetGrammar() :
1941 eGrammar );
1944 ScCompiler::~ScCompiler()
1948 void ScCompiler::CheckTabQuotes( OUString& rString,
1949 const FormulaGrammar::AddressConvention eConv )
1951 sal_Int32 nStartFlags = KParseTokens::ANY_LETTER_OR_NUMBER | KParseTokens::ASC_UNDERSCORE;
1952 sal_Int32 nContFlags = nStartFlags;
1953 ParseResult aRes = ScGlobal::getCharClass().parsePredefinedToken(
1954 KParseType::IDENTNAME, rString, 0, nStartFlags, OUString(), nContFlags, OUString());
1955 bool bNeedsQuote = !((aRes.TokenType & KParseType::IDENTNAME) && aRes.EndPos == rString.getLength());
1957 switch ( eConv )
1959 default :
1960 case FormulaGrammar::CONV_UNSPECIFIED :
1961 break;
1962 case FormulaGrammar::CONV_OOO :
1963 case FormulaGrammar::CONV_XL_A1 :
1964 case FormulaGrammar::CONV_XL_R1C1 :
1965 case FormulaGrammar::CONV_XL_OOX :
1966 case FormulaGrammar::CONV_ODF :
1967 if( bNeedsQuote )
1969 // escape embedded quotes
1970 rString = rString.replaceAll( "'", "''" );
1972 break;
1975 if ( !bNeedsQuote && CharClass::isAsciiNumeric( rString ) )
1977 // Prevent any possible confusion resulting from pure numeric sheet names.
1978 bNeedsQuote = true;
1981 if( bNeedsQuote )
1983 rString = "'" + rString + "'";
1987 sal_Int32 ScCompiler::GetDocTabPos( const OUString& rString )
1989 if (rString[0] != '\'')
1990 return -1;
1991 sal_Int32 nPos = ScGlobal::FindUnquoted( rString, SC_COMPILER_FILE_TAB_SEP);
1992 // it must be 'Doc'#
1993 if (nPos != -1 && rString[nPos-1] != '\'')
1994 nPos = -1;
1995 return nPos;
1998 void ScCompiler::SetRefConvention( FormulaGrammar::AddressConvention eConv )
2000 const Convention* p = GetRefConvention(eConv);
2001 if (p)
2002 SetRefConvention(p);
2005 const ScCompiler::Convention* ScCompiler::GetRefConvention( FormulaGrammar::AddressConvention eConv )
2008 switch (eConv)
2010 case FormulaGrammar::CONV_OOO:
2012 static const ConventionOOO_A1 ConvOOO_A1;
2013 return &ConvOOO_A1;
2015 case FormulaGrammar::CONV_ODF:
2017 static const ConventionOOO_A1_ODF ConvOOO_A1_ODF;
2018 return &ConvOOO_A1_ODF;
2020 case FormulaGrammar::CONV_XL_A1:
2022 static const ConventionXL_A1 ConvXL_A1;
2023 return &ConvXL_A1;
2025 case FormulaGrammar::CONV_XL_R1C1:
2027 static const ConventionXL_R1C1 ConvXL_R1C1;
2028 return &ConvXL_R1C1;
2030 case FormulaGrammar::CONV_XL_OOX:
2032 static const ConventionXL_OOX ConvXL_OOX;
2033 return &ConvXL_OOX;
2035 case FormulaGrammar::CONV_UNSPECIFIED:
2036 default:
2040 return nullptr;
2043 void ScCompiler::SetRefConvention( const ScCompiler::Convention *pConvP )
2045 pConv = pConvP;
2046 meGrammar = FormulaGrammar::mergeToGrammar( meGrammar, pConv->meConv);
2047 assert( FormulaGrammar::isSupported( meGrammar));
2050 void ScCompiler::SetError(FormulaError nError)
2052 if( pArr->GetCodeError() == FormulaError::NONE)
2053 pArr->SetCodeError( nError);
2056 static sal_Unicode* lcl_UnicodeStrNCpy( sal_Unicode* pDst, const sal_Unicode* pSrc, sal_Int32 nMax )
2058 const sal_Unicode* const pStop = pDst + nMax;
2059 while ( pDst < pStop )
2061 *pDst++ = *pSrc++;
2063 *pDst = 0;
2064 return pDst;
2067 // p1 MUST contain at least n characters, or terminate with NIL.
2068 // p2 MUST pass upper case letters, if any.
2069 // n MUST not be greater than length of p2
2070 static bool lcl_isUnicodeIgnoreAscii( const sal_Unicode* p1, const char* p2, size_t n )
2072 for (size_t i=0; i<n; ++i)
2074 if (!p1[i])
2075 return false;
2076 if (p1[i] != p2[i])
2078 if (p1[i] < 'a' || 'z' < p1[i])
2079 return false; // not a lower case letter
2080 if (p2[i] < 'A' || 'Z' < p2[i])
2081 return false; // not a letter to match
2082 if (p1[i] != p2[i] + 0x20)
2083 return false; // lower case doesn't match either
2086 return true;
2089 // static
2090 void ScCompiler::addWhitespace( std::vector<ScCompiler::Whitespace> & rvSpaces,
2091 ScCompiler::Whitespace & rSpace, sal_Unicode c, sal_Int32 n )
2093 if (rSpace.cChar != c)
2095 if (rSpace.cChar && rSpace.nCount > 0)
2096 rvSpaces.emplace_back(rSpace);
2097 rSpace.reset(c);
2099 rSpace.nCount += n;
2102 // NextSymbol
2104 // Parses the formula into separate symbols for further processing.
2105 // XXX NOTE: this is a rough sketch of the original idea, there are other
2106 // states that were added and didn't make it into this table and things are
2107 // more complicated. Use the source, Luke.
2109 // initial state = GetChar
2111 // old state | read character | action | new state
2112 //---------------+-------------------+-----------------------+---------------
2113 // GetChar | ;()+-*/^=& | Symbol=char | Stop
2114 // | <> | Symbol=char | GetBool
2115 // | $ letter | Symbol=char | GetWord
2116 // | number | Symbol=char | GetValue
2117 // | " | none | GetString
2118 // | other | none | GetChar
2119 //---------------+-------------------+-----------------------+---------------
2120 // GetBool | => | Symbol=Symbol+char | Stop
2121 // | other | Dec(CharPos) | Stop
2122 //---------------+-------------------+-----------------------+---------------
2123 // GetWord | SepSymbol | Dec(CharPos) | Stop
2124 // | ()+-*/^=<>&~ | |
2125 // | space | Dec(CharPos) | Stop
2126 // | $_:. | |
2127 // | letter, number | Symbol=Symbol+char | GetWord
2128 // | other | error | Stop
2129 //---------------+-------------------+-----------------------+---------------
2130 // GetValue | ;()*/^=<>& | |
2131 // | space | Dec(CharPos) | Stop
2132 // | number E+-%,. | Symbol=Symbol+char | GetValue
2133 // | other | error | Stop
2134 //---------------+-------------------+-----------------------+---------------
2135 // GetString | " | none | Stop
2136 // | other | Symbol=Symbol+char | GetString
2137 //---------------+-------------------+-----------------------+---------------
2139 std::vector<ScCompiler::Whitespace> ScCompiler::NextSymbol(bool bInArray)
2141 std::vector<Whitespace> vSpaces;
2142 cSymbol[MAXSTRLEN] = 0; // end
2143 sal_Unicode* pSym = cSymbol;
2144 const sal_Unicode* const pStart = aFormula.getStr();
2145 const sal_Unicode* pSrc = pStart + nSrcPos;
2146 bool bi18n = false;
2147 sal_Unicode c = *pSrc;
2148 sal_Unicode cLast = 0;
2149 bool bQuote = false;
2150 mnRangeOpPosInSymbol = -1;
2151 ScanState eState = ssGetChar;
2152 Whitespace aSpace;
2153 sal_Unicode cSep = mxSymbols->getSymbolChar( ocSep);
2154 sal_Unicode cArrayColSep = mxSymbols->getSymbolChar( ocArrayColSep);
2155 sal_Unicode cArrayRowSep = mxSymbols->getSymbolChar( ocArrayRowSep);
2156 sal_Unicode cDecSep = (mxSymbols->isEnglishLocale() ? '.' : ScGlobal::getLocaleData().getNumDecimalSep()[0]);
2157 sal_Unicode cDecSepAlt = (mxSymbols->isEnglishLocale() ? 0 : ScGlobal::getLocaleData().getNumDecimalSepAlt().toChar());
2159 // special symbols specific to address convention used
2160 sal_Unicode cSheetPrefix = pConv->getSpecialSymbol(ScCompiler::Convention::ABS_SHEET_PREFIX);
2161 sal_Unicode cSheetSep = pConv->getSpecialSymbol(ScCompiler::Convention::SHEET_SEPARATOR);
2163 int nDecSeps = 0;
2164 bool bAutoIntersection = false;
2165 size_t nAutoIntersectionSpacesPos = 0;
2166 int nRefInName = 0;
2167 bool bErrorConstantHadSlash = false;
2168 mnPredetectedReference = 0;
2169 // try to parse simple tokens before calling i18n parser
2170 while ((c != 0) && (eState != ssStop) )
2172 pSrc++;
2173 ScCharFlags nMask = GetCharTableFlags( c, cLast );
2175 // The parameter separator and the array column and row separators end
2176 // things unconditionally if not in string or reference.
2177 if (c == cSep || (bInArray && (c == cArrayColSep || c == cArrayRowSep)))
2179 switch (eState)
2181 // these are to be continued
2182 case ssGetString:
2183 case ssSkipString:
2184 case ssGetReference:
2185 case ssSkipReference:
2186 case ssGetTableRefItem:
2187 case ssGetTableRefColumn:
2188 break;
2189 default:
2190 if (eState == ssGetChar)
2191 *pSym++ = c;
2192 else
2193 pSrc--;
2194 eState = ssStop;
2197 Label_MaskStateMachine:
2198 switch (eState)
2200 case ssGetChar :
2202 // Order is important!
2203 if (eLastOp == ocTableRefOpen && c != '[' && c != '#' && c != ']')
2205 *pSym++ = c;
2206 eState = ssGetTableRefColumn;
2208 else if( nMask & ScCharFlags::OdfLabelOp )
2210 // '!!' automatic intersection
2211 if (GetCharTableFlags( pSrc[0], 0 ) & ScCharFlags::OdfLabelOp)
2213 /* TODO: For now the UI "space operator" is used, this
2214 * could be enhanced using a specialized OpCode to get
2215 * rid of the space ambiguity, which would need some
2216 * places to be adapted though. And we would still need
2217 * to support the ambiguous space operator for UI
2218 * purposes anyway. However, we then could check for
2219 * invalid usage of '!!', which currently isn't
2220 * possible. */
2221 if (!bAutoIntersection)
2223 ++pSrc;
2224 // Add 2 because it must match the character count
2225 // for bi18n.
2226 addWhitespace( vSpaces, aSpace, 0x20, 2);
2227 // Position of Whitespace where it will be added to
2228 // vector.
2229 nAutoIntersectionSpacesPos = vSpaces.size();
2230 bAutoIntersection = true;
2232 else
2234 pSrc--;
2235 eState = ssStop;
2238 else
2240 nMask &= ~ScCharFlags::OdfLabelOp;
2241 goto Label_MaskStateMachine;
2244 else if( nMask & ScCharFlags::OdfNameMarker )
2246 // '$$' defined name marker
2247 if (GetCharTableFlags( pSrc[0], 0 ) & ScCharFlags::OdfNameMarker)
2249 // both eaten, not added to pSym
2250 ++pSrc;
2252 else
2254 nMask &= ~ScCharFlags::OdfNameMarker;
2255 goto Label_MaskStateMachine;
2258 else if( nMask & ScCharFlags::Char )
2260 // '[' is a special case in Excel syntax, it can start an
2261 // external reference, ID in OOXML like [1]Sheet1!A1 or
2262 // Excel_A1 [filename]Sheet!A1 or Excel_R1C1
2263 // [filename]Sheet!R1C1 that needs to be scanned
2264 // entirely, or can be ocTableRefOpen, of which the first
2265 // transforms an ocDBArea into an ocTableRef.
2266 if (c == '[' && FormulaGrammar::isExcelSyntax( meGrammar)
2267 && eLastOp != ocDBArea && maTableRefs.empty())
2269 // [0]!Global_Range_Name, is a special case in OOXML
2270 // syntax, where the '0' is referencing to self and we
2271 // do not need it, so we should skip it, in order to
2272 // later it will be more recognisable for IsNamedRange.
2273 if (FormulaGrammar::isRefConventionOOXML(meGrammar) &&
2274 pSrc[0] == '0' && pSrc[1] == ']' && pSrc[2] == '!')
2276 pSrc += 3;
2277 c = *pSrc;
2278 continue;
2281 nMask &= ~ScCharFlags::Char;
2282 goto Label_MaskStateMachine;
2284 else
2286 *pSym++ = c;
2287 eState = ssStop;
2290 else if( nMask & ScCharFlags::OdfLBracket )
2292 // eaten, not added to pSym
2293 eState = ssGetReference;
2294 mnPredetectedReference = 1;
2296 else if( nMask & ScCharFlags::CharBool )
2298 *pSym++ = c;
2299 eState = ssGetBool;
2301 else if( nMask & ScCharFlags::CharValue )
2303 *pSym++ = c;
2304 eState = ssGetValue;
2306 else if( nMask & ScCharFlags::CharString )
2308 *pSym++ = c;
2309 eState = ssGetString;
2311 else if( nMask & ScCharFlags::CharErrConst )
2313 *pSym++ = c;
2314 sal_uInt16 nLevel;
2315 if (!maTableRefs.empty() && ((nLevel = maTableRefs.back().mnLevel) == 2 || nLevel == 1))
2316 eState = ssGetTableRefItem;
2317 else
2318 eState = ssGetErrorConstant;
2320 else if( nMask & ScCharFlags::CharDontCare )
2322 addWhitespace( vSpaces, aSpace, c);
2324 else if( nMask & ScCharFlags::CharIdent )
2325 { // try to get a simple ASCII identifier before calling
2326 // i18n, to gain performance during import
2327 *pSym++ = c;
2328 eState = ssGetIdent;
2330 else
2332 bi18n = true;
2333 eState = ssStop;
2336 break;
2337 case ssGetIdent:
2339 if ( nMask & ScCharFlags::Ident )
2340 { // This catches also $Sheet1.A$1, for example.
2341 if( pSym == &cSymbol[ MAXSTRLEN ] )
2343 SetError(FormulaError::StringOverflow);
2344 eState = ssStop;
2346 else
2347 *pSym++ = c;
2349 else if (c == '#' && lcl_isUnicodeIgnoreAscii( pSrc, "REF!", 4))
2351 // Completely ugly means to catch broken
2352 // [$]#REF!.[$]#REF![$]#REF! (one or multiple parts)
2353 // references that were written in ODF named ranges
2354 // (without embracing [] hence no predetected reference)
2355 // and to OOXML and handle them as one symbol.
2356 // Also catches these in UI, so we can process them
2357 // further.
2358 int i = 0;
2359 for ( ; i<5; ++i)
2361 if( pSym == &cSymbol[ MAXSTRLEN ] )
2363 SetError(FormulaError::StringOverflow);
2364 eState = ssStop;
2365 break; // for
2367 else
2369 *pSym++ = c;
2370 c = *pSrc++;
2373 if (i == 5)
2374 c = *((--pSrc)-1); // position last/next character correctly
2376 else if (c == ':' && mnRangeOpPosInSymbol < 0)
2378 // One range operator may form Sheet1.A:A, which we need to
2379 // pass as one entity to IsReference().
2380 if( pSym == &cSymbol[ MAXSTRLEN ] )
2382 SetError(FormulaError::StringOverflow);
2383 eState = ssStop;
2385 else
2387 mnRangeOpPosInSymbol = pSym - &cSymbol[0];
2388 *pSym++ = c;
2391 else if ( 128 <= c || '\'' == c )
2392 { // High values need reparsing with i18n,
2393 // single quoted $'sheet' names too (otherwise we'd had to
2394 // implement everything twice).
2395 bi18n = true;
2396 eState = ssStop;
2398 else
2400 pSrc--;
2401 eState = ssStop;
2404 break;
2405 case ssGetBool :
2407 if( nMask & ScCharFlags::Bool )
2409 *pSym++ = c;
2410 eState = ssStop;
2412 else
2414 pSrc--;
2415 eState = ssStop;
2418 break;
2419 case ssGetValue :
2421 if( pSym == &cSymbol[ MAXSTRLEN ] )
2423 SetError(FormulaError::StringOverflow);
2424 eState = ssStop;
2426 else if (c == cDecSep || (cDecSepAlt && c == cDecSepAlt))
2428 if (++nDecSeps > 1)
2430 // reparse with i18n, may be numeric sheet name as well
2431 bi18n = true;
2432 eState = ssStop;
2434 else
2435 *pSym++ = c;
2437 else if( nMask & ScCharFlags::Value )
2438 *pSym++ = c;
2439 else if( nMask & ScCharFlags::ValueSep )
2441 pSrc--;
2442 eState = ssStop;
2444 else if (c == 'E' || c == 'e')
2446 if (GetCharTableFlags( pSrc[0], 0 ) & ScCharFlags::ValueExp)
2447 *pSym++ = c;
2448 else
2450 // reparse with i18n
2451 bi18n = true;
2452 eState = ssStop;
2455 else if( nMask & ScCharFlags::ValueSign )
2457 if (((cLast == 'E') || (cLast == 'e')) &&
2458 (GetCharTableFlags( pSrc[0], 0 ) & ScCharFlags::ValueValue))
2460 *pSym++ = c;
2462 else
2464 pSrc--;
2465 eState = ssStop;
2468 else
2470 // reparse with i18n
2471 bi18n = true;
2472 eState = ssStop;
2475 break;
2476 case ssGetString :
2478 if( nMask & ScCharFlags::StringSep )
2480 if ( !bQuote )
2482 if ( *pSrc == '"' )
2483 bQuote = true; // "" => literal "
2484 else
2485 eState = ssStop;
2487 else
2488 bQuote = false;
2490 if ( !bQuote )
2492 if( pSym == &cSymbol[ MAXSTRLEN ] )
2494 SetError(FormulaError::StringOverflow);
2495 eState = ssSkipString;
2497 else
2498 *pSym++ = c;
2501 break;
2502 case ssSkipString:
2503 if( nMask & ScCharFlags::StringSep )
2504 eState = ssStop;
2505 break;
2506 case ssGetErrorConstant:
2508 // ODFF Error ::= '#' [A-Z0-9]+ ([!?] | ('/' ([A-Z] | ([0-9] [!?]))))
2509 // BUT, in UI these may have been translated! So don't
2510 // check for ASCII alnum. Note that this construct can't be
2511 // parsed with i18n.
2512 /* TODO: be strict when reading ODFF, check for ASCII alnum
2513 * and proper continuation after '/'. However, even with
2514 * the lax parsing only the error constants we have defined
2515 * as opcode symbols will be recognized and others result
2516 * in ocBad, so the result is actually conformant. */
2517 bool bAdd = true;
2518 if ('?' == c)
2519 eState = ssStop;
2520 else if ('!' == c)
2522 // Check if this is #REF! that starts an invalid reference.
2523 // Note we have an implicit '!' here at the end.
2524 if (pSym - &cSymbol[0] == 4 && lcl_isUnicodeIgnoreAscii( cSymbol, "#REF", 4) &&
2525 (GetCharTableFlags( *pSrc, c) & ScCharFlags::Ident))
2526 eState = ssGetIdent;
2527 else
2528 eState = ssStop;
2530 else if ('/' == c)
2532 if (!bErrorConstantHadSlash)
2533 bErrorConstantHadSlash = true;
2534 else
2536 bAdd = false;
2537 eState = ssStop;
2540 else if ((nMask & ScCharFlags::WordSep) ||
2541 (c < 128 && !rtl::isAsciiAlphanumeric( c)))
2543 bAdd = false;
2544 eState = ssStop;
2546 if (!bAdd)
2547 --pSrc;
2548 else
2550 if (pSym == &cSymbol[ MAXSTRLEN ])
2552 SetError( FormulaError::StringOverflow);
2553 eState = ssStop;
2555 else
2556 *pSym++ = c;
2559 break;
2560 case ssGetTableRefItem:
2562 // Scan whatever up to the next ']' closer.
2563 if (c != ']')
2565 if( pSym == &cSymbol[ MAXSTRLEN ] )
2567 SetError( FormulaError::StringOverflow);
2568 eState = ssStop;
2570 else
2571 *pSym++ = c;
2573 else
2575 --pSrc;
2576 eState = ssStop;
2579 break;
2580 case ssGetTableRefColumn:
2582 // Scan whatever up to the next unescaped ']' closer.
2583 if (c != ']' || cLast == '\'')
2585 if( pSym == &cSymbol[ MAXSTRLEN ] )
2587 SetError( FormulaError::StringOverflow);
2588 eState = ssStop;
2590 else
2591 *pSym++ = c;
2593 else
2595 --pSrc;
2596 eState = ssStop;
2599 break;
2600 case ssGetReference:
2601 if( pSym == &cSymbol[ MAXSTRLEN ] )
2603 SetError( FormulaError::StringOverflow);
2604 eState = ssSkipReference;
2606 [[fallthrough]];
2607 case ssSkipReference:
2608 // ODF reference: ['External'#$'Sheet'.A1:.B2] with dots being
2609 // mandatory also if no sheet name. 'External'# is optional,
2610 // sheet name is optional, quotes around sheet name are
2611 // optional if no quote contained. [#REF!] is valid.
2612 // 2nd usage: ['Sheet'.$$'DefinedName']
2613 // 3rd usage: ['External'#$$'DefinedName']
2614 // 4th usage: ['External'#$'Sheet'.$$'DefinedName']
2615 // Also for all these names quotes are optional if no quote
2616 // contained.
2619 // nRefInName: 0 := not in sheet name yet. 'External'
2620 // is parsed as if it was a sheet name and nRefInName
2621 // is reset when # is encountered immediately after closing
2622 // quote. Same with 'DefinedName', nRefInName is cleared
2623 // when : is encountered.
2625 // Encountered leading $ before sheet name.
2626 constexpr int kDollar = (1 << 1);
2627 // Encountered ' opening quote, which may be after $ or
2628 // not.
2629 constexpr int kOpen = (1 << 2);
2630 // Somewhere in name.
2631 constexpr int kName = (1 << 3);
2632 // Encountered ' in name, will be cleared if double or
2633 // transformed to kClose if not, in which case kOpen is
2634 // cleared.
2635 constexpr int kQuote = (1 << 4);
2636 // Past ' closing quote.
2637 constexpr int kClose = (1 << 5);
2638 // Encountered # file/sheet separator.
2639 constexpr int kFileSep = (1 << 6);
2640 // Past . sheet name separator.
2641 constexpr int kPast = (1 << 7);
2642 // Marked name $$ follows sheet name separator, detected
2643 // while we're still on the separator. Will be cleared when
2644 // entering the name.
2645 constexpr int kMarkAhead = (1 << 8);
2646 // In marked defined name.
2647 constexpr int kDefName = (1 << 9);
2648 // Encountered # of #REF!
2649 constexpr int kRefErr = (1 << 10);
2651 bool bAddToSymbol = true;
2652 if ((nMask & ScCharFlags::OdfRBracket) && !(nRefInName & kOpen))
2654 OSL_ENSURE( nRefInName & (kPast | kDefName | kRefErr),
2655 "ScCompiler::NextSymbol: reference: "
2656 "closing bracket ']' without prior sheet name separator '.' violates ODF spec");
2657 // eaten, not added to pSym
2658 bAddToSymbol = false;
2659 eState = ssStop;
2661 else if (cSheetSep == c && nRefInName == 0)
2663 // eat it, no sheet name [.A1]
2664 bAddToSymbol = false;
2665 nRefInName |= kPast;
2666 if ('$' == pSrc[0] && '$' == pSrc[1])
2667 nRefInName |= kMarkAhead;
2669 else if (!(nRefInName & kPast) || (nRefInName & (kMarkAhead | kDefName)))
2671 // Not in col/row yet.
2673 if (SC_COMPILER_FILE_TAB_SEP == c && (nRefInName & kFileSep))
2674 nRefInName = 0;
2675 else if ('$' == c && '$' == pSrc[0] && !(nRefInName & kOpen))
2677 nRefInName &= ~kMarkAhead;
2678 if (!(nRefInName & kDefName))
2680 // eaten, not added to pSym (2 chars)
2681 bAddToSymbol = false;
2682 ++pSrc;
2683 nRefInName &= kPast;
2684 nRefInName |= kDefName;
2686 else
2688 // ScAddress::Parse() will recognize this as
2689 // invalid later.
2690 if (eState != ssSkipReference)
2692 *pSym++ = c;
2694 if( pSym == &cSymbol[ MAXSTRLEN ] )
2696 SetError( FormulaError::StringOverflow);
2697 eState = ssStop;
2699 else
2700 *pSym++ = *pSrc++;
2702 bAddToSymbol = false;
2705 else if (cSheetPrefix == c && nRefInName == 0)
2706 nRefInName |= kDollar;
2707 else if ('\'' == c)
2709 // TODO: The conventions' parseExternalName()
2710 // should handle quoted names, but as long as they
2711 // don't remove non-embedded quotes here.
2712 if (!(nRefInName & kName))
2714 nRefInName |= (kOpen | kName);
2715 bAddToSymbol = !(nRefInName & kDefName);
2717 else if (!(nRefInName & kOpen))
2719 OSL_FAIL("ScCompiler::NextSymbol: reference: "
2720 "a ''' without the name being enclosed in '...' violates ODF spec");
2722 else if (nRefInName & kQuote)
2724 // escaped embedded quote
2725 nRefInName &= ~kQuote;
2727 else
2729 switch (pSrc[0])
2731 case '\'':
2732 // escapes embedded quote
2733 nRefInName |= kQuote;
2734 break;
2735 case SC_COMPILER_FILE_TAB_SEP:
2736 // sheet name should follow
2737 nRefInName |= kFileSep;
2738 [[fallthrough]];
2739 default:
2740 // quote not followed by quote => close
2741 nRefInName |= kClose;
2742 nRefInName &= ~kOpen;
2744 bAddToSymbol = !(nRefInName & kDefName);
2747 else if ('#' == c && nRefInName == 0)
2748 nRefInName |= kRefErr;
2749 else if (cSheetSep == c && !(nRefInName & kOpen))
2751 // unquoted sheet name separator
2752 nRefInName |= kPast;
2753 if ('$' == pSrc[0] && '$' == pSrc[1])
2754 nRefInName |= kMarkAhead;
2756 else if (':' == c && !(nRefInName & kOpen))
2758 OSL_FAIL("ScCompiler::NextSymbol: reference: "
2759 "range operator ':' without prior sheet name separator '.' violates ODF spec");
2760 nRefInName = 0;
2761 ++mnPredetectedReference;
2763 else if (!(nRefInName & kName))
2765 // start unquoted name
2766 nRefInName |= kName;
2769 else if (':' == c)
2771 // range operator
2772 nRefInName = 0;
2773 ++mnPredetectedReference;
2775 if (bAddToSymbol && eState != ssSkipReference)
2776 *pSym++ = c; // everything is part of reference
2778 break;
2779 case ssStop:
2780 ; // nothing, prevent warning
2781 break;
2783 cLast = c;
2784 c = *pSrc;
2787 if (aSpace.nCount && aSpace.cChar)
2788 vSpaces.emplace_back(aSpace);
2790 if ( bi18n )
2792 const sal_Int32 nOldSrcPos = nSrcPos;
2793 for (const auto& r : vSpaces)
2794 nSrcPos += r.nCount;
2795 // If group separator is not a possible operator and not one of any
2796 // separators then it may be parsed away in numbers. This is
2797 // specifically the case with NO-BREAK SPACE, which actually triggers
2798 // the bi18n case (which we don't want to include as yet another
2799 // special case above as it is rare enough and doesn't generally occur
2800 // in formulas).
2801 const sal_Unicode cGroupSep = ScGlobal::getLocaleData().getNumThousandSep()[0];
2802 const bool bGroupSeparator = (128 <= cGroupSep && cGroupSep != cSep &&
2803 cGroupSep != cArrayColSep && cGroupSep != cArrayRowSep &&
2804 cGroupSep != cDecSep && cGroupSep != cDecSepAlt &&
2805 cGroupSep != cSheetPrefix && cGroupSep != cSheetSep);
2806 // If a numeric context triggered bi18n then use the default locale's
2807 // CharClass, this may accept group separator as well.
2808 const CharClass* pMyCharClass = (ScGlobal::getCharClass().isDigit( OUString(pStart[nSrcPos]), 0) ?
2809 &ScGlobal::getCharClass() : pCharClass);
2810 OUStringBuffer aSymbol;
2811 mnRangeOpPosInSymbol = -1;
2812 FormulaError nErr = FormulaError::NONE;
2815 bi18n = false;
2816 // special case (e.g. $'sheetname' in OOO A1)
2817 if ( pStart[nSrcPos] == cSheetPrefix && pStart[nSrcPos+1] == '\'' )
2818 aSymbol.append(pStart[nSrcPos++]);
2820 ParseResult aRes = pConv->parseAnyToken( aFormula, nSrcPos, pMyCharClass, bGroupSeparator);
2822 if ( !aRes.TokenType )
2824 nErr = FormulaError::IllegalChar;
2825 SetError( nErr ); // parsed chars as string
2827 if ( aRes.EndPos <= nSrcPos )
2829 // Could not parse anything meaningful.
2830 assert(!aRes.TokenType);
2831 nErr = FormulaError::IllegalChar;
2832 SetError( nErr );
2833 // Caller has to act on an empty symbol for
2834 // nSrcPos < aFormula.getLength()
2835 nSrcPos = nOldSrcPos;
2836 aSymbol.setLength(0);
2838 else
2840 // When having parsed a second reference part, ensure that the
2841 // i18n parser did not mistakenly parse a number that included
2842 // a separator which happened to be meant as a parameter
2843 // separator instead.
2844 if (mnRangeOpPosInSymbol >= 0 && (aRes.TokenType & KParseType::ASC_NUMBER))
2846 for (sal_Int32 i = nSrcPos; i < aRes.EndPos; ++i)
2848 if (pStart[i] == cSep)
2849 aRes.EndPos = i; // also ends for
2852 aSymbol.append( pStart + nSrcPos, aRes.EndPos - nSrcPos);
2853 nSrcPos = aRes.EndPos;
2854 c = pStart[nSrcPos];
2855 if ( aRes.TokenType & KParseType::SINGLE_QUOTE_NAME )
2856 { // special cases (e.g. 'sheetname'. or 'filename'# in OOO A1)
2857 bi18n = (c == cSheetSep || c == SC_COMPILER_FILE_TAB_SEP);
2859 // One range operator restarts parsing for second reference.
2860 if (c == ':' && mnRangeOpPosInSymbol < 0)
2862 mnRangeOpPosInSymbol = aSymbol.getLength();
2863 bi18n = true;
2865 if ( bi18n )
2866 aSymbol.append(pStart[nSrcPos++]);
2868 } while ( bi18n && nErr == FormulaError::NONE );
2869 sal_Int32 nLen = aSymbol.getLength();
2870 if ( nLen > MAXSTRLEN )
2872 SetError( FormulaError::StringOverflow );
2873 nLen = MAXSTRLEN;
2875 if (mnRangeOpPosInSymbol >= nLen)
2876 mnRangeOpPosInSymbol = -1;
2877 lcl_UnicodeStrNCpy( cSymbol, aSymbol.getStr(), nLen );
2878 pSym = &cSymbol[nLen];
2880 else
2882 nSrcPos = pSrc - pStart;
2883 *pSym = 0;
2885 if (mnRangeOpPosInSymbol >= 0 && mnRangeOpPosInSymbol == (pSym-1) - &cSymbol[0])
2887 // This is a trailing range operator, which is nonsense. Will be caught
2888 // in next round.
2889 mnRangeOpPosInSymbol = -1;
2890 *--pSym = 0;
2891 --nSrcPos;
2893 if ( bAutoCorrect )
2894 aCorrectedSymbol = OUString(cSymbol, pSym - cSymbol);
2895 if (bAutoIntersection && vSpaces[nAutoIntersectionSpacesPos].nCount > 1)
2896 --vSpaces[nAutoIntersectionSpacesPos].nCount; // replace '!!' with only one space
2897 return vSpaces;
2900 // Convert symbol to token
2902 bool ScCompiler::ParseOpCode( const OUString& rName, bool bInArray )
2904 OpCodeHashMap::const_iterator iLook( mxSymbols->getHashMap().find( rName));
2905 bool bFound = (iLook != mxSymbols->getHashMap().end());
2906 if (bFound)
2908 OpCode eOp = iLook->second;
2909 if (bInArray)
2911 if (rName == mxSymbols->getSymbol(ocArrayColSep))
2912 eOp = ocArrayColSep;
2913 else if (rName == mxSymbols->getSymbol(ocArrayRowSep))
2914 eOp = ocArrayRowSep;
2916 else if (eOp == ocArrayColSep || eOp == ocArrayRowSep)
2918 if (rName == mxSymbols->getSymbol(ocSep))
2919 eOp = ocSep;
2920 else if (rName == ";")
2922 switch (FormulaGrammar::extractFormulaLanguage( meGrammar))
2924 // Only for languages/grammars that actually use ';'
2925 // parameter separator.
2926 case css::sheet::FormulaLanguage::NATIVE:
2927 case css::sheet::FormulaLanguage::ENGLISH:
2928 case css::sheet::FormulaLanguage::ODFF:
2929 case css::sheet::FormulaLanguage::ODF_11:
2930 eOp = ocSep;
2934 else if (eOp == ocCeil && mxSymbols->isOOXML())
2936 // Ensure that _xlfn.CEILING.MATH maps to ocCeil_Math. ocCeil is
2937 // unassigned for import.
2938 eOp = ocCeil_Math;
2940 else if (eOp == ocFloor && mxSymbols->isOOXML())
2942 // Ensure that _xlfn.FLOOR.MATH maps to ocFloor_Math. ocFloor is
2943 // unassigned for import.
2944 eOp = ocFloor_Math;
2946 maRawToken.SetOpCode(eOp);
2948 else if (mxSymbols->isODFF())
2950 // ODFF names that are not written in the current mapping but to be
2951 // recognized. New names will be written in a future release, then
2952 // exchange (!) with the names in
2953 // formula/source/core/resource/core_resource.src to be able to still
2954 // read the old names as well.
2955 struct FunctionName
2957 const char* pName;
2958 OpCode eOp;
2960 static const FunctionName aOdffAliases[] = {
2961 // Renamed old names, still accept them:
2962 { "B", ocB }, // B -> BINOM.DIST.RANGE
2963 { "TDIST", ocTDist }, // TDIST -> LEGACY.TDIST
2964 { "EASTERSUNDAY", ocEasterSunday }, // EASTERSUNDAY -> ORG.OPENOFFICE.EASTERSUNDAY
2965 { "ZGZ", ocRRI }, // ZGZ -> RRI
2966 { "COLOR", ocColor }, // COLOR -> ORG.LIBREOFFICE.COLOR
2967 { "GOALSEEK", ocBackSolver }, // GOALSEEK -> ORG.OPENOFFICE.GOALSEEK
2968 { "COM.MICROSOFT.F.DIST", ocFDist_LT }, // fdo#40835, -> FDIST -> COM.MICROSOFT.F.DIST
2969 { "COM.MICROSOFT.F.INV", ocFInv_LT } // tdf#94214, COM.MICROSOFT.F.INV -> FINV (ODF)
2970 // Renamed new names, prepare to read future names:
2971 //{ "ORG.OPENOFFICE.XXX", ocXXX } // XXX -> ORG.OPENOFFICE.XXX
2973 for (const FunctionName& rOdffAlias : aOdffAliases)
2975 if (rName.equalsIgnoreAsciiCaseAscii( rOdffAlias.pName))
2977 maRawToken.SetOpCode( rOdffAlias.eOp);
2978 bFound = true;
2979 break; // for
2983 else if (mxSymbols->isOOXML())
2985 // OOXML names that are not written in the current mapping but to be
2986 // recognized as old versions wrote them.
2987 struct FunctionName
2989 const char* pName;
2990 OpCode eOp;
2992 static const FunctionName aOoxmlAliases[] = {
2993 { "EFFECTIVE", ocEffect }, // EFFECTIVE -> EFFECT
2994 { "ERRORTYPE", ocErrorType }, // ERRORTYPE -> _xlfn.ORG.OPENOFFICE.ERRORTYPE
2995 { "MULTIRANGE", ocMultiArea }, // MULTIRANGE -> _xlfn.ORG.OPENOFFICE.MULTIRANGE
2996 { "GOALSEEK", ocBackSolver }, // GOALSEEK -> _xlfn.ORG.OPENOFFICE.GOALSEEK
2997 { "EASTERSUNDAY", ocEasterSunday }, // EASTERSUNDAY -> _xlfn.ORG.OPENOFFICE.EASTERSUNDAY
2998 { "CURRENT", ocCurrent }, // CURRENT -> _xlfn.ORG.OPENOFFICE.CURRENT
2999 { "STYLE", ocStyle } // STYLE -> _xlfn.ORG.OPENOFFICE.STYLE
3001 for (const FunctionName& rOoxmlAlias : aOoxmlAliases)
3003 if (rName.equalsIgnoreAsciiCaseAscii( rOoxmlAlias.pName))
3005 maRawToken.SetOpCode( rOoxmlAlias.eOp);
3006 bFound = true;
3007 break; // for
3011 else if (mxSymbols->isPODF())
3013 // PODF names are ODF 1.0/1.1 and also used in API XFunctionAccess.
3014 // We can't rename them in
3015 // formula/source/core/resource/core_resource.src but can add
3016 // additional names to be recognized here so they match the UI names if
3017 // those are renamed.
3018 struct FunctionName
3020 const char* pName;
3021 OpCode eOp;
3023 static const FunctionName aPodfAliases[] = {
3024 { "EFFECT", ocEffect } // EFFECTIVE -> EFFECT
3026 for (const FunctionName& rPodfAlias : aPodfAliases)
3028 if (rName.equalsIgnoreAsciiCaseAscii( rPodfAlias.pName))
3030 maRawToken.SetOpCode( rPodfAlias.eOp);
3031 bFound = true;
3032 break; // for
3037 if (!bFound)
3039 OUString aIntName;
3040 if (mxSymbols->hasExternals())
3042 // If symbols are set by filters get mapping to exact name.
3043 ExternalHashMap::const_iterator iExt(
3044 mxSymbols->getExternalHashMap().find( rName));
3045 if (iExt != mxSymbols->getExternalHashMap().end())
3047 if (ScGlobal::GetAddInCollection()->GetFuncData( (*iExt).second))
3048 aIntName = (*iExt).second;
3051 else
3053 // Old (deprecated) addins first for legacy.
3054 if (ScGlobal::GetLegacyFuncCollection()->findByName(OUString(cSymbol)))
3056 aIntName = cSymbol;
3058 else
3059 // bLocalFirst=false for (English) upper full original name
3060 // (service.function)
3061 aIntName = ScGlobal::GetAddInCollection()->FindFunction(
3062 rName, !mxSymbols->isEnglish());
3064 if (!aIntName.isEmpty())
3066 maRawToken.SetExternal( aIntName ); // international name
3067 bFound = true;
3070 if (!bFound)
3071 return false;
3072 OpCode eOp = maRawToken.GetOpCode();
3073 if (eOp == ocSub || eOp == ocNegSub)
3075 bool bShouldBeNegSub =
3076 (eLastOp == ocOpen || eLastOp == ocSep || eLastOp == ocNegSub ||
3077 (SC_OPCODE_START_BIN_OP <= eLastOp && eLastOp < SC_OPCODE_STOP_BIN_OP) ||
3078 eLastOp == ocArrayOpen ||
3079 eLastOp == ocArrayColSep || eLastOp == ocArrayRowSep);
3080 if (bShouldBeNegSub && eOp == ocSub)
3081 maRawToken.NewOpCode( ocNegSub );
3082 //TODO: if ocNegSub had ForceArray we'd have to set it here
3083 else if (!bShouldBeNegSub && eOp == ocNegSub)
3084 maRawToken.NewOpCode( ocSub );
3086 return bFound;
3089 bool ScCompiler::ParseOpCode2( std::u16string_view rName )
3091 for (sal_uInt16 i = ocInternalBegin; i <= ocInternalEnd; i++)
3093 if (o3tl::equalsAscii(rName, pInternal[i - ocInternalBegin]))
3095 maRawToken.SetOpCode(static_cast<OpCode>(i));
3096 return true;
3100 return false;
3103 static bool lcl_ParenthesisFollows( const sal_Unicode* p )
3105 while (*p == ' ')
3106 p++;
3107 return *p == '(';
3110 bool ScCompiler::ParseValue( const OUString& rSym )
3112 const sal_Int32 nFormulaLanguage = FormulaGrammar::extractFormulaLanguage( GetGrammar());
3113 if (nFormulaLanguage == css::sheet::FormulaLanguage::ODFF || nFormulaLanguage == css::sheet::FormulaLanguage::OOXML)
3115 // Speedup things for ODFF, only well-formed numbers, not locale
3116 // dependent nor user input.
3117 rtl_math_ConversionStatus eStatus;
3118 sal_Int32 nParseEnd;
3119 double fVal = rtl::math::stringToDouble( rSym, '.', 0, &eStatus, &nParseEnd);
3120 if (nParseEnd != rSym.getLength())
3122 // Not (only) a number.
3124 if (nParseEnd > 0)
3125 return false; // partially a number => no such thing
3127 if (lcl_ParenthesisFollows( aFormula.getStr() + nSrcPos))
3128 return false; // some function name, not a constant
3130 // Could be TRUE or FALSE constant.
3131 OpCode eOpFunc = ocNone;
3132 if (rSym.equalsIgnoreAsciiCase("TRUE"))
3133 eOpFunc = ocTrue;
3134 else if (rSym.equalsIgnoreAsciiCase("FALSE"))
3135 eOpFunc = ocFalse;
3136 if (eOpFunc != ocNone)
3138 maRawToken.SetOpCode(eOpFunc);
3139 // add missing trailing parentheses
3140 maPendingOpCodes.push(ocOpen);
3141 maPendingOpCodes.push(ocClose);
3142 return true;
3144 return false;
3146 if (eStatus == rtl_math_ConversionStatus_OutOfRange)
3148 // rtl::math::stringToDouble() recognizes XMLSchema-2 "INF" and
3149 // "NaN" (case sensitive) that could be named expressions or DB
3150 // areas as well.
3151 // rSym is already upper so "NaN" is not possible here.
3152 if (!std::isfinite(fVal) && rSym == "INF")
3154 SCTAB nSheet = -1;
3155 if (GetRangeData( nSheet, rSym))
3156 return false;
3157 if (rDoc.GetDBCollection()->getNamedDBs().findByUpperName(rSym))
3158 return false;
3160 /* TODO: is there a specific reason why we don't accept an infinity
3161 * value that would raise an error in the interpreter, instead of
3162 * setting the hard error at the token array already? */
3163 SetError( FormulaError::IllegalArgument );
3165 maRawToken.SetDouble( fVal );
3166 return true;
3169 double fVal;
3170 sal_uInt32 nIndex = mxSymbols->isEnglishLocale() ? mrInterpreterContext.NFGetStandardIndex(LANGUAGE_ENGLISH_US) : 0;
3172 if (!mrInterpreterContext.NFIsNumberFormat(rSym, nIndex, fVal))
3173 return false;
3175 SvNumFormatType nType = mrInterpreterContext.NFGetType(nIndex);
3177 // Don't accept 3:3 as time, it is a reference to entire row 3 instead.
3178 // Dates should never be entered directly and automatically converted
3179 // to serial, because the serial would be wrong if null-date changed.
3180 // Usually it wouldn't be accepted anyway because the date separator
3181 // clashed with other separators or operators.
3182 if (nType & (SvNumFormatType::TIME | SvNumFormatType::DATE))
3183 return false;
3185 if (nType == SvNumFormatType::LOGICAL)
3187 if (lcl_ParenthesisFollows( aFormula.getStr() + nSrcPos))
3188 return false; // Boolean function instead.
3191 if( nType == SvNumFormatType::TEXT )
3192 // HACK: number too big!
3193 SetError( FormulaError::IllegalArgument );
3194 maRawToken.SetDouble( fVal );
3195 return true;
3198 bool ScCompiler::ParseString()
3200 if ( cSymbol[0] != '"' )
3201 return false;
3202 const sal_Unicode* p = cSymbol+1;
3203 while ( *p )
3204 p++;
3205 sal_Int32 nLen = sal::static_int_cast<sal_Int32>( p - cSymbol - 1 );
3206 if (!nLen || cSymbol[nLen] != '"')
3207 return false;
3208 svl::SharedString aSS = rDoc.GetSharedStringPool().intern(OUString(cSymbol+1, nLen-1));
3209 maRawToken.SetString(aSS.getData(), aSS.getDataIgnoreCase());
3210 return true;
3213 bool ScCompiler::ParsePredetectedErrRefReference( const OUString& rName, const OUString* pErrRef )
3215 switch (mnPredetectedReference)
3217 case 1:
3218 return ParseSingleReference( rName, pErrRef);
3219 case 2:
3220 return ParseDoubleReference( rName, pErrRef);
3221 default:
3222 return false;
3226 bool ScCompiler::ParsePredetectedReference( const OUString& rName )
3228 // Speedup documents with lots of broken references, e.g. sheet deleted.
3229 // It could also be a broken invalidated reference that contains #REF!
3230 // (but is not equal to), which we wrote prior to ODFF and also to ODFF
3231 // between 2013 and 2016 until 5.1.4
3232 constexpr OUString aErrRef(u"#REF!"_ustr); // not localized in ODFF
3233 sal_Int32 nPos = rName.indexOf( aErrRef);
3234 if (nPos != -1)
3236 /* TODO: this may be enhanced by reusing scan information from
3237 * NextSymbol(), the positions of quotes and special characters found
3238 * there for $'sheet'.A1:... could be stored in a vector. We don't
3239 * fully rescan here whether found positions are within single quotes
3240 * for performance reasons. This code does not check for possible
3241 * occurrences of insane "valid" sheet names like
3242 * 'haha.#REF!1fooledyou' and will generate an error on such. */
3243 if (nPos == 0)
3245 // Per ODFF the correct string for a reference error is just #REF!,
3246 // so pass it on.
3247 if (rName.getLength() == 5)
3248 return ParseErrorConstant( rName);
3249 // #REF!.AB42 or #REF!42 or #REF!#REF!
3250 return ParsePredetectedErrRefReference( rName, &aErrRef);
3252 sal_Unicode c = rName[nPos-1]; // before #REF!
3253 if ('$' == c)
3255 if (nPos == 1)
3257 // $#REF!.AB42 or $#REF!42 or $#REF!#REF!
3258 return ParsePredetectedErrRefReference( rName, &aErrRef);
3260 c = rName[nPos-2]; // before $#REF!
3262 sal_Unicode c2 = nPos+5 < rName.getLength() ? rName[nPos+5] : 0; // after #REF!
3263 switch (c)
3265 case '.':
3266 if ('$' == c2 || '#' == c2 || ('0' <= c2 && c2 <= '9'))
3268 // sheet.#REF!42 or sheet.#REF!#REF!
3269 return ParsePredetectedErrRefReference( rName, &aErrRef);
3271 break;
3272 case ':':
3273 if (mnPredetectedReference > 1 &&
3274 ('.' == c2 || '$' == c2 || '#' == c2 ||
3275 ('0' <= c2 && c2 <= '9')))
3277 // :#REF!.AB42 or :#REF!42 or :#REF!#REF!
3278 return ParsePredetectedErrRefReference( rName, &aErrRef);
3280 break;
3281 default:
3282 if (rtl::isAsciiAlpha(c) &&
3283 ((mnPredetectedReference > 1 && ':' == c2) || 0 == c2))
3285 // AB#REF!: or AB#REF!
3286 return ParsePredetectedErrRefReference( rName, &aErrRef);
3290 switch (mnPredetectedReference)
3292 case 1:
3293 return ParseSingleReference( rName);
3294 case 2:
3295 return ParseDoubleReference( rName);
3297 return false;
3300 bool ScCompiler::ParseDoubleReference( const OUString& rName, const OUString* pErrRef )
3302 ScRange aRange( aPos, aPos );
3303 const ScAddress::Details aDetails( pConv->meConv, aPos );
3304 ScAddress::ExternalInfo aExtInfo;
3305 ScRefFlags nFlags = aRange.Parse( rName, rDoc, aDetails, &aExtInfo, &maExternalLinks, pErrRef );
3306 if( nFlags & ScRefFlags::VALID )
3308 ScComplexRefData aRef;
3309 aRef.InitRange( aRange );
3310 aRef.Ref1.SetColRel( (nFlags & ScRefFlags::COL_ABS) == ScRefFlags::ZERO );
3311 aRef.Ref1.SetRowRel( (nFlags & ScRefFlags::ROW_ABS) == ScRefFlags::ZERO );
3312 aRef.Ref1.SetTabRel( (nFlags & ScRefFlags::TAB_ABS) == ScRefFlags::ZERO );
3313 if ( !(nFlags & ScRefFlags::TAB_VALID) )
3314 aRef.Ref1.SetTabDeleted( true ); // #REF!
3315 aRef.Ref1.SetFlag3D( ( nFlags & ScRefFlags::TAB_3D ) != ScRefFlags::ZERO );
3316 aRef.Ref2.SetColRel( (nFlags & ScRefFlags::COL2_ABS) == ScRefFlags::ZERO );
3317 aRef.Ref2.SetRowRel( (nFlags & ScRefFlags::ROW2_ABS) == ScRefFlags::ZERO );
3318 aRef.Ref2.SetTabRel( (nFlags & ScRefFlags::TAB2_ABS) == ScRefFlags::ZERO );
3319 if ( !(nFlags & ScRefFlags::TAB2_VALID) )
3320 aRef.Ref2.SetTabDeleted( true ); // #REF!
3321 aRef.Ref2.SetFlag3D( ( nFlags & ScRefFlags::TAB2_3D ) != ScRefFlags::ZERO );
3322 aRef.SetRange(rDoc.GetSheetLimits(), aRange, aPos);
3323 if (aExtInfo.mbExternal)
3325 ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager();
3326 const OUString* pRealTab = pRefMgr->getRealTableName(aExtInfo.mnFileId, aExtInfo.maTabName);
3327 maRawToken.SetExternalDoubleRef(
3328 aExtInfo.mnFileId, pRealTab ? *pRealTab : aExtInfo.maTabName, aRef);
3329 maExternalFiles.push_back(aExtInfo.mnFileId);
3331 else
3333 maRawToken.SetDoubleReference(aRef);
3337 return ( nFlags & ScRefFlags::VALID ) != ScRefFlags::ZERO;
3340 bool ScCompiler::ParseSingleReference( const OUString& rName, const OUString* pErrRef )
3342 mnCurrentSheetEndPos = 0;
3343 mnCurrentSheetTab = -1;
3344 ScAddress aAddr( aPos );
3345 const ScAddress::Details aDetails( pConv->meConv, aPos );
3346 ScAddress::ExternalInfo aExtInfo;
3347 ScRefFlags nFlags = aAddr.Parse( rName, rDoc, aDetails,
3348 &aExtInfo, &maExternalLinks, &mnCurrentSheetEndPos, pErrRef);
3349 // Something must be valid in order to recognize Sheet1.blah or blah.a1
3350 // as a (wrong) reference.
3351 if( nFlags & ( ScRefFlags::COL_VALID|ScRefFlags::ROW_VALID|ScRefFlags::TAB_VALID ) )
3353 // Valid given tab and invalid col or row may indicate a sheet-local
3354 // named expression, bail out early and don't create a reference token.
3355 if (!(nFlags & ScRefFlags::VALID) && mnCurrentSheetEndPos > 0 &&
3356 (nFlags & ScRefFlags::TAB_VALID) && (nFlags & ScRefFlags::TAB_3D))
3358 if (aExtInfo.mbExternal)
3360 // External names are handled separately.
3361 mnCurrentSheetEndPos = 0;
3362 mnCurrentSheetTab = -1;
3364 else
3366 mnCurrentSheetTab = aAddr.Tab();
3368 return false;
3371 if( HasPossibleNamedRangeConflict( aAddr.Tab()))
3373 // A named range named e.g. 'num1' is valid with 1k columns, but would become a reference
3374 // when the document is opened later with 16k columns. Resolve the conflict by not
3375 // considering it a reference.
3376 OUString aUpper( ScGlobal::getCharClass().uppercase( rName ));
3377 mnCurrentSheetTab = aAddr.Tab(); // temporarily set for ParseNamedRange()
3378 if(ParseNamedRange( aUpper, true )) // only check
3379 return false;
3380 mnCurrentSheetTab = -1;
3383 ScSingleRefData aRef;
3384 aRef.InitAddress( aAddr );
3385 aRef.SetColRel( (nFlags & ScRefFlags::COL_ABS) == ScRefFlags::ZERO );
3386 aRef.SetRowRel( (nFlags & ScRefFlags::ROW_ABS) == ScRefFlags::ZERO );
3387 aRef.SetTabRel( (nFlags & ScRefFlags::TAB_ABS) == ScRefFlags::ZERO );
3388 aRef.SetFlag3D( ( nFlags & ScRefFlags::TAB_3D ) != ScRefFlags::ZERO );
3389 // the reference is really invalid
3390 if( !( nFlags & ScRefFlags::VALID ) )
3392 if( !( nFlags & ScRefFlags::COL_VALID ) )
3393 aRef.SetColDeleted(true);
3394 if( !( nFlags & ScRefFlags::ROW_VALID ) )
3395 aRef.SetRowDeleted(true);
3396 if( !( nFlags & ScRefFlags::TAB_VALID ) )
3397 aRef.SetTabDeleted(true);
3398 nFlags |= ScRefFlags::VALID;
3400 aRef.SetAddress(rDoc.GetSheetLimits(), aAddr, aPos);
3402 if (aExtInfo.mbExternal)
3404 ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager();
3405 const OUString* pRealTab = pRefMgr->getRealTableName(aExtInfo.mnFileId, aExtInfo.maTabName);
3406 maRawToken.SetExternalSingleRef(
3407 aExtInfo.mnFileId, pRealTab ? *pRealTab : aExtInfo.maTabName, aRef);
3408 maExternalFiles.push_back(aExtInfo.mnFileId);
3410 else
3411 maRawToken.SetSingleReference(aRef);
3414 return ( nFlags & ScRefFlags::VALID ) != ScRefFlags::ZERO;
3417 bool ScCompiler::ParseReference( const OUString& rName, const OUString* pErrRef )
3419 // Has to be called before ParseValue
3421 // A later ParseNamedRange() relies on these, being set in ParseSingleReference()
3422 // if so, reset in all cases.
3423 mnCurrentSheetEndPos = 0;
3424 mnCurrentSheetTab = -1;
3426 sal_Unicode ch1 = rName[0];
3427 sal_Unicode cDecSep = ( mxSymbols->isEnglishLocale() ? '.' : ScGlobal::getLocaleData().getNumDecimalSep()[0] );
3428 if ( ch1 == cDecSep )
3429 return false;
3430 // Code further down checks only if cDecSep=='.' so simply obtaining the
3431 // alternative decimal separator if it's not is sufficient.
3432 if (cDecSep != '.')
3434 cDecSep = ScGlobal::getLocaleData().getNumDecimalSepAlt().toChar();
3435 if ( ch1 == cDecSep )
3436 return false;
3438 // Who was that imbecile introducing '.' as the sheet name separator!?!
3439 if ( rtl::isAsciiDigit( ch1 ) && pConv->getSpecialSymbol( Convention::SHEET_SEPARATOR) == '.' )
3441 // Numerical sheet name is valid.
3442 // But English 1.E2 or 1.E+2 is value 100, 1.E-2 is 0.01
3443 // Don't create a #REF! of values. But also do not bail out on
3444 // something like 3:3, meaning entire row 3.
3447 const sal_Int32 nPos = ScGlobal::FindUnquoted( rName, '.');
3448 if ( nPos == -1 )
3450 if (ScGlobal::FindUnquoted( rName, ':') != -1)
3451 break; // may be 3:3, continue as usual
3452 return false;
3454 sal_Unicode const * const pTabSep = rName.getStr() + nPos;
3455 sal_Unicode ch2 = pTabSep[1]; // maybe a column identifier
3456 if ( !(ch2 == '$' || rtl::isAsciiAlpha( ch2 )) )
3457 return false;
3458 if ( cDecSep == '.' && (ch2 == 'E' || ch2 == 'e') // E + - digit
3459 && (GetCharTableFlags( pTabSep[2], pTabSep[1] ) & ScCharFlags::ValueExp) )
3461 // If it is an 1.E2 expression check if "1" is an existent sheet
3462 // name. If so, a desired value 1.E2 would have to be entered as
3463 // 1E2 or 1.0E2 or 1.E+2, sorry. Another possibility would be to
3464 // require numerical sheet names always being entered quoted, which
3465 // is not desirable (too many 1999, 2000, 2001 sheets in use).
3466 // Furthermore, XML files created with versions prior to SRC640e
3467 // wouldn't contain the quotes added by MakeTabStr()/CheckTabQuotes()
3468 // and would produce wrong formulas if the conditions here are met.
3469 // If you can live with these restrictions you may remove the
3470 // check and return an unconditional FALSE.
3471 OUString aTabName( rName.copy( 0, nPos ) );
3472 SCTAB nTab;
3473 if ( !rDoc.GetTable( aTabName, nTab ) )
3474 return false;
3475 // If sheet "1" exists and the expression is 1.E+2 continue as
3476 // usual, the ScRange/ScAddress parser will take care of it.
3478 } while(false);
3481 if (ParseSingleReference( rName, pErrRef))
3482 return true;
3484 // Though the range operator is handled explicitly, when encountering
3485 // something like Sheet1.A:A we will have to treat it as one entity if it
3486 // doesn't pass as single cell reference.
3487 if (mnRangeOpPosInSymbol > 0) // ":foo" would be nonsense
3489 if (ParseDoubleReference( rName, pErrRef))
3490 return true;
3491 // Now try with a symbol up to the range operator, rewind source
3492 // position.
3493 assert(mnRangeOpPosInSymbol < MAXSTRLEN); // We should have caught the maldoers.
3494 if (mnRangeOpPosInSymbol >= MAXSTRLEN) // TODO: this check and return
3495 return false; // can be removed when sure.
3496 sal_Int32 nLen = mnRangeOpPosInSymbol;
3497 while (cSymbol[++nLen])
3499 cSymbol[mnRangeOpPosInSymbol] = 0;
3500 nSrcPos -= (nLen - mnRangeOpPosInSymbol);
3501 mnRangeOpPosInSymbol = -1;
3502 mbRewind = true;
3503 return true; // end all checks
3505 else
3507 switch (pConv->meConv)
3509 case FormulaGrammar::CONV_XL_A1:
3510 case FormulaGrammar::CONV_XL_OOX:
3511 // Special treatment for the 'E:\[doc]Sheet1:Sheet3'!D5 Excel
3512 // sickness, mnRangeOpPosInSymbol did not catch the range
3513 // operator as it is within a quoted name.
3514 if (rName[0] != '\'')
3515 return false; // Document name has to be single quoted.
3516 [[fallthrough]];
3517 case FormulaGrammar::CONV_XL_R1C1:
3518 // C2 or C[1] are valid entire column references.
3519 if (ParseDoubleReference( rName, pErrRef))
3520 return true;
3521 break;
3522 default:
3523 ; // nothing
3526 return false;
3529 bool ScCompiler::ParseMacro( const OUString& rName )
3531 #if !HAVE_FEATURE_SCRIPTING
3532 (void) rName;
3534 return false;
3535 #else
3537 // Calling SfxObjectShell::GetBasic() may result in all sort of things
3538 // including obtaining the model and deep down in
3539 // SfxBaseModel::getDocumentStorage() acquiring the SolarMutex, which when
3540 // formulas are compiled from a threaded import may result in a deadlock.
3541 // Check first if we actually could acquire it and if not bail out.
3542 /* FIXME: yes, but how ... */
3543 vcl::SolarMutexTryAndBuyGuard g;
3544 if (!g.isAcquired())
3546 SAL_WARN( "sc.core", "ScCompiler::ParseMacro - SolarMutex would deadlock, not obtaining Basic");
3547 return false; // bad luck
3550 OUString aName( rName);
3551 StarBASIC* pObj = nullptr;
3552 ScDocShell* pDocSh = rDoc.GetDocumentShell();
3556 if( pDocSh )//XXX
3557 pObj = pDocSh->GetBasic();
3558 else
3559 pObj = SfxApplication::GetBasic();
3561 catch (...)
3563 return false;
3566 if (!pObj)
3567 return false;
3569 // ODFF recommends to store user-defined functions prefixed with "USER.",
3570 // use only unprefixed name if encountered. BASIC doesn't allow '.' in a
3571 // function name so a function "USER.FOO" could not exist, and macro check
3572 // is assigned the lowest priority in function name check.
3573 if (FormulaGrammar::isODFF( GetGrammar()) && aName.startsWithIgnoreAsciiCase("USER."))
3574 aName = aName.copy(5);
3576 SbxMethod* pMeth = static_cast<SbxMethod*>(pObj->Find( aName, SbxClassType::Method ));
3577 if( !pMeth )
3579 return false;
3581 // It really should be a BASIC function!
3582 if( pMeth->GetType() == SbxVOID
3583 || ( pMeth->IsFixed() && pMeth->GetType() == SbxEMPTY )
3584 || dynamic_cast<const SbMethod*>( pMeth) == nullptr )
3586 return false;
3588 maRawToken.SetExternal( aName );
3589 maRawToken.eOp = ocMacro;
3590 return true;
3591 #endif
3594 const ScRangeData* ScCompiler::GetRangeData( SCTAB& rSheet, const OUString& rUpperName ) const
3596 // try local names first
3597 rSheet = aPos.Tab();
3598 const ScRangeName* pRangeName = rDoc.GetRangeName(rSheet);
3599 const ScRangeData* pData = nullptr;
3600 if (pRangeName)
3601 pData = pRangeName->findByUpperName(rUpperName);
3602 if (!pData)
3604 pRangeName = rDoc.GetRangeName();
3605 if (pRangeName)
3606 pData = pRangeName->findByUpperName(rUpperName);
3607 if (pData)
3608 rSheet = -1;
3610 return pData;
3613 bool ScCompiler::HasPossibleNamedRangeConflict( SCTAB nTab ) const
3615 const ScRangeName* pRangeName = rDoc.GetRangeName();
3616 if (pRangeName && pRangeName->hasPossibleAddressConflict())
3617 return true;
3618 pRangeName = rDoc.GetRangeName(nTab);
3619 if (pRangeName && pRangeName->hasPossibleAddressConflict())
3620 return true;
3621 return false;
3624 bool ScCompiler::ParseNamedRange( const OUString& rUpperName, bool onlyCheck )
3626 // ParseNamedRange is called only from NextNewToken, with an upper-case string
3628 SCTAB nSheet = -1;
3629 const ScRangeData* pData = GetRangeData( nSheet, rUpperName);
3630 if (pData)
3632 if (!onlyCheck)
3633 maRawToken.SetName( nSheet, pData->GetIndex());
3634 return true;
3637 // Sheet-local name with sheet specified.
3638 if (mnCurrentSheetEndPos > 0 && mnCurrentSheetTab >= 0)
3640 OUString aName( rUpperName.copy( mnCurrentSheetEndPos));
3641 const ScRangeName* pRangeName = rDoc.GetRangeName( mnCurrentSheetTab);
3642 if (pRangeName)
3644 pData = pRangeName->findByUpperName(aName);
3645 if (pData)
3647 if (!onlyCheck)
3648 maRawToken.SetName( mnCurrentSheetTab, pData->GetIndex());
3649 return true;
3654 return false;
3657 bool ScCompiler::ParseLambdaFuncName( const OUString& aOrg )
3659 if (m_aLambda.bInLambdaFunction && !aOrg.isEmpty())
3661 OUString aName = aOrg;
3662 if (aOrg.startsWithIgnoreAsciiCase(u"_xlpm."))
3663 aName = aName.copy(6);
3665 if (m_aLambda.nParaPos % 2 == 1 && m_aLambda.nParaCount > m_aLambda.nParaPos)
3666 m_aLambda.aNameSet.insert(aName);
3667 else
3669 // should already exist the name
3670 if (m_aLambda.aNameSet.find(aName) == m_aLambda.aNameSet.end())
3671 return false;
3673 svl::SharedString aSS = rDoc.GetSharedStringPool().intern(aName);
3674 maRawToken.SetStringName(aSS.getData(), aSS.getDataIgnoreCase());
3675 return true;
3677 return false;
3680 bool ScCompiler::ParseExternalNamedRange( const OUString& rSymbol, bool& rbInvalidExternalNameRange )
3682 /* FIXME: This code currently (2008-12-02T15:41+0100 in CWS mooxlsc)
3683 * correctly parses external named references in OOo, as required per RFE
3684 * #i3740#, just that we can't store them in ODF yet. We will need an OASIS
3685 * spec first. Until then don't pretend to support external names that
3686 * wouldn't survive a save and reload cycle, return false instead. */
3688 rbInvalidExternalNameRange = false;
3690 if (!pConv)
3691 return false;
3693 OUString aFile, aName;
3694 if (!pConv->parseExternalName( rSymbol, aFile, aName, rDoc, &maExternalLinks))
3695 return false;
3697 if (aFile.getLength() > MAXSTRLEN || aName.getLength() > MAXSTRLEN)
3698 return false;
3700 ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager();
3701 OUString aTmp = aFile;
3702 pRefMgr->convertToAbsName(aTmp);
3703 aFile = aTmp;
3704 sal_uInt16 nFileId = pRefMgr->getExternalFileId(aFile);
3705 if (!pRefMgr->isValidRangeName(nFileId, aName))
3707 rbInvalidExternalNameRange = true;
3708 // range name doesn't exist in the source document.
3709 return false;
3712 const OUString* pRealName = pRefMgr->getRealRangeName(nFileId, aName);
3713 maRawToken.SetExternalName(nFileId, pRealName ? *pRealName : aTmp);
3714 maExternalFiles.push_back(nFileId);
3715 return true;
3718 bool ScCompiler::ParseDBRange( const OUString& rName )
3720 ScDBCollection::NamedDBs& rDBs = rDoc.GetDBCollection()->getNamedDBs();
3721 const ScDBData* p = rDBs.findByUpperName(rName);
3722 if (!p)
3723 return false;
3725 maRawToken.SetName( -1, p->GetIndex()); // DB range is always global.
3726 maRawToken.eOp = ocDBArea;
3727 return true;
3730 bool ScCompiler::ParseColRowName( const OUString& rName )
3732 bool bInList = false;
3733 bool bFound = false;
3734 ScSingleRefData aRef;
3735 OUString aName( rName );
3736 DeQuote( aName );
3737 SCTAB nThisTab = aPos.Tab();
3738 for ( short jThisTab = 1; jThisTab >= 0 && !bInList; jThisTab-- )
3739 { // first check ranges on this sheet, in case of duplicated names
3740 for ( short jRow=0; jRow<2 && !bInList; jRow++ )
3742 ScRangePairList* pRL;
3743 if ( !jRow )
3744 pRL = rDoc.GetColNameRanges();
3745 else
3746 pRL = rDoc.GetRowNameRanges();
3747 for ( size_t iPair = 0, nPairs = pRL->size(); iPair < nPairs && !bInList; ++iPair )
3749 const ScRangePair & rR = (*pRL)[iPair];
3750 const ScRange& rNameRange = rR.GetRange(0);
3751 if ( jThisTab && (rNameRange.aStart.Tab() > nThisTab ||
3752 nThisTab > rNameRange.aEnd.Tab()) )
3753 continue; // for
3754 ScCellIterator aIter( rDoc, rNameRange );
3755 for (bool bHas = aIter.first(); bHas && !bInList; bHas = aIter.next())
3757 // Don't crash if cell (via CompileNameFormula) encounters
3758 // a formula cell without code and
3759 // HasStringData/Interpret/Compile is executed and all that
3760 // recursively...
3761 // Furthermore, *this* cell won't be touched, since no RPN exists yet.
3762 CellType eType = aIter.getType();
3763 bool bOk = false;
3764 if (eType == CELLTYPE_FORMULA)
3766 ScFormulaCell* pFC = aIter.getFormulaCell();
3767 bOk = (pFC->GetCode()->GetCodeLen() > 0) && (pFC->aPos != aPos);
3769 else
3770 bOk = true;
3772 if (bOk && aIter.hasString())
3774 OUString aStr = aIter.getString();
3775 if ( ScGlobal::GetTransliteration().isEqual( aStr, aName ) )
3777 aRef.InitFlags();
3778 if ( !jRow )
3779 aRef.SetColRel( true ); // ColName
3780 else
3781 aRef.SetRowRel( true ); // RowName
3782 aRef.SetAddress(rDoc.GetSheetLimits(), aIter.GetPos(), aPos);
3783 bInList = bFound = true;
3790 if ( !bInList && rDoc.GetDocOptions().IsLookUpColRowNames() )
3791 { // search in current sheet
3792 tools::Long nDistance = 0, nMax = 0;
3793 tools::Long nMyCol = static_cast<tools::Long>(aPos.Col());
3794 tools::Long nMyRow = static_cast<tools::Long>(aPos.Row());
3795 bool bTwo = false;
3796 ScAddress aOne( 0, 0, aPos.Tab() );
3797 ScAddress aTwo( rDoc.MaxCol(), rDoc.MaxRow(), aPos.Tab() );
3799 ScAutoNameCache* pNameCache = rDoc.GetAutoNameCache();
3800 if ( pNameCache )
3802 // use GetNameOccurrences to collect all positions of aName on the sheet
3803 // (only once), similar to the outer part of the loop in the "else" branch.
3805 const ScAutoNameAddresses& rAddresses = pNameCache->GetNameOccurrences( aName, aPos.Tab() );
3807 // Loop through the found positions, similar to the inner part of the loop in the "else" branch.
3808 // The order of addresses in the vector is the same as from ScCellIterator.
3810 for ( const ScAddress& aAddress : rAddresses )
3812 if ( bFound )
3813 { // stop if everything else is further away
3814 if ( nMax < static_cast<tools::Long>(aAddress.Col()) )
3815 break; // aIter
3817 if ( aAddress != aPos )
3819 // same treatment as in isEqual case below
3821 SCCOL nCol = aAddress.Col();
3822 SCROW nRow = aAddress.Row();
3823 tools::Long nC = nMyCol - nCol;
3824 tools::Long nR = nMyRow - nRow;
3825 if ( bFound )
3827 tools::Long nD = nC * nC + nR * nR;
3828 if ( nD < nDistance )
3830 if ( nC < 0 || nR < 0 )
3831 { // right or below
3832 bTwo = true;
3833 aTwo.Set( nCol, nRow, aAddress.Tab() );
3834 nMax = std::max( nMyCol + std::abs( nC ), nMyRow + std::abs( nR ) );
3835 nDistance = nD;
3837 else if ( nRow >= aOne.Row() || nMyRow < static_cast<tools::Long>(aOne.Row()) )
3839 // upper left, only if not further up than the
3840 // current entry and nMyRow is below (CellIter
3841 // runs column-wise)
3842 bTwo = false;
3843 aOne.Set( nCol, nRow, aAddress.Tab() );
3844 nMax = std::max( nMyCol + nC, nMyRow + nR );
3845 nDistance = nD;
3849 else
3851 aOne.Set( nCol, nRow, aAddress.Tab() );
3852 nDistance = nC * nC + nR * nR;
3853 nMax = std::max( nMyCol + std::abs( nC ), nMyRow + std::abs( nR ) );
3856 bFound = true;
3860 else
3862 ScCellIterator aIter( rDoc, ScRange( aOne, aTwo ) );
3863 for (bool bHas = aIter.first(); bHas; bHas = aIter.next())
3865 if ( bFound )
3866 { // stop if everything else is further away
3867 if ( nMax < static_cast<tools::Long>(aIter.GetPos().Col()) )
3868 break; // aIter
3870 CellType eType = aIter.getType();
3871 bool bOk = false;
3872 if (eType == CELLTYPE_FORMULA)
3874 ScFormulaCell* pFC = aIter.getFormulaCell();
3875 bOk = (pFC->GetCode()->GetCodeLen() > 0) && (pFC->aPos != aPos);
3877 else
3878 bOk = true;
3880 if (bOk && aIter.hasString())
3882 OUString aStr = aIter.getString();
3883 if ( ScGlobal::GetTransliteration().isEqual( aStr, aName ) )
3885 SCCOL nCol = aIter.GetPos().Col();
3886 SCROW nRow = aIter.GetPos().Row();
3887 tools::Long nC = nMyCol - nCol;
3888 tools::Long nR = nMyRow - nRow;
3889 if ( bFound )
3891 tools::Long nD = nC * nC + nR * nR;
3892 if ( nD < nDistance )
3894 if ( nC < 0 || nR < 0 )
3895 { // right or below
3896 bTwo = true;
3897 aTwo.Set( nCol, nRow, aIter.GetPos().Tab() );
3898 nMax = std::max( nMyCol + std::abs( nC ), nMyRow + std::abs( nR ) );
3899 nDistance = nD;
3901 else if ( nRow >= aOne.Row() || nMyRow < static_cast<tools::Long>(aOne.Row()) )
3903 // upper left, only if not further up than the
3904 // current entry and nMyRow is below (CellIter
3905 // runs column-wise)
3906 bTwo = false;
3907 aOne.Set( nCol, nRow, aIter.GetPos().Tab() );
3908 nMax = std::max( nMyCol + nC, nMyRow + nR );
3909 nDistance = nD;
3913 else
3915 aOne.Set( nCol, nRow, aIter.GetPos().Tab() );
3916 nDistance = nC * nC + nR * nR;
3917 nMax = std::max( nMyCol + std::abs( nC ), nMyRow + std::abs( nR ) );
3919 bFound = true;
3925 if ( bFound )
3927 ScAddress aAdr;
3928 if ( bTwo )
3930 if ( nMyCol >= static_cast<tools::Long>(aOne.Col()) && nMyRow >= static_cast<tools::Long>(aOne.Row()) )
3931 aAdr = aOne; // upper left takes precedence
3932 else
3934 if ( nMyCol < static_cast<tools::Long>(aOne.Col()) )
3935 { // two to the right
3936 if ( nMyRow >= static_cast<tools::Long>(aTwo.Row()) )
3937 aAdr = aTwo; // directly right
3938 else
3939 aAdr = aOne;
3941 else
3942 { // two below or below and right, take the nearest
3943 tools::Long nC1 = nMyCol - aOne.Col();
3944 tools::Long nR1 = nMyRow - aOne.Row();
3945 tools::Long nC2 = nMyCol - aTwo.Col();
3946 tools::Long nR2 = nMyRow - aTwo.Row();
3947 if ( nC1 * nC1 + nR1 * nR1 <= nC2 * nC2 + nR2 * nR2 )
3948 aAdr = aOne;
3949 else
3950 aAdr = aTwo;
3954 else
3955 aAdr = aOne;
3956 aRef.InitAddress( aAdr );
3957 // Prioritize on column label; row label only if the next cell
3958 // above/below the found label cell is text, or if both are not and
3959 // the cell below is empty and the next cell to the right is
3960 // numeric.
3961 if ((aAdr.Row() < rDoc.MaxRow() && rDoc.HasStringData(
3962 aAdr.Col(), aAdr.Row() + 1, aAdr.Tab()))
3963 || (aAdr.Row() > 0 && rDoc.HasStringData(
3964 aAdr.Col(), aAdr.Row() - 1, aAdr.Tab()))
3965 || (aAdr.Row() < rDoc.MaxRow() && rDoc.GetRefCellValue(
3966 ScAddress( aAdr.Col(), aAdr.Row() + 1, aAdr.Tab())).isEmpty()
3967 && aAdr.Col() < rDoc.MaxCol() && rDoc.GetRefCellValue(
3968 ScAddress( aAdr.Col() + 1, aAdr.Row(), aAdr.Tab())).hasNumeric()))
3969 aRef.SetRowRel( true ); // RowName
3970 else
3971 aRef.SetColRel( true ); // ColName
3972 aRef.SetAddress(rDoc.GetSheetLimits(), aAdr, aPos);
3975 if ( bFound )
3977 maRawToken.SetSingleReference( aRef );
3978 maRawToken.eOp = ocColRowName;
3979 return true;
3981 else
3982 return false;
3985 bool ScCompiler::ParseBoolean( const OUString& rName )
3987 OpCodeHashMap::const_iterator iLook( mxSymbols->getHashMap().find( rName ) );
3988 if( iLook != mxSymbols->getHashMap().end() &&
3989 ((*iLook).second == ocTrue ||
3990 (*iLook).second == ocFalse) )
3992 maRawToken.SetOpCode( (*iLook).second );
3993 return true;
3995 else
3996 return false;
3999 bool ScCompiler::ParseErrorConstant( const OUString& rName )
4001 FormulaError nError = GetErrorConstant( rName);
4002 if (nError != FormulaError::NONE)
4004 maRawToken.SetErrorConstant( nError);
4005 return true;
4007 else
4008 return false;
4011 bool ScCompiler::ParseTableRefItem( const OUString& rName )
4013 bool bItem = false;
4014 OpCodeHashMap::const_iterator iLook( mxSymbols->getHashMap().find( rName));
4015 if (iLook != mxSymbols->getHashMap().end())
4017 // Only called when there actually is a current TableRef, hence
4018 // accessing maTableRefs.back() is safe.
4019 ScTableRefToken* p = maTableRefs.back().mxToken.get();
4020 assert(p); // not a ScTableRefToken can't be
4022 switch ((*iLook).second)
4024 case ocTableRefItemAll:
4025 bItem = true;
4026 p->AddItem( ScTableRefToken::ALL);
4027 break;
4028 case ocTableRefItemHeaders:
4029 bItem = true;
4030 p->AddItem( ScTableRefToken::HEADERS);
4031 break;
4032 case ocTableRefItemData:
4033 bItem = true;
4034 p->AddItem( ScTableRefToken::DATA);
4035 break;
4036 case ocTableRefItemTotals:
4037 bItem = true;
4038 p->AddItem( ScTableRefToken::TOTALS);
4039 break;
4040 case ocTableRefItemThisRow:
4041 bItem = true;
4042 p->AddItem( ScTableRefToken::THIS_ROW);
4043 break;
4044 default:
4047 if (bItem)
4048 maRawToken.SetOpCode( (*iLook).second );
4050 return bItem;
4053 namespace {
4054 OUString unescapeTableRefColumnSpecifier( const OUString& rStr )
4056 // '#', '[', ']' and '\'' are escaped with '\''
4058 if (rStr.indexOf( '\'' ) < 0)
4059 return rStr;
4061 const sal_Int32 n = rStr.getLength();
4062 OUStringBuffer aBuf( n );
4063 const sal_Unicode* p = rStr.getStr();
4064 const sal_Unicode* const pStop = p + n;
4065 bool bEscaped = false;
4066 for ( ; p < pStop; ++p)
4068 const sal_Unicode c = *p;
4069 if (bEscaped)
4071 aBuf.append( c );
4072 bEscaped = false;
4074 else if (c == '\'')
4075 bEscaped = true; // unescaped escaping '\''
4076 else
4077 aBuf.append( c );
4079 return aBuf.makeStringAndClear();
4083 bool ScCompiler::ParseTableRefColumn( const OUString& rName )
4085 // Only called when there actually is a current TableRef, hence
4086 // accessing maTableRefs.back() is safe.
4087 ScTableRefToken* p = maTableRefs.back().mxToken.get();
4088 assert(p); // not a ScTableRefToken can't be
4090 ScDBData* pDBData = rDoc.GetDBCollection()->getNamedDBs().findByIndex( p->GetIndex());
4091 if (!pDBData)
4092 return false;
4094 OUString aName( unescapeTableRefColumnSpecifier( rName));
4096 ScRange aRange;
4097 pDBData->GetArea( aRange);
4098 aRange.aEnd.SetTab( aRange.aStart.Tab());
4099 aRange.aEnd.SetRow( aRange.aStart.Row());
4101 // Prefer the stored internal table column name, which is also needed for
4102 // named expressions during document load time when cell content isn't
4103 // available yet. Also, avoiding a possible calculation step in case the
4104 // header cell is a formula cell is "a good thing".
4105 sal_Int32 nOffset = pDBData->GetColumnNameOffset( aName);
4106 if (nOffset >= 0)
4108 // This is sneaky... we always use the top row of the database range,
4109 // regardless of whether it is a header row or not. Code evaluating
4110 // this reference must take that into account and may have to act
4111 // differently if it is a header-less table. Which are two places,
4112 // HandleTableRef() (no change necessary there) and
4113 // CreateStringFromSingleRef() (must not fallback to cell lookup).
4114 ScSingleRefData aRef;
4115 ScAddress aAdr( aRange.aStart);
4116 aAdr.IncCol( nOffset);
4117 aRef.InitAddress( aAdr);
4118 maRawToken.SetSingleReference( aRef );
4119 return true;
4122 if (pDBData->HasHeader())
4124 // Quite similar to IsColRowName() but limited to one row of headers.
4125 ScCellIterator aIter( rDoc, aRange);
4126 for (bool bHas = aIter.first(); bHas; bHas = aIter.next())
4128 CellType eType = aIter.getType();
4129 bool bOk = false;
4130 if (eType == CELLTYPE_FORMULA)
4132 ScFormulaCell* pFC = aIter.getFormulaCell();
4133 bOk = (pFC->GetCode()->GetCodeLen() > 0) && (pFC->aPos != aPos);
4135 else
4136 bOk = true;
4138 if (bOk && aIter.hasString())
4140 OUString aStr = aIter.getString();
4141 if (ScGlobal::GetTransliteration().isEqual( aStr, aName))
4143 // If this is successful and the internal column name
4144 // lookup was not, it may be worth a warning.
4145 SAL_WARN("sc.core", "ScCompiler::IsTableRefColumn - falling back to cell lookup");
4147 /* XXX NOTE: we could init the column as relative so copying a
4148 * formula across columns would point to the relative column,
4149 * but do it absolute because:
4150 * a) it makes the reference work in named expressions without
4151 * having to distinguish
4152 * b) Excel does it the same. */
4153 ScSingleRefData aRef;
4154 aRef.InitAddress( aIter.GetPos());
4155 maRawToken.SetSingleReference( aRef );
4156 return true;
4162 return false;
4165 void ScCompiler::SetAutoCorrection( bool bVal )
4167 assert(mbJumpCommandReorder);
4168 bAutoCorrect = bVal;
4169 mbStopOnError = !bVal;
4172 void ScCompiler::AutoCorrectParsedSymbol()
4174 sal_Int32 nPos = aCorrectedSymbol.getLength();
4175 if ( !nPos )
4176 return;
4178 nPos--;
4179 const sal_Unicode cQuote = '\"';
4180 const sal_Unicode cx = 'x';
4181 const sal_Unicode cX = 'X';
4182 sal_Unicode c1 = aCorrectedSymbol[0];
4183 sal_Unicode c2 = aCorrectedSymbol[nPos];
4184 sal_Unicode c2p = nPos > 0 ? aCorrectedSymbol[nPos-1] : 0;
4185 if ( c1 == cQuote && c2 != cQuote )
4186 { // "...
4187 // What's not a word doesn't belong to it.
4188 // Don't be pedantic: c < 128 should be sufficient here.
4189 while ( nPos && ((aCorrectedSymbol[nPos] < 128) &&
4190 ((GetCharTableFlags(aCorrectedSymbol[nPos], aCorrectedSymbol[nPos-1]) &
4191 (ScCharFlags::Word | ScCharFlags::CharDontCare)) == ScCharFlags::NONE)) )
4192 nPos--;
4193 if ( nPos == MAXSTRLEN - 1 )
4194 aCorrectedSymbol = aCorrectedSymbol.replaceAt( nPos, 1, rtl::OUStringChar(cQuote) ); // '"' the MAXSTRLENth character
4195 else
4196 aCorrectedSymbol = aCorrectedSymbol.replaceAt( nPos + 1, 0, rtl::OUStringChar(cQuote) );
4197 bCorrected = true;
4199 else if ( c1 != cQuote && c2 == cQuote )
4200 { // ..."
4201 aCorrectedSymbol = OUStringChar(cQuote) + aCorrectedSymbol;
4202 bCorrected = true;
4204 else if ( nPos == 0 && (c1 == cx || c1 == cX) )
4205 { // x => *
4206 aCorrectedSymbol = mxSymbols->getSymbol(ocMul);
4207 bCorrected = true;
4209 else if ( (GetCharTableFlags( c1, 0 ) & ScCharFlags::CharValue)
4210 && (GetCharTableFlags( c2, c2p ) & ScCharFlags::CharValue) )
4212 if ( aCorrectedSymbol.indexOf(cx) >= 0 ) // At least two tokens separated by cx
4213 { // x => *
4214 sal_Unicode c = mxSymbols->getSymbolChar(ocMul);
4215 aCorrectedSymbol = aCorrectedSymbol.replaceAll(OUStringChar(cx), OUStringChar(c));
4216 bCorrected = true;
4218 if ( aCorrectedSymbol.indexOf(cX) >= 0 ) // At least two tokens separated by cX
4219 { // X => *
4220 sal_Unicode c = mxSymbols->getSymbolChar(ocMul);
4221 aCorrectedSymbol = aCorrectedSymbol.replaceAll(OUStringChar(cX), OUStringChar(c));
4222 bCorrected = true;
4225 else
4227 OUString aSymbol( aCorrectedSymbol );
4228 OUString aDoc;
4229 if ( aSymbol[0] == '\'' )
4231 sal_Int32 nPosition = aSymbol.indexOf( "'#" );
4232 if (nPosition != -1)
4233 { // Split off 'Doc'#, may be d:\... or whatever
4234 aDoc = aSymbol.copy(0, nPosition + 2);
4235 aSymbol = aSymbol.copy(nPosition + 2);
4238 sal_Int32 nRefs = comphelper::string::getTokenCount(aSymbol, ':');
4239 bool bColons;
4240 if ( nRefs > 2 )
4241 { // duplicated or too many ':'? B:2::C10 => B2:C10
4242 bColons = true;
4243 sal_Int32 nIndex = 0;
4244 OUString aTmp1( aSymbol.getToken( 0, ':', nIndex ) );
4245 sal_Int32 nLen1 = aTmp1.getLength();
4246 OUStringBuffer aSym;
4247 OUString aTmp2;
4248 bool bLastAlp = true;
4249 sal_Int32 nStrip = 0;
4250 sal_Int32 nCount = nRefs;
4251 for ( sal_Int32 j=1; j<nCount; j++ )
4253 aTmp2 = aSymbol.getToken( 0, ':', nIndex );
4254 sal_Int32 nLen2 = aTmp2.getLength();
4255 if ( nLen1 || nLen2 )
4257 if ( nLen1 )
4259 aSym.append(aTmp1);
4260 bLastAlp = CharClass::isAsciiAlpha( aTmp1 );
4262 if ( nLen2 )
4264 bool bNextNum = CharClass::isAsciiNumeric( aTmp2 );
4265 if ( bLastAlp == bNextNum && nStrip < 1 )
4267 // Must be alternating number/string, only
4268 // strip within a reference.
4269 nRefs--;
4270 nStrip++;
4272 else
4274 if ( !aSym.isEmpty() && aSym[aSym.getLength()-1] != ':')
4275 aSym.append(":");
4276 nStrip = 0;
4278 bLastAlp = !bNextNum;
4280 else
4281 { // ::
4282 nRefs--;
4283 if ( nLen1 )
4284 { // B10::C10 ? append ':' on next round
4285 if ( !bLastAlp && !CharClass::isAsciiNumeric( aTmp1 ) )
4286 nStrip++;
4289 aTmp1 = aTmp2;
4290 nLen1 = nLen2;
4292 else
4293 nRefs--;
4295 aSymbol = aSym + aTmp1;
4296 aSym.setLength(0);
4298 else
4299 bColons = false;
4300 if ( nRefs && nRefs <= 2 )
4301 { // reference twisted? 4A => A4 etc.
4302 OUString aTab[2], aRef[2];
4303 const ScAddress::Details aDetails( pConv->meConv, aPos );
4304 if ( nRefs == 2 )
4306 sal_Int32 nIdx{ 0 };
4307 aRef[0] = aSymbol.getToken( 0, ':', nIdx );
4308 aRef[1] = aSymbol.getToken( 0, ':', nIdx );
4310 else
4311 aRef[0] = aSymbol;
4313 bool bChanged = false;
4314 bool bOk = true;
4315 ScRefFlags nMask = ScRefFlags::VALID | ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID;
4316 for ( int j=0; j<nRefs; j++ )
4318 sal_Int32 nTmp = 0;
4319 sal_Int32 nDotPos = -1;
4320 while ( (nTmp = aRef[j].indexOf( '.', nTmp )) != -1 )
4321 nDotPos = nTmp++; // the last one counts
4322 if ( nDotPos != -1 )
4324 aTab[j] = aRef[j].copy( 0, nDotPos + 1 ); // with '.'
4325 aRef[j] = aRef[j].copy( nDotPos + 1 );
4327 OUString aOld( aRef[j] );
4328 OUStringBuffer aStr2;
4329 const sal_Unicode* p = aRef[j].getStr();
4330 while ( *p && rtl::isAsciiDigit( *p ) )
4331 aStr2.append(*p++);
4332 aRef[j] = OUString( p );
4333 aRef[j] += aStr2;
4334 if ( bColons || aRef[j] != aOld )
4336 bChanged = true;
4337 ScAddress aAdr;
4338 bOk &= ((aAdr.Parse( aRef[j], rDoc, aDetails ) & nMask) == nMask);
4341 if ( bChanged && bOk )
4343 aCorrectedSymbol = aDoc;
4344 aCorrectedSymbol += aTab[0];
4345 aCorrectedSymbol += aRef[0];
4346 if ( nRefs == 2 )
4348 aCorrectedSymbol += ":";
4349 aCorrectedSymbol += aTab[1];
4350 aCorrectedSymbol += aRef[1];
4352 bCorrected = true;
4358 bool ScCompiler::ToUpperAsciiOrI18nIsAscii( OUString& rUpper, const OUString& rOrg ) const
4360 if (FormulaGrammar::isODFF( meGrammar) || FormulaGrammar::isOOXML( meGrammar))
4362 // ODFF and OOXML have defined sets of English function names, avoid
4363 // i18n overhead.
4364 rUpper = rOrg.toAsciiUpperCase();
4365 return true;
4367 else
4369 // One of localized or English.
4370 rUpper = pCharClass->uppercase(rOrg);
4371 return false;
4375 short ScCompiler::GetPossibleParaCount( const std::u16string_view& rLambdaFormula ) const
4377 sal_Unicode cSep = mxSymbols->getSymbolChar(ocSep);
4378 sal_Unicode cOpen = mxSymbols->getSymbolChar(ocOpen);
4379 sal_Unicode cClose = mxSymbols->getSymbolChar(ocClose);
4380 sal_Unicode cArrayOpen = mxSymbols->getSymbolChar(ocArrayOpen);
4381 sal_Unicode cArrayClose = mxSymbols->getSymbolChar(ocArrayClose);
4382 short nBrackets = 0;
4384 short nCount = std::count_if(rLambdaFormula.begin(), rLambdaFormula.end(),
4385 [&](sal_Unicode c) {
4386 if (c == cOpen || c == cArrayOpen || c == '[') {
4387 nBrackets++;
4388 return false;
4390 else if (c == cClose || c == cArrayClose || c == ']') {
4391 nBrackets--;
4392 return false;
4394 else {
4395 if (nBrackets == 1)
4396 return c == cSep;
4397 else
4398 return false;
4402 return static_cast<short>(nCount + 1);
4405 bool ScCompiler::NextNewToken( bool bInArray )
4407 if (!maPendingOpCodes.empty())
4409 maRawToken.SetOpCode(maPendingOpCodes.front());
4410 maPendingOpCodes.pop();
4411 return true;
4414 bool bAllowBooleans = bInArray;
4415 const std::vector<Whitespace> & vSpaces = NextSymbol(bInArray);
4417 if (!cSymbol[0])
4419 if (nSrcPos < aFormula.getLength())
4421 // Nothing could be parsed, remainder as bad string.
4422 // NextSymbol() must had set an error for this.
4423 assert( pArr->GetCodeError() != FormulaError::NONE);
4424 const OUString aBad( aFormula.copy( nSrcPos));
4425 svl::SharedString aSS = rDoc.GetSharedStringPool().intern( aBad);
4426 maRawToken.SetString( aSS.getData(), aSS.getDataIgnoreCase());
4427 maRawToken.NewOpCode( ocBad);
4428 nSrcPos = aFormula.getLength();
4429 // Add bad string as last token.
4430 return true;
4432 return false;
4435 if (!vSpaces.empty())
4437 ScRawToken aToken;
4438 for (const auto& rSpace : vSpaces)
4440 if (rSpace.cChar == 0x20)
4442 // For now keep this a FormulaByteToken for the nasty
4443 // significant whitespace intersection. This probably can be
4444 // changed to a FormulaSpaceToken but then other places may
4445 // need to be adapted.
4446 aToken.SetOpCode( ocSpaces );
4447 aToken.sbyte.cByte = static_cast<sal_uInt8>( std::min<sal_Int32>(rSpace.nCount, 255) );
4449 else
4451 aToken.SetOpCode( ocWhitespace );
4452 aToken.whitespace.nCount = static_cast<sal_uInt8>( std::min<sal_Int32>(rSpace.nCount, 255) );
4453 aToken.whitespace.cChar = rSpace.cChar;
4455 if (!static_cast<ScTokenArray*>(pArr)->AddRawToken( aToken ))
4457 SetError(FormulaError::CodeOverflow);
4458 return false;
4463 // Short cut for references when reading ODF to speedup things.
4464 if (mnPredetectedReference)
4466 OUString aStr( cSymbol);
4467 bool bInvalidExternalNameRange;
4468 if (!ParsePredetectedReference( aStr) && !ParseExternalNamedRange( aStr, bInvalidExternalNameRange ))
4470 svl::SharedString aSS = rDoc.GetSharedStringPool().intern(aStr);
4471 maRawToken.SetString(aSS.getData(), aSS.getDataIgnoreCase());
4472 maRawToken.NewOpCode( ocBad );
4474 return true;
4477 if ( (cSymbol[0] == '#' || cSymbol[0] == '$') && cSymbol[1] == 0 &&
4478 !bAutoCorrect )
4479 { // special case to speed up broken [$]#REF documents
4480 /* FIXME: ISERROR(#REF!) would be valid and true and the formula to
4481 * be processed as usual. That would need some special treatment,
4482 * also in NextSymbol() because of possible combinations of
4483 * #REF!.#REF!#REF! parts. In case of reading ODF that is all
4484 * handled by IsPredetectedReference(), this case here remains for
4485 * manual/API input. */
4486 OUString aBad( aFormula.copy( nSrcPos-1 ) );
4487 const FormulaToken* pBadToken = pArr->AddBad(aBad);
4488 eLastOp = pBadToken ? pBadToken->GetOpCode() : ocNone;
4489 return false;
4492 if( ParseString() )
4493 return true;
4495 bool bMayBeFuncName;
4496 bool bAsciiNonAlnum; // operators, separators, ...
4497 if ( cSymbol[0] < 128 )
4499 bMayBeFuncName = rtl::isAsciiAlpha( cSymbol[0] );
4500 if (!bMayBeFuncName && (cSymbol[0] == '_' && cSymbol[1] == '_') && !comphelper::IsFuzzing())
4502 bMayBeFuncName = officecfg::Office::Common::Misc::ExperimentalMode::get();
4505 bAsciiNonAlnum = !bMayBeFuncName && !rtl::isAsciiDigit( cSymbol[0] );
4507 else
4509 OUString aTmpStr( cSymbol[0] );
4510 bMayBeFuncName = pCharClass->isLetter( aTmpStr, 0 );
4511 bAsciiNonAlnum = false;
4514 // Within a TableRef anything except an unescaped '[' or ']' is an item
4515 // or a column specifier, do not attempt to recognize any other single
4516 // operator there so even [,] or [+] for a single character column
4517 // specifier works. Note that space between two ocTableRefOpen is not
4518 // supported (Table[ [ColumnSpec]]), not only here. Note also that Table[]
4519 // without any item or column specifier is valid.
4520 if (bAsciiNonAlnum && cSymbol[1] == 0 && (eLastOp != ocTableRefOpen || cSymbol[0] == '[' || cSymbol[0] == ']'))
4522 // Shortcut for operators and separators that need no further checks or upper.
4523 if (ParseOpCode( OUString( cSymbol), bInArray ))
4524 return true;
4527 if ( bMayBeFuncName )
4529 // a function name must be followed by a parenthesis
4530 const sal_Unicode* p = aFormula.getStr() + nSrcPos;
4531 while( *p == ' ' )
4532 p++;
4533 bMayBeFuncName = ( *p == '(' );
4536 // Italian ARCTAN.2 resulted in #REF! => ParseOpcode() before
4537 // ParseReference().
4539 OUString aUpper;
4540 bool bAsciiUpper = false;
4542 Label_Rewind:
4546 const OUString aOrg( cSymbol );
4548 // Check for TableRef column specifier first, it may be anything.
4549 if (cSymbol[0] != '#' && !maTableRefs.empty() && maTableRefs.back().mnLevel)
4551 if (ParseTableRefColumn( aOrg ))
4552 return true;
4553 // Do not attempt to resolve as any other name.
4554 aUpper = aOrg; // for ocBad
4555 break; // do; create ocBad token or set error.
4558 mbRewind = false;
4559 aUpper.clear();
4560 bAsciiUpper = false;
4562 if (bAsciiNonAlnum)
4564 bAsciiUpper = ToUpperAsciiOrI18nIsAscii( aUpper, aOrg);
4565 if (cSymbol[0] == '#')
4567 // Check for TableRef item specifiers first.
4568 sal_uInt16 nLevel;
4569 if (!maTableRefs.empty() && ((nLevel = maTableRefs.back().mnLevel) == 2 || nLevel == 1))
4571 if (ParseTableRefItem( aUpper ))
4572 return true;
4575 // This can be either an error constant ...
4576 if (ParseErrorConstant( aUpper))
4577 return true;
4579 // ... or some invalidated reference starting with #REF!
4580 // which is handled after the do loop.
4582 break; // do; create ocBad token or set error.
4584 if (ParseOpCode( aUpper, bInArray ))
4585 return true;
4588 if (bMayBeFuncName)
4590 if (aUpper.isEmpty())
4591 bAsciiUpper = ToUpperAsciiOrI18nIsAscii( aUpper, aOrg);
4592 if (ParseOpCode( aUpper, bInArray ))
4593 return true;
4596 // Column 'DM' ("Deutsche Mark", German currency) couldn't be
4597 // referred => ParseReference() before ParseValue().
4598 // Preserve case of file names in external references.
4599 if (ParseReference( aOrg ))
4601 if (mbRewind) // Range operator, but no direct reference.
4602 continue; // do; up to range operator.
4603 // If a syntactically correct reference was recognized but invalid
4604 // e.g. because of non-existing sheet name => entire reference
4605 // ocBad to preserve input instead of #REF!.A1
4606 if (!maRawToken.IsValidReference(rDoc))
4608 aUpper = aOrg; // ensure for ocBad
4609 break; // do; create ocBad token or set error.
4611 return true;
4614 if (aUpper.isEmpty())
4615 bAsciiUpper = ToUpperAsciiOrI18nIsAscii( aUpper, aOrg);
4617 // ParseBoolean() before ParseValue() to catch inline bools without the kludge
4618 // for inline arrays.
4619 if (bAllowBooleans && ParseBoolean( aUpper ))
4620 return true;
4622 if (ParseValue( aUpper ))
4623 return true;
4625 // User defined names and such do need i18n upper also in ODF.
4626 if (bAsciiUpper || mbCharClassesDiffer)
4628 // Use current system locale here because user defined symbols are
4629 // more likely in that localized language than in the formula
4630 // language. This in corner cases needs to continue to work for
4631 // existing documents and environments.
4632 // Do not change bAsciiUpper from here on for the lowercase() call
4633 // below in the ocBad case to use the correct CharClass.
4634 aUpper = ScGlobal::getCharClass().uppercase( aOrg );
4637 if (ParseNamedRange( aUpper ))
4638 return true;
4640 // Compiling a named expression during collecting them in import shall
4641 // not match arbitrary names that otherwise if all named expressions
4642 // were present would be recognized as named expression. Such name will
4643 // flag an error below and will be recompiled in a second step later
4644 // with ScRangeData::CompileUnresolvedXML()
4645 if (meExtendedErrorDetection == EXTENDED_ERROR_DETECTION_NAME_NO_BREAK && rDoc.IsImportingXML())
4646 break; // while
4648 // Preserve case of file names in external references.
4649 bool bInvalidExternalNameRange;
4650 if (ParseExternalNamedRange( aOrg, bInvalidExternalNameRange ))
4651 return true;
4652 // Preserve case of file names in external references even when range
4653 // is not valid and previous check failed tdf#89330
4654 if (bInvalidExternalNameRange)
4656 // add ocBad but do not lowercase
4657 svl::SharedString aSS = rDoc.GetSharedStringPool().intern(aOrg);
4658 maRawToken.SetString(aSS.getData(), aSS.getDataIgnoreCase());
4659 maRawToken.NewOpCode( ocBad );
4660 return true;
4662 if (ParseDBRange( aUpper ))
4663 return true;
4664 // If followed by '(' (with or without space inbetween) it can not be a
4665 // column/row label. Prevent arbitrary content detection.
4666 if (!bMayBeFuncName && ParseColRowName( aUpper ))
4667 return true;
4668 if (bMayBeFuncName && ParseMacro( aUpper ))
4669 return true;
4670 if (bMayBeFuncName && ParseOpCode2( aUpper ))
4671 return true;
4673 if (ParseLambdaFuncName( aOrg ))
4674 return true;
4676 } while (mbRewind);
4678 // Last chance: it could be a broken invalidated reference that contains
4679 // #REF! (but is not equal to), which we also wrote to ODFF between 2013
4680 // and 2016 until 5.1.4
4681 OUString aErrRef( mxSymbols->getSymbol( ocErrRef));
4682 if (aUpper.indexOf( aErrRef) >= 0 && ParseReference( aUpper, &aErrRef))
4684 if (mbRewind)
4685 goto Label_Rewind;
4686 return true;
4689 if ( meExtendedErrorDetection != EXTENDED_ERROR_DETECTION_NONE )
4691 // set an error
4692 SetError( FormulaError::NoName );
4693 if (meExtendedErrorDetection == EXTENDED_ERROR_DETECTION_NAME_BREAK)
4694 return false; // end compilation
4697 // Provide single token information and continue. Do not set an error, that
4698 // would prematurely end compilation. Simple unknown names are handled by
4699 // the interpreter.
4700 // Use the same CharClass that was used for uppercase.
4701 aUpper = ((bAsciiUpper || mbCharClassesDiffer) ? ScGlobal::getCharClass() : *pCharClass).lowercase( aUpper );
4702 svl::SharedString aSS = rDoc.GetSharedStringPool().intern(aUpper);
4703 maRawToken.SetString(aSS.getData(), aSS.getDataIgnoreCase());
4704 maRawToken.NewOpCode( ocBad );
4705 if ( bAutoCorrect )
4706 AutoCorrectParsedSymbol();
4707 return true;
4710 void ScCompiler::CreateStringFromXMLTokenArray( OUString& rFormula, OUString& rFormulaNmsp )
4712 bool bExternal = GetGrammar() == FormulaGrammar::GRAM_EXTERNAL;
4713 sal_uInt16 nExpectedCount = bExternal ? 2 : 1;
4714 OSL_ENSURE( pArr->GetLen() == nExpectedCount, "ScCompiler::CreateStringFromXMLTokenArray - wrong number of tokens" );
4715 if( pArr->GetLen() == nExpectedCount )
4717 FormulaToken** ppTokens = pArr->GetArray();
4718 // string tokens expected, GetString() will assert if token type is wrong
4719 rFormula = ppTokens[0]->GetString().getString();
4720 if( bExternal )
4721 rFormulaNmsp = ppTokens[1]->GetString().getString();
4725 namespace {
4727 class ExternalFileInserter
4729 ScAddress maPos;
4730 ScExternalRefManager& mrRefMgr;
4731 public:
4732 ExternalFileInserter(const ScAddress& rPos, ScExternalRefManager& rRefMgr) :
4733 maPos(rPos), mrRefMgr(rRefMgr) {}
4735 void operator() (sal_uInt16 nFileId) const
4737 mrRefMgr.insertRefCell(nFileId, maPos);
4743 std::unique_ptr<ScTokenArray> ScCompiler::CompileString( const OUString& rFormula )
4745 OSL_ENSURE( meGrammar != FormulaGrammar::GRAM_EXTERNAL, "ScCompiler::CompileString - unexpected grammar GRAM_EXTERNAL" );
4746 if( meGrammar == FormulaGrammar::GRAM_EXTERNAL )
4747 SetGrammar( FormulaGrammar::GRAM_PODF );
4749 ScTokenArray aArr(rDoc);
4750 pArr = &aArr;
4751 maArrIterator = FormulaTokenArrayPlainIterator(*pArr);
4752 aFormula = comphelper::string::strip(rFormula, ' ');
4754 nSrcPos = 0;
4755 bCorrected = false;
4756 if ( bAutoCorrect )
4758 aCorrectedFormula.clear();
4759 aCorrectedSymbol.clear();
4761 sal_uInt8 nForced = 0; // ==formula forces recalc even if cell is not visible
4762 if( nSrcPos < aFormula.getLength() && aFormula[nSrcPos] == '=' )
4764 nSrcPos++;
4765 nForced++;
4766 if ( bAutoCorrect )
4767 aCorrectedFormula += "=";
4769 if( nSrcPos < aFormula.getLength() && aFormula[nSrcPos] == '=' )
4771 nSrcPos++;
4772 nForced++;
4773 if ( bAutoCorrect )
4774 aCorrectedFormula += "=";
4776 struct FunctionStack
4778 OpCode eOp;
4779 short nSep;
4781 // FunctionStack only used if PODF or OOXML!
4782 bool bPODF = FormulaGrammar::isPODF( meGrammar);
4783 bool bOOXML = FormulaGrammar::isOOXML( meGrammar);
4784 bool bUseFunctionStack = (bPODF || bOOXML);
4785 const size_t nAlloc = 512;
4786 FunctionStack aFuncs[ nAlloc ];
4787 FunctionStack* pFunctionStack = (bUseFunctionStack && o3tl::make_unsigned(rFormula.getLength()) > nAlloc ?
4788 new FunctionStack[rFormula.getLength()] : &aFuncs[0]);
4789 pFunctionStack[0].eOp = ocNone;
4790 pFunctionStack[0].nSep = 0;
4791 size_t nFunction = 0;
4792 size_t nHighWatermark = 0;
4793 short nBrackets = 0;
4794 bool bInArray = false;
4795 eLastOp = ocOpen;
4796 while( NextNewToken( bInArray ) )
4798 const OpCode eOp = maRawToken.GetOpCode();
4799 if (eOp == ocSkip)
4800 continue;
4802 switch (eOp)
4804 case ocOpen:
4806 if (eLastOp == ocLet)
4808 m_aLambda.bInLambdaFunction = true;
4809 m_aLambda.nBracketPos = nBrackets;
4810 m_aLambda.nParaPos++;
4811 m_aLambda.nParaCount = GetPossibleParaCount(rFormula.subView(nSrcPos - 1));
4814 ++nBrackets;
4815 if (bUseFunctionStack)
4817 ++nFunction;
4818 pFunctionStack[ nFunction ].eOp = eLastOp;
4819 pFunctionStack[ nFunction ].nSep = 0;
4820 nHighWatermark = nFunction;
4823 break;
4824 case ocClose:
4826 if( !nBrackets )
4828 SetError( FormulaError::PairExpected );
4829 if ( bAutoCorrect )
4831 bCorrected = true;
4832 aCorrectedSymbol.clear();
4835 else
4837 nBrackets--;
4838 if (m_aLambda.bInLambdaFunction && m_aLambda.nBracketPos == nBrackets)
4840 m_aLambda.bInLambdaFunction = false;
4841 m_aLambda.nBracketPos = nBrackets;
4844 if (bUseFunctionStack && nFunction)
4845 --nFunction;
4847 break;
4848 case ocSep:
4850 if (bUseFunctionStack)
4851 ++pFunctionStack[ nFunction ].nSep;
4853 if (m_aLambda.bInLambdaFunction && m_aLambda.nBracketPos + 1 == nBrackets)
4854 m_aLambda.nParaPos++;
4856 break;
4857 case ocArrayOpen:
4859 if( bInArray )
4860 SetError( FormulaError::NestedArray );
4861 else
4862 bInArray = true;
4863 // Don't count following column separator as parameter separator.
4864 if (bUseFunctionStack)
4866 ++nFunction;
4867 pFunctionStack[ nFunction ].eOp = eOp;
4868 pFunctionStack[ nFunction ].nSep = 0;
4869 nHighWatermark = nFunction;
4872 break;
4873 case ocArrayClose:
4875 if( bInArray )
4877 bInArray = false;
4879 else
4881 SetError( FormulaError::PairExpected );
4882 if ( bAutoCorrect )
4884 bCorrected = true;
4885 aCorrectedSymbol.clear();
4888 if (bUseFunctionStack && nFunction)
4889 --nFunction;
4891 break;
4892 case ocTableRefOpen:
4894 // Don't count following item separator as parameter separator.
4895 if (bUseFunctionStack)
4897 ++nFunction;
4898 pFunctionStack[ nFunction ].eOp = eOp;
4899 pFunctionStack[ nFunction ].nSep = 0;
4900 nHighWatermark = nFunction;
4903 break;
4904 case ocTableRefClose:
4906 if (bUseFunctionStack && nFunction)
4907 --nFunction;
4909 break;
4910 case ocColRowName:
4911 case ocColRowNameAuto:
4912 // The current implementation of column / row labels doesn't
4913 // function correctly in grouped cells.
4914 aArr.SetShareable(false);
4915 break;
4916 default:
4917 break;
4919 if ((eLastOp != ocOpen || eOp != ocClose) &&
4920 (eLastOp == ocOpen ||
4921 eLastOp == ocSep ||
4922 eLastOp == ocArrayRowSep ||
4923 eLastOp == ocArrayColSep ||
4924 eLastOp == ocArrayOpen) &&
4925 (eOp == ocSep ||
4926 eOp == ocClose ||
4927 eOp == ocArrayRowSep ||
4928 eOp == ocArrayColSep ||
4929 eOp == ocArrayClose))
4931 // TODO: should we check for known functions with optional empty
4932 // args so the correction dialog can do better?
4933 if ( !static_cast<ScTokenArray*>(pArr)->Add( new FormulaMissingToken ) )
4935 SetError(FormulaError::CodeOverflow); break;
4938 if (bOOXML)
4940 // Append a parameter for WEEKNUM, all 1.0
4941 // Function is already closed, parameter count is nSep+1
4942 size_t nFunc = nFunction + 1;
4943 if (eOp == ocClose && nFunc <= nHighWatermark &&
4944 pFunctionStack[ nFunc ].nSep == 0 &&
4945 pFunctionStack[ nFunc ].eOp == ocWeek) // 2nd week start
4947 if ( !static_cast<ScTokenArray*>(pArr)->Add( new FormulaToken( svSep, ocSep)) ||
4948 !static_cast<ScTokenArray*>(pArr)->Add( new FormulaDoubleToken( 1.0)))
4950 SetError(FormulaError::CodeOverflow); break;
4954 else if (bPODF)
4956 /* TODO: for now this is the only PODF adapter. If there were more,
4957 * factor this out. */
4958 // Insert ADDRESS() new empty parameter 4 if there is a 4th, now to be 5th.
4959 if (eOp == ocSep &&
4960 pFunctionStack[ nFunction ].eOp == ocAddress &&
4961 pFunctionStack[ nFunction ].nSep == 3)
4963 if ( !static_cast<ScTokenArray*>(pArr)->Add( new FormulaToken( svSep, ocSep)) ||
4964 !static_cast<ScTokenArray*>(pArr)->Add( new FormulaDoubleToken( 1.0)))
4966 SetError(FormulaError::CodeOverflow); break;
4968 ++pFunctionStack[ nFunction ].nSep;
4971 FormulaToken* pNewToken = static_cast<ScTokenArray*>(pArr)->Add( maRawToken.CreateToken(rDoc.GetSheetLimits()));
4972 if (!pNewToken && eOp == ocArrayClose && pArr->OpCodeBefore( pArr->GetLen()) == ocArrayClose)
4974 // Nested inline array or non-value/non-string in array. The
4975 // original tokens are still in the ScTokenArray and not merged
4976 // into an ScMatrixToken. Set error but keep on tokenizing.
4977 SetError( FormulaError::BadArrayContent);
4979 else if (!pNewToken)
4981 SetError(FormulaError::CodeOverflow);
4982 break;
4984 else if (eLastOp == ocRange && pNewToken->GetOpCode() == ocPush && pNewToken->GetType() == svSingleRef)
4986 static_cast<ScTokenArray*>(pArr)->MergeRangeReference( aPos);
4988 else if (eLastOp == ocDBArea && pNewToken->GetOpCode() == ocTableRefOpen)
4990 sal_uInt16 nIdx = pArr->GetLen() - 1;
4991 const FormulaToken* pPrev = pArr->PeekPrev( nIdx);
4992 if (pPrev && pPrev->GetOpCode() == ocDBArea)
4994 ScTableRefToken* pTableRefToken = new ScTableRefToken( pPrev->GetIndex(), ScTableRefToken::TABLE);
4995 maTableRefs.emplace_back( pTableRefToken);
4996 // pPrev may be dead hereafter.
4997 static_cast<ScTokenArray*>(pArr)->ReplaceToken( nIdx, pTableRefToken,
4998 FormulaTokenArray::ReplaceMode::CODE_ONLY);
5001 switch (eOp)
5003 case ocTableRefOpen:
5004 SAL_WARN_IF( maTableRefs.empty(), "sc.core", "ocTableRefOpen without TableRefEntry");
5005 if (maTableRefs.empty())
5006 SetError(FormulaError::Pair);
5007 else
5008 ++maTableRefs.back().mnLevel;
5009 break;
5010 case ocTableRefClose:
5011 SAL_WARN_IF( maTableRefs.empty(), "sc.core", "ocTableRefClose without TableRefEntry");
5012 if (maTableRefs.empty())
5013 SetError(FormulaError::Pair);
5014 else
5016 if (--maTableRefs.back().mnLevel == 0)
5017 maTableRefs.pop_back();
5019 break;
5020 default:
5021 break;
5023 eLastOp = maRawToken.GetOpCode();
5024 if ( bAutoCorrect )
5025 aCorrectedFormula += aCorrectedSymbol;
5027 if ( mbCloseBrackets )
5029 if( bInArray )
5031 FormulaByteToken aToken( ocArrayClose );
5032 if( !pArr->AddToken( aToken ) )
5034 SetError(FormulaError::CodeOverflow);
5036 else if ( bAutoCorrect )
5037 aCorrectedFormula += mxSymbols->getSymbol(ocArrayClose);
5040 if (nBrackets)
5042 FormulaToken aToken( svSep, ocClose );
5043 while( nBrackets-- )
5045 if( !pArr->AddToken( aToken ) )
5047 SetError(FormulaError::CodeOverflow);
5048 break; // while
5050 if ( bAutoCorrect )
5051 aCorrectedFormula += mxSymbols->getSymbol(ocClose);
5055 if ( nForced >= 2 )
5056 pArr->SetRecalcModeForced();
5058 if (pFunctionStack != &aFuncs[0])
5059 delete [] pFunctionStack;
5061 // remember pArr, in case a subsequent CompileTokenArray() is executed.
5062 std::unique_ptr<ScTokenArray> pNew(new ScTokenArray( aArr ));
5063 pNew->GenHash();
5064 // coverity[escape : FALSE] - ownership of pNew is retained by caller, so pArr remains valid
5065 pArr = pNew.get();
5066 maArrIterator = FormulaTokenArrayPlainIterator(*pArr);
5068 if (!maExternalFiles.empty())
5070 // Remove duplicates, and register all external files found in this cell.
5071 std::sort(maExternalFiles.begin(), maExternalFiles.end());
5072 std::vector<sal_uInt16>::iterator itEnd = std::unique(maExternalFiles.begin(), maExternalFiles.end());
5073 std::for_each(maExternalFiles.begin(), itEnd, ExternalFileInserter(aPos, *rDoc.GetExternalRefManager()));
5074 maExternalFiles.erase(itEnd, maExternalFiles.end());
5077 return pNew;
5080 std::unique_ptr<ScTokenArray> ScCompiler::CompileString( const OUString& rFormula, const OUString& rFormulaNmsp )
5082 OSL_ENSURE( (GetGrammar() == FormulaGrammar::GRAM_EXTERNAL) || rFormulaNmsp.isEmpty(),
5083 "ScCompiler::CompileString - unexpected formula namespace for internal grammar" );
5084 if( GetGrammar() == FormulaGrammar::GRAM_EXTERNAL ) try
5086 ScFormulaParserPool& rParserPool = rDoc.GetFormulaParserPool();
5087 uno::Reference< sheet::XFormulaParser > xParser( rParserPool.getFormulaParser( rFormulaNmsp ), uno::UNO_SET_THROW );
5088 table::CellAddress aReferencePos;
5089 ScUnoConversion::FillApiAddress( aReferencePos, aPos );
5090 uno::Sequence< sheet::FormulaToken > aTokenSeq = xParser->parseFormula( rFormula, aReferencePos );
5091 ScTokenArray aTokenArray(rDoc);
5092 if( ScTokenConversion::ConvertToTokenArray( rDoc, aTokenArray, aTokenSeq ) )
5094 // remember pArr, in case a subsequent CompileTokenArray() is executed.
5095 std::unique_ptr<ScTokenArray> pNew(new ScTokenArray( aTokenArray ));
5096 // coverity[escape : FALSE] - ownership of pNew is retained by caller, so pArr remains valid
5097 pArr = pNew.get();
5098 maArrIterator = FormulaTokenArrayPlainIterator(*pArr);
5099 return pNew;
5102 catch( uno::Exception& )
5105 // no success - fallback to some internal grammar and hope the best
5106 return CompileString( rFormula );
5109 ScRangeData* ScCompiler::GetRangeData( const FormulaToken& rToken ) const
5111 return rDoc.FindRangeNameBySheetAndIndex( rToken.GetSheet(), rToken.GetIndex());
5114 bool ScCompiler::HandleStringName()
5116 ScTokenArray* pNew = new ScTokenArray(rDoc);
5117 pNew->AddString(mpToken->GetString());
5118 PushTokenArray(pNew, true);
5119 return GetToken();
5122 bool ScCompiler::HandleRange()
5124 ScTokenArray* pNew;
5125 const ScRangeData* pRangeData = GetRangeData( *mpToken);
5126 if (pRangeData)
5128 FormulaError nErr = pRangeData->GetErrCode();
5129 if( nErr != FormulaError::NONE )
5130 SetError( nErr );
5131 else if (mbJumpCommandReorder)
5133 // put named formula into parentheses.
5134 // But only if there aren't any yet, parenthetical
5135 // ocSep doesn't work, e.g. SUM((...;...))
5136 // or if not directly between ocSep/parenthesis,
5137 // e.g. SUM(...;(...;...)) no, SUM(...;(...)*3) yes,
5138 // in short: if it isn't a self-contained expression.
5139 FormulaToken* p1 = maArrIterator.PeekPrevNoSpaces();
5140 FormulaToken* p2 = maArrIterator.PeekNextNoSpaces();
5141 OpCode eOp1 = (p1 ? p1->GetOpCode() : ocSep);
5142 OpCode eOp2 = (p2 ? p2->GetOpCode() : ocSep);
5143 bool bBorder1 = (eOp1 == ocSep || eOp1 == ocOpen);
5144 bool bBorder2 = (eOp2 == ocSep || eOp2 == ocClose);
5145 bool bAddPair = !(bBorder1 && bBorder2);
5146 if ( bAddPair )
5148 pNew = new ScTokenArray(rDoc);
5149 pNew->AddOpCode( ocClose );
5150 PushTokenArray( pNew, true );
5152 pNew = pRangeData->GetCode()->Clone().release();
5153 pNew->SetFromRangeName( true );
5154 PushTokenArray( pNew, true );
5155 if( pRangeData->HasReferences() )
5157 // Relative sheet references in sheet-local named expressions
5158 // shall still point to the same sheet as if used on the
5159 // original sheet, not shifted to the current position where
5160 // they are used.
5161 SCTAB nSheetTab = mpToken->GetSheet();
5162 if (nSheetTab >= 0 && nSheetTab != aPos.Tab())
5163 AdjustSheetLocalNameRelReferences( nSheetTab - aPos.Tab());
5165 SetRelNameReference();
5166 MoveRelWrap();
5168 maArrIterator.Reset();
5169 if ( bAddPair )
5171 pNew = new ScTokenArray(rDoc);
5172 pNew->AddOpCode( ocOpen );
5173 PushTokenArray( pNew, true );
5175 return GetToken();
5178 else
5180 // No ScRangeData for an already compiled token can happen in BIFF .xls
5181 // import if the original range is not present in the document.
5182 pNew = new ScTokenArray(rDoc);
5183 pNew->Add( new FormulaErrorToken( FormulaError::NoName));
5184 PushTokenArray( pNew, true );
5185 return GetToken();
5187 return true;
5190 bool ScCompiler::HandleExternalReference(const FormulaToken& _aToken)
5192 // Handle external range names.
5193 switch (_aToken.GetType())
5195 case svExternalSingleRef:
5196 case svExternalDoubleRef:
5197 break;
5198 case svExternalName:
5200 ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager();
5201 const OUString* pFile = pRefMgr->getExternalFileName(_aToken.GetIndex());
5202 if (!pFile)
5204 SetError(FormulaError::NoName);
5205 return true;
5208 OUString aName = _aToken.GetString().getString();
5209 ScExternalRefCache::TokenArrayRef xNew = pRefMgr->getRangeNameTokens(
5210 _aToken.GetIndex(), aName, &aPos);
5212 if (!xNew)
5214 SetError(FormulaError::NoName);
5215 return true;
5218 ScTokenArray* pNew = xNew->Clone().release();
5219 PushTokenArray( pNew, true);
5220 if (FormulaTokenArrayPlainIterator(*pNew).GetNextReference() != nullptr)
5222 SetRelNameReference();
5223 MoveRelWrap();
5225 maArrIterator.Reset();
5226 return GetToken();
5228 default:
5229 OSL_FAIL("Wrong type for external reference!");
5230 return false;
5232 return true;
5235 void ScCompiler::AdjustSheetLocalNameRelReferences( SCTAB nDelta )
5237 for ( auto t: pArr->References() )
5239 ScSingleRefData& rRef1 = *t->GetSingleRef();
5240 if (rRef1.IsTabRel())
5241 rRef1.IncTab( nDelta);
5242 if ( t->GetType() == svDoubleRef )
5244 ScSingleRefData& rRef2 = t->GetDoubleRef()->Ref2;
5245 if (rRef2.IsTabRel())
5246 rRef2.IncTab( nDelta);
5251 // reference of named range with relative references
5253 void ScCompiler::SetRelNameReference()
5255 for ( auto t: pArr->References() )
5257 ScSingleRefData& rRef1 = *t->GetSingleRef();
5258 if ( rRef1.IsColRel() || rRef1.IsRowRel() || rRef1.IsTabRel() )
5259 rRef1.SetRelName( true );
5260 if ( t->GetType() == svDoubleRef )
5262 ScSingleRefData& rRef2 = t->GetDoubleRef()->Ref2;
5263 if ( rRef2.IsColRel() || rRef2.IsRowRel() || rRef2.IsTabRel() )
5264 rRef2.SetRelName( true );
5269 // Wrap-adjust relative references of a RangeName to current position,
5270 // don't call for other token arrays!
5271 void ScCompiler::MoveRelWrap()
5273 for ( auto t: pArr->References() )
5275 if ( t->GetType() == svSingleRef || t->GetType() == svExternalSingleRef )
5276 ScRefUpdate::MoveRelWrap( rDoc, aPos, rDoc.MaxCol(), rDoc.MaxRow(), SingleDoubleRefModifier( *t->GetSingleRef() ).Ref() );
5277 else
5278 ScRefUpdate::MoveRelWrap( rDoc, aPos, rDoc.MaxCol(), rDoc.MaxRow(), *t->GetDoubleRef() );
5282 // Wrap-adjust relative references of a RangeName to current position,
5283 // don't call for other token arrays!
5284 void ScCompiler::MoveRelWrap( const ScTokenArray& rArr, const ScDocument& rDoc, const ScAddress& rPos,
5285 SCCOL nMaxCol, SCROW nMaxRow )
5287 for ( auto t: rArr.References() )
5289 if ( t->GetType() == svSingleRef || t->GetType() == svExternalSingleRef )
5290 ScRefUpdate::MoveRelWrap( rDoc, rPos, nMaxCol, nMaxRow, SingleDoubleRefModifier( *t->GetSingleRef() ).Ref() );
5291 else
5292 ScRefUpdate::MoveRelWrap( rDoc, rPos, nMaxCol, nMaxRow, *t->GetDoubleRef() );
5296 bool ScCompiler::IsCharFlagAllConventions(
5297 OUString const & rStr, sal_Int32 nPos, ScCharFlags nFlags )
5299 sal_Unicode c = rStr[ nPos ];
5300 sal_Unicode cLast = nPos > 0 ? rStr[ nPos-1 ] : 0;
5301 if (c < 128)
5303 for ( int nConv = formula::FormulaGrammar::CONV_UNSPECIFIED;
5304 ++nConv < formula::FormulaGrammar::CONV_LAST; )
5306 if (pConventions[nConv] &&
5307 ((pConventions[nConv]->getCharTableFlags(c, cLast) & nFlags) != nFlags))
5308 return false;
5309 // convention not known => assume valid
5311 return true;
5313 else
5314 return ScGlobal::getCharClass().isLetterNumeric( rStr, nPos );
5317 void ScCompiler::CreateStringFromExternal( OUStringBuffer& rBuffer, const FormulaToken* pTokenP ) const
5319 const FormulaToken* t = pTokenP;
5320 sal_uInt16 nFileId = t->GetIndex();
5321 ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager();
5322 sal_uInt16 nUsedFileId = pRefMgr->convertFileIdToUsedFileId(nFileId);
5323 const OUString* pFileName = pRefMgr->getExternalFileName(nFileId);
5324 if (!pFileName)
5325 return;
5327 switch (t->GetType())
5329 case svExternalName:
5330 rBuffer.append(pConv->makeExternalNameStr( nFileId, *pFileName, t->GetString().getString()));
5331 break;
5332 case svExternalSingleRef:
5333 pConv->makeExternalRefStr(rDoc.GetSheetLimits(),
5334 rBuffer, GetPos(), nUsedFileId, *pFileName, t->GetString().getString(),
5335 *t->GetSingleRef());
5336 break;
5337 case svExternalDoubleRef:
5339 vector<OUString> aTabNames;
5340 pRefMgr->getAllCachedTableNames(nFileId, aTabNames);
5341 // No sheet names is a valid case if external sheets were not
5342 // cached in this document and external document is not reachable,
5343 // else not and worth to be investigated.
5344 SAL_WARN_IF( aTabNames.empty(), "sc.core", "wrecked cache of external document? '" <<
5345 *pFileName << "' '" << t->GetString().getString() << "'");
5347 pConv->makeExternalRefStr(
5348 rDoc.GetSheetLimits(), rBuffer, GetPos(), nUsedFileId, *pFileName, aTabNames, t->GetString().getString(),
5349 *t->GetDoubleRef());
5351 break;
5352 default:
5353 // warning, not error, otherwise we may end up with a never
5354 // ending message box loop if this was the cursor cell to be redrawn.
5355 OSL_FAIL("ScCompiler::CreateStringFromToken: unknown type of ocExternalRef");
5359 void ScCompiler::CreateStringFromMatrix( OUStringBuffer& rBuffer, const FormulaToken* pTokenP ) const
5361 const ScMatrix* pMatrix = pTokenP->GetMatrix();
5362 SCSIZE nC, nMaxC, nR, nMaxR;
5364 pMatrix->GetDimensions( nMaxC, nMaxR);
5366 rBuffer.append( mxSymbols->getSymbol(ocArrayOpen) );
5367 for( nR = 0 ; nR < nMaxR ; nR++)
5369 if( nR > 0)
5371 rBuffer.append( mxSymbols->getSymbol(ocArrayRowSep) );
5374 for( nC = 0 ; nC < nMaxC ; nC++)
5376 if( nC > 0)
5378 rBuffer.append( mxSymbols->getSymbol(ocArrayColSep) );
5381 if( pMatrix->IsValue( nC, nR ) )
5383 if (pMatrix->IsBoolean(nC, nR))
5384 AppendBoolean(rBuffer, pMatrix->GetDouble(nC, nR) != 0.0);
5385 else
5387 FormulaError nErr = pMatrix->GetError(nC, nR);
5388 if (nErr != FormulaError::NONE)
5389 rBuffer.append(ScGlobal::GetErrorString(nErr));
5390 else
5391 AppendDouble(rBuffer, pMatrix->GetDouble(nC, nR));
5394 else if( pMatrix->IsEmpty( nC, nR ) )
5396 else if( pMatrix->IsStringOrEmpty( nC, nR ) )
5397 AppendString( rBuffer, pMatrix->GetString(nC, nR).getString() );
5400 rBuffer.append( mxSymbols->getSymbol(ocArrayClose) );
5403 namespace {
5404 void escapeTableRefColumnSpecifier( OUString& rStr )
5406 const sal_Int32 n = rStr.getLength();
5407 OUStringBuffer aBuf( n * 2 );
5408 const sal_Unicode* p = rStr.getStr();
5409 const sal_Unicode* const pStop = p + n;
5410 for ( ; p < pStop; ++p)
5412 const sal_Unicode c = *p;
5413 switch (c)
5415 case '\'':
5416 case '[':
5417 case '#':
5418 case ']':
5419 aBuf.append( '\'' );
5420 break;
5421 default:
5422 ; // nothing
5424 aBuf.append( c );
5426 rStr = aBuf.makeStringAndClear();
5430 void ScCompiler::CreateStringFromSingleRef( OUStringBuffer& rBuffer, const FormulaToken* _pTokenP ) const
5432 const FormulaToken* p;
5433 OUString aErrRef = GetCurrentOpCodeMap()->getSymbol(ocErrRef);
5434 const OpCode eOp = _pTokenP->GetOpCode();
5435 const ScSingleRefData& rRef = *_pTokenP->GetSingleRef();
5436 ScComplexRefData aRef;
5437 aRef.Ref1 = aRef.Ref2 = rRef;
5438 if ( eOp == ocColRowName )
5440 ScAddress aAbs = rRef.toAbs(rDoc, aPos);
5441 if (rDoc.HasStringData(aAbs.Col(), aAbs.Row(), aAbs.Tab()))
5443 OUString aStr = rDoc.GetString(aAbs, &mrInterpreterContext);
5444 // Enquote to SingleQuoted.
5445 aStr = aStr.replaceAll(u"'", u"''");
5446 rBuffer.append('\'');
5447 rBuffer.append(aStr);
5448 rBuffer.append('\'');
5450 else
5452 rBuffer.append(ScCompiler::GetNativeSymbol(ocErrName));
5453 pConv->makeRefStr(rDoc.GetSheetLimits(), rBuffer, meGrammar, aPos, aErrRef,
5454 GetSetupTabNames(), aRef, true, (pArr && pArr->IsFromRangeName()));
5457 else if (pArr && (p = maArrIterator.PeekPrevNoSpaces()) && p->GetOpCode() == ocTableRefOpen)
5459 OUString aStr;
5460 ScAddress aAbs = rRef.toAbs(rDoc, aPos);
5461 const ScDBData* pData = rDoc.GetDBAtCursor( aAbs.Col(), aAbs.Row(), aAbs.Tab(), ScDBDataPortion::AREA);
5462 SAL_WARN_IF( !pData, "sc.core", "ScCompiler::CreateStringFromSingleRef - TableRef without ScDBData: " <<
5463 aAbs.Format( ScRefFlags::VALID | ScRefFlags::TAB_3D, &rDoc));
5464 if (pData)
5465 aStr = pData->GetTableColumnName( aAbs.Col());
5466 if (aStr.isEmpty())
5468 if (pData && pData->HasHeader())
5470 SAL_WARN("sc.core", "ScCompiler::CreateStringFromSingleRef - TableRef falling back to cell: " <<
5471 aAbs.Format( ScRefFlags::VALID | ScRefFlags::TAB_3D, &rDoc));
5472 aStr = rDoc.GetString(aAbs, &mrInterpreterContext);
5474 else
5476 SAL_WARN("sc.core", "ScCompiler::CreateStringFromSingleRef - TableRef of empty header-less: " <<
5477 aAbs.Format( ScRefFlags::VALID | ScRefFlags::TAB_3D, &rDoc));
5478 aStr = aErrRef;
5481 escapeTableRefColumnSpecifier( aStr);
5482 rBuffer.append(aStr);
5484 else
5485 pConv->makeRefStr(rDoc.GetSheetLimits(), rBuffer, meGrammar, aPos, aErrRef,
5486 GetSetupTabNames(), aRef, true, (pArr && pArr->IsFromRangeName()));
5489 void ScCompiler::CreateStringFromDoubleRef( OUStringBuffer& rBuffer, const FormulaToken* _pTokenP ) const
5491 OUString aErrRef = GetCurrentOpCodeMap()->getSymbol(ocErrRef);
5492 pConv->makeRefStr(rDoc.GetSheetLimits(), rBuffer, meGrammar, aPos, aErrRef, GetSetupTabNames(),
5493 *_pTokenP->GetDoubleRef(), false, (pArr && pArr->IsFromRangeName()));
5496 void ScCompiler::CreateStringFromIndex( OUStringBuffer& rBuffer, const FormulaToken* _pTokenP ) const
5498 const OpCode eOp = _pTokenP->GetOpCode();
5499 OUStringBuffer aBuffer;
5500 switch ( eOp )
5502 case ocName:
5504 const ScRangeData* pData = GetRangeData( *_pTokenP);
5505 if (pData)
5507 SCTAB nTab = _pTokenP->GetSheet();
5508 if (nTab >= 0 && (nTab != aPos.Tab() || mbRefConventionChartOOXML))
5510 // Sheet-local on other sheet.
5511 OUString aName;
5512 if (rDoc.GetName( nTab, aName))
5514 ScCompiler::CheckTabQuotes( aName, pConv->meConv);
5515 aBuffer.append( aName);
5517 else
5518 aBuffer.append(ScCompiler::GetNativeSymbol(ocErrName));
5519 aBuffer.append( pConv->getSpecialSymbol( ScCompiler::Convention::SHEET_SEPARATOR));
5521 else if (mbRefConventionChartOOXML)
5523 aBuffer.append("[0]"
5524 + OUStringChar(pConv->getSpecialSymbol(ScCompiler::Convention::SHEET_SEPARATOR)));
5526 aBuffer.append(pData->GetName());
5529 break;
5530 case ocDBArea:
5532 const ScDBData* pDBData = rDoc.GetDBCollection()->getNamedDBs().findByIndex(_pTokenP->GetIndex());
5533 if (pDBData)
5534 aBuffer.append(pDBData->GetName());
5536 break;
5537 case ocTableRef:
5539 if (NeedsTableRefTransformation())
5541 // Write the resulting reference if TableRef is not supported.
5542 const ScTableRefToken* pTR = dynamic_cast<const ScTableRefToken*>(_pTokenP);
5543 if (!pTR)
5544 AppendErrorConstant( aBuffer, FormulaError::NoCode);
5545 else
5547 const FormulaToken* pRef = pTR->GetAreaRefRPN();
5548 if (!pRef)
5549 AppendErrorConstant( aBuffer, FormulaError::NoCode);
5550 else
5552 switch (pRef->GetType())
5554 case svSingleRef:
5555 CreateStringFromSingleRef( aBuffer, pRef);
5556 break;
5557 case svDoubleRef:
5558 CreateStringFromDoubleRef( aBuffer, pRef);
5559 break;
5560 case svError:
5561 AppendErrorConstant( aBuffer, pRef->GetError());
5562 break;
5563 default:
5564 AppendErrorConstant( aBuffer, FormulaError::NoCode);
5569 else
5571 const ScDBData* pDBData = rDoc.GetDBCollection()->getNamedDBs().findByIndex(_pTokenP->GetIndex());
5572 if (pDBData)
5573 aBuffer.append(pDBData->GetName());
5576 break;
5577 default:
5578 ; // nothing
5580 if ( !aBuffer.isEmpty() )
5581 rBuffer.append(aBuffer);
5582 else
5583 rBuffer.append(ScCompiler::GetNativeSymbol(ocErrName));
5586 void ScCompiler::LocalizeString( OUString& rName ) const
5588 ScGlobal::GetAddInCollection()->LocalizeString( rName );
5591 FormulaTokenRef ScCompiler::ExtendRangeReference( FormulaToken & rTok1, FormulaToken & rTok2 )
5593 return extendRangeReference( rDoc.GetSheetLimits(), rTok1, rTok2, aPos, true/*bReuseDoubleRef*/ );
5596 void ScCompiler::fillAddInToken(::std::vector< css::sheet::FormulaOpCodeMapEntry >& _rVec,bool _bIsEnglish) const
5598 // All known AddIn functions.
5599 sheet::FormulaOpCodeMapEntry aEntry;
5600 aEntry.Token.OpCode = ocExternal;
5602 const LanguageTag aEnglishLanguageTag(LANGUAGE_ENGLISH_US);
5603 ScUnoAddInCollection* pColl = ScGlobal::GetAddInCollection();
5604 const tools::Long nCount = pColl->GetFuncCount();
5605 for (tools::Long i=0; i < nCount; ++i)
5607 const ScUnoAddInFuncData* pFuncData = pColl->GetFuncData(i);
5608 if (pFuncData)
5610 if ( _bIsEnglish )
5612 // This is used with OOXML import, so GetExcelName() is really
5613 // wanted here until we'll have a parameter to differentiate
5614 // from the general css::sheet::XFormulaOpCodeMapper case and
5615 // use pFuncData->GetUpperEnglish().
5616 OUString aName;
5617 if (pFuncData->GetExcelName( aEnglishLanguageTag, aName))
5618 aEntry.Name = aName;
5619 else
5620 aEntry.Name = pFuncData->GetUpperName();
5622 else
5623 aEntry.Name = pFuncData->GetUpperLocal();
5624 aEntry.Token.Data <<= pFuncData->GetOriginalName();
5625 _rVec.push_back( aEntry);
5628 // FIXME: what about those old non-UNO AddIns?
5631 bool ScCompiler::HandleColRowName()
5633 ScSingleRefData& rRef = *mpToken->GetSingleRef();
5634 const ScAddress aAbs = rRef.toAbs(rDoc, aPos);
5635 if (!rDoc.ValidAddress(aAbs))
5637 SetError( FormulaError::NoRef );
5638 return true;
5640 SCCOL nCol = aAbs.Col();
5641 SCROW nRow = aAbs.Row();
5642 SCTAB nTab = aAbs.Tab();
5643 bool bColName = rRef.IsColRel();
5644 SCCOL nMyCol = aPos.Col();
5645 SCROW nMyRow = aPos.Row();
5646 bool bInList = false;
5647 bool bValidName = false;
5648 ScRangePairList* pRL = (bColName ?
5649 rDoc.GetColNameRanges() : rDoc.GetRowNameRanges());
5650 ScRange aRange;
5651 for ( size_t i = 0, nPairs = pRL->size(); i < nPairs; ++i )
5653 const ScRangePair & rR = (*pRL)[i];
5654 if ( rR.GetRange(0).Contains( aAbs ) )
5656 bInList = bValidName = true;
5657 aRange = rR.GetRange(1);
5658 if ( bColName )
5660 aRange.aStart.SetCol( nCol );
5661 aRange.aEnd.SetCol( nCol );
5663 else
5665 aRange.aStart.SetRow( nRow );
5666 aRange.aEnd.SetRow( nRow );
5668 break; // for
5671 if ( !bInList && rDoc.GetDocOptions().IsLookUpColRowNames() )
5672 { // automagically or created by copying and NamePos isn't in list
5673 ScRefCellValue aCell(rDoc, aAbs);
5674 bool bString = aCell.hasString();
5675 if (!bString && aCell.isEmpty())
5676 bString = true; // empty cell is ok
5677 if ( bString )
5678 { // corresponds with ScInterpreter::ScColRowNameAuto()
5679 bValidName = true;
5680 if ( bColName )
5681 { // ColName
5682 SCROW nStartRow = nRow + 1;
5683 if ( nStartRow > rDoc.MaxRow() )
5684 nStartRow = rDoc.MaxRow();
5685 SCROW nMaxRow = rDoc.MaxRow();
5686 if ( nMyCol == nCol )
5687 { // formula cell in same column
5688 if ( nMyRow == nStartRow )
5689 { // take remainder under name cell
5690 nStartRow++;
5691 if ( nStartRow > rDoc.MaxRow() )
5692 nStartRow = rDoc.MaxRow();
5694 else if ( nMyRow > nStartRow )
5695 { // from name cell down to formula cell
5696 nMaxRow = nMyRow - 1;
5699 for ( size_t i = 0, nPairs = pRL->size(); i < nPairs; ++i )
5700 { // next defined ColNameRange below limits row
5701 const ScRangePair & rR = (*pRL)[i];
5702 const ScRange& rRange = rR.GetRange(1);
5703 if ( rRange.aStart.Col() <= nCol && nCol <= rRange.aEnd.Col() )
5704 { // identical column range
5705 SCROW nTmp = rRange.aStart.Row();
5706 if ( nStartRow < nTmp && nTmp <= nMaxRow )
5707 nMaxRow = nTmp - 1;
5710 aRange.aStart.Set( nCol, nStartRow, nTab );
5711 aRange.aEnd.Set( nCol, nMaxRow, nTab );
5713 else
5714 { // RowName
5715 SCCOL nStartCol = nCol + 1;
5716 if ( nStartCol > rDoc.MaxCol() )
5717 nStartCol = rDoc.MaxCol();
5718 SCCOL nMaxCol = rDoc.MaxCol();
5719 if ( nMyRow == nRow )
5720 { // formula cell in same row
5721 if ( nMyCol == nStartCol )
5722 { // take remainder right from name cell
5723 nStartCol++;
5724 if ( nStartCol > rDoc.MaxCol() )
5725 nStartCol = rDoc.MaxCol();
5727 else if ( nMyCol > nStartCol )
5728 { // from name cell right to formula cell
5729 nMaxCol = nMyCol - 1;
5732 for ( size_t i = 0, nPairs = pRL->size(); i < nPairs; ++i )
5733 { // next defined RowNameRange to the right limits column
5734 const ScRangePair & rR = (*pRL)[i];
5735 const ScRange& rRange = rR.GetRange(1);
5736 if ( rRange.aStart.Row() <= nRow && nRow <= rRange.aEnd.Row() )
5737 { // identical row range
5738 SCCOL nTmp = rRange.aStart.Col();
5739 if ( nStartCol < nTmp && nTmp <= nMaxCol )
5740 nMaxCol = nTmp - 1;
5743 aRange.aStart.Set( nStartCol, nRow, nTab );
5744 aRange.aEnd.Set( nMaxCol, nRow, nTab );
5748 if ( bValidName )
5750 // And now the magic to distinguish between a range and a single
5751 // cell thereof, which is picked position-dependent of the formula
5752 // cell. If a direct neighbor is a binary operator (ocAdd, ...) a
5753 // SingleRef matching the column/row of the formula cell is
5754 // generated. A ocColRowName or ocIntersect as a neighbor results
5755 // in a range. Special case: if label is valid for a single cell, a
5756 // position independent SingleRef is generated.
5757 bool bSingle = (aRange.aStart == aRange.aEnd);
5758 bool bFound;
5759 if ( bSingle )
5760 bFound = true;
5761 else
5763 FormulaToken* p1 = maArrIterator.PeekPrevNoSpaces();
5764 FormulaToken* p2 = maArrIterator.PeekNextNoSpaces();
5765 // begin/end of a formula => single
5766 OpCode eOp1 = p1 ? p1->GetOpCode() : ocAdd;
5767 OpCode eOp2 = p2 ? p2->GetOpCode() : ocAdd;
5768 if ( eOp1 != ocColRowName && eOp1 != ocIntersect
5769 && eOp2 != ocColRowName && eOp2 != ocIntersect )
5771 if ( (SC_OPCODE_START_BIN_OP <= eOp1 && eOp1 < SC_OPCODE_STOP_BIN_OP) ||
5772 (SC_OPCODE_START_BIN_OP <= eOp2 && eOp2 < SC_OPCODE_STOP_BIN_OP))
5773 bSingle = true;
5775 if ( bSingle )
5776 { // column and/or row must match range
5777 if ( bColName )
5779 bFound = (aRange.aStart.Row() <= nMyRow
5780 && nMyRow <= aRange.aEnd.Row());
5781 if ( bFound )
5782 aRange.aStart.SetRow( nMyRow );
5784 else
5786 bFound = (aRange.aStart.Col() <= nMyCol
5787 && nMyCol <= aRange.aEnd.Col());
5788 if ( bFound )
5789 aRange.aStart.SetCol( nMyCol );
5792 else
5793 bFound = true;
5795 if ( !bFound )
5796 SetError(FormulaError::NoRef);
5797 else if (mbJumpCommandReorder)
5799 ScTokenArray* pNew = new ScTokenArray(rDoc);
5800 if ( bSingle )
5802 ScSingleRefData aRefData;
5803 aRefData.InitAddress( aRange.aStart );
5804 if ( bColName )
5805 aRefData.SetColRel( true );
5806 else
5807 aRefData.SetRowRel( true );
5808 aRefData.SetAddress(rDoc.GetSheetLimits(), aRange.aStart, aPos);
5809 pNew->AddSingleReference( aRefData );
5811 else
5813 ScComplexRefData aRefData;
5814 aRefData.InitRange( aRange );
5815 if ( bColName )
5817 aRefData.Ref1.SetColRel( true );
5818 aRefData.Ref2.SetColRel( true );
5820 else
5822 aRefData.Ref1.SetRowRel( true );
5823 aRefData.Ref2.SetRowRel( true );
5825 aRefData.SetRange(rDoc.GetSheetLimits(), aRange, aPos);
5826 if ( bInList )
5827 pNew->AddDoubleReference( aRefData );
5828 else
5829 { // automagically
5830 pNew->Add( new ScDoubleRefToken( rDoc.GetSheetLimits(), aRefData, ocColRowNameAuto ) );
5833 PushTokenArray( pNew, true );
5834 return GetToken();
5837 else
5838 SetError(FormulaError::NoName);
5839 return true;
5842 bool ScCompiler::HandleDbData()
5844 ScDBData* pDBData = rDoc.GetDBCollection()->getNamedDBs().findByIndex(mpToken->GetIndex());
5845 if ( !pDBData )
5846 SetError(FormulaError::NoName);
5847 else if (mbJumpCommandReorder)
5849 ScComplexRefData aRefData;
5850 aRefData.InitFlags();
5851 ScRange aRange;
5852 pDBData->GetArea(aRange);
5853 aRange.aEnd.SetTab(aRange.aStart.Tab());
5854 aRefData.SetRange(rDoc.GetSheetLimits(), aRange, aPos);
5855 ScTokenArray* pNew = new ScTokenArray(rDoc);
5856 pNew->AddDoubleReference( aRefData );
5857 PushTokenArray( pNew, true );
5858 return GetToken();
5860 return true;
5863 bool ScCompiler::GetTokenIfOpCode( OpCode eOp )
5865 const formula::FormulaToken* p = maArrIterator.PeekNextNoSpaces();
5866 if (p && p->GetOpCode() == eOp)
5867 return GetToken();
5868 return false;
5872 /* Documentation on MS-Excel Table structured references:
5873 * https://support.office.com/en-us/article/Use-structured-references-in-Excel-table-formulas-75fb07d3-826a-449c-b76f-363057e3d16f
5874 * * as of Excel 2013
5875 * [MS-XLSX]: Formulas https://msdn.microsoft.com/en-us/library/dd906358.aspx
5876 * * look for structure-reference
5879 bool ScCompiler::HandleTableRef()
5881 ScTableRefToken* pTR = dynamic_cast<ScTableRefToken*>(mpToken.get());
5882 if (!pTR)
5884 SetError(FormulaError::UnknownToken);
5885 return true;
5888 ScDBData* pDBData = rDoc.GetDBCollection()->getNamedDBs().findByIndex( pTR->GetIndex());
5889 if ( !pDBData )
5890 SetError(FormulaError::NoName);
5891 else if (mbJumpCommandReorder)
5893 ScRange aDBRange;
5894 pDBData->GetArea(aDBRange);
5895 aDBRange.aEnd.SetTab(aDBRange.aStart.Tab());
5896 ScRange aRange( aDBRange);
5897 FormulaError nError = FormulaError::NONE;
5898 bool bForwardToClose = false;
5899 ScTableRefToken::Item eItem = pTR->GetItem();
5900 switch (eItem)
5902 case ScTableRefToken::TABLE:
5904 // The table name without items references the table data,
5905 // without headers or totals.
5906 if (pDBData->HasHeader())
5907 aRange.aStart.IncRow();
5908 if (pDBData->HasTotals())
5909 aRange.aEnd.IncRow(-1);
5910 if (aRange.aEnd.Row() < aRange.aStart.Row())
5911 nError = FormulaError::NoRef;
5912 bForwardToClose = true;
5914 break;
5915 case ScTableRefToken::ALL:
5917 bForwardToClose = true;
5919 break;
5920 case ScTableRefToken::HEADERS:
5922 if (pDBData->HasHeader())
5923 aRange.aEnd.SetRow( aRange.aStart.Row());
5924 else
5925 nError = FormulaError::NoRef;
5926 bForwardToClose = true;
5928 break;
5929 case ScTableRefToken::DATA:
5931 if (pDBData->HasHeader())
5932 aRange.aStart.IncRow();
5934 [[fallthrough]];
5935 case ScTableRefToken::HEADERS_DATA:
5937 if (pDBData->HasTotals())
5938 aRange.aEnd.IncRow(-1);
5939 if (aRange.aEnd.Row() < aRange.aStart.Row())
5940 nError = FormulaError::NoRef;
5941 bForwardToClose = true;
5943 break;
5944 case ScTableRefToken::TOTALS:
5946 if (pDBData->HasTotals())
5947 aRange.aStart.SetRow( aRange.aEnd.Row());
5948 else
5949 nError = FormulaError::NoRef;
5950 bForwardToClose = true;
5952 break;
5953 case ScTableRefToken::DATA_TOTALS:
5955 if (pDBData->HasHeader())
5956 aRange.aStart.IncRow();
5957 if (aRange.aEnd.Row() < aRange.aStart.Row())
5958 nError = FormulaError::NoRef;
5959 bForwardToClose = true;
5961 break;
5962 case ScTableRefToken::THIS_ROW:
5964 if (aRange.aStart.Row() <= aPos.Row() && aPos.Row() <= aRange.aEnd.Row())
5966 aRange.aStart.SetRow( aPos.Row());
5967 aRange.aEnd.SetRow( aPos.Row());
5969 else
5971 nError = FormulaError::NoValue;
5972 // For *some* relative row reference in named
5973 // expressions' thisrow special handling below.
5974 aRange.aEnd.SetRow( aRange.aStart.Row());
5976 bForwardToClose = true;
5978 break;
5980 bool bColumnRange = false;
5981 bool bCol1Rel = false;
5982 bool bCol1RelName = false;
5983 int nLevel = 0;
5984 if (bForwardToClose && GetTokenIfOpCode( ocTableRefOpen))
5986 ++nLevel;
5987 enum
5989 sOpen,
5990 sItem,
5991 sClose,
5992 sSep,
5993 sLast,
5994 sStop
5995 } eState = sOpen;
5998 const formula::FormulaToken* p = maArrIterator.PeekNextNoSpaces();
5999 if (!p)
6000 eState = sStop;
6001 else
6003 switch (p->GetOpCode())
6005 case ocTableRefOpen:
6006 eState = ((eState == sOpen || eState == sSep) ? sOpen : sStop);
6007 if (++nLevel > 2)
6009 SetError( FormulaError::Pair);
6010 eState = sStop;
6012 break;
6013 case ocTableRefItemAll:
6014 case ocTableRefItemHeaders:
6015 case ocTableRefItemData:
6016 case ocTableRefItemTotals:
6017 case ocTableRefItemThisRow:
6018 eState = ((eState == sOpen) ? sItem : sStop);
6019 break;
6020 case ocTableRefClose:
6021 eState = ((eState == sItem || eState == sClose) ? sClose : sStop);
6022 if (eState != sStop && --nLevel == 0)
6023 eState = sLast;
6024 break;
6025 case ocSep:
6026 eState = ((eState == sClose) ? sSep : sStop);
6027 break;
6028 case ocPush:
6029 if (eState == sOpen && p->GetType() == svSingleRef)
6031 bColumnRange = true;
6032 bCol1Rel = p->GetSingleRef()->IsColRel();
6033 bCol1RelName = p->GetSingleRef()->IsRelName();
6034 eState = sLast;
6036 else
6038 eState = sStop;
6040 break;
6041 case ocBad:
6042 eState = sLast;
6043 if (nError == FormulaError::NONE)
6044 nError = FormulaError::NoName;
6045 break;
6046 default:
6047 eState = sStop;
6049 if (eState != sStop)
6050 GetToken();
6051 if (eState == sLast)
6052 eState = sStop;
6054 } while (eState != sStop);
6056 ScTokenArray* pNew = new ScTokenArray(rDoc);
6057 if (nError == FormulaError::NONE || nError == FormulaError::NoValue)
6059 bool bCol2Rel = false;
6060 bool bCol2RelName = false;
6061 // The FormulaError::NoValue case generates a thisrow reference that can be
6062 // used to save named expressions in A1 syntax notation.
6063 if (bColumnRange)
6065 // Limit range to specified columns.
6066 ScRange aColRange( ScAddress::INITIALIZE_INVALID );
6067 switch (mpToken->GetType())
6069 case svSingleRef:
6071 aColRange.aStart = aColRange.aEnd = mpToken->GetSingleRef()->toAbs(rDoc, aPos);
6072 if ( GetTokenIfOpCode( ocTableRefClose) && (nLevel--) &&
6073 GetTokenIfOpCode( ocRange) &&
6074 GetTokenIfOpCode( ocTableRefOpen) && (++nLevel) &&
6075 GetTokenIfOpCode( ocPush))
6077 if (mpToken->GetType() != svSingleRef)
6078 aColRange = ScRange( ScAddress::INITIALIZE_INVALID);
6079 else
6081 aColRange.aEnd = mpToken->GetSingleRef()->toAbs(rDoc, aPos);
6082 aColRange.PutInOrder();
6083 bCol2Rel = mpToken->GetSingleRef()->IsColRel();
6084 bCol2RelName = mpToken->GetSingleRef()->IsRelName();
6088 break;
6089 default:
6090 ; // nothing
6092 // coverity[copy_paste_error : FALSE] - this is correct, aStart in both aDBRange uses
6093 if (aColRange.aStart.Row() != aDBRange.aStart.Row() || aColRange.aEnd.Row() != aDBRange.aStart.Row())
6094 aRange = ScRange( ScAddress::INITIALIZE_INVALID);
6095 else
6097 aColRange.aEnd.SetRow( aRange.aEnd.Row());
6098 aRange = aRange.Intersection( aColRange);
6101 if (aRange.IsValid())
6103 if (aRange.aStart == aRange.aEnd)
6105 ScSingleRefData aRefData;
6106 aRefData.InitFlags();
6107 aRefData.SetColRel( bCol1Rel);
6108 if (eItem == ScTableRefToken::THIS_ROW)
6110 aRefData.SetRowRel( true);
6111 if (!bCol1RelName)
6112 bCol1RelName = pArr->IsFromRangeName();
6114 aRefData.SetRelName( bCol1RelName);
6115 aRefData.SetFlag3D( true);
6116 if (nError != FormulaError::NONE)
6118 aRefData.SetAddress( rDoc.GetSheetLimits(), aRange.aStart, aRange.aStart);
6119 pTR->SetAreaRefRPN( new ScSingleRefToken(rDoc.GetSheetLimits(), aRefData)); // set reference at TableRef
6120 pNew->Add( new FormulaErrorToken( nError)); // set error in RPN
6122 else
6124 aRefData.SetAddress( rDoc.GetSheetLimits(), aRange.aStart, aPos);
6125 pTR->SetAreaRefRPN( pNew->AddSingleReference( aRefData));
6128 else
6130 ScComplexRefData aRefData;
6131 aRefData.InitFlags();
6132 aRefData.Ref1.SetColRel( bCol1Rel);
6133 aRefData.Ref2.SetColRel( bCol2Rel);
6134 bool bRelName = bCol1RelName || bCol2RelName;
6135 if (eItem == ScTableRefToken::THIS_ROW)
6137 aRefData.Ref1.SetRowRel( true);
6138 aRefData.Ref2.SetRowRel( true);
6139 if (!bRelName)
6140 bRelName = pArr->IsFromRangeName();
6142 aRefData.Ref1.SetRelName( bRelName);
6143 aRefData.Ref2.SetRelName( bRelName);
6144 aRefData.Ref1.SetFlag3D( true);
6145 if (nError != FormulaError::NONE)
6147 aRefData.SetRange( rDoc.GetSheetLimits(), aRange, aRange.aStart);
6148 pTR->SetAreaRefRPN( new ScDoubleRefToken(rDoc.GetSheetLimits(), aRefData)); // set reference at TableRef
6149 pNew->Add( new FormulaErrorToken( nError)); // set error in RPN
6151 else
6153 aRefData.SetRange( rDoc.GetSheetLimits(), aRange, aPos);
6154 pTR->SetAreaRefRPN( pNew->AddDoubleReference( aRefData));
6158 else
6160 pTR->SetAreaRefRPN( pNew->Add( new FormulaErrorToken( FormulaError::NoRef)));
6163 else
6165 pTR->SetAreaRefRPN( pNew->Add( new FormulaErrorToken( nError)));
6167 while (nLevel-- > 0)
6169 if (!GetTokenIfOpCode( ocTableRefClose))
6170 SetError( FormulaError::Pair);
6172 PushTokenArray( pNew, true );
6173 return GetToken();
6175 return true;
6178 formula::ParamClass ScCompiler::GetForceArrayParameter( const formula::FormulaToken* pToken, sal_uInt16 nParam ) const
6180 return ScParameterClassification::GetParameterType( pToken, nParam);
6183 bool ScCompiler::ParameterMayBeImplicitIntersection(const FormulaToken* token, int parameter)
6185 formula::ParamClass param = ScParameterClassification::GetParameterType( token, parameter );
6186 return param == Value || param == Array;
6189 bool ScCompiler::SkipImplicitIntersectionOptimization(const FormulaToken* token) const
6191 if (mbMatrixFlag)
6192 return true;
6193 formula::ParamClass paramClass = token->GetInForceArray();
6194 if (paramClass == formula::ForceArray
6195 || paramClass == formula::ReferenceOrForceArray
6196 || paramClass == formula::SuppressedReferenceOrForceArray
6197 || paramClass == formula::ReferenceOrRefArray)
6199 return true;
6201 formula::ParamClass returnType = ScParameterClassification::GetParameterType( token, SAL_MAX_UINT16 );
6202 return returnType == formula::Reference;
6205 void ScCompiler::HandleIIOpCode(FormulaToken* token, FormulaToken*** pppToken, sal_uInt8 nNumParams)
6207 if (!mbComputeII)
6208 return;
6209 #ifdef DBG_UTIL
6210 if(!HandleIIOpCodeInternal(token, pppToken, nNumParams))
6211 mUnhandledPossibleImplicitIntersectionsOpCodes.insert(token->GetOpCode());
6212 #else
6213 HandleIIOpCodeInternal(token, pppToken, nNumParams);
6214 #endif
6217 // return true if opcode is handled
6218 bool ScCompiler::HandleIIOpCodeInternal(FormulaToken* token, FormulaToken*** pppToken, sal_uInt8 nNumParams)
6220 if (nNumParams > 0 && *pppToken[0] == nullptr)
6221 return false; // Bad expression (see the dummy creation in FormulaCompiler::CompileTokenArray())
6223 const OpCode nOpCode = token->GetOpCode();
6225 if (nOpCode == ocPush)
6227 if(token->GetType() == svDoubleRef)
6228 mUnhandledPossibleImplicitIntersections.insert( token );
6229 return true;
6231 else if (nOpCode == ocSumIf || nOpCode == ocAverageIf)
6233 if (nNumParams != 3)
6234 return false;
6236 if (!(pppToken[0] && pppToken[2] && *pppToken[0] && *pppToken[2]))
6237 return false;
6239 if ((*pppToken[0])->GetType() != svDoubleRef)
6240 return false;
6242 const StackVar eSumRangeType = (*pppToken[2])->GetType();
6244 if ( eSumRangeType != svSingleRef && eSumRangeType != svDoubleRef )
6245 return false;
6247 const ScComplexRefData& rBaseRange = *(*pppToken[0])->GetDoubleRef();
6249 ScComplexRefData aSumRange;
6250 if (eSumRangeType == svSingleRef)
6252 aSumRange.Ref1 = *(*pppToken[2])->GetSingleRef();
6253 aSumRange.Ref2 = aSumRange.Ref1;
6255 else
6256 aSumRange = *(*pppToken[2])->GetDoubleRef();
6258 CorrectSumRange(rBaseRange, aSumRange, pppToken[2]);
6259 // TODO mark parameters as handled
6260 return true;
6262 else if (nOpCode >= SC_OPCODE_START_1_PAR && nOpCode < SC_OPCODE_STOP_1_PAR)
6264 if (nNumParams != 1)
6265 return false;
6267 if( !ParameterMayBeImplicitIntersection( token, 0 ))
6268 return false;
6269 if (SkipImplicitIntersectionOptimization(token))
6270 return false;
6272 if ((*pppToken[0])->GetType() != svDoubleRef)
6273 return false;
6275 mPendingImplicitIntersectionOptimizations.emplace_back( pppToken[0], token );
6276 return true;
6278 else if ((nOpCode >= SC_OPCODE_START_BIN_OP && nOpCode < SC_OPCODE_STOP_BIN_OP
6279 && nOpCode != ocAnd && nOpCode != ocOr)
6280 || nOpCode == ocRound || nOpCode == ocRoundUp || nOpCode == ocRoundDown)
6282 if (nNumParams != 2)
6283 return false;
6285 if( !ParameterMayBeImplicitIntersection( token, 0 ) || !ParameterMayBeImplicitIntersection( token, 1 ))
6286 return false;
6287 if (SkipImplicitIntersectionOptimization(token))
6288 return false;
6290 // Convert only if the other parameter is not a matrix (which would force the result to be a matrix).
6291 if ((*pppToken[0])->GetType() == svDoubleRef && (*pppToken[1])->GetType() != svMatrix)
6292 mPendingImplicitIntersectionOptimizations.emplace_back( pppToken[0], token );
6293 if ((*pppToken[1])->GetType() == svDoubleRef && (*pppToken[0])->GetType() != svMatrix)
6294 mPendingImplicitIntersectionOptimizations.emplace_back( pppToken[1], token );
6295 return true;
6297 else if ((nOpCode >= SC_OPCODE_START_UN_OP && nOpCode < SC_OPCODE_STOP_UN_OP)
6298 || nOpCode == ocPercentSign)
6300 if (nNumParams != 1)
6301 return false;
6303 if( !ParameterMayBeImplicitIntersection( token, 0 ))
6304 return false;
6305 if (SkipImplicitIntersectionOptimization(token))
6306 return false;
6308 if ((*pppToken[0])->GetType() == svDoubleRef)
6309 mPendingImplicitIntersectionOptimizations.emplace_back( pppToken[0], token );
6310 return true;
6312 else if (nOpCode == ocVLookup)
6314 if (nNumParams != 3 && nNumParams != 4)
6315 return false;
6317 if (SkipImplicitIntersectionOptimization(token))
6318 return false;
6320 assert( ParameterMayBeImplicitIntersection( token, 0 ));
6321 assert( !ParameterMayBeImplicitIntersection( token, 1 ));
6322 assert( ParameterMayBeImplicitIntersection( token, 2 ));
6323 assert( ParameterMayBeImplicitIntersection( token, 3 ));
6324 if ((*pppToken[2])->GetType() == svDoubleRef)
6325 mPendingImplicitIntersectionOptimizations.emplace_back( pppToken[2], token );
6326 if ((*pppToken[0])->GetType() == svDoubleRef)
6327 mPendingImplicitIntersectionOptimizations.emplace_back( pppToken[0], token );
6328 if (nNumParams == 4 && (*pppToken[3])->GetType() == svDoubleRef)
6329 mPendingImplicitIntersectionOptimizations.emplace_back( pppToken[3], token );
6330 // a range for the second parameters is not an implicit intersection
6331 mUnhandledPossibleImplicitIntersections.erase( *pppToken[ 1 ] );
6332 return true;
6334 else
6336 bool possibleII = false;
6337 for( int i = 0; i < nNumParams; ++i )
6339 if( ParameterMayBeImplicitIntersection( token, i )
6340 && (*pppToken[i])->GetType() == svDoubleRef)
6342 possibleII = true;
6343 break;
6346 if( !possibleII )
6348 // all parameters have been handled, they are not implicit intersections
6349 for( int i = 0; i < nNumParams; ++i )
6350 mUnhandledPossibleImplicitIntersections.erase( *pppToken[ i ] );
6351 return true;
6355 return false;
6358 void ScCompiler::PostProcessCode()
6360 for( const PendingImplicitIntersectionOptimization& item : mPendingImplicitIntersectionOptimizations )
6362 if( *item.parameterLocation != item.parameter ) // the parameter has been changed somehow
6363 continue;
6364 if( item.parameterLocation >= pCode ) // the location is not inside the code (pCode points after the end)
6365 continue;
6366 // E.g. "SUMPRODUCT(I5:I6+1)" shouldn't do implicit intersection.
6367 if( item.operation->IsInForceArray())
6368 continue;
6369 ReplaceDoubleRefII( item.parameterLocation );
6371 mPendingImplicitIntersectionOptimizations.clear();
6374 void ScCompiler::AnnotateOperands()
6376 AnnotateTrimOnDoubleRefs();
6379 void ScCompiler::ReplaceDoubleRefII(FormulaToken** ppDoubleRefTok)
6381 const ScComplexRefData* pRange = (*ppDoubleRefTok)->GetDoubleRef();
6382 if (!pRange)
6383 return;
6385 const ScComplexRefData& rRange = *pRange;
6387 // Can't do optimization reliably in this case (when row references are absolute).
6388 // Example : =SIN(A$1:A$10) filled in a formula group starting at B5 and of length 100.
6389 // If we just optimize the argument $A$1:$A$10 to singleref "A5" for the top cell in the fg, then
6390 // the results in cells B11:B104 will be incorrect (sin(0) = 0, assuming empty cells in A11:A104)
6391 // instead of the #VALUE! errors we would expect. We need to know the formula-group length to
6392 // fix this, but that is unknown at this stage, so skip such cases.
6393 if (!rRange.Ref1.IsRowRel() && !rRange.Ref2.IsRowRel())
6394 return;
6396 ScRange aAbsRange = rRange.toAbs(rDoc, aPos);
6397 if (aAbsRange.aStart == aAbsRange.aEnd)
6398 return; // Nothing to do (trivial case).
6400 ScAddress aAddr;
6402 if (!DoubleRefToPosSingleRefScalarCase(aAbsRange, aAddr, aPos))
6403 return;
6405 ScSingleRefData aSingleRef;
6406 aSingleRef.InitFlags();
6407 aSingleRef.SetColRel(rRange.Ref1.IsColRel());
6408 aSingleRef.SetRowRel(true);
6409 aSingleRef.SetTabRel(rRange.Ref1.IsTabRel());
6410 aSingleRef.SetAddress(rDoc.GetSheetLimits(), aAddr, aPos);
6412 // Replace the original doubleref token with computed singleref token
6413 FormulaToken* pNewSingleRefTok = new ScSingleRefToken(rDoc.GetSheetLimits(), aSingleRef);
6414 (*ppDoubleRefTok)->DecRef();
6415 *ppDoubleRefTok = pNewSingleRefTok;
6416 pNewSingleRefTok->IncRef();
6419 bool ScCompiler::DoubleRefToPosSingleRefScalarCase(const ScRange& rRange, ScAddress& rAdr, const ScAddress& rFormulaPos)
6421 assert(rRange.aStart != rRange.aEnd);
6423 bool bOk = false;
6424 SCCOL nMyCol = rFormulaPos.Col();
6425 SCROW nMyRow = rFormulaPos.Row();
6426 SCTAB nMyTab = rFormulaPos.Tab();
6427 SCCOL nCol = 0;
6428 SCROW nRow = 0;
6429 SCTAB nTab;
6430 nTab = rRange.aStart.Tab();
6431 if ( rRange.aStart.Col() <= nMyCol && nMyCol <= rRange.aEnd.Col() )
6433 nRow = rRange.aStart.Row();
6434 if ( nRow == rRange.aEnd.Row() )
6436 bOk = true;
6437 nCol = nMyCol;
6439 else if ( nTab != nMyTab && nTab == rRange.aEnd.Tab()
6440 && rRange.aStart.Row() <= nMyRow && nMyRow <= rRange.aEnd.Row() )
6442 bOk = true;
6443 nCol = nMyCol;
6444 nRow = nMyRow;
6447 else if ( rRange.aStart.Row() <= nMyRow && nMyRow <= rRange.aEnd.Row() )
6449 nCol = rRange.aStart.Col();
6450 if ( nCol == rRange.aEnd.Col() )
6452 bOk = true;
6453 nRow = nMyRow;
6455 else if ( nTab != nMyTab && nTab == rRange.aEnd.Tab()
6456 && rRange.aStart.Col() <= nMyCol && nMyCol <= rRange.aEnd.Col() )
6458 bOk = true;
6459 nCol = nMyCol;
6460 nRow = nMyRow;
6463 if ( bOk )
6465 if ( nTab == rRange.aEnd.Tab() )
6466 ; // all done
6467 else if ( nTab <= nMyTab && nMyTab <= rRange.aEnd.Tab() )
6468 nTab = nMyTab;
6469 else
6470 bOk = false;
6471 if ( bOk )
6472 rAdr.Set( nCol, nRow, nTab );
6475 return bOk;
6478 static void lcl_GetColRowDeltas(const ScRange& rRange, SCCOL& rXDelta, SCROW& rYDelta)
6480 rXDelta = rRange.aEnd.Col() - rRange.aStart.Col();
6481 rYDelta = rRange.aEnd.Row() - rRange.aStart.Row();
6484 bool ScCompiler::AdjustSumRangeShape(const ScComplexRefData& rBaseRange, ScComplexRefData& rSumRange)
6486 ScRange aAbs = rSumRange.toAbs(rDoc, aPos);
6488 // Current sum-range end col/row
6489 SCCOL nEndCol = aAbs.aEnd.Col();
6490 SCROW nEndRow = aAbs.aEnd.Row();
6492 // Current behaviour is, we will get a #NAME? for the below case, so bail out.
6493 // Note that sum-range's End[Col,Row] are same as Start[Col,Row] if the original formula
6494 // has a single-ref as the sum-range.
6495 if (!rDoc.ValidCol(nEndCol) || !rDoc.ValidRow(nEndRow))
6496 return false;
6498 SCCOL nXDeltaSum = 0;
6499 SCROW nYDeltaSum = 0;
6501 lcl_GetColRowDeltas(aAbs, nXDeltaSum, nYDeltaSum);
6503 aAbs = rBaseRange.toAbs(rDoc, aPos);
6504 SCCOL nXDelta = 0;
6505 SCROW nYDelta = 0;
6507 lcl_GetColRowDeltas(aAbs, nXDelta, nYDelta);
6509 if (nXDelta == nXDeltaSum &&
6510 nYDelta == nYDeltaSum)
6511 return false; // shapes of base-range match current sum-range
6513 // Try to make the sum-range to take the same shape as base-range,
6514 // by adjusting Ref2 member of rSumRange if the resultant sum-range don't
6515 // go out-of-bounds.
6517 SCCOL nXInc = nXDelta - nXDeltaSum;
6518 SCROW nYInc = nYDelta - nYDeltaSum;
6520 // Don't let a valid End[Col,Row] go beyond (rDoc.MaxCol(),rDoc.MaxRow()) to match
6521 // what happens in ScInterpreter::IterateParametersIf(), but there it also shrinks
6522 // the base-range by the (out-of-bound)amount clipped off the sum-range.
6523 // TODO: Probably we can optimize (from threading perspective) rBaseRange
6524 // by shrinking it here correspondingly (?)
6525 if (nEndCol + nXInc > rDoc.MaxCol())
6526 nXInc = rDoc.MaxCol() - nEndCol;
6527 if (nEndRow + nYInc > rDoc.MaxRow())
6528 nYInc = rDoc.MaxRow() - nEndRow;
6530 rSumRange.Ref2.IncCol(nXInc);
6531 rSumRange.Ref2.IncRow(nYInc);
6533 return true;
6536 void ScCompiler::CorrectSumRange(const ScComplexRefData& rBaseRange,
6537 ScComplexRefData& rSumRange,
6538 FormulaToken** ppSumRangeToken)
6540 if (!AdjustSumRangeShape(rBaseRange, rSumRange))
6541 return;
6543 // Replace sum-range token
6544 FormulaToken* pNewSumRangeTok = new ScDoubleRefToken(rDoc.GetSheetLimits(), rSumRange);
6545 (*ppSumRangeToken)->DecRef();
6546 *ppSumRangeToken = pNewSumRangeTok;
6547 pNewSumRangeTok->IncRef();
6550 void ScCompiler::AnnotateTrimOnDoubleRefs()
6552 if (!pCode || !(*(pCode - 1)))
6553 return;
6555 // OpCode of the "root" operator (which is already in RPN array).
6556 OpCode eOpCode = (*(pCode - 1))->GetOpCode();
6557 // Param number of the "root" operator (which is already in RPN array).
6558 sal_uInt8 nRootParam = (*(pCode - 1))->GetByte();
6559 // eOpCode can be some operator which does not change with operands with or contains zero values.
6560 if (eOpCode == ocSum)
6562 FormulaToken** ppTok = pCode - 2; // exclude the root operator.
6563 // The following loop runs till a "pattern" is found or there is a mismatch
6564 // and marks the push DoubleRef arguments as trimmable when there is a match.
6565 // The pattern is
6566 // SUM(IF(<reference|double>=<reference|double>, <then-clause>)<a some operands with operators / or *>)
6567 // such that one of the operands of ocEqual is a double-ref.
6568 // Examples of formula that matches this are:
6569 // SUM(IF(D:D=$A$1,F:F)*$H$1*2.3/$G$2)
6570 // SUM((IF(D:D=$A$1,F:F)*$H$1*2.3/$G$2)*$H$2*5/$G$3)
6571 // SUM(IF(E:E=16,F:F)*$H$1*100)
6572 bool bTillClose = true;
6573 bool bCloseTillIf = false;
6574 sal_Int16 nToksTillIf = 0;
6575 constexpr sal_Int16 MAXDIST_IF = 15;
6576 while (*ppTok)
6578 FormulaToken* pTok = *ppTok;
6579 OpCode eCurrOp = pTok->GetOpCode();
6580 ++nToksTillIf;
6582 // TODO : Is there a better way to handle this ?
6583 // ocIf is too far off from the sum opcode.
6584 if (nToksTillIf > MAXDIST_IF)
6585 return;
6587 switch (eCurrOp)
6589 case ocDiv:
6590 case ocMul:
6591 if (!bTillClose)
6592 return;
6593 break;
6594 case ocPush:
6596 break;
6597 case ocClose:
6598 if (bTillClose)
6600 bTillClose = false;
6601 bCloseTillIf = true;
6603 else
6604 return;
6605 break;
6606 case ocIf:
6608 if (!bCloseTillIf)
6609 return;
6611 if (!pTok->IsInForceArray())
6612 return;
6614 const short nJumpCount = pTok->GetJump()[0];
6615 if (nJumpCount != 2) // Should have THEN but no ELSE.
6616 return;
6618 OpCode eCompOp = (*(ppTok - 1))->GetOpCode();
6619 if (eCompOp != ocEqual)
6620 return;
6622 FormulaToken* pLHS = *(ppTok - 2);
6623 FormulaToken* pRHS = *(ppTok - 3);
6624 if (((pLHS->GetType() == svSingleRef || pLHS->GetType() == svDouble) && pRHS->GetType() == svDoubleRef) ||
6625 ((pRHS->GetType() == svSingleRef || pRHS->GetType() == svDouble) && pLHS->GetType() == svDoubleRef))
6627 if (pLHS->GetType() == svDoubleRef)
6628 pLHS->GetDoubleRef()->SetTrimToData(true);
6629 else
6630 pRHS->GetDoubleRef()->SetTrimToData(true);
6631 return;
6634 break;
6635 default:
6636 return;
6638 --ppTok;
6641 else if (eOpCode == ocSumProduct)
6643 FormulaToken** ppTok = pCode - 2; // exclude the root operator.
6644 // The following loop runs till a "pattern" is found or there is a mismatch
6645 // and marks the push DoubleRef arguments as trimmable when there is a match.
6646 // The pattern is
6647 // SUMPRODUCT(IF(<reference|double>=<reference|double>, <then-clause>)<a some operands with operators / or *>)
6648 // such that one of the operands of ocEqual is a double-ref.
6649 // Examples of formula that matches this are:
6650 // SUMPRODUCT(IF($A:$A=$L12;$D:$D*G:G))
6651 // Also in case of DoubleRef arguments around other Binary operators can be trimmable inside one parameter
6652 // of the root operator:
6653 // SUMPRODUCT(($D:$D>M47:M47)*($D:$D<M48:M48)*($I:$I=N$41))
6654 bool bTillClose = true;
6655 bool bCloseTillIf = false;
6656 sal_Int16 nToksTillIf = 0;
6657 constexpr sal_Int16 MAXDIST_IF = 15;
6658 while (*ppTok)
6660 FormulaToken* pTok = *ppTok;
6661 OpCode eCurrOp = pTok->GetOpCode();
6662 ++nToksTillIf;
6664 // TODO : Is there a better way to handle this ?
6665 // ocIf is too far off from the sum opcode.
6666 if (nToksTillIf > MAXDIST_IF)
6667 return;
6669 switch (eCurrOp)
6671 case ocDiv:
6672 case ocMul:
6674 if (!pTok->IsInForceArray())
6675 break;
6676 FormulaToken* pLHS = *(ppTok - 1);
6677 FormulaToken* pRHS = *(ppTok - 2);
6678 StackVar lhsType = pLHS->GetType();
6679 StackVar rhsType = pRHS->GetType();
6680 if (lhsType == svDoubleRef && rhsType == svDoubleRef)
6682 pLHS->GetDoubleRef()->SetTrimToData(true);
6683 pRHS->GetDoubleRef()->SetTrimToData(true);
6686 break;
6687 case ocEqual:
6688 case ocAdd:
6689 case ocSub:
6690 case ocAmpersand:
6691 case ocPow:
6692 case ocNotEqual:
6693 case ocLess:
6694 case ocGreater:
6695 case ocLessEqual:
6696 case ocGreaterEqual:
6697 case ocAnd:
6698 case ocOr:
6699 case ocXor:
6700 case ocIntersect:
6701 case ocUnion:
6702 case ocRange:
6704 // tdf#160616: Double refs with these operators only
6705 // trimmable in case of one parameter
6706 if (!pTok->IsInForceArray() || nRootParam > 1)
6707 break;
6708 FormulaToken* pLHS = *(ppTok - 1);
6709 FormulaToken* pRHS = *(ppTok - 2);
6710 StackVar lhsType = pLHS->GetType();
6711 StackVar rhsType = pRHS->GetType();
6712 if (lhsType == svDoubleRef && (rhsType == svSingleRef || rhsType == svDoubleRef))
6714 pLHS->GetDoubleRef()->SetTrimToData(true);
6716 if (rhsType == svDoubleRef && (lhsType == svSingleRef || lhsType == svDoubleRef))
6718 pRHS->GetDoubleRef()->SetTrimToData(true);
6721 break;
6722 case ocPush:
6723 break;
6724 case ocClose:
6725 if (bTillClose)
6727 bTillClose = false;
6728 bCloseTillIf = true;
6730 else
6731 return;
6732 break;
6733 case ocIf:
6735 if (!bCloseTillIf)
6736 return;
6738 if (!pTok->IsInForceArray())
6739 return;
6741 const short nJumpCount = pTok->GetJump()[0];
6742 if (nJumpCount != 2) // Should have THEN but no ELSE.
6743 return;
6745 OpCode eCompOp = (*(ppTok - 1))->GetOpCode();
6746 if (eCompOp != ocEqual)
6747 return;
6749 FormulaToken* pLHS = *(ppTok - 2);
6750 FormulaToken* pRHS = *(ppTok - 3);
6751 StackVar lhsType = pLHS->GetType();
6752 StackVar rhsType = pRHS->GetType();
6753 if (lhsType == svDoubleRef && (rhsType == svSingleRef || rhsType == svDouble))
6755 pLHS->GetDoubleRef()->SetTrimToData(true);
6757 if ((lhsType == svSingleRef || lhsType == svDouble) && rhsType == svDoubleRef)
6759 pRHS->GetDoubleRef()->SetTrimToData(true);
6761 return;
6763 break;
6764 default:
6765 return;
6767 --ppTok;
6772 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */