tdf#130857 qt weld: Implement QtInstanceWidget::strip_mnemonic
[LibreOffice.git] / sc / source / core / tool / compiler.cxx
blob8b5c948e97790940f24c390c2c28745fbd33f663
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::fillFromAddInCollectionExcelName( const NonConstOpCodeMapPtr& xMap ) const
192 const LanguageTag aDestLang(LANGUAGE_ENGLISH_US);
193 ScUnoAddInCollection* pColl = ScGlobal::GetAddInCollection();
194 tools::Long nCount = pColl->GetFuncCount();
195 for (tools::Long i=0; i < nCount; ++i)
197 OUString aExcelName;
198 const ScUnoAddInFuncData* pFuncData = pColl->GetFuncData(i);
199 if (pFuncData && pFuncData->GetExcelName( aDestLang, aExcelName, true))
201 // Note that function names not defined in OOXML but implemented by
202 // Excel should have the "_xlfn." prefix. We have no way to check
203 // though what an Add-In actually implements.
204 xMap->putExternalSoftly( GetCharClassEnglish()->uppercase(aExcelName), pFuncData->GetOriginalName());
209 static std::mutex gCharClassMutex;
211 void ScCompiler::DeInit()
213 std::scoped_lock aGuard(gCharClassMutex);
214 if (pCharClassEnglish)
216 delete pCharClassEnglish;
217 pCharClassEnglish = nullptr;
219 if (pCharClassLocalized)
221 delete pCharClassLocalized;
222 pCharClassLocalized = nullptr;
226 bool ScCompiler::IsEnglishSymbol( const OUString& rName )
228 // function names are always case-insensitive
229 OUString aUpper = GetCharClassEnglish()->uppercase(rName);
231 // 1. built-in function name
232 formula::FormulaCompiler aCompiler;
233 OpCode eOp = aCompiler.GetEnglishOpCode( aUpper );
234 if ( eOp != ocNone )
236 return true;
238 // 2. old add in functions
239 if (ScGlobal::GetLegacyFuncCollection()->findByName(aUpper))
241 return true;
244 // 3. new (uno) add in functions
245 OUString aIntName = ScGlobal::GetAddInCollection()->FindFunction(aUpper, false);
246 return !aIntName.isEmpty(); // no valid function name
249 const CharClass* ScCompiler::GetCharClassEnglish()
251 std::scoped_lock aGuard(gCharClassMutex);
252 if (!pCharClassEnglish)
254 pCharClassEnglish = new CharClass( ::comphelper::getProcessComponentContext(),
255 LanguageTag( LANGUAGE_ENGLISH_US));
257 return pCharClassEnglish;
260 const CharClass* ScCompiler::GetCharClassLocalized()
262 // Switching UI language requires restart; if not, we would have to
263 // keep track of that.
264 std::scoped_lock aGuard(gCharClassMutex);
265 if (!pCharClassLocalized)
267 pCharClassLocalized = new CharClass( ::comphelper::getProcessComponentContext(),
268 Application::GetSettings().GetUILanguageTag());
270 return pCharClassLocalized;
273 void ScCompiler::SetGrammar( const FormulaGrammar::Grammar eGrammar )
275 assert( eGrammar != FormulaGrammar::GRAM_UNSPECIFIED && "ScCompiler::SetGrammar: don't pass FormulaGrammar::GRAM_UNSPECIFIED");
276 if (eGrammar == GetGrammar())
277 return; // nothing to be done
279 if( eGrammar == FormulaGrammar::GRAM_EXTERNAL )
281 meGrammar = eGrammar;
282 mxSymbols = GetFinalOpCodeMap( css::sheet::FormulaLanguage::NATIVE);
284 else
286 FormulaGrammar::Grammar eMyGrammar = eGrammar;
287 const sal_Int32 nFormulaLanguage = FormulaGrammar::extractFormulaLanguage( eMyGrammar);
288 OpCodeMapPtr xMap = GetFinalOpCodeMap( nFormulaLanguage);
289 OSL_ENSURE( xMap, "ScCompiler::SetGrammar: unknown formula language");
290 if (!xMap)
292 xMap = GetFinalOpCodeMap( css::sheet::FormulaLanguage::NATIVE);
293 eMyGrammar = xMap->getGrammar();
296 // Save old grammar for call to SetGrammarAndRefConvention().
297 FormulaGrammar::Grammar eOldGrammar = GetGrammar();
298 // This also sets the grammar associated with the map!
299 SetFormulaLanguage( xMap);
301 // Override if necessary.
302 if (eMyGrammar != GetGrammar())
303 SetGrammarAndRefConvention( eMyGrammar, eOldGrammar);
307 // Unclear how this was intended to be refreshed when the
308 // grammar or sheet count is changed ? Ideally this would be
309 // a method on Document that would globally cache these.
310 std::vector<OUString> &ScCompiler::GetSetupTabNames() const
312 std::vector<OUString> &rTabNames = const_cast<ScCompiler *>(this)->maTabNames;
314 if (rTabNames.empty())
316 rTabNames = rDoc.GetAllTableNames();
317 for (auto& rTabName : rTabNames)
318 ScCompiler::CheckTabQuotes(rTabName, formula::FormulaGrammar::extractRefConvention(meGrammar));
321 return rTabNames;
324 void ScCompiler::SetFormulaLanguage( const ScCompiler::OpCodeMapPtr & xMap )
326 if (!xMap)
327 return;
329 mxSymbols = xMap;
330 if (mxSymbols->isEnglish())
331 pCharClass = GetCharClassEnglish();
332 else
333 pCharClass = GetCharClassLocalized();
335 // The difference is needed for an uppercase() call that usually does not
336 // result in different strings but for a few languages like Turkish;
337 // though even de-DE and de-CH may differ in ß/SS handling..
338 // At least don't care if both are English.
339 // The current locale is more likely to not be "en" so check first.
340 const LanguageTag& rLT1 = ScGlobal::getCharClass().getLanguageTag();
341 const LanguageTag& rLT2 = pCharClass->getLanguageTag();
342 mbCharClassesDiffer = (rLT1 != rLT2 && (rLT1.getLanguage() != "en" || rLT2.getLanguage() != "en"));
344 SetGrammarAndRefConvention( mxSymbols->getGrammar(), GetGrammar());
347 void ScCompiler::SetGrammarAndRefConvention(
348 const FormulaGrammar::Grammar eNewGrammar, const FormulaGrammar::Grammar eOldGrammar )
350 meGrammar = eNewGrammar; // SetRefConvention needs the new grammar set!
351 FormulaGrammar::AddressConvention eConv = FormulaGrammar::extractRefConvention( meGrammar);
352 if (eConv == FormulaGrammar::CONV_UNSPECIFIED && eOldGrammar == FormulaGrammar::GRAM_UNSPECIFIED)
353 SetRefConvention( rDoc.GetAddressConvention());
354 else
355 SetRefConvention( eConv );
358 OUString ScCompiler::FindAddInFunction( const OUString& rUpperName, bool bLocalFirst ) const
360 return ScGlobal::GetAddInCollection()->FindFunction(rUpperName, bLocalFirst); // bLocalFirst=false for english
363 ScCompiler::Convention::~Convention()
367 ScCompiler::Convention::Convention( FormulaGrammar::AddressConvention eConv )
369 meConv( eConv )
371 int i;
372 ScCharFlags *t= new ScCharFlags [128];
374 ScCompiler::pConventions[ meConv ] = this;
375 mpCharTable.reset( t );
377 for (i = 0; i < 128; i++)
378 t[i] = ScCharFlags::Illegal;
380 // Allow tabs/newlines.
381 // Allow saving whitespace as is (as per OpenFormula specification v.1.2, clause 5.14 "Whitespace").
382 /* tab */ t[ 9] = ScCharFlags::CharDontCare | ScCharFlags::WordSep | ScCharFlags::ValueSep;
383 /* lf */ t[10] = ScCharFlags::CharDontCare | ScCharFlags::WordSep | ScCharFlags::ValueSep;
384 /* cr */ t[13] = ScCharFlags::CharDontCare | ScCharFlags::WordSep | ScCharFlags::ValueSep;
386 /* */ t[32] = ScCharFlags::CharDontCare | ScCharFlags::WordSep | ScCharFlags::ValueSep;
387 /* ! */ t[33] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep;
388 if (FormulaGrammar::CONV_ODF == meConv)
389 /* ! */ t[33] |= ScCharFlags::OdfLabelOp;
390 /* " */ t[34] = ScCharFlags::CharString | ScCharFlags::StringSep;
391 /* # */ t[35] = ScCharFlags::WordSep | ScCharFlags::CharErrConst;
392 /* $ */ t[36] = ScCharFlags::CharWord | ScCharFlags::Word | ScCharFlags::CharIdent | ScCharFlags::Ident;
393 if (FormulaGrammar::CONV_ODF == meConv)
394 /* $ */ t[36] |= ScCharFlags::OdfNameMarker;
395 /* % */ t[37] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep;
396 /* & */ t[38] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep;
397 /* ' */ t[39] = ScCharFlags::NameSep;
398 /* ( */ t[40] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep;
399 /* ) */ t[41] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep;
400 /* * */ t[42] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep;
401 /* + */ t[43] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueExp | ScCharFlags::ValueSign;
402 /* , */ t[44] = ScCharFlags::CharValue | ScCharFlags::Value;
403 /* - */ t[45] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueExp | ScCharFlags::ValueSign;
404 /* . */ t[46] = ScCharFlags::Word | ScCharFlags::CharValue | ScCharFlags::Value | ScCharFlags::Ident | ScCharFlags::Name;
405 /* / */ t[47] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep;
407 for (i = 48; i < 58; i++)
408 /* 0-9 */ t[i] = ScCharFlags::CharValue | ScCharFlags::Word | ScCharFlags::Value | ScCharFlags::ValueExp | ScCharFlags::ValueValue | ScCharFlags::Ident | ScCharFlags::Name;
410 /* : */ t[58] = ScCharFlags::Char | ScCharFlags::Word;
411 /* ; */ t[59] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep;
412 /* < */ t[60] = ScCharFlags::CharBool | ScCharFlags::WordSep | ScCharFlags::ValueSep;
413 /* = */ t[61] = ScCharFlags::Char | ScCharFlags::Bool | ScCharFlags::WordSep | ScCharFlags::ValueSep;
414 /* > */ t[62] = ScCharFlags::CharBool | ScCharFlags::Bool | ScCharFlags::WordSep | ScCharFlags::ValueSep;
415 /* ? */ t[63] = ScCharFlags::CharWord | ScCharFlags::Word | ScCharFlags::Name;
416 /* @ */ // FREE
418 for (i = 65; i < 91; i++)
419 /* A-Z */ t[i] = ScCharFlags::CharWord | ScCharFlags::Word | ScCharFlags::CharIdent | ScCharFlags::Ident | ScCharFlags::CharName | ScCharFlags::Name;
421 if (FormulaGrammar::CONV_ODF == meConv)
423 /* [ */ t[91] = ScCharFlags::OdfLBracket;
424 /* \ */ // FREE
425 /* ] */ t[93] = ScCharFlags::OdfRBracket;
427 else if (FormulaGrammar::CONV_OOO == meConv)
429 /* [ */ t[91] = ScCharFlags::Char;
430 /* \ */ // FREE
431 /* ] */ t[93] = ScCharFlags::Char;
433 else if (FormulaGrammar::CONV_XL_OOX == meConv)
435 /* [ */ t[91] = ScCharFlags::Char | ScCharFlags::CharIdent;
436 /* \ */ // FREE
437 /* ] */ t[93] = ScCharFlags::Char | ScCharFlags::Ident;
439 else if (FormulaGrammar::CONV_XL_A1 == meConv)
441 /* [ */ t[91] = ScCharFlags::Char;
442 /* \ */ // FREE
443 /* ] */ t[93] = ScCharFlags::Char;
445 else if( FormulaGrammar::CONV_XL_R1C1 == meConv )
447 /* [ */ t[91] = ScCharFlags::Ident;
448 /* \ */ // FREE
449 /* ] */ t[93] = ScCharFlags::Ident;
451 else
453 /* [ */ // FREE
454 /* \ */ // FREE
455 /* ] */ // FREE
458 /* ^ */ t[94] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep;
459 /* _ */ t[95] = ScCharFlags::CharWord | ScCharFlags::Word | ScCharFlags::CharIdent | ScCharFlags::Ident | ScCharFlags::CharName | ScCharFlags::Name;
460 /* ` */ // FREE
462 for (i = 97; i < 123; i++)
463 /* a-z */ t[i] = ScCharFlags::CharWord | ScCharFlags::Word | ScCharFlags::CharIdent | ScCharFlags::Ident | ScCharFlags::CharName | ScCharFlags::Name;
465 /* { */ t[123] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep; // array open
466 /* | */ t[124] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep; // array row sep (Should be OOo specific)
467 /* } */ t[125] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep; // array close
468 /* ~ */ t[126] = ScCharFlags::Char; // OOo specific
469 /* 127 */ // FREE
471 if( !(FormulaGrammar::CONV_XL_A1 == meConv || FormulaGrammar::CONV_XL_R1C1 == meConv || FormulaGrammar::CONV_XL_OOX == meConv) )
472 return;
474 /* */ t[32] |= ScCharFlags::Word;
475 /* ! */ t[33] |= ScCharFlags::Ident | ScCharFlags::Word;
476 /* " */ t[34] |= ScCharFlags::Word;
477 /* # */ t[35] &= ~ScCharFlags::WordSep;
478 /* # */ t[35] |= ScCharFlags::Word;
479 /* % */ t[37] |= ScCharFlags::Word;
480 /* & */ t[38] |= ScCharFlags::Word;
481 /* ' */ t[39] |= ScCharFlags::Word;
482 /* ( */ t[40] |= ScCharFlags::Word;
483 /* ) */ t[41] |= ScCharFlags::Word;
484 /* * */ t[42] |= ScCharFlags::Word;
485 /* + */ t[43] |= ScCharFlags::Word;
486 #if 0 /* this really needs to be locale specific. */
487 /* , */ t[44] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep;
488 #else
489 /* , */ t[44] |= ScCharFlags::Word;
490 #endif
491 /* - */ t[45] |= ScCharFlags::Word;
493 /* ; */ t[59] |= ScCharFlags::Word;
494 /* < */ t[60] |= ScCharFlags::Word;
495 /* = */ t[61] |= ScCharFlags::Word;
496 /* > */ t[62] |= ScCharFlags::Word;
497 /* ? */ // question really is not permitted in sheet name
498 /* @ */ t[64] |= ScCharFlags::Word;
499 /* [ */ t[91] |= ScCharFlags::Word;
500 /* ] */ t[93] |= ScCharFlags::Word;
501 /* { */ t[123]|= ScCharFlags::Word;
502 /* | */ t[124]|= ScCharFlags::Word;
503 /* } */ t[125]|= ScCharFlags::Word;
504 /* ~ */ t[126]|= ScCharFlags::Word;
507 static bool lcl_isValidQuotedText( std::u16string_view rFormula, size_t nSrcPos, ParseResult& rRes )
509 // Tokens that start at ' can have anything in them until a final '
510 // but '' marks an escaped '
511 // We've earlier guaranteed that a string containing '' will be
512 // surrounded by '
513 if (nSrcPos < rFormula.size() && rFormula[nSrcPos] == '\'')
515 size_t nPos = nSrcPos+1;
516 while (nPos < rFormula.size())
518 if (rFormula[nPos] == '\'')
520 if ( (nPos+1 == rFormula.size()) || (rFormula[nPos+1] != '\'') )
522 rRes.TokenType = KParseType::SINGLE_QUOTE_NAME;
523 rRes.EndPos = nPos+1;
524 return true;
526 ++nPos;
528 ++nPos;
532 return false;
535 static bool lcl_parseExternalName(
536 const OUString& rSymbol,
537 OUString& rFile,
538 OUString& rName,
539 const sal_Unicode cSep,
540 const ScDocument& rDoc,
541 const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks )
543 /* TODO: future versions will have to support sheet-local names too, thus
544 * return a possible sheet name as well. */
545 const sal_Unicode* const pStart = rSymbol.getStr();
546 const sal_Unicode* p = pStart;
547 sal_Int32 nLen = rSymbol.getLength();
548 OUString aTmpFile;
549 OUStringBuffer aTmpName;
550 sal_Int32 i = 0;
551 bool bInName = false;
552 if (cSep == '!')
554 // For XL use existing parser that resolves bracketed and quoted and
555 // indexed external document names.
556 ScRange aRange;
557 OUString aStartTabName, aEndTabName;
558 ScRefFlags nFlags = ScRefFlags::ZERO;
559 p = aRange.Parse_XL_Header( p, rDoc, aTmpFile, aStartTabName,
560 aEndTabName, nFlags, true, pExternalLinks );
561 if (!p || p == pStart)
562 return false;
563 i = sal_Int32(p - pStart);
565 for ( ; i < nLen; ++i, ++p)
567 sal_Unicode c = *p;
568 if (i == 0)
570 if (c == '.' || c == cSep)
571 return false;
573 if (c == '\'')
575 // Move to the next char and loop until the second single
576 // quote.
577 sal_Unicode cPrev = c;
578 ++i; ++p;
579 for (sal_Int32 j = i; j < nLen; ++j, ++p)
581 c = *p;
582 if (c == '\'')
584 if (j == i)
586 // empty quote e.g. (=''!Name)
587 return false;
590 if (cPrev == '\'')
592 // two consecutive quotes equal a single quote in
593 // the file name.
594 aTmpFile += OUStringChar(c);
595 cPrev = 'a';
597 else
598 cPrev = c;
600 continue;
603 if (cPrev == '\'' && j != i)
605 // this is not a quote but the previous one is. This
606 // ends the parsing of the quoted segment. At this
607 // point, the current char must equal the separator
608 // char.
610 i = j;
611 bInName = true;
612 aTmpName.append(c); // Keep the separator as part of the name.
613 break;
615 aTmpFile += OUStringChar(c);
616 cPrev = c;
619 if (!bInName)
621 // premature ending of the quoted segment.
622 return false;
625 if (c != cSep)
627 // only the separator is allowed after the closing quote.
628 return false;
631 continue;
635 if (bInName)
637 if (c == cSep)
639 // A second separator ? Not a valid external name.
640 return false;
642 aTmpName.append(c);
644 else
646 if (c == cSep)
648 bInName = true;
649 aTmpName.append(c); // Keep the separator as part of the name.
651 else
655 if (rtl::isAsciiAlphanumeric(c))
656 // allowed.
657 break;
659 if (c > 128)
660 // non-ASCII character is allowed.
661 break;
663 bool bValid = false;
664 switch (c)
666 case '_':
667 case '-':
668 case '.':
669 // these special characters are allowed.
670 bValid = true;
671 break;
673 if (bValid)
674 break;
676 return false;
678 while (false);
679 aTmpFile += OUStringChar(c);
684 if (!bInName)
686 // No name found - most likely the symbol has no '!'s.
687 return false;
690 sal_Int32 nNameLen = aTmpName.getLength();
691 if (nNameLen < 2)
693 // Name must be at least 2-char long (separator plus name).
694 return false;
697 if (aTmpName[0] != cSep)
699 // 1st char of the name must equal the separator.
700 return false;
703 if (aTmpName[nNameLen-1] == '!')
705 // Check against #REF!.
706 if (OUString::unacquired(aTmpName).equalsIgnoreAsciiCase("#REF!"))
707 return false;
710 rFile = aTmpFile;
711 rName = aTmpName.makeStringAndClear().copy(1); // Skip the first char as it is always the separator.
712 return true;
715 static OUString lcl_makeExternalNameStr(const OUString& rFile, const OUString& rName,
716 const sal_Unicode cSep, bool bODF )
718 OUString aEscQuote(u"''"_ustr);
719 OUString aFile(rFile.replaceAll("'", aEscQuote));
720 OUString aName(rName);
721 if (bODF)
722 aName = aName.replaceAll("'", aEscQuote);
723 OUStringBuffer aBuf(aFile.getLength() + aName.getLength() + 9);
724 if (bODF)
725 aBuf.append( '[');
726 aBuf.append( "'" + aFile + "'" + OUStringChar(cSep) );
727 if (bODF)
728 aBuf.append( "$$'" );
729 aBuf.append( aName);
730 if (bODF)
731 aBuf.append( "']" );
732 return aBuf.makeStringAndClear();
735 static bool lcl_getLastTabName( OUString& rTabName2, const OUString& rTabName1,
736 const vector<OUString>& rTabNames, const ScRange& rRef )
738 SCTAB nTabSpan = rRef.aEnd.Tab() - rRef.aStart.Tab();
739 if (nTabSpan > 0)
741 size_t nCount = rTabNames.size();
742 vector<OUString>::const_iterator itrBeg = rTabNames.begin(), itrEnd = rTabNames.end();
743 vector<OUString>::const_iterator itr = ::std::find(itrBeg, itrEnd, rTabName1);
744 if (itr == rTabNames.end())
746 rTabName2 = ScResId(STR_NO_REF_TABLE);
747 return false;
750 size_t nDist = ::std::distance(itrBeg, itr);
751 if (nDist + static_cast<size_t>(nTabSpan) >= nCount)
753 rTabName2 = ScResId(STR_NO_REF_TABLE);
754 return false;
757 rTabName2 = rTabNames[nDist+nTabSpan];
759 else
760 rTabName2 = rTabName1;
762 return true;
765 namespace {
767 struct Convention_A1 : public ScCompiler::Convention
769 explicit Convention_A1( FormulaGrammar::AddressConvention eConv ) : ScCompiler::Convention( eConv ) { }
770 static void MakeColStr( const ScSheetLimits& rLimits, OUStringBuffer& rBuffer, SCCOL nCol );
771 static void MakeRowStr( const ScSheetLimits& rLimits, OUStringBuffer& rBuffer, SCROW nRow );
773 ParseResult parseAnyToken( const OUString& rFormula,
774 sal_Int32 nSrcPos,
775 const CharClass* pCharClass,
776 bool bGroupSeparator) const override
778 ParseResult aRet;
779 if ( lcl_isValidQuotedText(rFormula, nSrcPos, aRet) )
780 return aRet;
782 constexpr sal_Int32 nStartFlags = KParseTokens::ANY_LETTER_OR_NUMBER |
783 KParseTokens::ASC_UNDERSCORE | KParseTokens::ASC_DOLLAR;
784 constexpr sal_Int32 nContFlags = nStartFlags | KParseTokens::ASC_DOT;
785 // '?' allowed in range names because of Xcl :-/
786 static constexpr OUString aAddAllowed(u"?#"_ustr);
787 return pCharClass->parseAnyToken( rFormula,
788 nSrcPos, nStartFlags, aAddAllowed,
789 (bGroupSeparator ? nContFlags | KParseTokens::GROUP_SEPARATOR_IN_NUMBER : nContFlags),
790 aAddAllowed );
793 virtual ScCharFlags getCharTableFlags( sal_Unicode c, sal_Unicode /*cLast*/ ) const override
795 return mpCharTable[static_cast<sal_uInt8>(c)];
801 void Convention_A1::MakeColStr( const ScSheetLimits& rLimits, OUStringBuffer& rBuffer, SCCOL nCol )
803 if ( !rLimits.ValidCol(nCol) )
804 rBuffer.append(ScResId(STR_NO_REF_TABLE));
805 else
806 ::ScColToAlpha( rBuffer, nCol);
809 void Convention_A1::MakeRowStr( const ScSheetLimits& rLimits, OUStringBuffer& rBuffer, SCROW nRow )
811 if ( !rLimits.ValidRow(nRow) )
812 rBuffer.append(ScResId(STR_NO_REF_TABLE));
813 else
814 rBuffer.append(sal_Int32(nRow + 1));
817 namespace {
819 struct ConventionOOO_A1 : public Convention_A1
821 ConventionOOO_A1() : Convention_A1 (FormulaGrammar::CONV_OOO) { }
822 explicit ConventionOOO_A1( FormulaGrammar::AddressConvention eConv ) : Convention_A1 (eConv) { }
824 static void MakeTabStr( OUStringBuffer &rBuf, const std::vector<OUString>& rTabNames, SCTAB nTab )
826 if (o3tl::make_unsigned(nTab) >= rTabNames.size())
827 rBuf.append(ScResId(STR_NO_REF_TABLE));
828 else
829 rBuf.append(rTabNames[nTab]);
830 rBuf.append('.');
833 enum SingletonDisplay
835 SINGLETON_NONE,
836 SINGLETON_COL,
837 SINGLETON_ROW
840 static void MakeOneRefStrImpl(
841 const ScSheetLimits& rLimits, OUStringBuffer& rBuffer,
842 std::u16string_view rErrRef, const std::vector<OUString>& rTabNames,
843 const ScSingleRefData& rRef, const ScAddress& rAbsRef,
844 bool bForceTab, bool bODF, SingletonDisplay eSingletonDisplay )
846 if( rRef.IsFlag3D() || bForceTab )
848 if (!ValidTab(rAbsRef.Tab()) || rRef.IsTabDeleted())
850 if (!rRef.IsTabRel())
851 rBuffer.append('$');
852 rBuffer.append(rErrRef);
853 rBuffer.append('.');
855 else
857 if (!rRef.IsTabRel())
858 rBuffer.append('$');
859 MakeTabStr(rBuffer, rTabNames, rAbsRef.Tab());
862 else if (bODF)
863 rBuffer.append('.');
865 if (eSingletonDisplay != SINGLETON_ROW)
867 if (!rRef.IsColRel())
868 rBuffer.append('$');
869 if (!rLimits.ValidCol(rAbsRef.Col()) || rRef.IsColDeleted())
870 rBuffer.append(rErrRef);
871 else
872 MakeColStr(rLimits, rBuffer, rAbsRef.Col());
875 if (eSingletonDisplay != SINGLETON_COL)
877 if (!rRef.IsRowRel())
878 rBuffer.append('$');
879 if (!rLimits.ValidRow(rAbsRef.Row()) || rRef.IsRowDeleted())
880 rBuffer.append(rErrRef);
881 else
882 MakeRowStr(rLimits, rBuffer, rAbsRef.Row());
886 static SingletonDisplay getSingletonDisplay( const ScSheetLimits& rLimits, const ScAddress& rAbs1, const ScAddress& rAbs2,
887 const ScComplexRefData& rRef, bool bFromRangeName )
889 // If any part is error, display as such.
890 if (!rLimits.ValidCol(rAbs1.Col()) || rRef.Ref1.IsColDeleted() || !rLimits.ValidRow(rAbs1.Row()) || rRef.Ref1.IsRowDeleted() ||
891 !rLimits.ValidCol(rAbs2.Col()) || rRef.Ref2.IsColDeleted() || !rLimits.ValidRow(rAbs2.Row()) || rRef.Ref2.IsRowDeleted())
892 return SINGLETON_NONE;
894 // A:A or $A:$A or A:$A or $A:A
895 if (rRef.IsEntireCol(rLimits))
896 return SINGLETON_COL;
898 // Same if not in named expression and both rows of entire columns are
899 // relative references.
900 if (!bFromRangeName && rAbs1.Row() == 0 && rAbs2.Row() == rLimits.mnMaxRow &&
901 rRef.Ref1.IsRowRel() && rRef.Ref2.IsRowRel())
902 return SINGLETON_COL;
904 // 1:1 or $1:$1 or 1:$1 or $1:1
905 if (rRef.IsEntireRow(rLimits))
906 return SINGLETON_ROW;
908 // Same if not in named expression and both columns of entire rows are
909 // relative references.
910 if (!bFromRangeName && rAbs1.Col() == 0 && rAbs2.Col() == rLimits.mnMaxCol &&
911 rRef.Ref1.IsColRel() && rRef.Ref2.IsColRel())
912 return SINGLETON_ROW;
914 return SINGLETON_NONE;
917 virtual void makeRefStr(
918 ScSheetLimits& rLimits,
919 OUStringBuffer& rBuffer,
920 formula::FormulaGrammar::Grammar /*eGram*/,
921 const ScAddress& rPos,
922 const OUString& rErrRef, const std::vector<OUString>& rTabNames,
923 const ScComplexRefData& rRef,
924 bool bSingleRef,
925 bool bFromRangeName ) const override
927 // In case absolute/relative positions weren't separately available:
928 // transform relative to absolute!
929 ScAddress aAbs1 = rRef.Ref1.toAbs(rLimits, rPos), aAbs2;
930 if( !bSingleRef )
931 aAbs2 = rRef.Ref2.toAbs(rLimits, rPos);
933 SingletonDisplay eSingleton = bSingleRef ? SINGLETON_NONE :
934 getSingletonDisplay( rLimits, aAbs1, aAbs2, rRef, bFromRangeName);
935 MakeOneRefStrImpl(rLimits, rBuffer, rErrRef, rTabNames, rRef.Ref1, aAbs1, false, false, eSingleton);
936 if (!bSingleRef)
938 rBuffer.append(':');
939 MakeOneRefStrImpl(rLimits, rBuffer, rErrRef, rTabNames, rRef.Ref2, aAbs2, aAbs1.Tab() != aAbs2.Tab(), false,
940 eSingleton);
944 virtual sal_Unicode getSpecialSymbol( SpecialSymbolType eSymType ) const override
946 switch (eSymType)
948 case ScCompiler::Convention::ABS_SHEET_PREFIX:
949 return '$';
950 case ScCompiler::Convention::SHEET_SEPARATOR:
951 return '.';
954 return u'\0';
957 virtual bool parseExternalName( const OUString& rSymbol, OUString& rFile, OUString& rName,
958 const ScDocument& rDoc,
959 const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks ) const override
961 return lcl_parseExternalName(rSymbol, rFile, rName, '#', rDoc, pExternalLinks);
964 virtual OUString makeExternalNameStr( sal_uInt16 /*nFileId*/, const OUString& rFile,
965 const OUString& rName ) const override
967 return lcl_makeExternalNameStr( rFile, rName, '#', false);
970 static bool makeExternalSingleRefStr(
971 const ScSheetLimits& rLimits,
972 OUStringBuffer& rBuffer, const OUString& rFileName, const OUString& rTabName,
973 const ScSingleRefData& rRef, const ScAddress& rPos, bool bDisplayTabName, bool bEncodeUrl )
975 ScAddress aAbsRef = rRef.toAbs(rLimits, rPos);
976 if (bDisplayTabName)
978 OUString aFile;
979 if (bEncodeUrl)
980 aFile = rFileName;
981 else
982 aFile = INetURLObject::decode(rFileName, INetURLObject::DecodeMechanism::Unambiguous);
984 rBuffer.append("'" + aFile.replaceAll("'", "''") + "'#");
986 if (!rRef.IsTabRel())
987 rBuffer.append('$');
988 ScRangeStringConverter::AppendTableName(rBuffer, rTabName);
990 rBuffer.append('.');
993 if (!rRef.IsColRel())
994 rBuffer.append('$');
995 MakeColStr( rLimits, rBuffer, aAbsRef.Col());
996 if (!rRef.IsRowRel())
997 rBuffer.append('$');
998 MakeRowStr( rLimits, rBuffer, aAbsRef.Row());
1000 return true;
1003 static void makeExternalRefStrImpl(
1004 const ScSheetLimits& rLimits,
1005 OUStringBuffer& rBuffer, const ScAddress& rPos, const OUString& rFileName,
1006 const OUString& rTabName, const ScSingleRefData& rRef, bool bODF )
1008 if (bODF)
1009 rBuffer.append( '[');
1011 bool bEncodeUrl = bODF;
1012 makeExternalSingleRefStr(rLimits, rBuffer, rFileName, rTabName, rRef, rPos, true, bEncodeUrl);
1013 if (bODF)
1014 rBuffer.append( ']');
1017 virtual void makeExternalRefStr(
1018 ScSheetLimits& rLimits,
1019 OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName,
1020 const OUString& rTabName, const ScSingleRefData& rRef ) const override
1022 makeExternalRefStrImpl(rLimits, rBuffer, rPos, rFileName, rTabName, rRef, false);
1025 static void makeExternalRefStrImpl(
1026 const ScSheetLimits& rLimits,
1027 OUStringBuffer& rBuffer, const ScAddress& rPos, const OUString& rFileName,
1028 const std::vector<OUString>& rTabNames, const OUString& rTabName,
1029 const ScComplexRefData& rRef, bool bODF )
1031 ScRange aAbsRange = rRef.toAbs(rLimits, rPos);
1033 if (bODF)
1034 rBuffer.append( '[');
1035 // Ensure that there's always a closing bracket, no premature returns.
1036 bool bEncodeUrl = bODF;
1040 if (!makeExternalSingleRefStr(rLimits, rBuffer, rFileName, rTabName, rRef.Ref1, rPos, true, bEncodeUrl))
1041 break;
1043 rBuffer.append(':');
1045 OUString aLastTabName;
1046 bool bDisplayTabName = (aAbsRange.aStart.Tab() != aAbsRange.aEnd.Tab());
1047 if (bDisplayTabName)
1049 // Get the name of the last table.
1050 if (!lcl_getLastTabName(aLastTabName, rTabName, rTabNames, aAbsRange))
1052 OSL_FAIL( "ConventionOOO_A1::makeExternalRefStrImpl: sheet name not found");
1053 // aLastTabName contains #REF!, proceed.
1056 else if (bODF)
1057 rBuffer.append( '.'); // need at least the sheet separator in ODF
1058 makeExternalSingleRefStr(rLimits,
1059 rBuffer, rFileName, aLastTabName, rRef.Ref2, rPos, bDisplayTabName, bEncodeUrl);
1060 } while (false);
1062 if (bODF)
1063 rBuffer.append( ']');
1066 virtual void makeExternalRefStr(
1067 ScSheetLimits& rLimits,
1068 OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName,
1069 const std::vector<OUString>& rTabNames, const OUString& rTabName,
1070 const ScComplexRefData& rRef ) const override
1072 makeExternalRefStrImpl(rLimits, rBuffer, rPos, rFileName, rTabNames, rTabName, rRef, false);
1076 struct ConventionOOO_A1_ODF : public ConventionOOO_A1
1078 ConventionOOO_A1_ODF() : ConventionOOO_A1 (FormulaGrammar::CONV_ODF) { }
1080 virtual void makeRefStr(
1081 ScSheetLimits& rLimits,
1082 OUStringBuffer& rBuffer,
1083 formula::FormulaGrammar::Grammar eGram,
1084 const ScAddress& rPos,
1085 const OUString& rErrRef, const std::vector<OUString>& rTabNames,
1086 const ScComplexRefData& rRef,
1087 bool bSingleRef,
1088 bool bFromRangeName ) const override
1090 rBuffer.append('[');
1091 // In case absolute/relative positions weren't separately available:
1092 // transform relative to absolute!
1093 ScAddress aAbs1 = rRef.Ref1.toAbs(rLimits, rPos), aAbs2;
1094 if( !bSingleRef )
1095 aAbs2 = rRef.Ref2.toAbs(rLimits, rPos);
1097 if (FormulaGrammar::isODFF(eGram) && (rRef.Ref1.IsDeleted() || !rLimits.ValidAddress(aAbs1) ||
1098 (!bSingleRef && (rRef.Ref2.IsDeleted() || !rLimits.ValidAddress(aAbs2)))))
1100 rBuffer.append(rErrRef);
1101 // For ODFF write [#REF!], but not for PODF so apps reading ODF
1102 // 1.0/1.1 may have a better chance if they implemented the old
1103 // form.
1105 else
1107 SingletonDisplay eSingleton = bSingleRef ? SINGLETON_NONE :
1108 getSingletonDisplay( rLimits, aAbs1, aAbs2, rRef, bFromRangeName);
1109 MakeOneRefStrImpl(rLimits, rBuffer, rErrRef, rTabNames, rRef.Ref1, aAbs1, false, true, eSingleton);
1110 if (!bSingleRef)
1112 rBuffer.append(':');
1113 MakeOneRefStrImpl(rLimits, rBuffer, rErrRef, rTabNames, rRef.Ref2, aAbs2, aAbs1.Tab() != aAbs2.Tab(), true,
1114 eSingleton);
1117 rBuffer.append(']');
1120 virtual OUString makeExternalNameStr( sal_uInt16 /*nFileId*/, const OUString& rFile,
1121 const OUString& rName ) const override
1123 return lcl_makeExternalNameStr( rFile, rName, '#', true);
1126 virtual void makeExternalRefStr(
1127 ScSheetLimits& rLimits,
1128 OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName,
1129 const OUString& rTabName, const ScSingleRefData& rRef ) const override
1131 makeExternalRefStrImpl(rLimits, rBuffer, rPos, rFileName, rTabName, rRef, true);
1134 virtual void makeExternalRefStr(
1135 ScSheetLimits& rLimits,
1136 OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName,
1137 const std::vector<OUString>& rTabNames,
1138 const OUString& rTabName, const ScComplexRefData& rRef ) const override
1140 makeExternalRefStrImpl(rLimits, rBuffer, rPos, rFileName, rTabNames, rTabName, rRef, true);
1144 struct ConventionXL
1146 virtual ~ConventionXL()
1150 static void GetTab(
1151 const ScSheetLimits& rLimits,
1152 const ScAddress& rPos, const std::vector<OUString>& rTabNames,
1153 const ScSingleRefData& rRef, OUString& rTabName )
1155 ScAddress aAbs = rRef.toAbs(rLimits, rPos);
1156 if (rRef.IsTabDeleted() || o3tl::make_unsigned(aAbs.Tab()) >= rTabNames.size())
1158 rTabName = ScResId( STR_NO_REF_TABLE );
1159 return;
1161 rTabName = rTabNames[aAbs.Tab()];
1164 static void MakeTabStr( const ScSheetLimits& rLimits, OUStringBuffer& rBuf,
1165 const ScAddress& rPos,
1166 const std::vector<OUString>& rTabNames,
1167 const ScComplexRefData& rRef,
1168 bool bSingleRef )
1170 if( !rRef.Ref1.IsFlag3D() )
1171 return;
1173 OUString aStartTabName, aEndTabName;
1175 GetTab(rLimits, rPos, rTabNames, rRef.Ref1, aStartTabName);
1177 if( !bSingleRef && rRef.Ref2.IsFlag3D() )
1179 GetTab(rLimits, rPos, rTabNames, rRef.Ref2, aEndTabName);
1182 const sal_Int32 nQuotePos = rBuf.getLength();
1183 rBuf.append( aStartTabName );
1184 if( !bSingleRef && rRef.Ref2.IsFlag3D() && aStartTabName != aEndTabName )
1186 ScCompiler::FormExcelSheetRange( rBuf, nQuotePos, aEndTabName);
1189 rBuf.append( '!' );
1192 static sal_Unicode getSpecialSymbol( ScCompiler::Convention::SpecialSymbolType eSymType )
1194 switch (eSymType)
1196 case ScCompiler::Convention::ABS_SHEET_PREFIX:
1197 return u'\0';
1198 case ScCompiler::Convention::SHEET_SEPARATOR:
1199 return '!';
1201 return u'\0';
1204 static bool parseExternalName( const OUString& rSymbol, OUString& rFile, OUString& rName,
1205 const ScDocument& rDoc,
1206 const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks )
1208 return lcl_parseExternalName( rSymbol, rFile, rName, '!', rDoc, pExternalLinks);
1211 static OUString makeExternalNameStr( const OUString& rFile, const OUString& rName )
1213 return lcl_makeExternalNameStr( rFile, rName, '!', false);
1216 static void makeExternalDocStr( OUStringBuffer& rBuffer, std::u16string_view rFullName )
1218 // Format that is easier to deal with inside OOo, because we use file
1219 // URL, and all characters are allowed. Check if it makes sense to do
1220 // it the way Gnumeric does it. Gnumeric doesn't use the URL form
1221 // and allows relative file path.
1223 // ['file:///path/to/source/filename.xls']
1225 rBuffer.append('[');
1226 rBuffer.append('\'');
1227 OUString aFullName = INetURLObject::decode(rFullName, INetURLObject::DecodeMechanism::Unambiguous);
1229 const sal_Unicode* pBuf = aFullName.getStr();
1230 sal_Int32 nLen = aFullName.getLength();
1231 for (sal_Int32 i = 0; i < nLen; ++i)
1233 const sal_Unicode c = pBuf[i];
1234 if (c == '\'')
1235 rBuffer.append(c);
1236 rBuffer.append(c);
1238 rBuffer.append('\'');
1239 rBuffer.append(']');
1242 static void makeExternalTabNameRange( OUStringBuffer& rBuf, const OUString& rTabName,
1243 const vector<OUString>& rTabNames,
1244 const ScRange& rRef )
1246 OUString aLastTabName;
1247 if (!lcl_getLastTabName(aLastTabName, rTabName, rTabNames, rRef))
1249 ScRangeStringConverter::AppendTableName(rBuf, aLastTabName);
1250 return;
1253 ScRangeStringConverter::AppendTableName(rBuf, rTabName);
1254 if (rTabName != aLastTabName)
1256 rBuf.append(':');
1257 ScRangeStringConverter::AppendTableName(rBuf, aLastTabName);
1261 virtual void parseExternalDocName( const OUString& rFormula, sal_Int32& rSrcPos ) const
1263 sal_Int32 nLen = rFormula.getLength();
1264 const sal_Unicode* p = rFormula.getStr();
1265 sal_Unicode cPrev = 0;
1266 for (sal_Int32 i = rSrcPos; i < nLen; ++i)
1268 sal_Unicode c = p[i];
1269 if (i == rSrcPos)
1271 // first character must be '['.
1272 if (c != '[')
1273 return;
1275 else if (i == rSrcPos + 1)
1277 // second character must be a single quote.
1278 if (c != '\'')
1279 return;
1281 else if (c == '\'')
1283 if (cPrev == '\'')
1284 // two successive single quote is treated as a single
1285 // valid character.
1286 c = 'a';
1288 else if (c == ']')
1290 if (cPrev == '\'')
1292 // valid source document path found. Increment the
1293 // current position to skip the source path.
1294 rSrcPos = i + 1;
1295 if (rSrcPos >= nLen)
1296 rSrcPos = nLen - 1;
1297 return;
1299 else
1300 return;
1302 else
1304 // any other character
1305 if (i > rSrcPos + 2 && cPrev == '\'')
1306 // unless it's the 3rd character, a normal character
1307 // following immediately a single quote is invalid.
1308 return;
1310 cPrev = c;
1315 struct ConventionXL_A1 : public Convention_A1, public ConventionXL
1317 ConventionXL_A1() : Convention_A1( FormulaGrammar::CONV_XL_A1 ) { }
1318 explicit ConventionXL_A1( FormulaGrammar::AddressConvention eConv ) : Convention_A1( eConv ) { }
1320 static void makeSingleCellStr( const ScSheetLimits& rLimits, OUStringBuffer& rBuf, const ScSingleRefData& rRef, const ScAddress& rAbs )
1322 if (!rRef.IsColRel())
1323 rBuf.append('$');
1324 MakeColStr(rLimits, rBuf, rAbs.Col());
1325 if (!rRef.IsRowRel())
1326 rBuf.append('$');
1327 MakeRowStr(rLimits, rBuf, rAbs.Row());
1330 virtual void makeRefStr(
1331 ScSheetLimits& rLimits,
1332 OUStringBuffer& rBuf,
1333 formula::FormulaGrammar::Grammar /*eGram*/,
1334 const ScAddress& rPos,
1335 const OUString& rErrRef, const std::vector<OUString>& rTabNames,
1336 const ScComplexRefData& rRef,
1337 bool bSingleRef,
1338 bool /*bFromRangeName*/ ) const override
1340 ScComplexRefData aRef( rRef );
1342 // Play fast and loose with invalid refs. There is not much point in producing
1343 // Foo!A1:#REF! versus #REF! at this point
1344 ScAddress aAbs1 = aRef.Ref1.toAbs(rLimits, rPos), aAbs2;
1346 MakeTabStr(rLimits, rBuf, rPos, rTabNames, aRef, bSingleRef);
1348 if (!rLimits.ValidAddress(aAbs1))
1350 rBuf.append(rErrRef);
1351 return;
1354 if( !bSingleRef )
1356 aAbs2 = aRef.Ref2.toAbs(rLimits, rPos);
1357 if (!rLimits.ValidAddress(aAbs2))
1359 rBuf.append(rErrRef);
1360 return;
1363 if (aAbs1.Col() == 0 && aAbs2.Col() >= rLimits.mnMaxCol)
1365 if (!aRef.Ref1.IsRowRel())
1366 rBuf.append( '$' );
1367 MakeRowStr(rLimits, rBuf, aAbs1.Row());
1368 rBuf.append( ':' );
1369 if (!aRef.Ref2.IsRowRel())
1370 rBuf.append( '$' );
1371 MakeRowStr(rLimits, rBuf, aAbs2.Row());
1372 return;
1375 if (aAbs1.Row() == 0 && aAbs2.Row() >= rLimits.mnMaxRow)
1377 if (!aRef.Ref1.IsColRel())
1378 rBuf.append( '$' );
1379 MakeColStr(rLimits, rBuf, aAbs1.Col());
1380 rBuf.append( ':' );
1381 if (!aRef.Ref2.IsColRel())
1382 rBuf.append( '$' );
1383 MakeColStr(rLimits, rBuf, aAbs2.Col());
1384 return;
1388 makeSingleCellStr(rLimits, rBuf, aRef.Ref1, aAbs1);
1389 if (!bSingleRef && (aAbs1.Row() != aAbs2.Row() || aAbs1.Col() != aAbs2.Col()))
1391 rBuf.append( ':' );
1392 makeSingleCellStr(rLimits, rBuf, aRef.Ref2, aAbs2);
1396 virtual ParseResult parseAnyToken( const OUString& rFormula,
1397 sal_Int32 nSrcPos,
1398 const CharClass* pCharClass,
1399 bool bGroupSeparator) const override
1401 parseExternalDocName(rFormula, nSrcPos);
1403 ParseResult aRet;
1404 if ( lcl_isValidQuotedText(rFormula, nSrcPos, aRet) )
1405 return aRet;
1407 constexpr sal_Int32 nStartFlags = KParseTokens::ANY_LETTER_OR_NUMBER |
1408 KParseTokens::ASC_UNDERSCORE | KParseTokens::ASC_DOLLAR;
1409 constexpr sal_Int32 nContFlags = nStartFlags | KParseTokens::ASC_DOT;
1410 // '?' allowed in range names
1411 static constexpr OUString aAddAllowed(u"?!"_ustr);
1412 return pCharClass->parseAnyToken( rFormula,
1413 nSrcPos, nStartFlags, aAddAllowed,
1414 (bGroupSeparator ? nContFlags | KParseTokens::GROUP_SEPARATOR_IN_NUMBER : nContFlags),
1415 aAddAllowed );
1418 virtual sal_Unicode getSpecialSymbol( SpecialSymbolType eSymType ) const override
1420 return ConventionXL::getSpecialSymbol(eSymType);
1423 virtual bool parseExternalName( const OUString& rSymbol, OUString& rFile, OUString& rName,
1424 const ScDocument& rDoc,
1425 const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks ) const override
1427 return ConventionXL::parseExternalName( rSymbol, rFile, rName, rDoc, pExternalLinks);
1430 virtual OUString makeExternalNameStr( sal_uInt16 /*nFileId*/, const OUString& rFile,
1431 const OUString& rName ) const override
1433 return ConventionXL::makeExternalNameStr(rFile, rName);
1436 virtual void makeExternalRefStr(
1437 ScSheetLimits& rLimits,
1438 OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName,
1439 const OUString& rTabName, const ScSingleRefData& rRef ) const override
1441 // ['file:///path/to/file/filename.xls']'Sheet Name'!$A$1
1442 // This is a little different from the format Excel uses, as Excel
1443 // puts [] only around the file name. But we need to enclose the
1444 // whole file path with [] because the file name can contain any
1445 // characters.
1447 ConventionXL::makeExternalDocStr(rBuffer, rFileName);
1448 ScRangeStringConverter::AppendTableName(rBuffer, rTabName);
1449 rBuffer.append('!');
1451 makeSingleCellStr(rLimits, rBuffer, rRef, rRef.toAbs(rLimits, rPos));
1454 virtual void makeExternalRefStr(
1455 ScSheetLimits& rLimits,
1456 OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName,
1457 const std::vector<OUString>& rTabNames, const OUString& rTabName,
1458 const ScComplexRefData& rRef ) const override
1460 ScRange aAbsRef = rRef.toAbs(rLimits, rPos);
1462 ConventionXL::makeExternalDocStr(rBuffer, rFileName);
1463 ConventionXL::makeExternalTabNameRange(rBuffer, rTabName, rTabNames, aAbsRef);
1464 rBuffer.append('!');
1466 makeSingleCellStr(rLimits, rBuffer, rRef.Ref1, aAbsRef.aStart);
1467 if (aAbsRef.aStart != aAbsRef.aEnd)
1469 rBuffer.append(':');
1470 makeSingleCellStr(rLimits, rBuffer, rRef.Ref2, aAbsRef.aEnd);
1475 struct ConventionXL_OOX : public ConventionXL_A1
1477 ConventionXL_OOX() : ConventionXL_A1( FormulaGrammar::CONV_XL_OOX ) { }
1479 virtual void makeRefStr( ScSheetLimits& rLimits,
1480 OUStringBuffer& rBuf,
1481 formula::FormulaGrammar::Grammar eGram,
1482 const ScAddress& rPos,
1483 const OUString& rErrRef, const std::vector<OUString>& rTabNames,
1484 const ScComplexRefData& rRef,
1485 bool bSingleRef,
1486 bool bFromRangeName ) const override
1488 // In OOXML relative references in named expressions are relative to
1489 // column 0 and row 0. Relative sheet references don't exist.
1490 ScAddress aPos( rPos );
1491 if (bFromRangeName)
1493 // XXX NOTE: by decrementing the reference position we may end up
1494 // with resolved references with negative values. There's no proper
1495 // way to solve that or wrap them around without sheet dimensions
1496 // that are stored along. That, or blindly assume fixed dimensions
1497 // here and in import.
1498 /* TODO: maybe do that blind fixed dimensions wrap? */
1499 aPos.SetCol(0);
1500 aPos.SetRow(0);
1503 if (rRef.Ref1.IsDeleted() || (!bSingleRef && rRef.Ref2.IsDeleted()))
1505 // For OOXML write plain "#REF!" instead of detailed sheet/col/row
1506 // information.
1507 rBuf.append(rErrRef);
1508 return;
1512 ScAddress aAbs1 = rRef.Ref1.toAbs(rLimits, rPos);
1513 if (!rLimits.ValidAddress(aAbs1)
1514 || o3tl::make_unsigned(aAbs1.Tab()) >= rTabNames.size())
1516 rBuf.append(rErrRef);
1517 return;
1521 if (!bSingleRef)
1523 ScAddress aAbs2 = rRef.Ref2.toAbs(rLimits, rPos);
1524 if (!rLimits.ValidAddress(aAbs2)
1525 || o3tl::make_unsigned(aAbs2.Tab()) >= rTabNames.size())
1527 rBuf.append(rErrRef);
1528 return;
1532 ConventionXL_A1::makeRefStr( rLimits, rBuf, eGram, aPos, rErrRef, rTabNames, rRef, bSingleRef, bFromRangeName);
1535 virtual OUString makeExternalNameStr( sal_uInt16 nFileId, const OUString& /*rFile*/,
1536 const OUString& rName ) const override
1538 // [N]!DefinedName is a workbook global name.
1539 return OUString( "[" + OUString::number(nFileId+1) + "]!" + rName );
1541 /* TODO: add support for sheet local names, would be
1542 * [N]'Sheet Name'!DefinedName
1543 * Similar to makeExternalRefStr() but with DefinedName instead of
1544 * CellStr. */
1547 virtual void parseExternalDocName(const OUString& rFormula, sal_Int32& rSrcPos) const override
1549 sal_Int32 nLen = rFormula.getLength();
1550 const sal_Unicode* p = rFormula.getStr();
1551 for (sal_Int32 i = rSrcPos; i < nLen; ++i)
1553 sal_Unicode c = p[i];
1554 if (i == rSrcPos)
1556 // first character must be '['.
1557 if (c != '[')
1558 return;
1560 else if (c == ']')
1562 rSrcPos = i + 1;
1563 return;
1568 virtual void makeExternalRefStr(
1569 ScSheetLimits& rLimits,
1570 OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 nFileId, const OUString& /*rFileName*/,
1571 const OUString& rTabName, const ScSingleRefData& rRef ) const override
1573 // '[N]Sheet Name'!$A$1 or [N]SheetName!$A$1
1574 // Where N is a 1-based positive integer number of a file name in OOXML
1575 // xl/externalLinks/externalLinkN.xml
1577 OUString aQuotedTab( rTabName);
1578 ScCompiler::CheckTabQuotes( aQuotedTab);
1579 if (!aQuotedTab.isEmpty() && aQuotedTab[0] == '\'')
1581 rBuffer.append('\'');
1582 ConventionXL_OOX::makeExternalDocStr( rBuffer, nFileId);
1583 rBuffer.append( aQuotedTab.subView(1));
1585 else
1587 ConventionXL_OOX::makeExternalDocStr( rBuffer, nFileId);
1588 rBuffer.append( aQuotedTab);
1590 rBuffer.append('!');
1592 makeSingleCellStr(rLimits, rBuffer, rRef, rRef.toAbs(rLimits, rPos));
1595 virtual void makeExternalRefStr(
1596 ScSheetLimits& rLimits,
1597 OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 nFileId, const OUString& /*rFileName*/,
1598 const std::vector<OUString>& rTabNames, const OUString& rTabName,
1599 const ScComplexRefData& rRef ) const override
1601 // '[N]Sheet One':'Sheet Two'!A1:B2 or [N]SheetOne!A1:B2
1602 // Actually Excel writes '[N]Sheet One:Sheet Two'!A1:B2 but reads the
1603 // simpler to produce and more logical form with independently quoted
1604 // sheet names as well. The [N] having to be within the quoted sheet
1605 // name is ugly enough...
1607 ScRange aAbsRef = rRef.toAbs(rLimits, rPos);
1609 OUStringBuffer aBuf;
1610 ConventionXL::makeExternalTabNameRange( aBuf, rTabName, rTabNames, aAbsRef);
1611 if (!aBuf.isEmpty() && aBuf[0] == '\'')
1613 rBuffer.append('\'');
1614 ConventionXL_OOX::makeExternalDocStr( rBuffer, nFileId);
1615 rBuffer.append( aBuf.subView(1));
1617 else
1619 ConventionXL_OOX::makeExternalDocStr( rBuffer, nFileId);
1620 rBuffer.append( aBuf);
1622 rBuffer.append('!');
1624 makeSingleCellStr(rLimits, rBuffer, rRef.Ref1, aAbsRef.aStart);
1625 if (aAbsRef.aStart != aAbsRef.aEnd)
1627 rBuffer.append(':');
1628 makeSingleCellStr(rLimits, rBuffer, rRef.Ref2, aAbsRef.aEnd);
1632 static void makeExternalDocStr( OUStringBuffer& rBuffer, sal_uInt16 nFileId )
1634 rBuffer.append("[" + OUString::number( static_cast<sal_Int32>(nFileId+1) ) + "]");
1640 static void
1641 r1c1_add_col( OUStringBuffer &rBuf, const ScSingleRefData& rRef, const ScAddress& rAbsRef )
1643 rBuf.append( 'C' );
1644 if( rRef.IsColRel() )
1646 SCCOL nCol = rRef.Col();
1647 if (nCol != 0)
1648 rBuf.append("[" + OUString::number(nCol) + "]");
1650 else
1651 rBuf.append( static_cast<sal_Int32>(rAbsRef.Col() + 1) );
1653 static void
1654 r1c1_add_row( OUStringBuffer &rBuf, const ScSingleRefData& rRef, const ScAddress& rAbsRef )
1656 rBuf.append( 'R' );
1657 if( rRef.IsRowRel() )
1659 if (rRef.Row() != 0)
1661 rBuf.append("[" + OUString::number(rRef.Row()) + "]");
1664 else
1665 rBuf.append( rAbsRef.Row() + 1 );
1668 namespace {
1670 struct ConventionXL_R1C1 : public ScCompiler::Convention, public ConventionXL
1672 ConventionXL_R1C1() : ScCompiler::Convention( FormulaGrammar::CONV_XL_R1C1 ) { }
1674 virtual void makeRefStr( ScSheetLimits& rLimits,
1675 OUStringBuffer& rBuf,
1676 formula::FormulaGrammar::Grammar /*eGram*/,
1677 const ScAddress& rPos,
1678 const OUString& rErrRef, const std::vector<OUString>& rTabNames,
1679 const ScComplexRefData& rRef,
1680 bool bSingleRef,
1681 bool /*bFromRangeName*/ ) const override
1683 ScRange aAbsRef = rRef.toAbs(rLimits, rPos);
1684 ScComplexRefData aRef( rRef );
1686 MakeTabStr(rLimits, rBuf, rPos, rTabNames, aRef, bSingleRef);
1688 // Play fast and loose with invalid refs. There is not much point in producing
1689 // Foo!A1:#REF! versus #REF! at this point
1690 if (!rLimits.ValidCol(aAbsRef.aStart.Col()) || !rLimits.ValidRow(aAbsRef.aStart.Row()))
1692 rBuf.append(rErrRef);
1693 return;
1696 if( !bSingleRef )
1698 if (!rLimits.ValidCol(aAbsRef.aEnd.Col()) || !rLimits.ValidRow(aAbsRef.aEnd.Row()))
1700 rBuf.append(rErrRef);
1701 return;
1704 if (aAbsRef.aStart.Col() == 0 && aAbsRef.aEnd.Col() >= rLimits.mnMaxCol)
1706 r1c1_add_row(rBuf, rRef.Ref1, aAbsRef.aStart);
1707 if (aAbsRef.aStart.Row() != aAbsRef.aEnd.Row() ||
1708 rRef.Ref1.IsRowRel() != rRef.Ref2.IsRowRel() )
1710 rBuf.append( ':' );
1711 r1c1_add_row(rBuf, rRef.Ref2, aAbsRef.aEnd);
1713 return;
1717 if (aAbsRef.aStart.Row() == 0 && aAbsRef.aEnd.Row() >= rLimits.mnMaxRow)
1719 r1c1_add_col(rBuf, rRef.Ref1, aAbsRef.aStart);
1720 if (aAbsRef.aStart.Col() != aAbsRef.aEnd.Col() ||
1721 rRef.Ref1.IsColRel() != rRef.Ref2.IsColRel())
1723 rBuf.append( ':' );
1724 r1c1_add_col(rBuf, rRef.Ref2, aAbsRef.aEnd);
1726 return;
1730 r1c1_add_row(rBuf, rRef.Ref1, aAbsRef.aStart);
1731 r1c1_add_col(rBuf, rRef.Ref1, aAbsRef.aStart);
1732 // We can't parse a single col/row reference in the context of a R1C1
1733 // 3D reference back yet, otherwise (if Excel understands it) an
1734 // additional condition similar to ConventionXL_A1::makeRefStr() could
1735 // be
1737 // && (aAbsRef.aStart.Row() != aAbsRef.aEnd.Row() || aAbsRef.aStart.Col() != aAbsRef.aEnd.Col())
1738 if (!bSingleRef)
1740 rBuf.append( ':' );
1741 r1c1_add_row(rBuf, rRef.Ref2, aAbsRef.aEnd);
1742 r1c1_add_col(rBuf, rRef.Ref2, aAbsRef.aEnd);
1746 ParseResult parseAnyToken( const OUString& rFormula,
1747 sal_Int32 nSrcPos,
1748 const CharClass* pCharClass,
1749 bool bGroupSeparator) const override
1751 parseExternalDocName(rFormula, nSrcPos);
1753 ParseResult aRet;
1754 if ( lcl_isValidQuotedText(rFormula, nSrcPos, aRet) )
1755 return aRet;
1757 constexpr sal_Int32 nStartFlags = KParseTokens::ANY_LETTER_OR_NUMBER |
1758 KParseTokens::ASC_UNDERSCORE ;
1759 constexpr sal_Int32 nContFlags = nStartFlags | KParseTokens::ASC_DOT;
1760 // '?' allowed in range names
1761 static constexpr OUString aAddAllowed(u"?-[]!"_ustr);
1763 return pCharClass->parseAnyToken( rFormula,
1764 nSrcPos, nStartFlags, aAddAllowed,
1765 (bGroupSeparator ? nContFlags | KParseTokens::GROUP_SEPARATOR_IN_NUMBER : nContFlags),
1766 aAddAllowed );
1769 virtual sal_Unicode getSpecialSymbol( SpecialSymbolType eSymType ) const override
1771 return ConventionXL::getSpecialSymbol(eSymType);
1774 virtual bool parseExternalName( const OUString& rSymbol, OUString& rFile, OUString& rName,
1775 const ScDocument& rDoc,
1776 const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks ) const override
1778 return ConventionXL::parseExternalName( rSymbol, rFile, rName, rDoc, pExternalLinks);
1781 virtual OUString makeExternalNameStr( sal_uInt16 /*nFileId*/, const OUString& rFile,
1782 const OUString& rName ) const override
1784 return ConventionXL::makeExternalNameStr(rFile, rName);
1787 virtual void makeExternalRefStr(
1788 ScSheetLimits& rLimits,
1789 OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName,
1790 const OUString& rTabName, const ScSingleRefData& rRef ) const override
1792 // ['file:///path/to/file/filename.xls']'Sheet Name'!$A$1
1793 // This is a little different from the format Excel uses, as Excel
1794 // puts [] only around the file name. But we need to enclose the
1795 // whole file path with [] because the file name can contain any
1796 // characters.
1798 ScAddress aAbsRef = rRef.toAbs(rLimits, rPos);
1799 ConventionXL::makeExternalDocStr(rBuffer, rFileName);
1800 ScRangeStringConverter::AppendTableName(rBuffer, rTabName);
1801 rBuffer.append('!');
1803 r1c1_add_row(rBuffer, rRef, aAbsRef);
1804 r1c1_add_col(rBuffer, rRef, aAbsRef);
1807 virtual void makeExternalRefStr(
1808 ScSheetLimits& rLimits,
1809 OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName,
1810 const std::vector<OUString>& rTabNames, const OUString& rTabName,
1811 const ScComplexRefData& rRef ) const override
1813 ScRange aAbsRef = rRef.toAbs(rLimits, rPos);
1815 ConventionXL::makeExternalDocStr(rBuffer, rFileName);
1816 ConventionXL::makeExternalTabNameRange(rBuffer, rTabName, rTabNames, aAbsRef);
1817 rBuffer.append('!');
1819 if (!rLimits.ValidCol(aAbsRef.aEnd.Col()) || !rLimits.ValidRow(aAbsRef.aEnd.Row()))
1821 rBuffer.append(ScResId(STR_NO_REF_TABLE));
1822 return;
1825 if (aAbsRef.aStart.Col() == 0 && aAbsRef.aEnd.Col() >= rLimits.mnMaxCol)
1827 r1c1_add_row(rBuffer, rRef.Ref1, aAbsRef.aStart);
1828 if (aAbsRef.aStart.Row() != aAbsRef.aEnd.Row() || rRef.Ref1.IsRowRel() != rRef.Ref2.IsRowRel())
1830 rBuffer.append(':');
1831 r1c1_add_row(rBuffer, rRef.Ref2, aAbsRef.aEnd);
1833 return;
1836 if (aAbsRef.aStart.Row() == 0 && aAbsRef.aEnd.Row() >= rLimits.mnMaxRow)
1838 r1c1_add_col(rBuffer, rRef.Ref1, aAbsRef.aStart);
1839 if (aAbsRef.aStart.Col() != aAbsRef.aEnd.Col() || rRef.Ref1.IsColRel() != rRef.Ref2.IsColRel())
1841 rBuffer.append(':');
1842 r1c1_add_col(rBuffer, rRef.Ref2, aAbsRef.aEnd);
1844 return;
1847 r1c1_add_row(rBuffer, rRef.Ref1, aAbsRef.aStart);
1848 r1c1_add_col(rBuffer, rRef.Ref1, aAbsRef.aStart);
1849 rBuffer.append(':');
1850 r1c1_add_row(rBuffer, rRef.Ref2, aAbsRef.aEnd);
1851 r1c1_add_col(rBuffer, rRef.Ref2, aAbsRef.aEnd);
1854 virtual ScCharFlags getCharTableFlags( sal_Unicode c, sal_Unicode cLast ) const override
1856 ScCharFlags nFlags = mpCharTable[static_cast<sal_uInt8>(c)];
1857 if (c == '-' && cLast == '[')
1858 // '-' can occur within a reference string only after '[' e.g. R[-1]C.
1859 nFlags |= ScCharFlags::Ident;
1860 return nFlags;
1866 ScCompiler::ScCompiler( sc::CompileFormulaContext& rCxt, const ScAddress& rPos, ScTokenArray& rArr,
1867 bool bComputeII, bool bMatrixFlag, ScInterpreterContext* pContext )
1868 : FormulaCompiler(rArr, bComputeII, bMatrixFlag),
1869 rDoc(rCxt.getDoc()),
1870 aPos(rPos),
1871 mrInterpreterContext(pContext ? *pContext : rDoc.GetNonThreadedContext()),
1872 mnCurrentSheetTab(-1),
1873 mnCurrentSheetEndPos(0),
1874 pCharClass(&ScGlobal::getCharClass()),
1875 mbCharClassesDiffer(false),
1876 mnPredetectedReference(0),
1877 mnRangeOpPosInSymbol(-1),
1878 pConv(GetRefConvention(FormulaGrammar::CONV_OOO)),
1879 meExtendedErrorDetection(EXTENDED_ERROR_DETECTION_NONE),
1880 mbCloseBrackets(true),
1881 mbRewind(false),
1882 mbRefConventionChartOOXML(false),
1883 maTabNames(rCxt.getTabNames())
1885 SetGrammar(rCxt.getGrammar());
1886 m_oODFSavingVersion = rCxt.getODFSavingVersion();
1889 ScCompiler::ScCompiler( ScDocument& rDocument, const ScAddress& rPos, ScTokenArray& rArr,
1890 formula::FormulaGrammar::Grammar eGrammar,
1891 bool bComputeII, bool bMatrixFlag, ScInterpreterContext* pContext )
1892 : FormulaCompiler(rArr, bComputeII, bMatrixFlag),
1893 rDoc( rDocument ),
1894 aPos( rPos ),
1895 mrInterpreterContext(pContext ? *pContext : rDoc.GetNonThreadedContext()),
1896 mnCurrentSheetTab(-1),
1897 mnCurrentSheetEndPos(0),
1898 nSrcPos(0),
1899 pCharClass( &ScGlobal::getCharClass() ),
1900 mbCharClassesDiffer(false),
1901 mnPredetectedReference(0),
1902 mnRangeOpPosInSymbol(-1),
1903 pConv( GetRefConvention( FormulaGrammar::CONV_OOO ) ),
1904 meExtendedErrorDetection( EXTENDED_ERROR_DETECTION_NONE ),
1905 mbCloseBrackets( true ),
1906 mbRewind( false ),
1907 mbRefConventionChartOOXML( false )
1909 SetGrammar( (eGrammar == formula::FormulaGrammar::GRAM_UNSPECIFIED) ?
1910 rDocument.GetGrammar() :
1911 eGrammar );
1914 ScCompiler::ScCompiler( sc::CompileFormulaContext& rCxt, const ScAddress& rPos,
1915 bool bComputeII, bool bMatrixFlag, ScInterpreterContext* pContext )
1916 : FormulaCompiler(bComputeII, bMatrixFlag),
1917 rDoc(rCxt.getDoc()),
1918 aPos(rPos),
1919 mrInterpreterContext(pContext ? *pContext : rDoc.GetNonThreadedContext()),
1920 mnCurrentSheetTab(-1),
1921 mnCurrentSheetEndPos(0),
1922 pCharClass(&ScGlobal::getCharClass()),
1923 mbCharClassesDiffer(false),
1924 mnPredetectedReference(0),
1925 mnRangeOpPosInSymbol(-1),
1926 pConv(GetRefConvention(FormulaGrammar::CONV_OOO)),
1927 meExtendedErrorDetection(EXTENDED_ERROR_DETECTION_NONE),
1928 mbCloseBrackets(true),
1929 mbRewind(false),
1930 mbRefConventionChartOOXML(false),
1931 maTabNames(rCxt.getTabNames())
1933 SetGrammar(rCxt.getGrammar());
1936 ScCompiler::ScCompiler( ScDocument& rDocument, const ScAddress& rPos,
1937 formula::FormulaGrammar::Grammar eGrammar,
1938 bool bComputeII, bool bMatrixFlag, ScInterpreterContext* pContext )
1939 : FormulaCompiler(bComputeII, bMatrixFlag),
1940 rDoc( rDocument ),
1941 aPos( rPos ),
1942 mrInterpreterContext(pContext ? *pContext : rDoc.GetNonThreadedContext()),
1943 mnCurrentSheetTab(-1),
1944 mnCurrentSheetEndPos(0),
1945 nSrcPos(0),
1946 pCharClass( &ScGlobal::getCharClass() ),
1947 mbCharClassesDiffer(false),
1948 mnPredetectedReference(0),
1949 mnRangeOpPosInSymbol(-1),
1950 pConv( GetRefConvention( FormulaGrammar::CONV_OOO ) ),
1951 meExtendedErrorDetection( EXTENDED_ERROR_DETECTION_NONE ),
1952 mbCloseBrackets( true ),
1953 mbRewind( false ),
1954 mbRefConventionChartOOXML( false )
1956 SetGrammar( (eGrammar == formula::FormulaGrammar::GRAM_UNSPECIFIED) ?
1957 rDocument.GetGrammar() :
1958 eGrammar );
1961 ScCompiler::~ScCompiler()
1965 void ScCompiler::CheckTabQuotes( OUString& rString,
1966 const FormulaGrammar::AddressConvention eConv )
1968 sal_Int32 nStartFlags = KParseTokens::ANY_LETTER_OR_NUMBER | KParseTokens::ASC_UNDERSCORE;
1969 sal_Int32 nContFlags = nStartFlags;
1970 ParseResult aRes = ScGlobal::getCharClass().parsePredefinedToken(
1971 KParseType::IDENTNAME, rString, 0, nStartFlags, OUString(), nContFlags, OUString());
1972 bool bNeedsQuote = !((aRes.TokenType & KParseType::IDENTNAME) && aRes.EndPos == rString.getLength());
1974 switch ( eConv )
1976 default :
1977 case FormulaGrammar::CONV_UNSPECIFIED :
1978 break;
1979 case FormulaGrammar::CONV_OOO :
1980 case FormulaGrammar::CONV_XL_A1 :
1981 case FormulaGrammar::CONV_XL_R1C1 :
1982 case FormulaGrammar::CONV_XL_OOX :
1983 case FormulaGrammar::CONV_ODF :
1984 if( bNeedsQuote )
1986 // escape embedded quotes
1987 rString = rString.replaceAll( "'", "''" );
1989 break;
1992 if ( !bNeedsQuote && CharClass::isAsciiNumeric( rString ) )
1994 // Prevent any possible confusion resulting from pure numeric sheet names.
1995 bNeedsQuote = true;
1998 if( bNeedsQuote )
2000 rString = "'" + rString + "'";
2004 void ScCompiler::FormExcelSheetRange( OUStringBuffer& rBuf, sal_Int32 nQuotePos, const OUString& rEndTabName )
2006 OUString aEndTabName(rEndTabName);
2007 if (nQuotePos < rBuf.getLength())
2009 const bool bQuoted2 = (!aEndTabName.isEmpty() && aEndTabName[0] == '\'');
2010 if (bQuoted2)
2011 aEndTabName = aEndTabName.subView(1); // Sheet2'
2012 if (rBuf[nQuotePos] == '\'') // 'Sheet1'
2014 const sal_Int32 nLast = rBuf.getLength() - 1;
2015 if (rBuf[nLast] == '\'')
2016 rBuf.remove(nLast, 1); // 'Sheet1
2018 else if (bQuoted2) // Sheet1
2020 rBuf.insert(nQuotePos, '\''); // 'Sheet1
2023 rBuf.append( ':' );
2024 rBuf.append( aEndTabName );
2027 sal_Int32 ScCompiler::GetDocTabPos( const OUString& rString )
2029 if (rString[0] != '\'')
2030 return -1;
2031 sal_Int32 nPos = ScGlobal::FindUnquoted( rString, SC_COMPILER_FILE_TAB_SEP);
2032 // it must be 'Doc'#
2033 if (nPos != -1 && rString[nPos-1] != '\'')
2034 nPos = -1;
2035 return nPos;
2038 void ScCompiler::SetRefConvention( FormulaGrammar::AddressConvention eConv )
2040 const Convention* p = GetRefConvention(eConv);
2041 if (p)
2042 SetRefConvention(p);
2045 const ScCompiler::Convention* ScCompiler::GetRefConvention( FormulaGrammar::AddressConvention eConv )
2048 switch (eConv)
2050 case FormulaGrammar::CONV_OOO:
2052 static const ConventionOOO_A1 ConvOOO_A1;
2053 return &ConvOOO_A1;
2055 case FormulaGrammar::CONV_ODF:
2057 static const ConventionOOO_A1_ODF ConvOOO_A1_ODF;
2058 return &ConvOOO_A1_ODF;
2060 case FormulaGrammar::CONV_XL_A1:
2062 static const ConventionXL_A1 ConvXL_A1;
2063 return &ConvXL_A1;
2065 case FormulaGrammar::CONV_XL_R1C1:
2067 static const ConventionXL_R1C1 ConvXL_R1C1;
2068 return &ConvXL_R1C1;
2070 case FormulaGrammar::CONV_XL_OOX:
2072 static const ConventionXL_OOX ConvXL_OOX;
2073 return &ConvXL_OOX;
2075 case FormulaGrammar::CONV_UNSPECIFIED:
2076 default:
2080 return nullptr;
2083 void ScCompiler::SetRefConvention( const ScCompiler::Convention *pConvP )
2085 pConv = pConvP;
2086 meGrammar = FormulaGrammar::mergeToGrammar( meGrammar, pConv->meConv);
2087 assert( FormulaGrammar::isSupported( meGrammar));
2090 void ScCompiler::SetError(FormulaError nError)
2092 if( pArr->GetCodeError() == FormulaError::NONE)
2093 pArr->SetCodeError( nError);
2096 static sal_Unicode* lcl_UnicodeStrNCpy( sal_Unicode* pDst, const sal_Unicode* pSrc, sal_Int32 nMax )
2098 const sal_Unicode* const pStop = pDst + nMax;
2099 while ( pDst < pStop )
2101 *pDst++ = *pSrc++;
2103 *pDst = 0;
2104 return pDst;
2107 // p1 MUST contain at least n characters, or terminate with NIL.
2108 // p2 MUST pass upper case letters, if any.
2109 // n MUST not be greater than length of p2
2110 static bool lcl_isUnicodeIgnoreAscii( const sal_Unicode* p1, const char* p2, size_t n )
2112 for (size_t i=0; i<n; ++i)
2114 if (!p1[i])
2115 return false;
2116 if (p1[i] != p2[i])
2118 if (p1[i] < 'a' || 'z' < p1[i])
2119 return false; // not a lower case letter
2120 if (p2[i] < 'A' || 'Z' < p2[i])
2121 return false; // not a letter to match
2122 if (p1[i] != p2[i] + 0x20)
2123 return false; // lower case doesn't match either
2126 return true;
2129 // static
2130 void ScCompiler::addWhitespace( std::vector<ScCompiler::Whitespace> & rvSpaces,
2131 ScCompiler::Whitespace & rSpace, sal_Unicode c, sal_Int32 n )
2133 if (rSpace.cChar != c)
2135 if (rSpace.cChar && rSpace.nCount > 0)
2136 rvSpaces.emplace_back(rSpace);
2137 rSpace.reset(c);
2139 rSpace.nCount += n;
2142 // NextSymbol
2144 // Parses the formula into separate symbols for further processing.
2145 // XXX NOTE: this is a rough sketch of the original idea, there are other
2146 // states that were added and didn't make it into this table and things are
2147 // more complicated. Use the source, Luke.
2149 // initial state = GetChar
2151 // old state | read character | action | new state
2152 //---------------+-------------------+-----------------------+---------------
2153 // GetChar | ;()+-*/^=& | Symbol=char | Stop
2154 // | <> | Symbol=char | GetBool
2155 // | $ letter | Symbol=char | GetWord
2156 // | number | Symbol=char | GetValue
2157 // | " | none | GetString
2158 // | other | none | GetChar
2159 //---------------+-------------------+-----------------------+---------------
2160 // GetBool | => | Symbol=Symbol+char | Stop
2161 // | other | Dec(CharPos) | Stop
2162 //---------------+-------------------+-----------------------+---------------
2163 // GetWord | SepSymbol | Dec(CharPos) | Stop
2164 // | ()+-*/^=<>&~ | |
2165 // | space | Dec(CharPos) | Stop
2166 // | $_:. | |
2167 // | letter, number | Symbol=Symbol+char | GetWord
2168 // | other | error | Stop
2169 //---------------+-------------------+-----------------------+---------------
2170 // GetValue | ;()*/^=<>& | |
2171 // | space | Dec(CharPos) | Stop
2172 // | number E+-%,. | Symbol=Symbol+char | GetValue
2173 // | other | error | Stop
2174 //---------------+-------------------+-----------------------+---------------
2175 // GetString | " | none | Stop
2176 // | other | Symbol=Symbol+char | GetString
2177 //---------------+-------------------+-----------------------+---------------
2179 std::vector<ScCompiler::Whitespace> ScCompiler::NextSymbol(bool bInArray)
2181 std::vector<Whitespace> vSpaces;
2182 cSymbol[MAXSTRLEN] = 0; // end
2183 sal_Unicode* pSym = cSymbol;
2184 const sal_Unicode* const pStart = aFormula.getStr();
2185 const sal_Unicode* pSrc = pStart + nSrcPos;
2186 bool bi18n = false;
2187 sal_Unicode c = *pSrc;
2188 sal_Unicode cLast = 0;
2189 bool bQuote = false;
2190 mnRangeOpPosInSymbol = -1;
2191 ScanState eState = ssGetChar;
2192 Whitespace aSpace;
2193 sal_Unicode cSep = mxSymbols->getSymbolChar( ocSep);
2194 sal_Unicode cArrayColSep = mxSymbols->getSymbolChar( ocArrayColSep);
2195 sal_Unicode cArrayRowSep = mxSymbols->getSymbolChar( ocArrayRowSep);
2196 sal_Unicode cDecSep = (mxSymbols->isEnglishLocale() ? '.' : ScGlobal::getLocaleData().getNumDecimalSep()[0]);
2197 sal_Unicode cDecSepAlt = (mxSymbols->isEnglishLocale() ? 0 : ScGlobal::getLocaleData().getNumDecimalSepAlt().toChar());
2199 // special symbols specific to address convention used
2200 sal_Unicode cSheetPrefix = pConv->getSpecialSymbol(ScCompiler::Convention::ABS_SHEET_PREFIX);
2201 sal_Unicode cSheetSep = pConv->getSpecialSymbol(ScCompiler::Convention::SHEET_SEPARATOR);
2203 int nDecSeps = 0;
2204 bool bAutoIntersection = false;
2205 size_t nAutoIntersectionSpacesPos = 0;
2206 int nRefInName = 0;
2207 bool bErrorConstantHadSlash = false;
2208 mnPredetectedReference = 0;
2209 // try to parse simple tokens before calling i18n parser
2210 while ((c != 0) && (eState != ssStop) )
2212 pSrc++;
2213 ScCharFlags nMask = GetCharTableFlags( c, cLast );
2215 // The parameter separator and the array column and row separators end
2216 // things unconditionally if not in string or reference.
2217 if (c == cSep || (bInArray && (c == cArrayColSep || c == cArrayRowSep)))
2219 switch (eState)
2221 // these are to be continued
2222 case ssGetString:
2223 case ssSkipString:
2224 case ssGetReference:
2225 case ssSkipReference:
2226 case ssGetTableRefItem:
2227 case ssGetTableRefColumn:
2228 break;
2229 default:
2230 if (eState == ssGetChar)
2231 *pSym++ = c;
2232 else
2233 pSrc--;
2234 eState = ssStop;
2237 Label_MaskStateMachine:
2238 switch (eState)
2240 case ssGetChar :
2242 // Order is important!
2243 if (eLastOp == ocTableRefOpen && c != '[' && c != '#' && c != ']')
2245 *pSym++ = c;
2246 eState = ssGetTableRefColumn;
2248 else if( nMask & ScCharFlags::OdfLabelOp )
2250 // '!!' automatic intersection
2251 if (GetCharTableFlags( pSrc[0], 0 ) & ScCharFlags::OdfLabelOp)
2253 /* TODO: For now the UI "space operator" is used, this
2254 * could be enhanced using a specialized OpCode to get
2255 * rid of the space ambiguity, which would need some
2256 * places to be adapted though. And we would still need
2257 * to support the ambiguous space operator for UI
2258 * purposes anyway. However, we then could check for
2259 * invalid usage of '!!', which currently isn't
2260 * possible. */
2261 if (!bAutoIntersection)
2263 ++pSrc;
2264 // Add 2 because it must match the character count
2265 // for bi18n.
2266 addWhitespace( vSpaces, aSpace, 0x20, 2);
2267 // Position of Whitespace where it will be added to
2268 // vector.
2269 nAutoIntersectionSpacesPos = vSpaces.size();
2270 bAutoIntersection = true;
2272 else
2274 pSrc--;
2275 eState = ssStop;
2278 else
2280 nMask &= ~ScCharFlags::OdfLabelOp;
2281 goto Label_MaskStateMachine;
2284 else if( nMask & ScCharFlags::OdfNameMarker )
2286 // '$$' defined name marker
2287 if (GetCharTableFlags( pSrc[0], 0 ) & ScCharFlags::OdfNameMarker)
2289 // both eaten, not added to pSym
2290 ++pSrc;
2292 else
2294 nMask &= ~ScCharFlags::OdfNameMarker;
2295 goto Label_MaskStateMachine;
2298 else if( nMask & ScCharFlags::Char )
2300 // '[' is a special case in Excel syntax, it can start an
2301 // external reference, ID in OOXML like [1]Sheet1!A1 or
2302 // Excel_A1 [filename]Sheet!A1 or Excel_R1C1
2303 // [filename]Sheet!R1C1 that needs to be scanned
2304 // entirely, or can be ocTableRefOpen, of which the first
2305 // transforms an ocDBArea into an ocTableRef.
2306 if (c == '[' && FormulaGrammar::isExcelSyntax( meGrammar)
2307 && eLastOp != ocDBArea && maTableRefs.empty())
2309 // [0]!Global_Range_Name, is a special case in OOXML
2310 // syntax, where the '0' is referencing to self and we
2311 // do not need it, so we should skip it, in order to
2312 // later it will be more recognisable for IsNamedRange.
2313 if (FormulaGrammar::isRefConventionOOXML(meGrammar) &&
2314 pSrc[0] == '0' && pSrc[1] == ']' && pSrc[2] == '!')
2316 pSrc += 3;
2317 c = *pSrc;
2318 continue;
2321 nMask &= ~ScCharFlags::Char;
2322 goto Label_MaskStateMachine;
2324 else
2326 *pSym++ = c;
2327 eState = ssStop;
2330 else if( nMask & ScCharFlags::OdfLBracket )
2332 // eaten, not added to pSym
2333 eState = ssGetReference;
2334 mnPredetectedReference = 1;
2336 else if( nMask & ScCharFlags::CharBool )
2338 *pSym++ = c;
2339 eState = ssGetBool;
2341 else if( nMask & ScCharFlags::CharValue )
2343 *pSym++ = c;
2344 eState = ssGetValue;
2346 else if( nMask & ScCharFlags::CharString )
2348 *pSym++ = c;
2349 eState = ssGetString;
2351 else if( nMask & ScCharFlags::CharErrConst )
2353 *pSym++ = c;
2354 sal_uInt16 nLevel;
2355 if (!maTableRefs.empty() && ((nLevel = maTableRefs.back().mnLevel) == 2 || nLevel == 1))
2356 eState = ssGetTableRefItem;
2357 else
2358 eState = ssGetErrorConstant;
2360 else if( nMask & ScCharFlags::CharDontCare )
2362 addWhitespace( vSpaces, aSpace, c);
2364 else if( nMask & ScCharFlags::CharIdent )
2365 { // try to get a simple ASCII identifier before calling
2366 // i18n, to gain performance during import
2367 *pSym++ = c;
2368 eState = ssGetIdent;
2370 else
2372 bi18n = true;
2373 eState = ssStop;
2376 break;
2377 case ssGetIdent:
2379 if ( nMask & ScCharFlags::Ident )
2380 { // This catches also $Sheet1.A$1, for example.
2381 if( pSym == &cSymbol[ MAXSTRLEN ] )
2383 SetError(FormulaError::StringOverflow);
2384 eState = ssStop;
2386 else
2387 *pSym++ = c;
2389 else if (c == '#' && lcl_isUnicodeIgnoreAscii( pSrc, "REF!", 4))
2391 // Completely ugly means to catch broken
2392 // [$]#REF!.[$]#REF![$]#REF! (one or multiple parts)
2393 // references that were written in ODF named ranges
2394 // (without embracing [] hence no predetected reference)
2395 // and to OOXML and handle them as one symbol.
2396 // Also catches these in UI, so we can process them
2397 // further.
2398 int i = 0;
2399 for ( ; i<5; ++i)
2401 if( pSym == &cSymbol[ MAXSTRLEN ] )
2403 SetError(FormulaError::StringOverflow);
2404 eState = ssStop;
2405 break; // for
2407 else
2409 *pSym++ = c;
2410 c = *pSrc++;
2413 if (i == 5)
2414 c = *((--pSrc)-1); // position last/next character correctly
2416 else if (c == ':' && mnRangeOpPosInSymbol < 0)
2418 // One range operator may form Sheet1.A:A, which we need to
2419 // pass as one entity to IsReference().
2420 if( pSym == &cSymbol[ MAXSTRLEN ] )
2422 SetError(FormulaError::StringOverflow);
2423 eState = ssStop;
2425 else
2427 mnRangeOpPosInSymbol = pSym - &cSymbol[0];
2428 *pSym++ = c;
2431 else if ( 128 <= c || '\'' == c )
2432 { // High values need reparsing with i18n,
2433 // single quoted $'sheet' names too (otherwise we'd had to
2434 // implement everything twice).
2435 bi18n = true;
2436 eState = ssStop;
2438 else
2440 pSrc--;
2441 eState = ssStop;
2444 break;
2445 case ssGetBool :
2447 if( nMask & ScCharFlags::Bool )
2449 *pSym++ = c;
2450 eState = ssStop;
2452 else
2454 pSrc--;
2455 eState = ssStop;
2458 break;
2459 case ssGetValue :
2461 if( pSym == &cSymbol[ MAXSTRLEN ] )
2463 SetError(FormulaError::StringOverflow);
2464 eState = ssStop;
2466 else if (c == cDecSep || (cDecSepAlt && c == cDecSepAlt))
2468 if (++nDecSeps > 1)
2470 // reparse with i18n, may be numeric sheet name as well
2471 bi18n = true;
2472 eState = ssStop;
2474 else
2475 *pSym++ = c;
2477 else if( nMask & ScCharFlags::Value )
2478 *pSym++ = c;
2479 else if( nMask & ScCharFlags::ValueSep )
2481 pSrc--;
2482 eState = ssStop;
2484 else if (c == 'E' || c == 'e')
2486 if (GetCharTableFlags( pSrc[0], 0 ) & ScCharFlags::ValueExp)
2487 *pSym++ = c;
2488 else
2490 // reparse with i18n
2491 bi18n = true;
2492 eState = ssStop;
2495 else if( nMask & ScCharFlags::ValueSign )
2497 if (((cLast == 'E') || (cLast == 'e')) &&
2498 (GetCharTableFlags( pSrc[0], 0 ) & ScCharFlags::ValueValue))
2500 *pSym++ = c;
2502 else
2504 pSrc--;
2505 eState = ssStop;
2508 else
2510 // reparse with i18n
2511 bi18n = true;
2512 eState = ssStop;
2515 break;
2516 case ssGetString :
2518 if( nMask & ScCharFlags::StringSep )
2520 if ( !bQuote )
2522 if ( *pSrc == '"' )
2523 bQuote = true; // "" => literal "
2524 else
2525 eState = ssStop;
2527 else
2528 bQuote = false;
2530 if ( !bQuote )
2532 if( pSym == &cSymbol[ MAXSTRLEN ] )
2534 SetError(FormulaError::StringOverflow);
2535 eState = ssSkipString;
2537 else
2538 *pSym++ = c;
2541 break;
2542 case ssSkipString:
2543 if( nMask & ScCharFlags::StringSep )
2544 eState = ssStop;
2545 break;
2546 case ssGetErrorConstant:
2548 // ODFF Error ::= '#' [A-Z0-9]+ ([!?] | ('/' ([A-Z] | ([0-9] [!?]))))
2549 // BUT, in UI these may have been translated! So don't
2550 // check for ASCII alnum. Note that this construct can't be
2551 // parsed with i18n.
2552 /* TODO: be strict when reading ODFF, check for ASCII alnum
2553 * and proper continuation after '/'. However, even with
2554 * the lax parsing only the error constants we have defined
2555 * as opcode symbols will be recognized and others result
2556 * in ocBad, so the result is actually conformant. */
2557 bool bAdd = true;
2558 if ('?' == c)
2559 eState = ssStop;
2560 else if ('!' == c)
2562 // Check if this is #REF! that starts an invalid reference.
2563 // Note we have an implicit '!' here at the end.
2564 if (pSym - &cSymbol[0] == 4 && lcl_isUnicodeIgnoreAscii( cSymbol, "#REF", 4) &&
2565 (GetCharTableFlags( *pSrc, c) & ScCharFlags::Ident))
2566 eState = ssGetIdent;
2567 else
2568 eState = ssStop;
2570 else if ('/' == c)
2572 if (!bErrorConstantHadSlash)
2573 bErrorConstantHadSlash = true;
2574 else
2576 bAdd = false;
2577 eState = ssStop;
2580 else if ((nMask & ScCharFlags::WordSep) ||
2581 (c < 128 && !rtl::isAsciiAlphanumeric( c)))
2583 bAdd = false;
2584 eState = ssStop;
2586 if (!bAdd)
2587 --pSrc;
2588 else
2590 if (pSym == &cSymbol[ MAXSTRLEN ])
2592 SetError( FormulaError::StringOverflow);
2593 eState = ssStop;
2595 else
2596 *pSym++ = c;
2599 break;
2600 case ssGetTableRefItem:
2602 // Scan whatever up to the next ']' closer.
2603 if (c != ']')
2605 if( pSym == &cSymbol[ MAXSTRLEN ] )
2607 SetError( FormulaError::StringOverflow);
2608 eState = ssStop;
2610 else
2611 *pSym++ = c;
2613 else
2615 --pSrc;
2616 eState = ssStop;
2619 break;
2620 case ssGetTableRefColumn:
2622 // Scan whatever up to the next unescaped ']' closer.
2623 if (c != ']' || cLast == '\'')
2625 if( pSym == &cSymbol[ MAXSTRLEN ] )
2627 SetError( FormulaError::StringOverflow);
2628 eState = ssStop;
2630 else
2631 *pSym++ = c;
2633 else
2635 --pSrc;
2636 eState = ssStop;
2639 break;
2640 case ssGetReference:
2641 if( pSym == &cSymbol[ MAXSTRLEN ] )
2643 SetError( FormulaError::StringOverflow);
2644 eState = ssSkipReference;
2646 [[fallthrough]];
2647 case ssSkipReference:
2648 // ODF reference: ['External'#$'Sheet'.A1:.B2] with dots being
2649 // mandatory also if no sheet name. 'External'# is optional,
2650 // sheet name is optional, quotes around sheet name are
2651 // optional if no quote contained. [#REF!] is valid.
2652 // 2nd usage: ['Sheet'.$$'DefinedName']
2653 // 3rd usage: ['External'#$$'DefinedName']
2654 // 4th usage: ['External'#$'Sheet'.$$'DefinedName']
2655 // Also for all these names quotes are optional if no quote
2656 // contained.
2659 // nRefInName: 0 := not in sheet name yet. 'External'
2660 // is parsed as if it was a sheet name and nRefInName
2661 // is reset when # is encountered immediately after closing
2662 // quote. Same with 'DefinedName', nRefInName is cleared
2663 // when : is encountered.
2665 // Encountered leading $ before sheet name.
2666 constexpr int kDollar = (1 << 1);
2667 // Encountered ' opening quote, which may be after $ or
2668 // not.
2669 constexpr int kOpen = (1 << 2);
2670 // Somewhere in name.
2671 constexpr int kName = (1 << 3);
2672 // Encountered ' in name, will be cleared if double or
2673 // transformed to kClose if not, in which case kOpen is
2674 // cleared.
2675 constexpr int kQuote = (1 << 4);
2676 // Past ' closing quote.
2677 constexpr int kClose = (1 << 5);
2678 // Encountered # file/sheet separator.
2679 constexpr int kFileSep = (1 << 6);
2680 // Past . sheet name separator.
2681 constexpr int kPast = (1 << 7);
2682 // Marked name $$ follows sheet name separator, detected
2683 // while we're still on the separator. Will be cleared when
2684 // entering the name.
2685 constexpr int kMarkAhead = (1 << 8);
2686 // In marked defined name.
2687 constexpr int kDefName = (1 << 9);
2688 // Encountered # of #REF!
2689 constexpr int kRefErr = (1 << 10);
2691 bool bAddToSymbol = true;
2692 if ((nMask & ScCharFlags::OdfRBracket) && !(nRefInName & kOpen))
2694 OSL_ENSURE( nRefInName & (kPast | kDefName | kRefErr),
2695 "ScCompiler::NextSymbol: reference: "
2696 "closing bracket ']' without prior sheet name separator '.' violates ODF spec");
2697 // eaten, not added to pSym
2698 bAddToSymbol = false;
2699 eState = ssStop;
2701 else if (cSheetSep == c && nRefInName == 0)
2703 // eat it, no sheet name [.A1]
2704 bAddToSymbol = false;
2705 nRefInName |= kPast;
2706 if ('$' == pSrc[0] && '$' == pSrc[1])
2707 nRefInName |= kMarkAhead;
2709 else if (!(nRefInName & kPast) || (nRefInName & (kMarkAhead | kDefName)))
2711 // Not in col/row yet.
2713 if (SC_COMPILER_FILE_TAB_SEP == c && (nRefInName & kFileSep))
2714 nRefInName = 0;
2715 else if ('$' == c && '$' == pSrc[0] && !(nRefInName & kOpen))
2717 nRefInName &= ~kMarkAhead;
2718 if (!(nRefInName & kDefName))
2720 // eaten, not added to pSym (2 chars)
2721 bAddToSymbol = false;
2722 ++pSrc;
2723 nRefInName &= kPast;
2724 nRefInName |= kDefName;
2726 else
2728 // ScAddress::Parse() will recognize this as
2729 // invalid later.
2730 if (eState != ssSkipReference)
2732 *pSym++ = c;
2734 if( pSym == &cSymbol[ MAXSTRLEN ] )
2736 SetError( FormulaError::StringOverflow);
2737 eState = ssStop;
2739 else
2740 *pSym++ = *pSrc++;
2742 bAddToSymbol = false;
2745 else if (cSheetPrefix == c && nRefInName == 0)
2746 nRefInName |= kDollar;
2747 else if ('\'' == c)
2749 // TODO: The conventions' parseExternalName()
2750 // should handle quoted names, but as long as they
2751 // don't remove non-embedded quotes here.
2752 if (!(nRefInName & kName))
2754 nRefInName |= (kOpen | kName);
2755 bAddToSymbol = !(nRefInName & kDefName);
2757 else if (!(nRefInName & kOpen))
2759 OSL_FAIL("ScCompiler::NextSymbol: reference: "
2760 "a ''' without the name being enclosed in '...' violates ODF spec");
2762 else if (nRefInName & kQuote)
2764 // escaped embedded quote
2765 nRefInName &= ~kQuote;
2767 else
2769 switch (pSrc[0])
2771 case '\'':
2772 // escapes embedded quote
2773 nRefInName |= kQuote;
2774 break;
2775 case SC_COMPILER_FILE_TAB_SEP:
2776 // sheet name should follow
2777 nRefInName |= kFileSep;
2778 [[fallthrough]];
2779 default:
2780 // quote not followed by quote => close
2781 nRefInName |= kClose;
2782 nRefInName &= ~kOpen;
2784 bAddToSymbol = !(nRefInName & kDefName);
2787 else if ('#' == c && nRefInName == 0)
2788 nRefInName |= kRefErr;
2789 else if (cSheetSep == c && !(nRefInName & kOpen))
2791 // unquoted sheet name separator
2792 nRefInName |= kPast;
2793 if ('$' == pSrc[0] && '$' == pSrc[1])
2794 nRefInName |= kMarkAhead;
2796 else if (':' == c && !(nRefInName & kOpen))
2798 OSL_FAIL("ScCompiler::NextSymbol: reference: "
2799 "range operator ':' without prior sheet name separator '.' violates ODF spec");
2800 nRefInName = 0;
2801 ++mnPredetectedReference;
2803 else if (!(nRefInName & kName))
2805 // start unquoted name
2806 nRefInName |= kName;
2809 else if (':' == c)
2811 // range operator
2812 nRefInName = 0;
2813 ++mnPredetectedReference;
2815 if (bAddToSymbol && eState != ssSkipReference)
2816 *pSym++ = c; // everything is part of reference
2818 break;
2819 case ssStop:
2820 ; // nothing, prevent warning
2821 break;
2823 cLast = c;
2824 c = *pSrc;
2827 if (aSpace.nCount && aSpace.cChar)
2828 vSpaces.emplace_back(aSpace);
2830 if ( bi18n )
2832 const sal_Int32 nOldSrcPos = nSrcPos;
2833 for (const auto& r : vSpaces)
2834 nSrcPos += r.nCount;
2835 // If group separator is not a possible operator and not one of any
2836 // separators then it may be parsed away in numbers. This is
2837 // specifically the case with NO-BREAK SPACE, which actually triggers
2838 // the bi18n case (which we don't want to include as yet another
2839 // special case above as it is rare enough and doesn't generally occur
2840 // in formulas).
2841 const sal_Unicode cGroupSep = ScGlobal::getLocaleData().getNumThousandSep()[0];
2842 const bool bGroupSeparator = (128 <= cGroupSep && cGroupSep != cSep &&
2843 cGroupSep != cArrayColSep && cGroupSep != cArrayRowSep &&
2844 cGroupSep != cDecSep && cGroupSep != cDecSepAlt &&
2845 cGroupSep != cSheetPrefix && cGroupSep != cSheetSep);
2846 // If a numeric context triggered bi18n then use the default locale's
2847 // CharClass, this may accept group separator as well.
2848 const CharClass* pMyCharClass = (ScGlobal::getCharClass().isDigit( OUString(pStart[nSrcPos]), 0) ?
2849 &ScGlobal::getCharClass() : pCharClass);
2850 OUStringBuffer aSymbol;
2851 mnRangeOpPosInSymbol = -1;
2852 FormulaError nErr = FormulaError::NONE;
2855 bi18n = false;
2856 // special case (e.g. $'sheetname' in OOO A1)
2857 if ( pStart[nSrcPos] == cSheetPrefix && pStart[nSrcPos+1] == '\'' )
2858 aSymbol.append(pStart[nSrcPos++]);
2860 ParseResult aRes = pConv->parseAnyToken( aFormula, nSrcPos, pMyCharClass, bGroupSeparator);
2862 if ( !aRes.TokenType )
2864 nErr = FormulaError::IllegalChar;
2865 SetError( nErr ); // parsed chars as string
2867 if ( aRes.EndPos <= nSrcPos )
2869 // Could not parse anything meaningful.
2870 assert(!aRes.TokenType);
2871 nErr = FormulaError::IllegalChar;
2872 SetError( nErr );
2873 // Caller has to act on an empty symbol for
2874 // nSrcPos < aFormula.getLength()
2875 nSrcPos = nOldSrcPos;
2876 aSymbol.setLength(0);
2878 else
2880 // When having parsed a second reference part, ensure that the
2881 // i18n parser did not mistakenly parse a number that included
2882 // a separator which happened to be meant as a parameter
2883 // separator instead.
2884 if (mnRangeOpPosInSymbol >= 0 && (aRes.TokenType & KParseType::ASC_NUMBER))
2886 for (sal_Int32 i = nSrcPos; i < aRes.EndPos; ++i)
2888 if (pStart[i] == cSep)
2889 aRes.EndPos = i; // also ends for
2892 aSymbol.append( pStart + nSrcPos, aRes.EndPos - nSrcPos);
2893 nSrcPos = aRes.EndPos;
2894 c = pStart[nSrcPos];
2895 if ( aRes.TokenType & KParseType::SINGLE_QUOTE_NAME )
2896 { // special cases (e.g. 'sheetname'. or 'filename'# in OOO A1)
2897 bi18n = (c == cSheetSep || c == SC_COMPILER_FILE_TAB_SEP);
2899 // One range operator restarts parsing for second reference.
2900 if (c == ':' && mnRangeOpPosInSymbol < 0)
2902 mnRangeOpPosInSymbol = aSymbol.getLength();
2903 bi18n = true;
2905 if ( bi18n )
2906 aSymbol.append(pStart[nSrcPos++]);
2908 } while ( bi18n && nErr == FormulaError::NONE );
2909 sal_Int32 nLen = aSymbol.getLength();
2910 if ( nLen > MAXSTRLEN )
2912 SetError( FormulaError::StringOverflow );
2913 nLen = MAXSTRLEN;
2915 if (mnRangeOpPosInSymbol >= nLen)
2916 mnRangeOpPosInSymbol = -1;
2917 lcl_UnicodeStrNCpy( cSymbol, aSymbol.getStr(), nLen );
2918 pSym = &cSymbol[nLen];
2920 else
2922 nSrcPos = pSrc - pStart;
2923 *pSym = 0;
2925 if (mnRangeOpPosInSymbol >= 0 && mnRangeOpPosInSymbol == (pSym-1) - &cSymbol[0])
2927 // This is a trailing range operator, which is nonsense. Will be caught
2928 // in next round.
2929 mnRangeOpPosInSymbol = -1;
2930 *--pSym = 0;
2931 --nSrcPos;
2933 if ( bAutoCorrect )
2934 aCorrectedSymbol = OUString(cSymbol, pSym - cSymbol);
2935 if (bAutoIntersection && vSpaces[nAutoIntersectionSpacesPos].nCount > 1)
2936 --vSpaces[nAutoIntersectionSpacesPos].nCount; // replace '!!' with only one space
2937 return vSpaces;
2940 // Convert symbol to token
2942 bool ScCompiler::ParseOpCode( const OUString& rName, bool bInArray )
2944 OpCodeHashMap::const_iterator iLook( mxSymbols->getHashMap().find( rName));
2945 bool bFound = (iLook != mxSymbols->getHashMap().end());
2946 if (bFound)
2948 OpCode eOp = iLook->second;
2949 if (bInArray)
2951 if (rName == mxSymbols->getSymbol(ocArrayColSep))
2952 eOp = ocArrayColSep;
2953 else if (rName == mxSymbols->getSymbol(ocArrayRowSep))
2954 eOp = ocArrayRowSep;
2956 else if (eOp == ocArrayColSep || eOp == ocArrayRowSep)
2958 if (rName == mxSymbols->getSymbol(ocSep))
2959 eOp = ocSep;
2960 else if (rName == ";")
2962 switch (FormulaGrammar::extractFormulaLanguage( meGrammar))
2964 // Only for languages/grammars that actually use ';'
2965 // parameter separator.
2966 case css::sheet::FormulaLanguage::NATIVE:
2967 case css::sheet::FormulaLanguage::ENGLISH:
2968 case css::sheet::FormulaLanguage::ODFF:
2969 case css::sheet::FormulaLanguage::ODF_11:
2970 eOp = ocSep;
2974 else if (eOp == ocCeil && mxSymbols->isOOXML())
2976 // Ensure that _xlfn.CEILING.MATH maps to ocCeil_Math. ocCeil is
2977 // unassigned for import.
2978 eOp = ocCeil_Math;
2980 else if (eOp == ocFloor && mxSymbols->isOOXML())
2982 // Ensure that _xlfn.FLOOR.MATH maps to ocFloor_Math. ocFloor is
2983 // unassigned for import.
2984 eOp = ocFloor_Math;
2986 maRawToken.SetOpCode(eOp);
2988 else if (mxSymbols->isODFF())
2990 // ODFF names that are not written in the current mapping but to be
2991 // recognized. New names will be written in a future release, then
2992 // exchange (!) with the names in
2993 // formula/source/core/resource/core_resource.src to be able to still
2994 // read the old names as well.
2995 struct FunctionName
2997 const char* pName;
2998 OpCode eOp;
3000 static const FunctionName aOdffAliases[] = {
3001 // Renamed old names, still accept them:
3002 { "B", ocB }, // B -> BINOM.DIST.RANGE
3003 { "TDIST", ocTDist }, // TDIST -> LEGACY.TDIST
3004 { "ORG.OPENOFFICE.EASTERSUNDAY", ocEasterSunday }, // ORG.OPENOFFICE.EASTERSUNDAY -> EASTERSUNDAY
3005 { "ZGZ", ocRRI }, // ZGZ -> RRI
3006 { "COLOR", ocColor }, // COLOR -> ORG.LIBREOFFICE.COLOR
3007 { "GOALSEEK", ocBackSolver }, // GOALSEEK -> ORG.OPENOFFICE.GOALSEEK
3008 { "COM.MICROSOFT.F.DIST", ocFDist_LT }, // fdo#40835, -> FDIST -> COM.MICROSOFT.F.DIST
3009 { "COM.MICROSOFT.F.INV", ocFInv_LT } // tdf#94214, COM.MICROSOFT.F.INV -> FINV (ODF)
3010 // Renamed new names, prepare to read future names:
3011 //{ "ORG.OPENOFFICE.XXX", ocXXX } // XXX -> ORG.OPENOFFICE.XXX
3013 for (const FunctionName& rOdffAlias : aOdffAliases)
3015 if (rName.equalsIgnoreAsciiCaseAscii( rOdffAlias.pName))
3017 maRawToken.SetOpCode( rOdffAlias.eOp);
3018 bFound = true;
3019 break; // for
3023 else if (mxSymbols->isOOXML())
3025 // OOXML names that are not written in the current mapping but to be
3026 // recognized as old versions wrote them.
3027 struct FunctionName
3029 const char* pName;
3030 OpCode eOp;
3032 static const FunctionName aOoxmlAliases[] = {
3033 { "EFFECTIVE", ocEffect }, // EFFECTIVE -> EFFECT
3034 { "ERRORTYPE", ocErrorType }, // ERRORTYPE -> _xlfn.ORG.OPENOFFICE.ERRORTYPE
3035 { "MULTIRANGE", ocMultiArea }, // MULTIRANGE -> _xlfn.ORG.OPENOFFICE.MULTIRANGE
3036 { "GOALSEEK", ocBackSolver }, // GOALSEEK -> _xlfn.ORG.OPENOFFICE.GOALSEEK
3037 { "EASTERSUNDAY", ocEasterSunday }, // EASTERSUNDAY -> _xlfn.ORG.OPENOFFICE.EASTERSUNDAY
3038 { "CURRENT", ocCurrent }, // CURRENT -> _xlfn.ORG.OPENOFFICE.CURRENT
3039 { "STYLE", ocStyle } // STYLE -> _xlfn.ORG.OPENOFFICE.STYLE
3041 for (const FunctionName& rOoxmlAlias : aOoxmlAliases)
3043 if (rName.equalsIgnoreAsciiCaseAscii( rOoxmlAlias.pName))
3045 maRawToken.SetOpCode( rOoxmlAlias.eOp);
3046 bFound = true;
3047 break; // for
3051 else if (mxSymbols->isPODF())
3053 // PODF names are ODF 1.0/1.1 and also used in API XFunctionAccess.
3054 // We can't rename them in
3055 // formula/source/core/resource/core_resource.src but can add
3056 // additional names to be recognized here so they match the UI names if
3057 // those are renamed.
3058 struct FunctionName
3060 const char* pName;
3061 OpCode eOp;
3063 static const FunctionName aPodfAliases[] = {
3064 { "EFFECT", ocEffect } // EFFECTIVE -> EFFECT
3066 for (const FunctionName& rPodfAlias : aPodfAliases)
3068 if (rName.equalsIgnoreAsciiCaseAscii( rPodfAlias.pName))
3070 maRawToken.SetOpCode( rPodfAlias.eOp);
3071 bFound = true;
3072 break; // for
3077 if (!bFound)
3079 OUString aIntName;
3080 if (mxSymbols->hasExternals())
3082 // If symbols are set by filters get mapping to exact name.
3083 ExternalHashMap::const_iterator iExt(
3084 mxSymbols->getExternalHashMap().find( rName));
3085 if (iExt != mxSymbols->getExternalHashMap().end())
3087 if (ScGlobal::GetAddInCollection()->GetFuncData( (*iExt).second))
3088 aIntName = (*iExt).second;
3091 else
3093 // Old (deprecated) addins first for legacy.
3094 if (ScGlobal::GetLegacyFuncCollection()->findByName(OUString(cSymbol)))
3096 aIntName = cSymbol;
3098 else
3099 // bLocalFirst=false for (English) upper full original name
3100 // (service.function)
3101 aIntName = ScGlobal::GetAddInCollection()->FindFunction(
3102 rName, !mxSymbols->isEnglish());
3104 if (!aIntName.isEmpty())
3106 maRawToken.SetExternal( aIntName ); // international name
3107 bFound = true;
3110 if (!bFound)
3111 return false;
3112 OpCode eOp = maRawToken.GetOpCode();
3113 if (eOp == ocSub || eOp == ocNegSub)
3115 bool bShouldBeNegSub =
3116 (eLastOp == ocOpen || eLastOp == ocSep || eLastOp == ocNegSub ||
3117 (SC_OPCODE_START_BIN_OP <= eLastOp && eLastOp < SC_OPCODE_STOP_BIN_OP) ||
3118 eLastOp == ocArrayOpen ||
3119 eLastOp == ocArrayColSep || eLastOp == ocArrayRowSep);
3120 if (bShouldBeNegSub && eOp == ocSub)
3121 maRawToken.NewOpCode( ocNegSub );
3122 //TODO: if ocNegSub had ForceArray we'd have to set it here
3123 else if (!bShouldBeNegSub && eOp == ocNegSub)
3124 maRawToken.NewOpCode( ocSub );
3126 return bFound;
3129 bool ScCompiler::ParseOpCode2( std::u16string_view rName )
3131 for (sal_uInt16 i = ocInternalBegin; i <= ocInternalEnd; i++)
3133 if (o3tl::equalsAscii(rName, pInternal[i - ocInternalBegin]))
3135 maRawToken.SetOpCode(static_cast<OpCode>(i));
3136 return true;
3140 return false;
3143 static bool lcl_ParenthesisFollows( const sal_Unicode* p )
3145 while (*p == ' ')
3146 p++;
3147 return *p == '(';
3150 bool ScCompiler::ParseValue( const OUString& rSym )
3152 const sal_Int32 nFormulaLanguage = FormulaGrammar::extractFormulaLanguage( GetGrammar());
3153 if (nFormulaLanguage == css::sheet::FormulaLanguage::ODFF || nFormulaLanguage == css::sheet::FormulaLanguage::OOXML)
3155 // Speedup things for ODFF, only well-formed numbers, not locale
3156 // dependent nor user input.
3157 rtl_math_ConversionStatus eStatus;
3158 sal_Int32 nParseEnd;
3159 double fVal = rtl::math::stringToDouble( rSym, '.', 0, &eStatus, &nParseEnd);
3160 if (nParseEnd != rSym.getLength())
3162 // Not (only) a number.
3164 if (nParseEnd > 0)
3165 return false; // partially a number => no such thing
3167 if (lcl_ParenthesisFollows( aFormula.getStr() + nSrcPos))
3168 return false; // some function name, not a constant
3170 // Could be TRUE or FALSE constant.
3171 OpCode eOpFunc = ocNone;
3172 if (rSym.equalsIgnoreAsciiCase("TRUE"))
3173 eOpFunc = ocTrue;
3174 else if (rSym.equalsIgnoreAsciiCase("FALSE"))
3175 eOpFunc = ocFalse;
3176 if (eOpFunc != ocNone)
3178 maRawToken.SetOpCode(eOpFunc);
3179 // add missing trailing parentheses
3180 maPendingOpCodes.push(ocOpen);
3181 maPendingOpCodes.push(ocClose);
3182 return true;
3184 return false;
3186 if (eStatus == rtl_math_ConversionStatus_OutOfRange)
3188 // rtl::math::stringToDouble() recognizes XMLSchema-2 "INF" and
3189 // "NaN" (case sensitive) that could be named expressions or DB
3190 // areas as well.
3191 // rSym is already upper so "NaN" is not possible here.
3192 if (!std::isfinite(fVal) && rSym == "INF")
3194 SCTAB nSheet = -1;
3195 if (GetRangeData( nSheet, rSym))
3196 return false;
3197 if (rDoc.GetDBCollection()->getNamedDBs().findByUpperName(rSym))
3198 return false;
3200 /* TODO: is there a specific reason why we don't accept an infinity
3201 * value that would raise an error in the interpreter, instead of
3202 * setting the hard error at the token array already? */
3203 SetError( FormulaError::IllegalArgument );
3205 maRawToken.SetDouble( fVal );
3206 return true;
3209 double fVal;
3210 sal_uInt32 nIndex = mxSymbols->isEnglishLocale() ? mrInterpreterContext.NFGetStandardIndex(LANGUAGE_ENGLISH_US) : 0;
3212 if (!mrInterpreterContext.NFIsNumberFormat(rSym, nIndex, fVal))
3213 return false;
3215 SvNumFormatType nType = mrInterpreterContext.NFGetType(nIndex);
3217 // Don't accept 3:3 as time, it is a reference to entire row 3 instead.
3218 // Dates should never be entered directly and automatically converted
3219 // to serial, because the serial would be wrong if null-date changed.
3220 // Usually it wouldn't be accepted anyway because the date separator
3221 // clashed with other separators or operators.
3222 if (nType & (SvNumFormatType::TIME | SvNumFormatType::DATE))
3223 return false;
3225 if (nType == SvNumFormatType::LOGICAL)
3227 if (lcl_ParenthesisFollows( aFormula.getStr() + nSrcPos))
3228 return false; // Boolean function instead.
3231 if( nType == SvNumFormatType::TEXT )
3232 // HACK: number too big!
3233 SetError( FormulaError::IllegalArgument );
3234 maRawToken.SetDouble( fVal );
3235 return true;
3238 bool ScCompiler::ParseString()
3240 if ( cSymbol[0] != '"' )
3241 return false;
3242 const sal_Unicode* p = cSymbol+1;
3243 while ( *p )
3244 p++;
3245 sal_Int32 nLen = sal::static_int_cast<sal_Int32>( p - cSymbol - 1 );
3246 if (!nLen || cSymbol[nLen] != '"')
3247 return false;
3248 svl::SharedString aSS = rDoc.GetSharedStringPool().intern(OUString(cSymbol+1, nLen-1));
3249 maRawToken.SetString(aSS.getData(), aSS.getDataIgnoreCase());
3250 return true;
3253 bool ScCompiler::ParsePredetectedErrRefReference( const OUString& rName, const OUString* pErrRef )
3255 switch (mnPredetectedReference)
3257 case 1:
3258 return ParseSingleReference( rName, pErrRef);
3259 case 2:
3260 return ParseDoubleReference( rName, pErrRef);
3261 default:
3262 return false;
3266 bool ScCompiler::ParsePredetectedReference( const OUString& rName )
3268 // Speedup documents with lots of broken references, e.g. sheet deleted.
3269 // It could also be a broken invalidated reference that contains #REF!
3270 // (but is not equal to), which we wrote prior to ODFF and also to ODFF
3271 // between 2013 and 2016 until 5.1.4
3272 static constexpr OUString aErrRef(u"#REF!"_ustr); // not localized in ODFF
3273 sal_Int32 nPos = rName.indexOf( aErrRef);
3274 if (nPos != -1)
3276 /* TODO: this may be enhanced by reusing scan information from
3277 * NextSymbol(), the positions of quotes and special characters found
3278 * there for $'sheet'.A1:... could be stored in a vector. We don't
3279 * fully rescan here whether found positions are within single quotes
3280 * for performance reasons. This code does not check for possible
3281 * occurrences of insane "valid" sheet names like
3282 * 'haha.#REF!1fooledyou' and will generate an error on such. */
3283 if (nPos == 0)
3285 // Per ODFF the correct string for a reference error is just #REF!,
3286 // so pass it on.
3287 if (rName.getLength() == 5)
3288 return ParseErrorConstant( rName);
3289 // #REF!.AB42 or #REF!42 or #REF!#REF!
3290 return ParsePredetectedErrRefReference( rName, &aErrRef);
3292 sal_Unicode c = rName[nPos-1]; // before #REF!
3293 if ('$' == c)
3295 if (nPos == 1)
3297 // $#REF!.AB42 or $#REF!42 or $#REF!#REF!
3298 return ParsePredetectedErrRefReference( rName, &aErrRef);
3300 c = rName[nPos-2]; // before $#REF!
3302 sal_Unicode c2 = nPos+5 < rName.getLength() ? rName[nPos+5] : 0; // after #REF!
3303 switch (c)
3305 case '.':
3306 if ('$' == c2 || '#' == c2 || ('0' <= c2 && c2 <= '9'))
3308 // sheet.#REF!42 or sheet.#REF!#REF!
3309 return ParsePredetectedErrRefReference( rName, &aErrRef);
3311 break;
3312 case ':':
3313 if (mnPredetectedReference > 1 &&
3314 ('.' == c2 || '$' == c2 || '#' == c2 ||
3315 ('0' <= c2 && c2 <= '9')))
3317 // :#REF!.AB42 or :#REF!42 or :#REF!#REF!
3318 return ParsePredetectedErrRefReference( rName, &aErrRef);
3320 break;
3321 default:
3322 if (rtl::isAsciiAlpha(c) &&
3323 ((mnPredetectedReference > 1 && ':' == c2) || 0 == c2))
3325 // AB#REF!: or AB#REF!
3326 return ParsePredetectedErrRefReference( rName, &aErrRef);
3330 switch (mnPredetectedReference)
3332 case 1:
3333 return ParseSingleReference( rName);
3334 case 2:
3335 return ParseDoubleReference( rName);
3337 return false;
3340 bool ScCompiler::ParseDoubleReference( const OUString& rName, const OUString* pErrRef )
3342 ScRange aRange( aPos, aPos );
3343 const ScAddress::Details aDetails( pConv->meConv, aPos );
3344 ScAddress::ExternalInfo aExtInfo;
3345 ScRefFlags nFlags = aRange.Parse( rName, rDoc, aDetails, &aExtInfo, &maExternalLinks, pErrRef );
3346 if( nFlags & ScRefFlags::VALID )
3348 ScComplexRefData aRef;
3349 aRef.InitRange( aRange );
3350 aRef.Ref1.SetColRel( (nFlags & ScRefFlags::COL_ABS) == ScRefFlags::ZERO );
3351 aRef.Ref1.SetRowRel( (nFlags & ScRefFlags::ROW_ABS) == ScRefFlags::ZERO );
3352 aRef.Ref1.SetTabRel( (nFlags & ScRefFlags::TAB_ABS) == ScRefFlags::ZERO );
3353 if ( !(nFlags & ScRefFlags::TAB_VALID) )
3354 aRef.Ref1.SetTabDeleted( true ); // #REF!
3355 aRef.Ref1.SetFlag3D( ( nFlags & ScRefFlags::TAB_3D ) != ScRefFlags::ZERO );
3356 aRef.Ref2.SetColRel( (nFlags & ScRefFlags::COL2_ABS) == ScRefFlags::ZERO );
3357 aRef.Ref2.SetRowRel( (nFlags & ScRefFlags::ROW2_ABS) == ScRefFlags::ZERO );
3358 aRef.Ref2.SetTabRel( (nFlags & ScRefFlags::TAB2_ABS) == ScRefFlags::ZERO );
3359 if ( !(nFlags & ScRefFlags::TAB2_VALID) )
3360 aRef.Ref2.SetTabDeleted( true ); // #REF!
3361 aRef.Ref2.SetFlag3D( ( nFlags & ScRefFlags::TAB2_3D ) != ScRefFlags::ZERO );
3362 aRef.SetRange(rDoc.GetSheetLimits(), aRange, aPos);
3363 if (aExtInfo.mbExternal)
3365 ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager();
3366 const OUString* pRealTab = pRefMgr->getRealTableName(aExtInfo.mnFileId, aExtInfo.maTabName);
3367 maRawToken.SetExternalDoubleRef(
3368 aExtInfo.mnFileId, pRealTab ? *pRealTab : aExtInfo.maTabName, aRef);
3369 maExternalFiles.push_back(aExtInfo.mnFileId);
3371 else
3373 maRawToken.SetDoubleReference(aRef);
3377 return ( nFlags & ScRefFlags::VALID ) != ScRefFlags::ZERO;
3380 bool ScCompiler::ParseSingleReference( const OUString& rName, const OUString* pErrRef )
3382 mnCurrentSheetEndPos = 0;
3383 mnCurrentSheetTab = -1;
3384 ScAddress aAddr( aPos );
3385 const ScAddress::Details aDetails( pConv->meConv, aPos );
3386 ScAddress::ExternalInfo aExtInfo;
3387 ScRefFlags nFlags = aAddr.Parse( rName, rDoc, aDetails,
3388 &aExtInfo, &maExternalLinks, &mnCurrentSheetEndPos, pErrRef);
3389 // Something must be valid in order to recognize Sheet1.blah or blah.a1
3390 // as a (wrong) reference.
3391 if( nFlags & ( ScRefFlags::COL_VALID|ScRefFlags::ROW_VALID|ScRefFlags::TAB_VALID ) )
3393 // Valid given tab and invalid col or row may indicate a sheet-local
3394 // named expression, bail out early and don't create a reference token.
3395 if (!(nFlags & ScRefFlags::VALID) && mnCurrentSheetEndPos > 0 &&
3396 (nFlags & ScRefFlags::TAB_VALID) && (nFlags & ScRefFlags::TAB_3D))
3398 if (aExtInfo.mbExternal)
3400 // External names are handled separately.
3401 mnCurrentSheetEndPos = 0;
3402 mnCurrentSheetTab = -1;
3404 else
3406 mnCurrentSheetTab = aAddr.Tab();
3408 return false;
3411 if( HasPossibleNamedRangeConflict( aAddr.Tab()))
3413 // A named range named e.g. 'num1' is valid with 1k columns, but would become a reference
3414 // when the document is opened later with 16k columns. Resolve the conflict by not
3415 // considering it a reference.
3416 OUString aUpper( ScGlobal::getCharClass().uppercase( rName ));
3417 mnCurrentSheetTab = aAddr.Tab(); // temporarily set for ParseNamedRange()
3418 if(ParseNamedRange( aUpper, true )) // only check
3419 return false;
3420 mnCurrentSheetTab = -1;
3423 ScSingleRefData aRef;
3424 aRef.InitAddress( aAddr );
3425 aRef.SetColRel( (nFlags & ScRefFlags::COL_ABS) == ScRefFlags::ZERO );
3426 aRef.SetRowRel( (nFlags & ScRefFlags::ROW_ABS) == ScRefFlags::ZERO );
3427 aRef.SetTabRel( (nFlags & ScRefFlags::TAB_ABS) == ScRefFlags::ZERO );
3428 aRef.SetFlag3D( ( nFlags & ScRefFlags::TAB_3D ) != ScRefFlags::ZERO );
3429 // the reference is really invalid
3430 if( !( nFlags & ScRefFlags::VALID ) )
3432 if( !( nFlags & ScRefFlags::COL_VALID ) )
3433 aRef.SetColDeleted(true);
3434 if( !( nFlags & ScRefFlags::ROW_VALID ) )
3435 aRef.SetRowDeleted(true);
3436 if( !( nFlags & ScRefFlags::TAB_VALID ) )
3437 aRef.SetTabDeleted(true);
3438 nFlags |= ScRefFlags::VALID;
3440 aRef.SetAddress(rDoc.GetSheetLimits(), aAddr, aPos);
3442 if (aExtInfo.mbExternal)
3444 ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager();
3445 const OUString* pRealTab = pRefMgr->getRealTableName(aExtInfo.mnFileId, aExtInfo.maTabName);
3446 maRawToken.SetExternalSingleRef(
3447 aExtInfo.mnFileId, pRealTab ? *pRealTab : aExtInfo.maTabName, aRef);
3448 maExternalFiles.push_back(aExtInfo.mnFileId);
3450 else
3451 maRawToken.SetSingleReference(aRef);
3454 return ( nFlags & ScRefFlags::VALID ) != ScRefFlags::ZERO;
3457 bool ScCompiler::ParseReference( const OUString& rName, const OUString* pErrRef )
3459 // Has to be called before ParseValue
3461 // A later ParseNamedRange() relies on these, being set in ParseSingleReference()
3462 // if so, reset in all cases.
3463 mnCurrentSheetEndPos = 0;
3464 mnCurrentSheetTab = -1;
3466 sal_Unicode ch1 = rName[0];
3467 sal_Unicode cDecSep = ( mxSymbols->isEnglishLocale() ? '.' : ScGlobal::getLocaleData().getNumDecimalSep()[0] );
3468 if ( ch1 == cDecSep )
3469 return false;
3470 // Code further down checks only if cDecSep=='.' so simply obtaining the
3471 // alternative decimal separator if it's not is sufficient.
3472 if (cDecSep != '.')
3474 cDecSep = ScGlobal::getLocaleData().getNumDecimalSepAlt().toChar();
3475 if ( ch1 == cDecSep )
3476 return false;
3478 // Who was that imbecile introducing '.' as the sheet name separator!?!
3479 if ( rtl::isAsciiDigit( ch1 ) && pConv->getSpecialSymbol( Convention::SHEET_SEPARATOR) == '.' )
3481 // Numerical sheet name is valid.
3482 // But English 1.E2 or 1.E+2 is value 100, 1.E-2 is 0.01
3483 // Don't create a #REF! of values. But also do not bail out on
3484 // something like 3:3, meaning entire row 3.
3487 const sal_Int32 nPos = ScGlobal::FindUnquoted( rName, '.');
3488 if ( nPos == -1 )
3490 if (ScGlobal::FindUnquoted( rName, ':') != -1)
3491 break; // may be 3:3, continue as usual
3492 return false;
3494 sal_Unicode const * const pTabSep = rName.getStr() + nPos;
3495 sal_Unicode ch2 = pTabSep[1]; // maybe a column identifier
3496 if ( !(ch2 == '$' || rtl::isAsciiAlpha( ch2 )) )
3497 return false;
3498 if ( cDecSep == '.' && (ch2 == 'E' || ch2 == 'e') // E + - digit
3499 && (GetCharTableFlags( pTabSep[2], pTabSep[1] ) & ScCharFlags::ValueExp) )
3501 // If it is an 1.E2 expression check if "1" is an existent sheet
3502 // name. If so, a desired value 1.E2 would have to be entered as
3503 // 1E2 or 1.0E2 or 1.E+2, sorry. Another possibility would be to
3504 // require numerical sheet names always being entered quoted, which
3505 // is not desirable (too many 1999, 2000, 2001 sheets in use).
3506 // Furthermore, XML files created with versions prior to SRC640e
3507 // wouldn't contain the quotes added by MakeTabStr()/CheckTabQuotes()
3508 // and would produce wrong formulas if the conditions here are met.
3509 // If you can live with these restrictions you may remove the
3510 // check and return an unconditional FALSE.
3511 OUString aTabName( rName.copy( 0, nPos ) );
3512 SCTAB nTab;
3513 if ( !rDoc.GetTable( aTabName, nTab ) )
3514 return false;
3515 // If sheet "1" exists and the expression is 1.E+2 continue as
3516 // usual, the ScRange/ScAddress parser will take care of it.
3518 } while(false);
3521 if (ParseSingleReference( rName, pErrRef))
3522 return true;
3524 // Though the range operator is handled explicitly, when encountering
3525 // something like Sheet1.A:A we will have to treat it as one entity if it
3526 // doesn't pass as single cell reference.
3527 if (mnRangeOpPosInSymbol > 0) // ":foo" would be nonsense
3529 if (ParseDoubleReference( rName, pErrRef))
3530 return true;
3531 // Now try with a symbol up to the range operator, rewind source
3532 // position.
3533 assert(mnRangeOpPosInSymbol < MAXSTRLEN); // We should have caught the maldoers.
3534 if (mnRangeOpPosInSymbol >= MAXSTRLEN) // TODO: this check and return
3535 return false; // can be removed when sure.
3536 sal_Int32 nLen = mnRangeOpPosInSymbol;
3537 while (cSymbol[++nLen])
3539 cSymbol[mnRangeOpPosInSymbol] = 0;
3540 nSrcPos -= (nLen - mnRangeOpPosInSymbol);
3541 mnRangeOpPosInSymbol = -1;
3542 mbRewind = true;
3543 return true; // end all checks
3545 else
3547 switch (pConv->meConv)
3549 case FormulaGrammar::CONV_XL_A1:
3550 case FormulaGrammar::CONV_XL_OOX:
3551 // Special treatment for the 'E:\[doc]Sheet1:Sheet3'!D5 Excel
3552 // sickness, mnRangeOpPosInSymbol did not catch the range
3553 // operator as it is within a quoted name.
3554 if (rName[0] != '\'')
3555 return false; // Document name has to be single quoted.
3556 [[fallthrough]];
3557 case FormulaGrammar::CONV_XL_R1C1:
3558 // C2 or C[1] are valid entire column references.
3559 if (ParseDoubleReference( rName, pErrRef))
3560 return true;
3561 break;
3562 default:
3563 ; // nothing
3566 return false;
3569 bool ScCompiler::ParseMacro( const OUString& rName )
3571 #if !HAVE_FEATURE_SCRIPTING
3572 (void) rName;
3574 return false;
3575 #else
3577 // Calling SfxObjectShell::GetBasic() may result in all sort of things
3578 // including obtaining the model and deep down in
3579 // SfxBaseModel::getDocumentStorage() acquiring the SolarMutex, which when
3580 // formulas are compiled from a threaded import may result in a deadlock.
3581 // Check first if we actually could acquire it and if not bail out.
3582 /* FIXME: yes, but how ... */
3583 vcl::SolarMutexTryAndBuyGuard g;
3584 if (!g.isAcquired())
3586 SAL_WARN( "sc.core", "ScCompiler::ParseMacro - SolarMutex would deadlock, not obtaining Basic");
3587 return false; // bad luck
3590 OUString aName( rName);
3591 StarBASIC* pObj = nullptr;
3592 ScDocShell* pDocSh = rDoc.GetDocumentShell();
3596 if( pDocSh )//XXX
3597 pObj = pDocSh->GetBasic();
3598 else
3599 pObj = SfxApplication::GetBasic();
3601 catch (...)
3603 return false;
3606 if (!pObj)
3607 return false;
3609 // ODFF recommends to store user-defined functions prefixed with "USER.",
3610 // use only unprefixed name if encountered. BASIC doesn't allow '.' in a
3611 // function name so a function "USER.FOO" could not exist, and macro check
3612 // is assigned the lowest priority in function name check.
3613 if (FormulaGrammar::isODFF( GetGrammar()) && aName.startsWithIgnoreAsciiCase("USER."))
3614 aName = aName.copy(5);
3616 SbxMethod* pMeth = static_cast<SbxMethod*>(pObj->Find( aName, SbxClassType::Method ));
3617 if( !pMeth )
3619 return false;
3621 // It really should be a BASIC function!
3622 if( pMeth->GetType() == SbxVOID
3623 || ( pMeth->IsFixed() && pMeth->GetType() == SbxEMPTY )
3624 || dynamic_cast<const SbMethod*>( pMeth) == nullptr )
3626 return false;
3628 maRawToken.SetExternal( aName );
3629 maRawToken.eOp = ocMacro;
3630 return true;
3631 #endif
3634 const ScRangeData* ScCompiler::GetRangeData( SCTAB& rSheet, const OUString& rUpperName ) const
3636 // try local names first
3637 rSheet = aPos.Tab();
3638 const ScRangeName* pRangeName = rDoc.GetRangeName(rSheet);
3639 const ScRangeData* pData = nullptr;
3640 if (pRangeName)
3641 pData = pRangeName->findByUpperName(rUpperName);
3642 if (!pData)
3644 pRangeName = rDoc.GetRangeName();
3645 if (pRangeName)
3646 pData = pRangeName->findByUpperName(rUpperName);
3647 if (pData)
3648 rSheet = -1;
3650 return pData;
3653 bool ScCompiler::HasPossibleNamedRangeConflict( SCTAB nTab ) const
3655 const ScRangeName* pRangeName = rDoc.GetRangeName();
3656 if (pRangeName && pRangeName->hasPossibleAddressConflict())
3657 return true;
3658 pRangeName = rDoc.GetRangeName(nTab);
3659 if (pRangeName && pRangeName->hasPossibleAddressConflict())
3660 return true;
3661 return false;
3664 bool ScCompiler::ParseNamedRange( const OUString& rUpperName, bool onlyCheck )
3666 // ParseNamedRange is called only from NextNewToken, with an upper-case string
3668 SCTAB nSheet = -1;
3669 const ScRangeData* pData = GetRangeData( nSheet, rUpperName);
3670 if (pData)
3672 if (!onlyCheck)
3673 maRawToken.SetName( nSheet, pData->GetIndex());
3674 return true;
3677 // Sheet-local name with sheet specified.
3678 if (mnCurrentSheetEndPos > 0 && mnCurrentSheetTab >= 0)
3680 OUString aName( rUpperName.copy( mnCurrentSheetEndPos));
3681 const ScRangeName* pRangeName = rDoc.GetRangeName( mnCurrentSheetTab);
3682 if (pRangeName)
3684 pData = pRangeName->findByUpperName(aName);
3685 if (pData)
3687 if (!onlyCheck)
3688 maRawToken.SetName( mnCurrentSheetTab, pData->GetIndex());
3689 return true;
3694 return false;
3697 bool ScCompiler::ParseLambdaFuncName( const OUString& aOrg )
3699 if (m_aLambda.bInLambdaFunction && !aOrg.isEmpty())
3701 OUString aName = aOrg;
3702 if (aOrg.startsWithIgnoreAsciiCase(u"_xlpm."))
3703 aName = aName.copy(6);
3705 if (m_aLambda.nParaPos % 2 == 1 && m_aLambda.nParaCount > m_aLambda.nParaPos)
3706 m_aLambda.aNameSet.insert(aName);
3707 else
3709 // should already exist the name
3710 if (m_aLambda.aNameSet.find(aName) == m_aLambda.aNameSet.end())
3711 return false;
3713 svl::SharedString aSS = rDoc.GetSharedStringPool().intern(aName);
3714 maRawToken.SetStringName(aSS.getData(), aSS.getDataIgnoreCase());
3715 return true;
3717 return false;
3720 bool ScCompiler::ParseExternalNamedRange( const OUString& rSymbol, bool& rbInvalidExternalNameRange )
3722 /* FIXME: This code currently (2008-12-02T15:41+0100 in CWS mooxlsc)
3723 * correctly parses external named references in OOo, as required per RFE
3724 * #i3740#, just that we can't store them in ODF yet. We will need an OASIS
3725 * spec first. Until then don't pretend to support external names that
3726 * wouldn't survive a save and reload cycle, return false instead. */
3728 rbInvalidExternalNameRange = false;
3730 if (!pConv)
3731 return false;
3733 OUString aFile, aName;
3734 if (!pConv->parseExternalName( rSymbol, aFile, aName, rDoc, &maExternalLinks))
3735 return false;
3737 if (aFile.getLength() > MAXSTRLEN || aName.getLength() > MAXSTRLEN)
3738 return false;
3740 ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager();
3741 OUString aTmp = aFile;
3742 pRefMgr->convertToAbsName(aTmp);
3743 aFile = aTmp;
3744 sal_uInt16 nFileId = pRefMgr->getExternalFileId(aFile);
3745 if (!pRefMgr->isValidRangeName(nFileId, aName))
3747 rbInvalidExternalNameRange = true;
3748 // range name doesn't exist in the source document.
3749 return false;
3752 const OUString* pRealName = pRefMgr->getRealRangeName(nFileId, aName);
3753 maRawToken.SetExternalName(nFileId, pRealName ? *pRealName : aTmp);
3754 maExternalFiles.push_back(nFileId);
3755 return true;
3758 bool ScCompiler::ParseDBRange( const OUString& rName )
3760 ScDBCollection::NamedDBs& rDBs = rDoc.GetDBCollection()->getNamedDBs();
3761 const ScDBData* p = rDBs.findByUpperName(rName);
3762 if (!p)
3763 return false;
3765 maRawToken.SetName( -1, p->GetIndex()); // DB range is always global.
3766 maRawToken.eOp = ocDBArea;
3767 return true;
3770 bool ScCompiler::ParseColRowName( const OUString& rName )
3772 bool bInList = false;
3773 bool bFound = false;
3774 ScSingleRefData aRef;
3775 OUString aName( rName );
3776 DeQuote( aName );
3777 SCTAB nThisTab = aPos.Tab();
3778 for ( short jThisTab = 1; jThisTab >= 0 && !bInList; jThisTab-- )
3779 { // first check ranges on this sheet, in case of duplicated names
3780 for ( short jRow=0; jRow<2 && !bInList; jRow++ )
3782 ScRangePairList* pRL;
3783 if ( !jRow )
3784 pRL = rDoc.GetColNameRanges();
3785 else
3786 pRL = rDoc.GetRowNameRanges();
3787 for ( size_t iPair = 0, nPairs = pRL->size(); iPair < nPairs && !bInList; ++iPair )
3789 const ScRangePair & rR = (*pRL)[iPair];
3790 const ScRange& rNameRange = rR.GetRange(0);
3791 if ( jThisTab && (rNameRange.aStart.Tab() > nThisTab ||
3792 nThisTab > rNameRange.aEnd.Tab()) )
3793 continue; // for
3794 ScCellIterator aIter( rDoc, rNameRange );
3795 for (bool bHas = aIter.first(); bHas && !bInList; bHas = aIter.next())
3797 // Don't crash if cell (via CompileNameFormula) encounters
3798 // a formula cell without code and
3799 // HasStringData/Interpret/Compile is executed and all that
3800 // recursively...
3801 // Furthermore, *this* cell won't be touched, since no RPN exists yet.
3802 CellType eType = aIter.getType();
3803 bool bOk = false;
3804 if (eType == CELLTYPE_FORMULA)
3806 ScFormulaCell* pFC = aIter.getFormulaCell();
3807 bOk = (pFC->GetCode()->GetCodeLen() > 0) && (pFC->aPos != aPos);
3809 else
3810 bOk = true;
3812 if (bOk && aIter.hasString())
3814 OUString aStr = aIter.getString();
3815 if ( ScGlobal::GetTransliteration().isEqual( aStr, aName ) )
3817 aRef.InitFlags();
3818 if ( !jRow )
3819 aRef.SetColRel( true ); // ColName
3820 else
3821 aRef.SetRowRel( true ); // RowName
3822 aRef.SetAddress(rDoc.GetSheetLimits(), aIter.GetPos(), aPos);
3823 bInList = bFound = true;
3830 if ( !bInList && rDoc.GetDocOptions().IsLookUpColRowNames() )
3831 { // search in current sheet
3832 tools::Long nDistance = 0, nMax = 0;
3833 tools::Long nMyCol = static_cast<tools::Long>(aPos.Col());
3834 tools::Long nMyRow = static_cast<tools::Long>(aPos.Row());
3835 bool bTwo = false;
3836 ScAddress aOne( 0, 0, aPos.Tab() );
3837 ScAddress aTwo( rDoc.MaxCol(), rDoc.MaxRow(), aPos.Tab() );
3839 ScAutoNameCache* pNameCache = rDoc.GetAutoNameCache();
3840 if ( pNameCache )
3842 // use GetNameOccurrences to collect all positions of aName on the sheet
3843 // (only once), similar to the outer part of the loop in the "else" branch.
3845 const ScAutoNameAddresses& rAddresses = pNameCache->GetNameOccurrences( aName, aPos.Tab() );
3847 // Loop through the found positions, similar to the inner part of the loop in the "else" branch.
3848 // The order of addresses in the vector is the same as from ScCellIterator.
3850 for ( const ScAddress& aAddress : rAddresses )
3852 if ( bFound )
3853 { // stop if everything else is further away
3854 if ( nMax < static_cast<tools::Long>(aAddress.Col()) )
3855 break; // aIter
3857 if ( aAddress != aPos )
3859 // same treatment as in isEqual case below
3861 SCCOL nCol = aAddress.Col();
3862 SCROW nRow = aAddress.Row();
3863 tools::Long nC = nMyCol - nCol;
3864 tools::Long nR = nMyRow - nRow;
3865 if ( bFound )
3867 tools::Long nD = nC * nC + nR * nR;
3868 if ( nD < nDistance )
3870 if ( nC < 0 || nR < 0 )
3871 { // right or below
3872 bTwo = true;
3873 aTwo.Set( nCol, nRow, aAddress.Tab() );
3874 nMax = std::max( nMyCol + std::abs( nC ), nMyRow + std::abs( nR ) );
3875 nDistance = nD;
3877 else if ( nRow >= aOne.Row() || nMyRow < static_cast<tools::Long>(aOne.Row()) )
3879 // upper left, only if not further up than the
3880 // current entry and nMyRow is below (CellIter
3881 // runs column-wise)
3882 bTwo = false;
3883 aOne.Set( nCol, nRow, aAddress.Tab() );
3884 nMax = std::max( nMyCol + nC, nMyRow + nR );
3885 nDistance = nD;
3889 else
3891 aOne.Set( nCol, nRow, aAddress.Tab() );
3892 nDistance = nC * nC + nR * nR;
3893 nMax = std::max( nMyCol + std::abs( nC ), nMyRow + std::abs( nR ) );
3896 bFound = true;
3900 else
3902 ScCellIterator aIter( rDoc, ScRange( aOne, aTwo ) );
3903 for (bool bHas = aIter.first(); bHas; bHas = aIter.next())
3905 if ( bFound )
3906 { // stop if everything else is further away
3907 if ( nMax < static_cast<tools::Long>(aIter.GetPos().Col()) )
3908 break; // aIter
3910 CellType eType = aIter.getType();
3911 bool bOk = false;
3912 if (eType == CELLTYPE_FORMULA)
3914 ScFormulaCell* pFC = aIter.getFormulaCell();
3915 bOk = (pFC->GetCode()->GetCodeLen() > 0) && (pFC->aPos != aPos);
3917 else
3918 bOk = true;
3920 if (bOk && aIter.hasString())
3922 OUString aStr = aIter.getString();
3923 if ( ScGlobal::GetTransliteration().isEqual( aStr, aName ) )
3925 SCCOL nCol = aIter.GetPos().Col();
3926 SCROW nRow = aIter.GetPos().Row();
3927 tools::Long nC = nMyCol - nCol;
3928 tools::Long nR = nMyRow - nRow;
3929 if ( bFound )
3931 tools::Long nD = nC * nC + nR * nR;
3932 if ( nD < nDistance )
3934 if ( nC < 0 || nR < 0 )
3935 { // right or below
3936 bTwo = true;
3937 aTwo.Set( nCol, nRow, aIter.GetPos().Tab() );
3938 nMax = std::max( nMyCol + std::abs( nC ), nMyRow + std::abs( nR ) );
3939 nDistance = nD;
3941 else if ( nRow >= aOne.Row() || nMyRow < static_cast<tools::Long>(aOne.Row()) )
3943 // upper left, only if not further up than the
3944 // current entry and nMyRow is below (CellIter
3945 // runs column-wise)
3946 bTwo = false;
3947 aOne.Set( nCol, nRow, aIter.GetPos().Tab() );
3948 nMax = std::max( nMyCol + nC, nMyRow + nR );
3949 nDistance = nD;
3953 else
3955 aOne.Set( nCol, nRow, aIter.GetPos().Tab() );
3956 nDistance = nC * nC + nR * nR;
3957 nMax = std::max( nMyCol + std::abs( nC ), nMyRow + std::abs( nR ) );
3959 bFound = true;
3965 if ( bFound )
3967 ScAddress aAdr;
3968 if ( bTwo )
3970 if ( nMyCol >= static_cast<tools::Long>(aOne.Col()) && nMyRow >= static_cast<tools::Long>(aOne.Row()) )
3971 aAdr = aOne; // upper left takes precedence
3972 else
3974 if ( nMyCol < static_cast<tools::Long>(aOne.Col()) )
3975 { // two to the right
3976 if ( nMyRow >= static_cast<tools::Long>(aTwo.Row()) )
3977 aAdr = aTwo; // directly right
3978 else
3979 aAdr = aOne;
3981 else
3982 { // two below or below and right, take the nearest
3983 tools::Long nC1 = nMyCol - aOne.Col();
3984 tools::Long nR1 = nMyRow - aOne.Row();
3985 tools::Long nC2 = nMyCol - aTwo.Col();
3986 tools::Long nR2 = nMyRow - aTwo.Row();
3987 if ( nC1 * nC1 + nR1 * nR1 <= nC2 * nC2 + nR2 * nR2 )
3988 aAdr = aOne;
3989 else
3990 aAdr = aTwo;
3994 else
3995 aAdr = aOne;
3996 aRef.InitAddress( aAdr );
3997 // Prioritize on column label; row label only if the next cell
3998 // above/below the found label cell is text, or if both are not and
3999 // the cell below is empty and the next cell to the right is
4000 // numeric.
4001 if ((aAdr.Row() < rDoc.MaxRow() && rDoc.HasStringData(
4002 aAdr.Col(), aAdr.Row() + 1, aAdr.Tab()))
4003 || (aAdr.Row() > 0 && rDoc.HasStringData(
4004 aAdr.Col(), aAdr.Row() - 1, aAdr.Tab()))
4005 || (aAdr.Row() < rDoc.MaxRow() && rDoc.GetRefCellValue(
4006 ScAddress( aAdr.Col(), aAdr.Row() + 1, aAdr.Tab())).isEmpty()
4007 && aAdr.Col() < rDoc.MaxCol() && rDoc.GetRefCellValue(
4008 ScAddress( aAdr.Col() + 1, aAdr.Row(), aAdr.Tab())).hasNumeric()))
4009 aRef.SetRowRel( true ); // RowName
4010 else
4011 aRef.SetColRel( true ); // ColName
4012 aRef.SetAddress(rDoc.GetSheetLimits(), aAdr, aPos);
4015 if ( bFound )
4017 maRawToken.SetSingleReference( aRef );
4018 maRawToken.eOp = ocColRowName;
4019 return true;
4021 else
4022 return false;
4025 bool ScCompiler::ParseBoolean( const OUString& rName )
4027 OpCodeHashMap::const_iterator iLook( mxSymbols->getHashMap().find( rName ) );
4028 if( iLook != mxSymbols->getHashMap().end() &&
4029 ((*iLook).second == ocTrue ||
4030 (*iLook).second == ocFalse) )
4032 maRawToken.SetOpCode( (*iLook).second );
4033 return true;
4035 else
4036 return false;
4039 bool ScCompiler::ParseErrorConstant( const OUString& rName )
4041 FormulaError nError = GetErrorConstant( rName);
4042 if (nError != FormulaError::NONE)
4044 maRawToken.SetErrorConstant( nError);
4045 return true;
4047 else
4048 return false;
4051 bool ScCompiler::ParseTableRefItem( const OUString& rName )
4053 bool bItem = false;
4054 OpCodeHashMap::const_iterator iLook( mxSymbols->getHashMap().find( rName));
4055 if (iLook != mxSymbols->getHashMap().end())
4057 // Only called when there actually is a current TableRef, hence
4058 // accessing maTableRefs.back() is safe.
4059 ScTableRefToken* p = maTableRefs.back().mxToken.get();
4060 assert(p); // not a ScTableRefToken can't be
4062 switch ((*iLook).second)
4064 case ocTableRefItemAll:
4065 bItem = true;
4066 p->AddItem( ScTableRefToken::ALL);
4067 break;
4068 case ocTableRefItemHeaders:
4069 bItem = true;
4070 p->AddItem( ScTableRefToken::HEADERS);
4071 break;
4072 case ocTableRefItemData:
4073 bItem = true;
4074 p->AddItem( ScTableRefToken::DATA);
4075 break;
4076 case ocTableRefItemTotals:
4077 bItem = true;
4078 p->AddItem( ScTableRefToken::TOTALS);
4079 break;
4080 case ocTableRefItemThisRow:
4081 bItem = true;
4082 p->AddItem( ScTableRefToken::THIS_ROW);
4083 break;
4084 default:
4087 if (bItem)
4088 maRawToken.SetOpCode( (*iLook).second );
4090 return bItem;
4093 namespace {
4094 OUString unescapeTableRefColumnSpecifier( const OUString& rStr )
4096 // '#', '[', ']' and '\'' are escaped with '\''
4098 if (rStr.indexOf( '\'' ) < 0)
4099 return rStr;
4101 const sal_Int32 n = rStr.getLength();
4102 OUStringBuffer aBuf( n );
4103 const sal_Unicode* p = rStr.getStr();
4104 const sal_Unicode* const pStop = p + n;
4105 bool bEscaped = false;
4106 for ( ; p < pStop; ++p)
4108 const sal_Unicode c = *p;
4109 if (bEscaped)
4111 aBuf.append( c );
4112 bEscaped = false;
4114 else if (c == '\'')
4115 bEscaped = true; // unescaped escaping '\''
4116 else
4117 aBuf.append( c );
4119 return aBuf.makeStringAndClear();
4123 bool ScCompiler::ParseTableRefColumn( const OUString& rName )
4125 // Only called when there actually is a current TableRef, hence
4126 // accessing maTableRefs.back() is safe.
4127 ScTableRefToken* p = maTableRefs.back().mxToken.get();
4128 assert(p); // not a ScTableRefToken can't be
4130 ScDBData* pDBData = rDoc.GetDBCollection()->getNamedDBs().findByIndex( p->GetIndex());
4131 if (!pDBData)
4132 return false;
4134 OUString aName( unescapeTableRefColumnSpecifier( rName));
4136 ScRange aRange;
4137 pDBData->GetArea( aRange);
4138 aRange.aEnd.SetTab( aRange.aStart.Tab());
4139 aRange.aEnd.SetRow( aRange.aStart.Row());
4141 // Prefer the stored internal table column name, which is also needed for
4142 // named expressions during document load time when cell content isn't
4143 // available yet. Also, avoiding a possible calculation step in case the
4144 // header cell is a formula cell is "a good thing".
4145 sal_Int32 nOffset = pDBData->GetColumnNameOffset( aName);
4146 if (nOffset >= 0)
4148 // This is sneaky... we always use the top row of the database range,
4149 // regardless of whether it is a header row or not. Code evaluating
4150 // this reference must take that into account and may have to act
4151 // differently if it is a header-less table. Which are two places,
4152 // HandleTableRef() (no change necessary there) and
4153 // CreateStringFromSingleRef() (must not fallback to cell lookup).
4154 ScSingleRefData aRef;
4155 ScAddress aAdr( aRange.aStart);
4156 aAdr.IncCol( nOffset);
4157 aRef.InitAddress( aAdr);
4158 maRawToken.SetSingleReference( aRef );
4159 return true;
4162 if (pDBData->HasHeader())
4164 // Quite similar to IsColRowName() but limited to one row of headers.
4165 ScCellIterator aIter( rDoc, aRange);
4166 for (bool bHas = aIter.first(); bHas; bHas = aIter.next())
4168 CellType eType = aIter.getType();
4169 bool bOk = false;
4170 if (eType == CELLTYPE_FORMULA)
4172 ScFormulaCell* pFC = aIter.getFormulaCell();
4173 bOk = (pFC->GetCode()->GetCodeLen() > 0) && (pFC->aPos != aPos);
4175 else
4176 bOk = true;
4178 if (bOk && aIter.hasString())
4180 OUString aStr = aIter.getString();
4181 if (ScGlobal::GetTransliteration().isEqual( aStr, aName))
4183 // If this is successful and the internal column name
4184 // lookup was not, it may be worth a warning.
4185 SAL_WARN("sc.core", "ScCompiler::IsTableRefColumn - falling back to cell lookup");
4187 /* XXX NOTE: we could init the column as relative so copying a
4188 * formula across columns would point to the relative column,
4189 * but do it absolute because:
4190 * a) it makes the reference work in named expressions without
4191 * having to distinguish
4192 * b) Excel does it the same. */
4193 ScSingleRefData aRef;
4194 aRef.InitAddress( aIter.GetPos());
4195 maRawToken.SetSingleReference( aRef );
4196 return true;
4202 return false;
4205 void ScCompiler::SetAutoCorrection( bool bVal )
4207 assert(mbJumpCommandReorder);
4208 bAutoCorrect = bVal;
4209 mbStopOnError = !bVal;
4212 void ScCompiler::AutoCorrectParsedSymbol()
4214 sal_Int32 nPos = aCorrectedSymbol.getLength();
4215 if ( !nPos )
4216 return;
4218 nPos--;
4219 const sal_Unicode cQuote = '\"';
4220 const sal_Unicode cx = 'x';
4221 const sal_Unicode cX = 'X';
4222 sal_Unicode c1 = aCorrectedSymbol[0];
4223 sal_Unicode c2 = aCorrectedSymbol[nPos];
4224 sal_Unicode c2p = nPos > 0 ? aCorrectedSymbol[nPos-1] : 0;
4225 if ( c1 == cQuote && c2 != cQuote )
4226 { // "...
4227 // What's not a word doesn't belong to it.
4228 // Don't be pedantic: c < 128 should be sufficient here.
4229 while ( nPos && ((aCorrectedSymbol[nPos] < 128) &&
4230 ((GetCharTableFlags(aCorrectedSymbol[nPos], aCorrectedSymbol[nPos-1]) &
4231 (ScCharFlags::Word | ScCharFlags::CharDontCare)) == ScCharFlags::NONE)) )
4232 nPos--;
4233 if ( nPos == MAXSTRLEN - 1 )
4234 aCorrectedSymbol = aCorrectedSymbol.replaceAt( nPos, 1, rtl::OUStringChar(cQuote) ); // '"' the MAXSTRLENth character
4235 else
4236 aCorrectedSymbol = aCorrectedSymbol.replaceAt( nPos + 1, 0, rtl::OUStringChar(cQuote) );
4237 bCorrected = true;
4239 else if ( c1 != cQuote && c2 == cQuote )
4240 { // ..."
4241 aCorrectedSymbol = OUStringChar(cQuote) + aCorrectedSymbol;
4242 bCorrected = true;
4244 else if ( nPos == 0 && (c1 == cx || c1 == cX) )
4245 { // x => *
4246 aCorrectedSymbol = mxSymbols->getSymbol(ocMul);
4247 bCorrected = true;
4249 else if ( (GetCharTableFlags( c1, 0 ) & ScCharFlags::CharValue)
4250 && (GetCharTableFlags( c2, c2p ) & ScCharFlags::CharValue) )
4252 if ( aCorrectedSymbol.indexOf(cx) >= 0 ) // At least two tokens separated by cx
4253 { // x => *
4254 sal_Unicode c = mxSymbols->getSymbolChar(ocMul);
4255 aCorrectedSymbol = aCorrectedSymbol.replaceAll(OUStringChar(cx), OUStringChar(c));
4256 bCorrected = true;
4258 if ( aCorrectedSymbol.indexOf(cX) >= 0 ) // At least two tokens separated by cX
4259 { // X => *
4260 sal_Unicode c = mxSymbols->getSymbolChar(ocMul);
4261 aCorrectedSymbol = aCorrectedSymbol.replaceAll(OUStringChar(cX), OUStringChar(c));
4262 bCorrected = true;
4265 else
4267 OUString aSymbol( aCorrectedSymbol );
4268 OUString aDoc;
4269 if ( aSymbol[0] == '\'' )
4271 sal_Int32 nPosition = aSymbol.indexOf( "'#" );
4272 if (nPosition != -1)
4273 { // Split off 'Doc'#, may be d:\... or whatever
4274 aDoc = aSymbol.copy(0, nPosition + 2);
4275 aSymbol = aSymbol.copy(nPosition + 2);
4278 sal_Int32 nRefs = comphelper::string::getTokenCount(aSymbol, ':');
4279 bool bColons;
4280 if ( nRefs > 2 )
4281 { // duplicated or too many ':'? B:2::C10 => B2:C10
4282 bColons = true;
4283 sal_Int32 nIndex = 0;
4284 OUString aTmp1( aSymbol.getToken( 0, ':', nIndex ) );
4285 sal_Int32 nLen1 = aTmp1.getLength();
4286 OUStringBuffer aSym;
4287 OUString aTmp2;
4288 bool bLastAlp = true;
4289 sal_Int32 nStrip = 0;
4290 sal_Int32 nCount = nRefs;
4291 for ( sal_Int32 j=1; j<nCount; j++ )
4293 aTmp2 = aSymbol.getToken( 0, ':', nIndex );
4294 sal_Int32 nLen2 = aTmp2.getLength();
4295 if ( nLen1 || nLen2 )
4297 if ( nLen1 )
4299 aSym.append(aTmp1);
4300 bLastAlp = CharClass::isAsciiAlpha( aTmp1 );
4302 if ( nLen2 )
4304 bool bNextNum = CharClass::isAsciiNumeric( aTmp2 );
4305 if ( bLastAlp == bNextNum && nStrip < 1 )
4307 // Must be alternating number/string, only
4308 // strip within a reference.
4309 nRefs--;
4310 nStrip++;
4312 else
4314 if ( !aSym.isEmpty() && aSym[aSym.getLength()-1] != ':')
4315 aSym.append(":");
4316 nStrip = 0;
4318 bLastAlp = !bNextNum;
4320 else
4321 { // ::
4322 nRefs--;
4323 if ( nLen1 )
4324 { // B10::C10 ? append ':' on next round
4325 if ( !bLastAlp && !CharClass::isAsciiNumeric( aTmp1 ) )
4326 nStrip++;
4329 aTmp1 = aTmp2;
4330 nLen1 = nLen2;
4332 else
4333 nRefs--;
4335 aSymbol = aSym + aTmp1;
4336 aSym.setLength(0);
4338 else
4339 bColons = false;
4340 if ( nRefs && nRefs <= 2 )
4341 { // reference twisted? 4A => A4 etc.
4342 OUString aTab[2], aRef[2];
4343 const ScAddress::Details aDetails( pConv->meConv, aPos );
4344 if ( nRefs == 2 )
4346 sal_Int32 nIdx{ 0 };
4347 aRef[0] = aSymbol.getToken( 0, ':', nIdx );
4348 aRef[1] = aSymbol.getToken( 0, ':', nIdx );
4350 else
4351 aRef[0] = aSymbol;
4353 bool bChanged = false;
4354 bool bOk = true;
4355 ScRefFlags nMask = ScRefFlags::VALID | ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID;
4356 for ( int j=0; j<nRefs; j++ )
4358 sal_Int32 nTmp = 0;
4359 sal_Int32 nDotPos = -1;
4360 while ( (nTmp = aRef[j].indexOf( '.', nTmp )) != -1 )
4361 nDotPos = nTmp++; // the last one counts
4362 if ( nDotPos != -1 )
4364 aTab[j] = aRef[j].copy( 0, nDotPos + 1 ); // with '.'
4365 aRef[j] = aRef[j].copy( nDotPos + 1 );
4367 OUString aOld( aRef[j] );
4368 OUStringBuffer aStr2;
4369 const sal_Unicode* p = aRef[j].getStr();
4370 while ( *p && rtl::isAsciiDigit( *p ) )
4371 aStr2.append(*p++);
4372 aRef[j] = OUString( p );
4373 aRef[j] += aStr2;
4374 if ( bColons || aRef[j] != aOld )
4376 bChanged = true;
4377 ScAddress aAdr;
4378 bOk &= ((aAdr.Parse( aRef[j], rDoc, aDetails ) & nMask) == nMask);
4381 if ( bChanged && bOk )
4383 aCorrectedSymbol = aDoc;
4384 aCorrectedSymbol += aTab[0];
4385 aCorrectedSymbol += aRef[0];
4386 if ( nRefs == 2 )
4388 aCorrectedSymbol += ":";
4389 aCorrectedSymbol += aTab[1];
4390 aCorrectedSymbol += aRef[1];
4392 bCorrected = true;
4398 bool ScCompiler::ToUpperAsciiOrI18nIsAscii( OUString& rUpper, const OUString& rOrg ) const
4400 if (FormulaGrammar::isODFF( meGrammar) || FormulaGrammar::isOOXML( meGrammar))
4402 // ODFF and OOXML have defined sets of English function names, avoid
4403 // i18n overhead.
4404 rUpper = rOrg.toAsciiUpperCase();
4405 return true;
4407 else
4409 // One of localized or English.
4410 rUpper = pCharClass->uppercase(rOrg);
4411 return false;
4415 short ScCompiler::GetPossibleParaCount( std::u16string_view rLambdaFormula ) const
4417 sal_Unicode cSep = mxSymbols->getSymbolChar(ocSep);
4418 sal_Unicode cOpen = mxSymbols->getSymbolChar(ocOpen);
4419 sal_Unicode cClose = mxSymbols->getSymbolChar(ocClose);
4420 sal_Unicode cArrayOpen = mxSymbols->getSymbolChar(ocArrayOpen);
4421 sal_Unicode cArrayClose = mxSymbols->getSymbolChar(ocArrayClose);
4422 short nBrackets = 0;
4424 short nCount = std::count_if(rLambdaFormula.begin(), rLambdaFormula.end(),
4425 [&](sal_Unicode c) {
4426 if (c == cOpen || c == cArrayOpen || c == '[') {
4427 nBrackets++;
4428 return false;
4430 else if (c == cClose || c == cArrayClose || c == ']') {
4431 nBrackets--;
4432 return false;
4434 else {
4435 if (nBrackets == 1)
4436 return c == cSep;
4437 else
4438 return false;
4442 return static_cast<short>(nCount + 1);
4445 bool ScCompiler::NextNewToken( bool bInArray )
4447 if (!maPendingOpCodes.empty())
4449 maRawToken.SetOpCode(maPendingOpCodes.front());
4450 maPendingOpCodes.pop();
4451 return true;
4454 bool bAllowBooleans = bInArray;
4455 const std::vector<Whitespace> vSpaces = NextSymbol(bInArray);
4457 if (!cSymbol[0])
4459 if (nSrcPos < aFormula.getLength())
4461 // Nothing could be parsed, remainder as bad string.
4462 // NextSymbol() must had set an error for this.
4463 assert( pArr->GetCodeError() != FormulaError::NONE);
4464 const OUString aBad( aFormula.copy( nSrcPos));
4465 svl::SharedString aSS = rDoc.GetSharedStringPool().intern( aBad);
4466 maRawToken.SetString( aSS.getData(), aSS.getDataIgnoreCase());
4467 maRawToken.NewOpCode( ocBad);
4468 nSrcPos = aFormula.getLength();
4469 // Add bad string as last token.
4470 return true;
4472 return false;
4475 if (!vSpaces.empty())
4477 ScRawToken aToken;
4478 for (const auto& rSpace : vSpaces)
4480 if (rSpace.cChar == 0x20)
4482 // For now keep this a FormulaByteToken for the nasty
4483 // significant whitespace intersection. This probably can be
4484 // changed to a FormulaSpaceToken but then other places may
4485 // need to be adapted.
4486 aToken.SetOpCode( ocSpaces );
4487 aToken.sbyte.cByte = static_cast<sal_uInt8>( std::min<sal_Int32>(rSpace.nCount, 255) );
4489 else
4491 aToken.SetOpCode( ocWhitespace );
4492 aToken.whitespace.nCount = static_cast<sal_uInt8>( std::min<sal_Int32>(rSpace.nCount, 255) );
4493 aToken.whitespace.cChar = rSpace.cChar;
4495 if (!static_cast<ScTokenArray*>(pArr)->AddRawToken( aToken ))
4497 SetError(FormulaError::CodeOverflow);
4498 return false;
4503 // Short cut for references when reading ODF to speedup things.
4504 if (mnPredetectedReference)
4506 OUString aStr( cSymbol);
4507 bool bInvalidExternalNameRange;
4508 if (!ParsePredetectedReference( aStr) && !ParseExternalNamedRange( aStr, bInvalidExternalNameRange ))
4510 svl::SharedString aSS = rDoc.GetSharedStringPool().intern(aStr);
4511 maRawToken.SetString(aSS.getData(), aSS.getDataIgnoreCase());
4512 maRawToken.NewOpCode( ocBad );
4514 return true;
4517 if ( (cSymbol[0] == '#' || cSymbol[0] == '$') && cSymbol[1] == 0 &&
4518 !bAutoCorrect )
4519 { // special case to speed up broken [$]#REF documents
4520 /* FIXME: ISERROR(#REF!) would be valid and true and the formula to
4521 * be processed as usual. That would need some special treatment,
4522 * also in NextSymbol() because of possible combinations of
4523 * #REF!.#REF!#REF! parts. In case of reading ODF that is all
4524 * handled by IsPredetectedReference(), this case here remains for
4525 * manual/API input. */
4526 OUString aBad( aFormula.copy( nSrcPos-1 ) );
4527 const FormulaToken* pBadToken = pArr->AddBad(aBad);
4528 eLastOp = pBadToken ? pBadToken->GetOpCode() : ocNone;
4529 return false;
4532 if( ParseString() )
4533 return true;
4535 bool bMayBeFuncName;
4536 bool bAsciiNonAlnum; // operators, separators, ...
4537 if ( cSymbol[0] < 128 )
4539 bMayBeFuncName = rtl::isAsciiAlpha(cSymbol[0])
4540 || (cSymbol[0] == '_' && mxSymbols->isOOXML() && rtl::isAsciiAlpha(cSymbol[1]));
4541 if (!bMayBeFuncName && (cSymbol[0] == '_' && cSymbol[1] == '_') && !comphelper::IsFuzzing())
4543 bMayBeFuncName = officecfg::Office::Common::Misc::ExperimentalMode::get();
4546 bAsciiNonAlnum = !bMayBeFuncName && !rtl::isAsciiDigit( cSymbol[0] );
4548 else
4550 OUString aTmpStr( cSymbol[0] );
4551 bMayBeFuncName = pCharClass->isLetter( aTmpStr, 0 );
4552 bAsciiNonAlnum = false;
4555 // Within a TableRef anything except an unescaped '[' or ']' is an item
4556 // or a column specifier, do not attempt to recognize any other single
4557 // operator there so even [,] or [+] for a single character column
4558 // specifier works. Note that space between two ocTableRefOpen is not
4559 // supported (Table[ [ColumnSpec]]), not only here. Note also that Table[]
4560 // without any item or column specifier is valid.
4561 if (bAsciiNonAlnum && cSymbol[1] == 0 && (eLastOp != ocTableRefOpen || cSymbol[0] == '[' || cSymbol[0] == ']'))
4563 // Shortcut for operators and separators that need no further checks or upper.
4564 if (ParseOpCode( OUString( cSymbol), bInArray ))
4565 return true;
4568 if ( bMayBeFuncName )
4570 // a function name must be followed by a parenthesis
4571 const sal_Unicode* p = aFormula.getStr() + nSrcPos;
4572 while( *p == ' ' )
4573 p++;
4574 bMayBeFuncName = ( *p == '(' );
4577 // Italian ARCTAN.2 resulted in #REF! => ParseOpcode() before
4578 // ParseReference().
4580 OUString aUpper;
4581 bool bAsciiUpper = false;
4583 Label_Rewind:
4587 const OUString aOrg( cSymbol );
4589 // Check for TableRef column specifier first, it may be anything.
4590 if (cSymbol[0] != '#' && !maTableRefs.empty() && maTableRefs.back().mnLevel)
4592 if (ParseTableRefColumn( aOrg ))
4593 return true;
4594 // Do not attempt to resolve as any other name.
4595 aUpper = aOrg; // for ocBad
4596 break; // do; create ocBad token or set error.
4599 mbRewind = false;
4600 aUpper.clear();
4601 bAsciiUpper = false;
4603 if (bAsciiNonAlnum)
4605 bAsciiUpper = ToUpperAsciiOrI18nIsAscii( aUpper, aOrg);
4606 if (cSymbol[0] == '#')
4608 // Check for TableRef item specifiers first.
4609 sal_uInt16 nLevel;
4610 if (!maTableRefs.empty() && ((nLevel = maTableRefs.back().mnLevel) == 2 || nLevel == 1))
4612 if (ParseTableRefItem( aUpper ))
4613 return true;
4616 // This can be either an error constant ...
4617 if (ParseErrorConstant( aUpper))
4618 return true;
4620 // ... or some invalidated reference starting with #REF!
4621 // which is handled after the do loop.
4623 break; // do; create ocBad token or set error.
4625 if (ParseOpCode( aUpper, bInArray ))
4626 return true;
4629 if (bMayBeFuncName)
4631 if (aUpper.isEmpty())
4632 bAsciiUpper = ToUpperAsciiOrI18nIsAscii( aUpper, aOrg);
4633 if (ParseOpCode( aUpper, bInArray ))
4634 return true;
4637 // Column 'DM' ("Deutsche Mark", German currency) couldn't be
4638 // referred => ParseReference() before ParseValue().
4639 // Preserve case of file names in external references.
4640 if (ParseReference( aOrg ))
4642 if (mbRewind) // Range operator, but no direct reference.
4643 continue; // do; up to range operator.
4644 // If a syntactically correct reference was recognized but invalid
4645 // e.g. because of non-existing sheet name => entire reference
4646 // ocBad to preserve input instead of #REF!.A1
4647 if (!maRawToken.IsValidReference(rDoc))
4649 aUpper = aOrg; // ensure for ocBad
4650 break; // do; create ocBad token or set error.
4652 return true;
4655 if (aUpper.isEmpty())
4656 bAsciiUpper = ToUpperAsciiOrI18nIsAscii( aUpper, aOrg);
4658 // ParseBoolean() before ParseValue() to catch inline bools without the kludge
4659 // for inline arrays.
4660 if (bAllowBooleans && ParseBoolean( aUpper ))
4661 return true;
4663 if (ParseValue( aUpper ))
4664 return true;
4666 // User defined names and such do need i18n upper also in ODF.
4667 if (bAsciiUpper || mbCharClassesDiffer)
4669 // Use current system locale here because user defined symbols are
4670 // more likely in that localized language than in the formula
4671 // language. This in corner cases needs to continue to work for
4672 // existing documents and environments.
4673 // Do not change bAsciiUpper from here on for the lowercase() call
4674 // below in the ocBad case to use the correct CharClass.
4675 aUpper = ScGlobal::getCharClass().uppercase( aOrg );
4678 if (ParseNamedRange( aUpper ))
4679 return true;
4681 // Compiling a named expression during collecting them in import shall
4682 // not match arbitrary names that otherwise if all named expressions
4683 // were present would be recognized as named expression. Such name will
4684 // flag an error below and will be recompiled in a second step later
4685 // with ScRangeData::CompileUnresolvedXML()
4686 if (meExtendedErrorDetection == EXTENDED_ERROR_DETECTION_NAME_NO_BREAK && rDoc.IsImportingXML())
4687 break; // while
4689 // Preserve case of file names in external references.
4690 bool bInvalidExternalNameRange;
4691 if (ParseExternalNamedRange( aOrg, bInvalidExternalNameRange ))
4692 return true;
4693 // Preserve case of file names in external references even when range
4694 // is not valid and previous check failed tdf#89330
4695 if (bInvalidExternalNameRange)
4697 // add ocBad but do not lowercase
4698 svl::SharedString aSS = rDoc.GetSharedStringPool().intern(aOrg);
4699 maRawToken.SetString(aSS.getData(), aSS.getDataIgnoreCase());
4700 maRawToken.NewOpCode( ocBad );
4701 return true;
4703 if (ParseDBRange( aUpper ))
4704 return true;
4705 // If followed by '(' (with or without space inbetween) it can not be a
4706 // column/row label. Prevent arbitrary content detection.
4707 if (!bMayBeFuncName && ParseColRowName( aUpper ))
4708 return true;
4709 if (bMayBeFuncName && ParseMacro( aUpper ))
4710 return true;
4711 if (bMayBeFuncName && ParseOpCode2( aUpper ))
4712 return true;
4714 if (ParseLambdaFuncName( aOrg ))
4715 return true;
4717 } while (mbRewind);
4719 // Last chance: it could be a broken invalidated reference that contains
4720 // #REF! (but is not equal to), which we also wrote to ODFF between 2013
4721 // and 2016 until 5.1.4
4722 OUString aErrRef( mxSymbols->getSymbol( ocErrRef));
4723 if (aUpper.indexOf( aErrRef) >= 0 && ParseReference( aUpper, &aErrRef))
4725 if (mbRewind)
4726 goto Label_Rewind;
4727 return true;
4730 if ( meExtendedErrorDetection != EXTENDED_ERROR_DETECTION_NONE )
4732 // set an error
4733 SetError( FormulaError::NoName );
4734 if (meExtendedErrorDetection == EXTENDED_ERROR_DETECTION_NAME_BREAK)
4735 return false; // end compilation
4738 // Provide single token information and continue. Do not set an error, that
4739 // would prematurely end compilation. Simple unknown names are handled by
4740 // the interpreter.
4741 // Use the same CharClass that was used for uppercase.
4742 aUpper = ((bAsciiUpper || mbCharClassesDiffer) ? ScGlobal::getCharClass() : *pCharClass).lowercase( aUpper );
4743 svl::SharedString aSS = rDoc.GetSharedStringPool().intern(aUpper);
4744 maRawToken.SetString(aSS.getData(), aSS.getDataIgnoreCase());
4745 maRawToken.NewOpCode( ocBad );
4746 if ( bAutoCorrect )
4747 AutoCorrectParsedSymbol();
4748 return true;
4751 void ScCompiler::CreateStringFromXMLTokenArray( OUString& rFormula, OUString& rFormulaNmsp )
4753 bool bExternal = GetGrammar() == FormulaGrammar::GRAM_EXTERNAL;
4754 sal_uInt16 nExpectedCount = bExternal ? 2 : 1;
4755 OSL_ENSURE( pArr->GetLen() == nExpectedCount, "ScCompiler::CreateStringFromXMLTokenArray - wrong number of tokens" );
4756 if( pArr->GetLen() == nExpectedCount )
4758 FormulaToken** ppTokens = pArr->GetArray();
4759 // string tokens expected, GetString() will assert if token type is wrong
4760 rFormula = ppTokens[0]->GetString().getString();
4761 if( bExternal )
4762 rFormulaNmsp = ppTokens[1]->GetString().getString();
4766 namespace {
4768 class ExternalFileInserter
4770 ScAddress maPos;
4771 ScExternalRefManager& mrRefMgr;
4772 public:
4773 ExternalFileInserter(const ScAddress& rPos, ScExternalRefManager& rRefMgr) :
4774 maPos(rPos), mrRefMgr(rRefMgr) {}
4776 void operator() (sal_uInt16 nFileId) const
4778 mrRefMgr.insertRefCell(nFileId, maPos);
4784 std::unique_ptr<ScTokenArray> ScCompiler::CompileString( const OUString& rFormula )
4786 OSL_ENSURE( meGrammar != FormulaGrammar::GRAM_EXTERNAL, "ScCompiler::CompileString - unexpected grammar GRAM_EXTERNAL" );
4787 if( meGrammar == FormulaGrammar::GRAM_EXTERNAL )
4788 SetGrammar( FormulaGrammar::GRAM_PODF );
4790 ScTokenArray aArr(rDoc);
4791 pArr = &aArr;
4792 maArrIterator = FormulaTokenArrayPlainIterator(*pArr);
4793 aFormula = comphelper::string::strip(rFormula, ' ');
4795 nSrcPos = 0;
4796 bCorrected = false;
4797 if ( bAutoCorrect )
4799 aCorrectedFormula.clear();
4800 aCorrectedSymbol.clear();
4802 sal_uInt8 nForced = 0; // ==formula forces recalc even if cell is not visible
4803 if( nSrcPos < aFormula.getLength() && aFormula[nSrcPos] == '=' )
4805 nSrcPos++;
4806 nForced++;
4807 if ( bAutoCorrect )
4808 aCorrectedFormula += "=";
4810 if( nSrcPos < aFormula.getLength() && aFormula[nSrcPos] == '=' )
4812 nSrcPos++;
4813 nForced++;
4814 if ( bAutoCorrect )
4815 aCorrectedFormula += "=";
4817 struct FunctionStack
4819 OpCode eOp;
4820 short nSep;
4822 // FunctionStack only used if PODF or OOXML!
4823 bool bPODF = FormulaGrammar::isPODF( meGrammar);
4824 bool bOOXML = FormulaGrammar::isOOXML( meGrammar);
4825 bool bUseFunctionStack = (bPODF || bOOXML);
4826 const size_t nAlloc = 512;
4827 FunctionStack aFuncs[ nAlloc ];
4828 FunctionStack* pFunctionStack = (bUseFunctionStack && o3tl::make_unsigned(rFormula.getLength()) > nAlloc ?
4829 new FunctionStack[rFormula.getLength()] : &aFuncs[0]);
4830 pFunctionStack[0].eOp = ocNone;
4831 pFunctionStack[0].nSep = 0;
4832 size_t nFunction = 0;
4833 size_t nHighWatermark = 0;
4834 short nBrackets = 0;
4835 bool bInArray = false;
4836 eLastOp = ocOpen;
4837 while( NextNewToken( bInArray ) )
4839 const OpCode eOp = maRawToken.GetOpCode();
4840 if (eOp == ocSkip)
4841 continue;
4843 switch (eOp)
4845 case ocOpen:
4847 if (eLastOp == ocLet)
4849 m_aLambda.bInLambdaFunction = true;
4850 m_aLambda.nBracketPos = nBrackets;
4851 m_aLambda.nParaPos++;
4852 m_aLambda.nParaCount = GetPossibleParaCount(rFormula.subView(nSrcPos - 1));
4855 ++nBrackets;
4856 if (bUseFunctionStack)
4858 ++nFunction;
4859 pFunctionStack[ nFunction ].eOp = eLastOp;
4860 pFunctionStack[ nFunction ].nSep = 0;
4861 nHighWatermark = nFunction;
4864 break;
4865 case ocClose:
4867 if( !nBrackets )
4869 SetError( FormulaError::PairExpected );
4870 if ( bAutoCorrect )
4872 bCorrected = true;
4873 aCorrectedSymbol.clear();
4876 else
4878 nBrackets--;
4879 if (m_aLambda.bInLambdaFunction && m_aLambda.nBracketPos == nBrackets)
4881 m_aLambda.bInLambdaFunction = false;
4882 m_aLambda.nBracketPos = nBrackets;
4885 if (bUseFunctionStack && nFunction)
4886 --nFunction;
4888 break;
4889 case ocSep:
4891 if (bUseFunctionStack)
4892 ++pFunctionStack[ nFunction ].nSep;
4894 if (m_aLambda.bInLambdaFunction && m_aLambda.nBracketPos + 1 == nBrackets)
4895 m_aLambda.nParaPos++;
4897 break;
4898 case ocArrayOpen:
4900 if( bInArray )
4901 SetError( FormulaError::NestedArray );
4902 else
4903 bInArray = true;
4904 // Don't count following column separator as parameter separator.
4905 if (bUseFunctionStack)
4907 ++nFunction;
4908 pFunctionStack[ nFunction ].eOp = eOp;
4909 pFunctionStack[ nFunction ].nSep = 0;
4910 nHighWatermark = nFunction;
4913 break;
4914 case ocArrayClose:
4916 if( bInArray )
4918 bInArray = false;
4920 else
4922 SetError( FormulaError::PairExpected );
4923 if ( bAutoCorrect )
4925 bCorrected = true;
4926 aCorrectedSymbol.clear();
4929 if (bUseFunctionStack && nFunction)
4930 --nFunction;
4932 break;
4933 case ocTableRefOpen:
4935 // Don't count following item separator as parameter separator.
4936 if (bUseFunctionStack)
4938 ++nFunction;
4939 pFunctionStack[ nFunction ].eOp = eOp;
4940 pFunctionStack[ nFunction ].nSep = 0;
4941 nHighWatermark = nFunction;
4944 break;
4945 case ocTableRefClose:
4947 if (bUseFunctionStack && nFunction)
4948 --nFunction;
4950 break;
4951 case ocColRowName:
4952 case ocColRowNameAuto:
4953 // The current implementation of column / row labels doesn't
4954 // function correctly in grouped cells.
4955 aArr.SetShareable(false);
4956 break;
4957 default:
4958 break;
4960 if ((eLastOp != ocOpen || eOp != ocClose) &&
4961 (eLastOp == ocOpen ||
4962 eLastOp == ocSep ||
4963 eLastOp == ocArrayRowSep ||
4964 eLastOp == ocArrayColSep ||
4965 eLastOp == ocArrayOpen) &&
4966 (eOp == ocSep ||
4967 eOp == ocClose ||
4968 eOp == ocArrayRowSep ||
4969 eOp == ocArrayColSep ||
4970 eOp == ocArrayClose))
4972 // TODO: should we check for known functions with optional empty
4973 // args so the correction dialog can do better?
4974 if ( !static_cast<ScTokenArray*>(pArr)->Add( new FormulaMissingToken ) )
4976 SetError(FormulaError::CodeOverflow); break;
4979 if (bOOXML)
4981 // Append a parameter for WEEKNUM, all 1.0
4982 // Function is already closed, parameter count is nSep+1
4983 size_t nFunc = nFunction + 1;
4984 if (eOp == ocClose && nFunc <= nHighWatermark &&
4985 pFunctionStack[ nFunc ].nSep == 0 &&
4986 pFunctionStack[ nFunc ].eOp == ocWeek) // 2nd week start
4988 if ( !static_cast<ScTokenArray*>(pArr)->Add( new FormulaToken( svSep, ocSep)) ||
4989 !static_cast<ScTokenArray*>(pArr)->Add( new FormulaDoubleToken( 1.0)))
4991 SetError(FormulaError::CodeOverflow); break;
4995 else if (bPODF)
4997 /* TODO: for now this is the only PODF adapter. If there were more,
4998 * factor this out. */
4999 // Insert ADDRESS() new empty parameter 4 if there is a 4th, now to be 5th.
5000 if (eOp == ocSep &&
5001 pFunctionStack[ nFunction ].eOp == ocAddress &&
5002 pFunctionStack[ nFunction ].nSep == 3)
5004 if ( !static_cast<ScTokenArray*>(pArr)->Add( new FormulaToken( svSep, ocSep)) ||
5005 !static_cast<ScTokenArray*>(pArr)->Add( new FormulaDoubleToken( 1.0)))
5007 SetError(FormulaError::CodeOverflow); break;
5009 ++pFunctionStack[ nFunction ].nSep;
5012 FormulaToken* pNewToken = static_cast<ScTokenArray*>(pArr)->Add( maRawToken.CreateToken(rDoc.GetSheetLimits()));
5013 if (!pNewToken && eOp == ocArrayClose && pArr->OpCodeBefore( pArr->GetLen()) == ocArrayClose)
5015 // Nested inline array or non-value/non-string in array. The
5016 // original tokens are still in the ScTokenArray and not merged
5017 // into an ScMatrixToken. Set error but keep on tokenizing.
5018 SetError( FormulaError::BadArrayContent);
5020 else if (!pNewToken)
5022 SetError(FormulaError::CodeOverflow);
5023 break;
5025 else if (eLastOp == ocRange && pNewToken->GetOpCode() == ocPush && pNewToken->GetType() == svSingleRef)
5027 static_cast<ScTokenArray*>(pArr)->MergeRangeReference( aPos);
5029 else if (eLastOp == ocDBArea && pNewToken->GetOpCode() == ocTableRefOpen)
5031 sal_uInt16 nIdx = pArr->GetLen() - 1;
5032 const FormulaToken* pPrev = pArr->PeekPrev( nIdx);
5033 if (pPrev && pPrev->GetOpCode() == ocDBArea)
5035 ScTableRefToken* pTableRefToken = new ScTableRefToken( pPrev->GetIndex(), ScTableRefToken::TABLE);
5036 maTableRefs.emplace_back( pTableRefToken);
5037 // pPrev may be dead hereafter.
5038 static_cast<ScTokenArray*>(pArr)->ReplaceToken( nIdx, pTableRefToken,
5039 FormulaTokenArray::ReplaceMode::CODE_ONLY);
5042 switch (eOp)
5044 case ocTableRefOpen:
5045 SAL_WARN_IF( maTableRefs.empty(), "sc.core", "ocTableRefOpen without TableRefEntry");
5046 if (maTableRefs.empty())
5047 SetError(FormulaError::Pair);
5048 else
5049 ++maTableRefs.back().mnLevel;
5050 break;
5051 case ocTableRefClose:
5052 SAL_WARN_IF( maTableRefs.empty(), "sc.core", "ocTableRefClose without TableRefEntry");
5053 if (maTableRefs.empty())
5054 SetError(FormulaError::Pair);
5055 else
5057 if (--maTableRefs.back().mnLevel == 0)
5058 maTableRefs.pop_back();
5060 break;
5061 default:
5062 break;
5064 eLastOp = maRawToken.GetOpCode();
5065 if ( bAutoCorrect )
5066 aCorrectedFormula += aCorrectedSymbol;
5068 if ( mbCloseBrackets )
5070 if( bInArray )
5072 FormulaByteToken aToken( ocArrayClose );
5073 if( !pArr->AddToken( aToken ) )
5075 SetError(FormulaError::CodeOverflow);
5077 else if ( bAutoCorrect )
5078 aCorrectedFormula += mxSymbols->getSymbol(ocArrayClose);
5081 if (nBrackets)
5083 FormulaToken aToken( svSep, ocClose );
5084 while( nBrackets-- )
5086 if( !pArr->AddToken( aToken ) )
5088 SetError(FormulaError::CodeOverflow);
5089 break; // while
5091 if ( bAutoCorrect )
5092 aCorrectedFormula += mxSymbols->getSymbol(ocClose);
5096 if ( nForced >= 2 )
5097 pArr->SetRecalcModeForced();
5099 if (pFunctionStack != &aFuncs[0])
5100 delete [] pFunctionStack;
5102 // remember pArr, in case a subsequent CompileTokenArray() is executed.
5103 std::unique_ptr<ScTokenArray> pNew(new ScTokenArray( std::move(aArr) ));
5104 pNew->GenHash();
5105 // coverity[escape : FALSE] - ownership of pNew is retained by caller, so pArr remains valid
5106 pArr = pNew.get();
5107 maArrIterator = FormulaTokenArrayPlainIterator(*pArr);
5109 if (!maExternalFiles.empty())
5111 // Remove duplicates, and register all external files found in this cell.
5112 std::sort(maExternalFiles.begin(), maExternalFiles.end());
5113 std::vector<sal_uInt16>::iterator itEnd = std::unique(maExternalFiles.begin(), maExternalFiles.end());
5114 std::for_each(maExternalFiles.begin(), itEnd, ExternalFileInserter(aPos, *rDoc.GetExternalRefManager()));
5115 maExternalFiles.erase(itEnd, maExternalFiles.end());
5118 return pNew;
5121 std::unique_ptr<ScTokenArray> ScCompiler::CompileString( const OUString& rFormula, const OUString& rFormulaNmsp )
5123 OSL_ENSURE( (GetGrammar() == FormulaGrammar::GRAM_EXTERNAL) || rFormulaNmsp.isEmpty(),
5124 "ScCompiler::CompileString - unexpected formula namespace for internal grammar" );
5125 if( GetGrammar() == FormulaGrammar::GRAM_EXTERNAL ) try
5127 ScFormulaParserPool& rParserPool = rDoc.GetFormulaParserPool();
5128 uno::Reference< sheet::XFormulaParser > xParser( rParserPool.getFormulaParser( rFormulaNmsp ), uno::UNO_SET_THROW );
5129 table::CellAddress aReferencePos;
5130 ScUnoConversion::FillApiAddress( aReferencePos, aPos );
5131 uno::Sequence< sheet::FormulaToken > aTokenSeq = xParser->parseFormula( rFormula, aReferencePos );
5132 ScTokenArray aTokenArray(rDoc);
5133 if( ScTokenConversion::ConvertToTokenArray( rDoc, aTokenArray, aTokenSeq ) )
5135 // remember pArr, in case a subsequent CompileTokenArray() is executed.
5136 std::unique_ptr<ScTokenArray> pNew(new ScTokenArray( std::move(aTokenArray) ));
5137 // coverity[escape : FALSE] - ownership of pNew is retained by caller, so pArr remains valid
5138 pArr = pNew.get();
5139 maArrIterator = FormulaTokenArrayPlainIterator(*pArr);
5140 return pNew;
5143 catch( uno::Exception& )
5146 // no success - fallback to some internal grammar and hope the best
5147 return CompileString( rFormula );
5150 ScRangeData* ScCompiler::GetRangeData( const FormulaToken& rToken ) const
5152 return rDoc.FindRangeNameBySheetAndIndex( rToken.GetSheet(), rToken.GetIndex());
5155 bool ScCompiler::HandleStringName()
5157 ScTokenArray* pNew = new ScTokenArray(rDoc);
5158 pNew->AddString(mpToken->GetString());
5159 PushTokenArray(pNew, true);
5160 return GetToken();
5163 bool ScCompiler::HandleRange()
5165 ScTokenArray* pNew;
5166 const ScRangeData* pRangeData = GetRangeData( *mpToken);
5167 if (pRangeData)
5169 FormulaError nErr = pRangeData->GetErrCode();
5170 if( nErr != FormulaError::NONE )
5171 SetError( nErr );
5172 else if (mbJumpCommandReorder)
5174 // put named formula into parentheses.
5175 // But only if there aren't any yet, parenthetical
5176 // ocSep doesn't work, e.g. SUM((...;...))
5177 // or if not directly between ocSep/parenthesis,
5178 // e.g. SUM(...;(...;...)) no, SUM(...;(...)*3) yes,
5179 // in short: if it isn't a self-contained expression.
5180 FormulaToken* p1 = maArrIterator.PeekPrevNoSpaces();
5181 FormulaToken* p2 = maArrIterator.PeekNextNoSpaces();
5182 OpCode eOp1 = (p1 ? p1->GetOpCode() : ocSep);
5183 OpCode eOp2 = (p2 ? p2->GetOpCode() : ocSep);
5184 bool bBorder1 = (eOp1 == ocSep || eOp1 == ocOpen);
5185 bool bBorder2 = (eOp2 == ocSep || eOp2 == ocClose);
5186 bool bAddPair = !(bBorder1 && bBorder2);
5187 if ( bAddPair )
5189 pNew = new ScTokenArray(rDoc);
5190 pNew->AddOpCode( ocClose );
5191 PushTokenArray( pNew, true );
5193 pNew = pRangeData->GetCode()->Clone().release();
5194 pNew->SetFromRangeName( true );
5195 PushTokenArray( pNew, true );
5196 if( pRangeData->HasReferences() )
5198 // Relative sheet references in sheet-local named expressions
5199 // shall still point to the same sheet as if used on the
5200 // original sheet, not shifted to the current position where
5201 // they are used.
5202 SCTAB nSheetTab = mpToken->GetSheet();
5203 if (nSheetTab >= 0 && nSheetTab != aPos.Tab())
5204 AdjustSheetLocalNameRelReferences( nSheetTab - aPos.Tab());
5206 SetRelNameReference();
5207 MoveRelWrap();
5209 maArrIterator.Reset();
5210 if ( bAddPair )
5212 pNew = new ScTokenArray(rDoc);
5213 pNew->AddOpCode( ocOpen );
5214 PushTokenArray( pNew, true );
5216 return GetToken();
5219 else
5221 // No ScRangeData for an already compiled token can happen in BIFF .xls
5222 // import if the original range is not present in the document.
5223 pNew = new ScTokenArray(rDoc);
5224 pNew->Add( new FormulaErrorToken( FormulaError::NoName));
5225 PushTokenArray( pNew, true );
5226 return GetToken();
5228 return true;
5231 bool ScCompiler::HandleExternalReference(const FormulaToken& _aToken)
5233 // Handle external range names.
5234 switch (_aToken.GetType())
5236 case svExternalSingleRef:
5237 case svExternalDoubleRef:
5238 break;
5239 case svExternalName:
5241 ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager();
5242 const OUString* pFile = pRefMgr->getExternalFileName(_aToken.GetIndex());
5243 if (!pFile)
5245 SetError(FormulaError::NoName);
5246 return true;
5249 OUString aName = _aToken.GetString().getString();
5250 ScExternalRefCache::TokenArrayRef xNew = pRefMgr->getRangeNameTokens(
5251 _aToken.GetIndex(), aName, &aPos);
5253 if (!xNew)
5255 SetError(FormulaError::NoName);
5256 return true;
5259 ScTokenArray* pNew = xNew->Clone().release();
5260 PushTokenArray( pNew, true);
5261 if (FormulaTokenArrayPlainIterator(*pNew).GetNextReference() != nullptr)
5263 SetRelNameReference();
5264 MoveRelWrap();
5266 maArrIterator.Reset();
5267 return GetToken();
5269 default:
5270 OSL_FAIL("Wrong type for external reference!");
5271 return false;
5273 return true;
5276 void ScCompiler::AdjustSheetLocalNameRelReferences( SCTAB nDelta )
5278 for ( auto t: pArr->References() )
5280 ScSingleRefData& rRef1 = *t->GetSingleRef();
5281 if (rRef1.IsTabRel())
5282 rRef1.IncTab( nDelta);
5283 if ( t->GetType() == svDoubleRef )
5285 ScSingleRefData& rRef2 = t->GetDoubleRef()->Ref2;
5286 if (rRef2.IsTabRel())
5287 rRef2.IncTab( nDelta);
5292 // reference of named range with relative references
5294 void ScCompiler::SetRelNameReference()
5296 for ( auto t: pArr->References() )
5298 ScSingleRefData& rRef1 = *t->GetSingleRef();
5299 if ( rRef1.IsColRel() || rRef1.IsRowRel() || rRef1.IsTabRel() )
5300 rRef1.SetRelName( true );
5301 if ( t->GetType() == svDoubleRef )
5303 ScSingleRefData& rRef2 = t->GetDoubleRef()->Ref2;
5304 if ( rRef2.IsColRel() || rRef2.IsRowRel() || rRef2.IsTabRel() )
5305 rRef2.SetRelName( true );
5310 // Wrap-adjust relative references of a RangeName to current position,
5311 // don't call for other token arrays!
5312 void ScCompiler::MoveRelWrap()
5314 for ( auto t: pArr->References() )
5316 if ( t->GetType() == svSingleRef || t->GetType() == svExternalSingleRef )
5317 ScRefUpdate::MoveRelWrap( rDoc, aPos, rDoc.MaxCol(), rDoc.MaxRow(), SingleDoubleRefModifier( *t->GetSingleRef() ).Ref() );
5318 else
5319 ScRefUpdate::MoveRelWrap( rDoc, aPos, rDoc.MaxCol(), rDoc.MaxRow(), *t->GetDoubleRef() );
5323 // Wrap-adjust relative references of a RangeName to current position,
5324 // don't call for other token arrays!
5325 void ScCompiler::MoveRelWrap( const ScTokenArray& rArr, const ScDocument& rDoc, const ScAddress& rPos,
5326 SCCOL nMaxCol, SCROW nMaxRow )
5328 for ( auto t: rArr.References() )
5330 if ( t->GetType() == svSingleRef || t->GetType() == svExternalSingleRef )
5331 ScRefUpdate::MoveRelWrap( rDoc, rPos, nMaxCol, nMaxRow, SingleDoubleRefModifier( *t->GetSingleRef() ).Ref() );
5332 else
5333 ScRefUpdate::MoveRelWrap( rDoc, rPos, nMaxCol, nMaxRow, *t->GetDoubleRef() );
5337 bool ScCompiler::IsCharFlagAllConventions(
5338 OUString const & rStr, sal_Int32 nPos, ScCharFlags nFlags )
5340 sal_Unicode c = rStr[ nPos ];
5341 sal_Unicode cLast = nPos > 0 ? rStr[ nPos-1 ] : 0;
5342 if (c < 128)
5344 for ( int nConv = formula::FormulaGrammar::CONV_UNSPECIFIED;
5345 ++nConv < formula::FormulaGrammar::CONV_LAST; )
5347 if (pConventions[nConv] &&
5348 ((pConventions[nConv]->getCharTableFlags(c, cLast) & nFlags) != nFlags))
5349 return false;
5350 // convention not known => assume valid
5352 return true;
5354 else
5355 return ScGlobal::getCharClass().isLetterNumeric( rStr, nPos );
5358 void ScCompiler::CreateStringFromExternal( OUStringBuffer& rBuffer, const FormulaToken* pTokenP ) const
5360 const FormulaToken* t = pTokenP;
5361 sal_uInt16 nFileId = t->GetIndex();
5362 ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager();
5363 sal_uInt16 nUsedFileId = pRefMgr->convertFileIdToUsedFileId(nFileId);
5364 const OUString* pFileName = pRefMgr->getExternalFileName(nFileId);
5365 if (!pFileName)
5366 return;
5368 switch (t->GetType())
5370 case svExternalName:
5371 rBuffer.append(pConv->makeExternalNameStr( nFileId, *pFileName, t->GetString().getString()));
5372 break;
5373 case svExternalSingleRef:
5374 pConv->makeExternalRefStr(rDoc.GetSheetLimits(),
5375 rBuffer, GetPos(), nUsedFileId, *pFileName, t->GetString().getString(),
5376 *t->GetSingleRef());
5377 break;
5378 case svExternalDoubleRef:
5380 vector<OUString> aTabNames;
5381 pRefMgr->getAllCachedTableNames(nFileId, aTabNames);
5382 // No sheet names is a valid case if external sheets were not
5383 // cached in this document and external document is not reachable,
5384 // else not and worth to be investigated.
5385 SAL_WARN_IF( aTabNames.empty(), "sc.core", "wrecked cache of external document? '" <<
5386 *pFileName << "' '" << t->GetString().getString() << "'");
5388 pConv->makeExternalRefStr(
5389 rDoc.GetSheetLimits(), rBuffer, GetPos(), nUsedFileId, *pFileName, aTabNames, t->GetString().getString(),
5390 *t->GetDoubleRef());
5392 break;
5393 default:
5394 // warning, not error, otherwise we may end up with a never
5395 // ending message box loop if this was the cursor cell to be redrawn.
5396 OSL_FAIL("ScCompiler::CreateStringFromToken: unknown type of ocExternalRef");
5400 void ScCompiler::CreateStringFromMatrix( OUStringBuffer& rBuffer, const FormulaToken* pTokenP ) const
5402 const ScMatrix* pMatrix = pTokenP->GetMatrix();
5403 SCSIZE nC, nMaxC, nR, nMaxR;
5405 pMatrix->GetDimensions( nMaxC, nMaxR);
5407 rBuffer.append( mxSymbols->getSymbol(ocArrayOpen) );
5408 for( nR = 0 ; nR < nMaxR ; nR++)
5410 if( nR > 0)
5412 rBuffer.append( mxSymbols->getSymbol(ocArrayRowSep) );
5415 for( nC = 0 ; nC < nMaxC ; nC++)
5417 if( nC > 0)
5419 rBuffer.append( mxSymbols->getSymbol(ocArrayColSep) );
5422 if( pMatrix->IsValue( nC, nR ) )
5424 if (pMatrix->IsBoolean(nC, nR))
5425 AppendBoolean(rBuffer, pMatrix->GetDouble(nC, nR) != 0.0);
5426 else
5428 FormulaError nErr = pMatrix->GetError(nC, nR);
5429 if (nErr != FormulaError::NONE)
5430 rBuffer.append(ScGlobal::GetErrorString(nErr));
5431 else
5432 AppendDouble(rBuffer, pMatrix->GetDouble(nC, nR));
5435 else if( pMatrix->IsEmpty( nC, nR ) )
5437 else if( pMatrix->IsStringOrEmpty( nC, nR ) )
5438 AppendString( rBuffer, pMatrix->GetString(nC, nR).getString() );
5441 rBuffer.append( mxSymbols->getSymbol(ocArrayClose) );
5444 namespace {
5445 void escapeTableRefColumnSpecifier( OUString& rStr )
5447 const sal_Int32 n = rStr.getLength();
5448 OUStringBuffer aBuf( n * 2 );
5449 const sal_Unicode* p = rStr.getStr();
5450 const sal_Unicode* const pStop = p + n;
5451 for ( ; p < pStop; ++p)
5453 const sal_Unicode c = *p;
5454 switch (c)
5456 case '\'':
5457 case '[':
5458 case '#':
5459 case ']':
5460 aBuf.append( '\'' );
5461 break;
5462 default:
5463 ; // nothing
5465 aBuf.append( c );
5467 rStr = aBuf.makeStringAndClear();
5471 void ScCompiler::CreateStringFromSingleRef( OUStringBuffer& rBuffer, const FormulaToken* _pTokenP ) const
5473 const FormulaToken* p;
5474 OUString aErrRef = GetCurrentOpCodeMap()->getSymbol(ocErrRef);
5475 const OpCode eOp = _pTokenP->GetOpCode();
5476 const ScSingleRefData& rRef = *_pTokenP->GetSingleRef();
5477 ScComplexRefData aRef;
5478 aRef.Ref1 = aRef.Ref2 = rRef;
5479 if ( eOp == ocColRowName )
5481 ScAddress aAbs = rRef.toAbs(rDoc, aPos);
5482 if (rDoc.HasStringData(aAbs.Col(), aAbs.Row(), aAbs.Tab()))
5484 OUString aStr = rDoc.GetString(aAbs, &mrInterpreterContext);
5485 // Enquote to SingleQuoted.
5486 aStr = aStr.replaceAll(u"'", u"''");
5487 rBuffer.append('\'');
5488 rBuffer.append(aStr);
5489 rBuffer.append('\'');
5491 else
5493 rBuffer.append(ScCompiler::GetNativeSymbol(ocErrName));
5494 pConv->makeRefStr(rDoc.GetSheetLimits(), rBuffer, meGrammar, aPos, aErrRef,
5495 GetSetupTabNames(), aRef, true, (pArr && pArr->IsFromRangeName()));
5498 else if (pArr && (p = maArrIterator.PeekPrevNoSpaces()) && p->GetOpCode() == ocTableRefOpen)
5500 OUString aStr;
5501 ScAddress aAbs = rRef.toAbs(rDoc, aPos);
5502 const ScDBData* pData = rDoc.GetDBAtCursor( aAbs.Col(), aAbs.Row(), aAbs.Tab(), ScDBDataPortion::AREA);
5503 SAL_WARN_IF( !pData, "sc.core", "ScCompiler::CreateStringFromSingleRef - TableRef without ScDBData: " <<
5504 aAbs.Format( ScRefFlags::VALID | ScRefFlags::TAB_3D, &rDoc));
5505 if (pData)
5506 aStr = pData->GetTableColumnName( aAbs.Col());
5507 if (aStr.isEmpty())
5509 if (pData && pData->HasHeader())
5511 SAL_WARN("sc.core", "ScCompiler::CreateStringFromSingleRef - TableRef falling back to cell: " <<
5512 aAbs.Format( ScRefFlags::VALID | ScRefFlags::TAB_3D, &rDoc));
5513 aStr = rDoc.GetString(aAbs, &mrInterpreterContext);
5515 else
5517 SAL_WARN("sc.core", "ScCompiler::CreateStringFromSingleRef - TableRef of empty header-less: " <<
5518 aAbs.Format( ScRefFlags::VALID | ScRefFlags::TAB_3D, &rDoc));
5519 aStr = aErrRef;
5522 escapeTableRefColumnSpecifier( aStr);
5523 rBuffer.append(aStr);
5525 else
5526 pConv->makeRefStr(rDoc.GetSheetLimits(), rBuffer, meGrammar, aPos, aErrRef,
5527 GetSetupTabNames(), aRef, true, (pArr && pArr->IsFromRangeName()));
5530 void ScCompiler::CreateStringFromDoubleRef( OUStringBuffer& rBuffer, const FormulaToken* _pTokenP ) const
5532 OUString aErrRef = GetCurrentOpCodeMap()->getSymbol(ocErrRef);
5533 pConv->makeRefStr(rDoc.GetSheetLimits(), rBuffer, meGrammar, aPos, aErrRef, GetSetupTabNames(),
5534 *_pTokenP->GetDoubleRef(), false, (pArr && pArr->IsFromRangeName()));
5537 void ScCompiler::CreateStringFromIndex( OUStringBuffer& rBuffer, const FormulaToken* _pTokenP ) const
5539 const OpCode eOp = _pTokenP->GetOpCode();
5540 OUStringBuffer aBuffer;
5541 switch ( eOp )
5543 case ocName:
5545 const ScRangeData* pData = GetRangeData( *_pTokenP);
5546 if (pData)
5548 SCTAB nTab = _pTokenP->GetSheet();
5549 if (nTab >= 0 && (nTab != aPos.Tab() || mbRefConventionChartOOXML))
5551 // Sheet-local on other sheet.
5552 OUString aName;
5553 if (rDoc.GetName( nTab, aName))
5555 ScCompiler::CheckTabQuotes( aName, pConv->meConv);
5556 aBuffer.append( aName);
5558 else
5559 aBuffer.append(ScCompiler::GetNativeSymbol(ocErrName));
5560 aBuffer.append( pConv->getSpecialSymbol( ScCompiler::Convention::SHEET_SEPARATOR));
5562 else if (mbRefConventionChartOOXML)
5564 aBuffer.append("[0]"
5565 + OUStringChar(pConv->getSpecialSymbol(ScCompiler::Convention::SHEET_SEPARATOR)));
5567 aBuffer.append(pData->GetName());
5570 break;
5571 case ocDBArea:
5573 const ScDBData* pDBData = rDoc.GetDBCollection()->getNamedDBs().findByIndex(_pTokenP->GetIndex());
5574 if (pDBData)
5575 aBuffer.append(pDBData->GetName());
5577 break;
5578 case ocTableRef:
5580 if (NeedsTableRefTransformation())
5582 // Write the resulting reference if TableRef is not supported.
5583 const ScTableRefToken* pTR = dynamic_cast<const ScTableRefToken*>(_pTokenP);
5584 if (!pTR)
5585 AppendErrorConstant( aBuffer, FormulaError::NoCode);
5586 else
5588 const FormulaToken* pRef = pTR->GetAreaRefRPN();
5589 if (!pRef)
5590 AppendErrorConstant( aBuffer, FormulaError::NoCode);
5591 else
5593 switch (pRef->GetType())
5595 case svSingleRef:
5596 CreateStringFromSingleRef( aBuffer, pRef);
5597 break;
5598 case svDoubleRef:
5599 CreateStringFromDoubleRef( aBuffer, pRef);
5600 break;
5601 case svError:
5602 AppendErrorConstant( aBuffer, pRef->GetError());
5603 break;
5604 default:
5605 AppendErrorConstant( aBuffer, FormulaError::NoCode);
5610 else
5612 const ScDBData* pDBData = rDoc.GetDBCollection()->getNamedDBs().findByIndex(_pTokenP->GetIndex());
5613 if (pDBData)
5614 aBuffer.append(pDBData->GetName());
5617 break;
5618 default:
5619 ; // nothing
5621 if ( !aBuffer.isEmpty() )
5622 rBuffer.append(aBuffer);
5623 else
5624 rBuffer.append(ScCompiler::GetNativeSymbol(ocErrName));
5627 void ScCompiler::LocalizeString( OUString& rName ) const
5629 ScGlobal::GetAddInCollection()->LocalizeString( rName );
5632 bool ScCompiler::GetExcelName( OUString& rName ) const
5634 return ScGlobal::GetAddInCollection()->GetExcelName( rName, LANGUAGE_ENGLISH_US, rName);
5637 FormulaTokenRef ScCompiler::ExtendRangeReference( FormulaToken & rTok1, FormulaToken & rTok2 )
5639 return extendRangeReference( rDoc.GetSheetLimits(), rTok1, rTok2, aPos, true/*bReuseDoubleRef*/ );
5642 void ScCompiler::fillAddInToken(::std::vector< css::sheet::FormulaOpCodeMapEntry >& _rVec,bool _bIsEnglish) const
5644 // All known AddIn functions.
5645 sheet::FormulaOpCodeMapEntry aEntry;
5646 aEntry.Token.OpCode = ocExternal;
5648 const LanguageTag aEnglishLanguageTag(LANGUAGE_ENGLISH_US);
5649 ScUnoAddInCollection* pColl = ScGlobal::GetAddInCollection();
5650 const tools::Long nCount = pColl->GetFuncCount();
5651 for (tools::Long i=0; i < nCount; ++i)
5653 const ScUnoAddInFuncData* pFuncData = pColl->GetFuncData(i);
5654 if (pFuncData)
5656 if ( _bIsEnglish )
5658 // This is used with OOXML import, so GetExcelName() is really
5659 // wanted here until we'll have a parameter to differentiate
5660 // from the general css::sheet::XFormulaOpCodeMapper case and
5661 // use pFuncData->GetUpperEnglish().
5662 OUString aName;
5663 if (pFuncData->GetExcelName( aEnglishLanguageTag, aName))
5664 aEntry.Name = aName;
5665 else
5666 aEntry.Name = pFuncData->GetUpperName();
5668 else
5669 aEntry.Name = pFuncData->GetUpperLocal();
5670 aEntry.Token.Data <<= pFuncData->GetOriginalName();
5671 _rVec.push_back( aEntry);
5674 // FIXME: what about those old non-UNO AddIns?
5677 bool ScCompiler::HandleColRowName()
5679 ScSingleRefData& rRef = *mpToken->GetSingleRef();
5680 const ScAddress aAbs = rRef.toAbs(rDoc, aPos);
5681 if (!rDoc.ValidAddress(aAbs))
5683 SetError( FormulaError::NoRef );
5684 return true;
5686 SCCOL nCol = aAbs.Col();
5687 SCROW nRow = aAbs.Row();
5688 SCTAB nTab = aAbs.Tab();
5689 bool bColName = rRef.IsColRel();
5690 SCCOL nMyCol = aPos.Col();
5691 SCROW nMyRow = aPos.Row();
5692 bool bInList = false;
5693 bool bValidName = false;
5694 ScRangePairList* pRL = (bColName ?
5695 rDoc.GetColNameRanges() : rDoc.GetRowNameRanges());
5696 ScRange aRange;
5697 for ( size_t i = 0, nPairs = pRL->size(); i < nPairs; ++i )
5699 const ScRangePair & rR = (*pRL)[i];
5700 if ( rR.GetRange(0).Contains( aAbs ) )
5702 bInList = bValidName = true;
5703 aRange = rR.GetRange(1);
5704 if ( bColName )
5706 aRange.aStart.SetCol( nCol );
5707 aRange.aEnd.SetCol( nCol );
5709 else
5711 aRange.aStart.SetRow( nRow );
5712 aRange.aEnd.SetRow( nRow );
5714 break; // for
5717 if ( !bInList && rDoc.GetDocOptions().IsLookUpColRowNames() )
5718 { // automagically or created by copying and NamePos isn't in list
5719 ScRefCellValue aCell(rDoc, aAbs);
5720 bool bString = aCell.hasString();
5721 if (!bString && aCell.isEmpty())
5722 bString = true; // empty cell is ok
5723 if ( bString )
5724 { // corresponds with ScInterpreter::ScColRowNameAuto()
5725 bValidName = true;
5726 if ( bColName )
5727 { // ColName
5728 SCROW nStartRow = nRow + 1;
5729 if ( nStartRow > rDoc.MaxRow() )
5730 nStartRow = rDoc.MaxRow();
5731 SCROW nMaxRow = rDoc.MaxRow();
5732 if ( nMyCol == nCol )
5733 { // formula cell in same column
5734 if ( nMyRow == nStartRow )
5735 { // take remainder under name cell
5736 nStartRow++;
5737 if ( nStartRow > rDoc.MaxRow() )
5738 nStartRow = rDoc.MaxRow();
5740 else if ( nMyRow > nStartRow )
5741 { // from name cell down to formula cell
5742 nMaxRow = nMyRow - 1;
5745 for ( size_t i = 0, nPairs = pRL->size(); i < nPairs; ++i )
5746 { // next defined ColNameRange below limits row
5747 const ScRangePair & rR = (*pRL)[i];
5748 const ScRange& rRange = rR.GetRange(1);
5749 if ( rRange.aStart.Col() <= nCol && nCol <= rRange.aEnd.Col() )
5750 { // identical column range
5751 SCROW nTmp = rRange.aStart.Row();
5752 if ( nStartRow < nTmp && nTmp <= nMaxRow )
5753 nMaxRow = nTmp - 1;
5756 aRange.aStart.Set( nCol, nStartRow, nTab );
5757 aRange.aEnd.Set( nCol, nMaxRow, nTab );
5759 else
5760 { // RowName
5761 SCCOL nStartCol = nCol + 1;
5762 if ( nStartCol > rDoc.MaxCol() )
5763 nStartCol = rDoc.MaxCol();
5764 SCCOL nMaxCol = rDoc.MaxCol();
5765 if ( nMyRow == nRow )
5766 { // formula cell in same row
5767 if ( nMyCol == nStartCol )
5768 { // take remainder right from name cell
5769 nStartCol++;
5770 if ( nStartCol > rDoc.MaxCol() )
5771 nStartCol = rDoc.MaxCol();
5773 else if ( nMyCol > nStartCol )
5774 { // from name cell right to formula cell
5775 nMaxCol = nMyCol - 1;
5778 for ( size_t i = 0, nPairs = pRL->size(); i < nPairs; ++i )
5779 { // next defined RowNameRange to the right limits column
5780 const ScRangePair & rR = (*pRL)[i];
5781 const ScRange& rRange = rR.GetRange(1);
5782 if ( rRange.aStart.Row() <= nRow && nRow <= rRange.aEnd.Row() )
5783 { // identical row range
5784 SCCOL nTmp = rRange.aStart.Col();
5785 if ( nStartCol < nTmp && nTmp <= nMaxCol )
5786 nMaxCol = nTmp - 1;
5789 aRange.aStart.Set( nStartCol, nRow, nTab );
5790 aRange.aEnd.Set( nMaxCol, nRow, nTab );
5794 if ( bValidName )
5796 // And now the magic to distinguish between a range and a single
5797 // cell thereof, which is picked position-dependent of the formula
5798 // cell. If a direct neighbor is a binary operator (ocAdd, ...) a
5799 // SingleRef matching the column/row of the formula cell is
5800 // generated. A ocColRowName or ocIntersect as a neighbor results
5801 // in a range. Special case: if label is valid for a single cell, a
5802 // position independent SingleRef is generated.
5803 bool bSingle = (aRange.aStart == aRange.aEnd);
5804 bool bFound;
5805 if ( bSingle )
5806 bFound = true;
5807 else
5809 FormulaToken* p1 = maArrIterator.PeekPrevNoSpaces();
5810 FormulaToken* p2 = maArrIterator.PeekNextNoSpaces();
5811 // begin/end of a formula => single
5812 OpCode eOp1 = p1 ? p1->GetOpCode() : ocAdd;
5813 OpCode eOp2 = p2 ? p2->GetOpCode() : ocAdd;
5814 if ( eOp1 != ocColRowName && eOp1 != ocIntersect
5815 && eOp2 != ocColRowName && eOp2 != ocIntersect )
5817 if ( (SC_OPCODE_START_BIN_OP <= eOp1 && eOp1 < SC_OPCODE_STOP_BIN_OP) ||
5818 (SC_OPCODE_START_BIN_OP <= eOp2 && eOp2 < SC_OPCODE_STOP_BIN_OP))
5819 bSingle = true;
5821 if ( bSingle )
5822 { // column and/or row must match range
5823 if ( bColName )
5825 bFound = (aRange.aStart.Row() <= nMyRow
5826 && nMyRow <= aRange.aEnd.Row());
5827 if ( bFound )
5828 aRange.aStart.SetRow( nMyRow );
5830 else
5832 bFound = (aRange.aStart.Col() <= nMyCol
5833 && nMyCol <= aRange.aEnd.Col());
5834 if ( bFound )
5835 aRange.aStart.SetCol( nMyCol );
5838 else
5839 bFound = true;
5841 if ( !bFound )
5842 SetError(FormulaError::NoRef);
5843 else if (mbJumpCommandReorder)
5845 ScTokenArray* pNew = new ScTokenArray(rDoc);
5846 if ( bSingle )
5848 ScSingleRefData aRefData;
5849 aRefData.InitAddress( aRange.aStart );
5850 if ( bColName )
5851 aRefData.SetColRel( true );
5852 else
5853 aRefData.SetRowRel( true );
5854 aRefData.SetAddress(rDoc.GetSheetLimits(), aRange.aStart, aPos);
5855 pNew->AddSingleReference( aRefData );
5857 else
5859 ScComplexRefData aRefData;
5860 aRefData.InitRange( aRange );
5861 if ( bColName )
5863 aRefData.Ref1.SetColRel( true );
5864 aRefData.Ref2.SetColRel( true );
5866 else
5868 aRefData.Ref1.SetRowRel( true );
5869 aRefData.Ref2.SetRowRel( true );
5871 aRefData.SetRange(rDoc.GetSheetLimits(), aRange, aPos);
5872 if ( bInList )
5873 pNew->AddDoubleReference( aRefData );
5874 else
5875 { // automagically
5876 pNew->Add( new ScDoubleRefToken( rDoc.GetSheetLimits(), aRefData, ocColRowNameAuto ) );
5879 PushTokenArray( pNew, true );
5880 return GetToken();
5883 else
5884 SetError(FormulaError::NoName);
5885 return true;
5888 bool ScCompiler::HandleDbData()
5890 ScDBData* pDBData = rDoc.GetDBCollection()->getNamedDBs().findByIndex(mpToken->GetIndex());
5891 if ( !pDBData )
5892 SetError(FormulaError::NoName);
5893 else if (mbJumpCommandReorder)
5895 ScComplexRefData aRefData;
5896 aRefData.InitFlags();
5897 ScRange aRange;
5898 pDBData->GetArea(aRange);
5899 aRange.aEnd.SetTab(aRange.aStart.Tab());
5900 aRefData.SetRange(rDoc.GetSheetLimits(), aRange, aPos);
5901 ScTokenArray* pNew = new ScTokenArray(rDoc);
5902 pNew->AddDoubleReference( aRefData );
5903 PushTokenArray( pNew, true );
5904 return GetToken();
5906 return true;
5909 bool ScCompiler::GetTokenIfOpCode( OpCode eOp )
5911 const formula::FormulaToken* p = maArrIterator.PeekNextNoSpaces();
5912 if (p && p->GetOpCode() == eOp)
5913 return GetToken();
5914 return false;
5918 /* Documentation on MS-Excel Table structured references:
5919 * https://support.office.com/en-us/article/Use-structured-references-in-Excel-table-formulas-75fb07d3-826a-449c-b76f-363057e3d16f
5920 * * as of Excel 2013
5921 * [MS-XLSX]: Formulas https://msdn.microsoft.com/en-us/library/dd906358.aspx
5922 * * look for structure-reference
5925 bool ScCompiler::HandleTableRef()
5927 ScTableRefToken* pTR = dynamic_cast<ScTableRefToken*>(mpToken.get());
5928 if (!pTR)
5930 SetError(FormulaError::UnknownToken);
5931 return true;
5934 ScDBData* pDBData = rDoc.GetDBCollection()->getNamedDBs().findByIndex( pTR->GetIndex());
5935 if ( !pDBData )
5936 SetError(FormulaError::NoName);
5937 else if (mbJumpCommandReorder)
5939 ScRange aDBRange;
5940 pDBData->GetArea(aDBRange);
5941 aDBRange.aEnd.SetTab(aDBRange.aStart.Tab());
5942 ScRange aRange( aDBRange);
5943 FormulaError nError = FormulaError::NONE;
5944 bool bForwardToClose = false;
5945 ScTableRefToken::Item eItem = pTR->GetItem();
5946 switch (eItem)
5948 case ScTableRefToken::TABLE:
5950 // The table name without items references the table data,
5951 // without headers or totals.
5952 if (pDBData->HasHeader())
5953 aRange.aStart.IncRow();
5954 if (pDBData->HasTotals())
5955 aRange.aEnd.IncRow(-1);
5956 if (aRange.aEnd.Row() < aRange.aStart.Row())
5957 nError = FormulaError::NoRef;
5958 bForwardToClose = true;
5960 break;
5961 case ScTableRefToken::ALL:
5963 bForwardToClose = true;
5965 break;
5966 case ScTableRefToken::HEADERS:
5968 if (pDBData->HasHeader())
5969 aRange.aEnd.SetRow( aRange.aStart.Row());
5970 else
5971 nError = FormulaError::NoRef;
5972 bForwardToClose = true;
5974 break;
5975 case ScTableRefToken::DATA:
5977 if (pDBData->HasHeader())
5978 aRange.aStart.IncRow();
5980 [[fallthrough]];
5981 case ScTableRefToken::HEADERS_DATA:
5983 if (pDBData->HasTotals())
5984 aRange.aEnd.IncRow(-1);
5985 if (aRange.aEnd.Row() < aRange.aStart.Row())
5986 nError = FormulaError::NoRef;
5987 bForwardToClose = true;
5989 break;
5990 case ScTableRefToken::TOTALS:
5992 if (pDBData->HasTotals())
5993 aRange.aStart.SetRow( aRange.aEnd.Row());
5994 else
5995 nError = FormulaError::NoRef;
5996 bForwardToClose = true;
5998 break;
5999 case ScTableRefToken::DATA_TOTALS:
6001 if (pDBData->HasHeader())
6002 aRange.aStart.IncRow();
6003 if (aRange.aEnd.Row() < aRange.aStart.Row())
6004 nError = FormulaError::NoRef;
6005 bForwardToClose = true;
6007 break;
6008 case ScTableRefToken::THIS_ROW:
6010 if (aRange.aStart.Row() <= aPos.Row() && aPos.Row() <= aRange.aEnd.Row())
6012 aRange.aStart.SetRow( aPos.Row());
6013 aRange.aEnd.SetRow( aPos.Row());
6015 else
6017 nError = FormulaError::NoValue;
6018 // For *some* relative row reference in named
6019 // expressions' thisrow special handling below.
6020 aRange.aEnd.SetRow( aRange.aStart.Row());
6022 bForwardToClose = true;
6024 break;
6026 bool bColumnRange = false;
6027 bool bCol1Rel = false;
6028 bool bCol1RelName = false;
6029 int nLevel = 0;
6030 if (bForwardToClose && GetTokenIfOpCode( ocTableRefOpen))
6032 ++nLevel;
6033 enum
6035 sOpen,
6036 sItem,
6037 sClose,
6038 sSep,
6039 sLast,
6040 sStop
6041 } eState = sOpen;
6044 const formula::FormulaToken* p = maArrIterator.PeekNextNoSpaces();
6045 if (!p)
6046 eState = sStop;
6047 else
6049 switch (p->GetOpCode())
6051 case ocTableRefOpen:
6052 eState = ((eState == sOpen || eState == sSep) ? sOpen : sStop);
6053 if (++nLevel > 2)
6055 SetError( FormulaError::Pair);
6056 eState = sStop;
6058 break;
6059 case ocTableRefItemAll:
6060 case ocTableRefItemHeaders:
6061 case ocTableRefItemData:
6062 case ocTableRefItemTotals:
6063 case ocTableRefItemThisRow:
6064 eState = ((eState == sOpen) ? sItem : sStop);
6065 break;
6066 case ocTableRefClose:
6067 eState = ((eState == sItem || eState == sClose) ? sClose : sStop);
6068 if (eState != sStop && --nLevel == 0)
6069 eState = sLast;
6070 break;
6071 case ocSep:
6072 eState = ((eState == sClose) ? sSep : sStop);
6073 break;
6074 case ocPush:
6075 if (eState == sOpen && p->GetType() == svSingleRef)
6077 bColumnRange = true;
6078 bCol1Rel = p->GetSingleRef()->IsColRel();
6079 bCol1RelName = p->GetSingleRef()->IsRelName();
6080 eState = sLast;
6082 else
6084 eState = sStop;
6086 break;
6087 case ocBad:
6088 eState = sLast;
6089 if (nError == FormulaError::NONE)
6090 nError = FormulaError::NoName;
6091 break;
6092 default:
6093 eState = sStop;
6095 if (eState != sStop)
6096 GetToken();
6097 if (eState == sLast)
6098 eState = sStop;
6100 } while (eState != sStop);
6102 ScTokenArray* pNew = new ScTokenArray(rDoc);
6103 if (nError == FormulaError::NONE || nError == FormulaError::NoValue)
6105 bool bCol2Rel = false;
6106 bool bCol2RelName = false;
6107 // The FormulaError::NoValue case generates a thisrow reference that can be
6108 // used to save named expressions in A1 syntax notation.
6109 if (bColumnRange)
6111 // Limit range to specified columns.
6112 ScRange aColRange( ScAddress::INITIALIZE_INVALID );
6113 switch (mpToken->GetType())
6115 case svSingleRef:
6117 aColRange.aStart = aColRange.aEnd = mpToken->GetSingleRef()->toAbs(rDoc, aPos);
6118 if ( GetTokenIfOpCode( ocTableRefClose) && (nLevel--) &&
6119 GetTokenIfOpCode( ocRange) &&
6120 GetTokenIfOpCode( ocTableRefOpen) && (++nLevel) &&
6121 GetTokenIfOpCode( ocPush))
6123 if (mpToken->GetType() != svSingleRef)
6124 aColRange = ScRange( ScAddress::INITIALIZE_INVALID);
6125 else
6127 aColRange.aEnd = mpToken->GetSingleRef()->toAbs(rDoc, aPos);
6128 aColRange.PutInOrder();
6129 bCol2Rel = mpToken->GetSingleRef()->IsColRel();
6130 bCol2RelName = mpToken->GetSingleRef()->IsRelName();
6134 break;
6135 default:
6136 ; // nothing
6138 // coverity[copy_paste_error : FALSE] - this is correct, aStart in both aDBRange uses
6139 if (aColRange.aStart.Row() != aDBRange.aStart.Row() || aColRange.aEnd.Row() != aDBRange.aStart.Row())
6140 aRange = ScRange( ScAddress::INITIALIZE_INVALID);
6141 else
6143 aColRange.aEnd.SetRow( aRange.aEnd.Row());
6144 aRange = aRange.Intersection( aColRange);
6147 if (aRange.IsValid())
6149 if (aRange.aStart == aRange.aEnd)
6151 ScSingleRefData aRefData;
6152 aRefData.InitFlags();
6153 aRefData.SetColRel( bCol1Rel);
6154 if (eItem == ScTableRefToken::THIS_ROW)
6156 aRefData.SetRowRel( true);
6157 if (!bCol1RelName)
6158 bCol1RelName = pArr->IsFromRangeName();
6160 aRefData.SetRelName( bCol1RelName);
6161 aRefData.SetFlag3D( true);
6162 if (nError != FormulaError::NONE)
6164 aRefData.SetAddress( rDoc.GetSheetLimits(), aRange.aStart, aRange.aStart);
6165 pTR->SetAreaRefRPN( new ScSingleRefToken(rDoc.GetSheetLimits(), aRefData)); // set reference at TableRef
6166 pNew->Add( new FormulaErrorToken( nError)); // set error in RPN
6168 else
6170 aRefData.SetAddress( rDoc.GetSheetLimits(), aRange.aStart, aPos);
6171 pTR->SetAreaRefRPN( pNew->AddSingleReference( aRefData));
6174 else
6176 ScComplexRefData aRefData;
6177 aRefData.InitFlags();
6178 aRefData.Ref1.SetColRel( bCol1Rel);
6179 aRefData.Ref2.SetColRel( bCol2Rel);
6180 bool bRelName = bCol1RelName || bCol2RelName;
6181 if (eItem == ScTableRefToken::THIS_ROW)
6183 aRefData.Ref1.SetRowRel( true);
6184 aRefData.Ref2.SetRowRel( true);
6185 if (!bRelName)
6186 bRelName = pArr->IsFromRangeName();
6188 aRefData.Ref1.SetRelName( bRelName);
6189 aRefData.Ref2.SetRelName( bRelName);
6190 aRefData.Ref1.SetFlag3D( true);
6191 if (nError != FormulaError::NONE)
6193 aRefData.SetRange( rDoc.GetSheetLimits(), aRange, aRange.aStart);
6194 pTR->SetAreaRefRPN( new ScDoubleRefToken(rDoc.GetSheetLimits(), aRefData)); // set reference at TableRef
6195 pNew->Add( new FormulaErrorToken( nError)); // set error in RPN
6197 else
6199 aRefData.SetRange( rDoc.GetSheetLimits(), aRange, aPos);
6200 pTR->SetAreaRefRPN( pNew->AddDoubleReference( aRefData));
6204 else
6206 pTR->SetAreaRefRPN( pNew->Add( new FormulaErrorToken( FormulaError::NoRef)));
6209 else
6211 pTR->SetAreaRefRPN( pNew->Add( new FormulaErrorToken( nError)));
6213 while (nLevel-- > 0)
6215 if (!GetTokenIfOpCode( ocTableRefClose))
6216 SetError( FormulaError::Pair);
6218 PushTokenArray( pNew, true );
6219 return GetToken();
6221 return true;
6224 formula::ParamClass ScCompiler::GetForceArrayParameter( const formula::FormulaToken* pToken, sal_uInt16 nParam ) const
6226 return ScParameterClassification::GetParameterType( pToken, nParam);
6229 bool ScCompiler::ParameterMayBeImplicitIntersection(const FormulaToken* token, int parameter)
6231 formula::ParamClass param = ScParameterClassification::GetParameterType( token, parameter );
6232 return param == Value || param == Array;
6235 bool ScCompiler::SkipImplicitIntersectionOptimization(const FormulaToken* token) const
6237 if (mbMatrixFlag)
6238 return true;
6239 formula::ParamClass paramClass = token->GetInForceArray();
6240 if (paramClass == formula::ForceArray
6241 || paramClass == formula::ReferenceOrForceArray
6242 || paramClass == formula::SuppressedReferenceOrForceArray
6243 || paramClass == formula::ReferenceOrRefArray)
6245 return true;
6247 formula::ParamClass returnType = ScParameterClassification::GetParameterType( token, SAL_MAX_UINT16 );
6248 return returnType == formula::Reference;
6251 void ScCompiler::HandleIIOpCode(FormulaToken* token, FormulaToken*** pppToken, sal_uInt8 nNumParams)
6253 if (!mbComputeII)
6254 return;
6255 #ifdef DBG_UTIL
6256 if(!HandleIIOpCodeInternal(token, pppToken, nNumParams))
6257 mUnhandledPossibleImplicitIntersectionsOpCodes.insert(token->GetOpCode());
6258 #else
6259 HandleIIOpCodeInternal(token, pppToken, nNumParams);
6260 #endif
6263 // return true if opcode is handled
6264 bool ScCompiler::HandleIIOpCodeInternal(FormulaToken* token, FormulaToken*** pppToken, sal_uInt8 nNumParams)
6266 if (nNumParams > 0 && *pppToken[0] == nullptr)
6267 return false; // Bad expression (see the dummy creation in FormulaCompiler::CompileTokenArray())
6269 const OpCode nOpCode = token->GetOpCode();
6271 if (nOpCode == ocPush)
6273 if(token->GetType() == svDoubleRef)
6274 mUnhandledPossibleImplicitIntersections.insert( token );
6275 return true;
6277 else if (nOpCode == ocSumIf || nOpCode == ocAverageIf)
6279 if (nNumParams != 3)
6280 return false;
6282 if (!(pppToken[0] && pppToken[2] && *pppToken[0] && *pppToken[2]))
6283 return false;
6285 if ((*pppToken[0])->GetType() != svDoubleRef)
6286 return false;
6288 const StackVar eSumRangeType = (*pppToken[2])->GetType();
6290 if ( eSumRangeType != svSingleRef && eSumRangeType != svDoubleRef )
6291 return false;
6293 const ScComplexRefData& rBaseRange = *(*pppToken[0])->GetDoubleRef();
6295 ScComplexRefData aSumRange;
6296 if (eSumRangeType == svSingleRef)
6298 aSumRange.Ref1 = *(*pppToken[2])->GetSingleRef();
6299 aSumRange.Ref2 = aSumRange.Ref1;
6301 else
6302 aSumRange = *(*pppToken[2])->GetDoubleRef();
6304 CorrectSumRange(rBaseRange, aSumRange, pppToken[2]);
6305 // TODO mark parameters as handled
6306 return true;
6308 else if (nOpCode >= SC_OPCODE_START_1_PAR && nOpCode < SC_OPCODE_STOP_1_PAR)
6310 if (nNumParams != 1)
6311 return false;
6313 if( !ParameterMayBeImplicitIntersection( token, 0 ))
6314 return false;
6315 if (SkipImplicitIntersectionOptimization(token))
6316 return false;
6318 if ((*pppToken[0])->GetType() != svDoubleRef)
6319 return false;
6321 mPendingImplicitIntersectionOptimizations.emplace_back( pppToken[0], token );
6322 return true;
6324 else if ((nOpCode >= SC_OPCODE_START_BIN_OP && nOpCode < SC_OPCODE_STOP_BIN_OP
6325 && nOpCode != ocAnd && nOpCode != ocOr)
6326 || nOpCode == ocRound || nOpCode == ocRoundUp || nOpCode == ocRoundDown)
6328 if (nNumParams != 2)
6329 return false;
6331 if( !ParameterMayBeImplicitIntersection( token, 0 ) || !ParameterMayBeImplicitIntersection( token, 1 ))
6332 return false;
6333 if (SkipImplicitIntersectionOptimization(token))
6334 return false;
6336 // Convert only if the other parameter is not a matrix (which would force the result to be a matrix).
6337 if ((*pppToken[0])->GetType() == svDoubleRef && (*pppToken[1])->GetType() != svMatrix)
6338 mPendingImplicitIntersectionOptimizations.emplace_back( pppToken[0], token );
6339 if ((*pppToken[1])->GetType() == svDoubleRef && (*pppToken[0])->GetType() != svMatrix)
6340 mPendingImplicitIntersectionOptimizations.emplace_back( pppToken[1], token );
6341 return true;
6343 else if ((nOpCode >= SC_OPCODE_START_UN_OP && nOpCode < SC_OPCODE_STOP_UN_OP)
6344 || nOpCode == ocPercentSign)
6346 if (nNumParams != 1)
6347 return false;
6349 if( !ParameterMayBeImplicitIntersection( token, 0 ))
6350 return false;
6351 if (SkipImplicitIntersectionOptimization(token))
6352 return false;
6354 if ((*pppToken[0])->GetType() == svDoubleRef)
6355 mPendingImplicitIntersectionOptimizations.emplace_back( pppToken[0], token );
6356 return true;
6358 else if (nOpCode == ocVLookup)
6360 if (nNumParams != 3 && nNumParams != 4)
6361 return false;
6363 if (SkipImplicitIntersectionOptimization(token))
6364 return false;
6366 assert( ParameterMayBeImplicitIntersection( token, 0 ));
6367 assert( !ParameterMayBeImplicitIntersection( token, 1 ));
6368 assert( ParameterMayBeImplicitIntersection( token, 2 ));
6369 assert( ParameterMayBeImplicitIntersection( token, 3 ));
6370 if ((*pppToken[2])->GetType() == svDoubleRef)
6371 mPendingImplicitIntersectionOptimizations.emplace_back( pppToken[2], token );
6372 if ((*pppToken[0])->GetType() == svDoubleRef)
6373 mPendingImplicitIntersectionOptimizations.emplace_back( pppToken[0], token );
6374 if (nNumParams == 4 && (*pppToken[3])->GetType() == svDoubleRef)
6375 mPendingImplicitIntersectionOptimizations.emplace_back( pppToken[3], token );
6376 // a range for the second parameters is not an implicit intersection
6377 mUnhandledPossibleImplicitIntersections.erase( *pppToken[ 1 ] );
6378 return true;
6380 else
6382 bool possibleII = false;
6383 for( int i = 0; i < nNumParams; ++i )
6385 if( ParameterMayBeImplicitIntersection( token, i )
6386 && (*pppToken[i])->GetType() == svDoubleRef)
6388 possibleII = true;
6389 break;
6392 if( !possibleII )
6394 // all parameters have been handled, they are not implicit intersections
6395 for( int i = 0; i < nNumParams; ++i )
6396 mUnhandledPossibleImplicitIntersections.erase( *pppToken[ i ] );
6397 return true;
6401 return false;
6404 void ScCompiler::PostProcessCode()
6406 for( const PendingImplicitIntersectionOptimization& item : mPendingImplicitIntersectionOptimizations )
6408 if( *item.parameterLocation != item.parameter ) // the parameter has been changed somehow
6409 continue;
6410 if( item.parameterLocation >= pCode ) // the location is not inside the code (pCode points after the end)
6411 continue;
6412 // E.g. "SUMPRODUCT(I5:I6+1)" shouldn't do implicit intersection.
6413 if( item.operation->IsInForceArray())
6414 continue;
6415 ReplaceDoubleRefII( item.parameterLocation );
6417 mPendingImplicitIntersectionOptimizations.clear();
6420 void ScCompiler::AnnotateOperands()
6422 AnnotateTrimOnDoubleRefs();
6425 void ScCompiler::ReplaceDoubleRefII(FormulaToken** ppDoubleRefTok)
6427 const ScComplexRefData* pRange = (*ppDoubleRefTok)->GetDoubleRef();
6428 if (!pRange)
6429 return;
6431 const ScComplexRefData& rRange = *pRange;
6433 // Can't do optimization reliably in this case (when row references are absolute).
6434 // Example : =SIN(A$1:A$10) filled in a formula group starting at B5 and of length 100.
6435 // If we just optimize the argument $A$1:$A$10 to singleref "A5" for the top cell in the fg, then
6436 // the results in cells B11:B104 will be incorrect (sin(0) = 0, assuming empty cells in A11:A104)
6437 // instead of the #VALUE! errors we would expect. We need to know the formula-group length to
6438 // fix this, but that is unknown at this stage, so skip such cases.
6439 if (!rRange.Ref1.IsRowRel() && !rRange.Ref2.IsRowRel())
6440 return;
6442 ScRange aAbsRange = rRange.toAbs(rDoc, aPos);
6443 if (aAbsRange.aStart == aAbsRange.aEnd)
6444 return; // Nothing to do (trivial case).
6446 ScAddress aAddr;
6448 if (!DoubleRefToPosSingleRefScalarCase(aAbsRange, aAddr, aPos))
6449 return;
6451 ScSingleRefData aSingleRef;
6452 aSingleRef.InitFlags();
6453 aSingleRef.SetColRel(rRange.Ref1.IsColRel());
6454 aSingleRef.SetRowRel(true);
6455 aSingleRef.SetTabRel(rRange.Ref1.IsTabRel());
6456 aSingleRef.SetAddress(rDoc.GetSheetLimits(), aAddr, aPos);
6458 // Replace the original doubleref token with computed singleref token
6459 FormulaToken* pNewSingleRefTok = new ScSingleRefToken(rDoc.GetSheetLimits(), aSingleRef);
6460 (*ppDoubleRefTok)->DecRef();
6461 *ppDoubleRefTok = pNewSingleRefTok;
6462 pNewSingleRefTok->IncRef();
6465 bool ScCompiler::DoubleRefToPosSingleRefScalarCase(const ScRange& rRange, ScAddress& rAdr, const ScAddress& rFormulaPos)
6467 assert(rRange.aStart != rRange.aEnd);
6469 bool bOk = false;
6470 SCCOL nMyCol = rFormulaPos.Col();
6471 SCROW nMyRow = rFormulaPos.Row();
6472 SCTAB nMyTab = rFormulaPos.Tab();
6473 SCCOL nCol = 0;
6474 SCROW nRow = 0;
6475 SCTAB nTab;
6476 nTab = rRange.aStart.Tab();
6477 if ( rRange.aStart.Col() <= nMyCol && nMyCol <= rRange.aEnd.Col() )
6479 nRow = rRange.aStart.Row();
6480 if ( nRow == rRange.aEnd.Row() )
6482 bOk = true;
6483 nCol = nMyCol;
6485 else if ( nTab != nMyTab && nTab == rRange.aEnd.Tab()
6486 && rRange.aStart.Row() <= nMyRow && nMyRow <= rRange.aEnd.Row() )
6488 bOk = true;
6489 nCol = nMyCol;
6490 nRow = nMyRow;
6493 else if ( rRange.aStart.Row() <= nMyRow && nMyRow <= rRange.aEnd.Row() )
6495 nCol = rRange.aStart.Col();
6496 if ( nCol == rRange.aEnd.Col() )
6498 bOk = true;
6499 nRow = nMyRow;
6501 else if ( nTab != nMyTab && nTab == rRange.aEnd.Tab()
6502 && rRange.aStart.Col() <= nMyCol && nMyCol <= rRange.aEnd.Col() )
6504 bOk = true;
6505 nCol = nMyCol;
6506 nRow = nMyRow;
6509 if ( bOk )
6511 if ( nTab == rRange.aEnd.Tab() )
6512 ; // all done
6513 else if ( nTab <= nMyTab && nMyTab <= rRange.aEnd.Tab() )
6514 nTab = nMyTab;
6515 else
6516 bOk = false;
6517 if ( bOk )
6518 rAdr.Set( nCol, nRow, nTab );
6521 return bOk;
6524 static void lcl_GetColRowDeltas(const ScRange& rRange, SCCOL& rXDelta, SCROW& rYDelta)
6526 rXDelta = rRange.aEnd.Col() - rRange.aStart.Col();
6527 rYDelta = rRange.aEnd.Row() - rRange.aStart.Row();
6530 bool ScCompiler::AdjustSumRangeShape(const ScComplexRefData& rBaseRange, ScComplexRefData& rSumRange)
6532 ScRange aAbs = rSumRange.toAbs(rDoc, aPos);
6534 // Current sum-range end col/row
6535 SCCOL nEndCol = aAbs.aEnd.Col();
6536 SCROW nEndRow = aAbs.aEnd.Row();
6538 // Current behaviour is, we will get a #NAME? for the below case, so bail out.
6539 // Note that sum-range's End[Col,Row] are same as Start[Col,Row] if the original formula
6540 // has a single-ref as the sum-range.
6541 if (!rDoc.ValidCol(nEndCol) || !rDoc.ValidRow(nEndRow))
6542 return false;
6544 SCCOL nXDeltaSum = 0;
6545 SCROW nYDeltaSum = 0;
6547 lcl_GetColRowDeltas(aAbs, nXDeltaSum, nYDeltaSum);
6549 aAbs = rBaseRange.toAbs(rDoc, aPos);
6550 SCCOL nXDelta = 0;
6551 SCROW nYDelta = 0;
6553 lcl_GetColRowDeltas(aAbs, nXDelta, nYDelta);
6555 if (nXDelta == nXDeltaSum &&
6556 nYDelta == nYDeltaSum)
6557 return false; // shapes of base-range match current sum-range
6559 // Try to make the sum-range to take the same shape as base-range,
6560 // by adjusting Ref2 member of rSumRange if the resultant sum-range don't
6561 // go out-of-bounds.
6563 SCCOL nXInc = nXDelta - nXDeltaSum;
6564 SCROW nYInc = nYDelta - nYDeltaSum;
6566 // Don't let a valid End[Col,Row] go beyond (rDoc.MaxCol(),rDoc.MaxRow()) to match
6567 // what happens in ScInterpreter::IterateParametersIf(), but there it also shrinks
6568 // the base-range by the (out-of-bound)amount clipped off the sum-range.
6569 // TODO: Probably we can optimize (from threading perspective) rBaseRange
6570 // by shrinking it here correspondingly (?)
6571 if (nEndCol + nXInc > rDoc.MaxCol())
6572 nXInc = rDoc.MaxCol() - nEndCol;
6573 if (nEndRow + nYInc > rDoc.MaxRow())
6574 nYInc = rDoc.MaxRow() - nEndRow;
6576 rSumRange.Ref2.IncCol(nXInc);
6577 rSumRange.Ref2.IncRow(nYInc);
6579 return true;
6582 void ScCompiler::CorrectSumRange(const ScComplexRefData& rBaseRange,
6583 ScComplexRefData& rSumRange,
6584 FormulaToken** ppSumRangeToken)
6586 if (!AdjustSumRangeShape(rBaseRange, rSumRange))
6587 return;
6589 // Replace sum-range token
6590 FormulaToken* pNewSumRangeTok = new ScDoubleRefToken(rDoc.GetSheetLimits(), rSumRange);
6591 (*ppSumRangeToken)->DecRef();
6592 *ppSumRangeToken = pNewSumRangeTok;
6593 pNewSumRangeTok->IncRef();
6596 void ScCompiler::AnnotateTrimOnDoubleRefs()
6598 if (!pCode || !(*(pCode - 1)))
6599 return;
6601 // OpCode of the "root" operator (which is already in RPN array).
6602 OpCode eOpCode = (*(pCode - 1))->GetOpCode();
6603 // Param number of the "root" operator (which is already in RPN array).
6604 sal_uInt8 nRootParam = (*(pCode - 1))->GetByte();
6605 // eOpCode can be some operator which does not change with operands with or contains zero values.
6606 if (eOpCode == ocSum)
6608 FormulaToken** ppTok = pCode - 2; // exclude the root operator.
6609 // The following loop runs till a "pattern" is found or there is a mismatch
6610 // and marks the push DoubleRef arguments as trimmable when there is a match.
6611 // The pattern is
6612 // SUM(IF(<reference|double>=<reference|double>, <then-clause>)<a some operands with operators / or *>)
6613 // such that one of the operands of ocEqual is a double-ref.
6614 // Examples of formula that matches this are:
6615 // SUM(IF(D:D=$A$1,F:F)*$H$1*2.3/$G$2)
6616 // SUM((IF(D:D=$A$1,F:F)*$H$1*2.3/$G$2)*$H$2*5/$G$3)
6617 // SUM(IF(E:E=16,F:F)*$H$1*100)
6618 bool bTillClose = true;
6619 bool bCloseTillIf = false;
6620 sal_Int16 nToksTillIf = 0;
6621 constexpr sal_Int16 MAXDIST_IF = 15;
6622 while (*ppTok)
6624 FormulaToken* pTok = *ppTok;
6625 OpCode eCurrOp = pTok->GetOpCode();
6626 ++nToksTillIf;
6628 // TODO : Is there a better way to handle this ?
6629 // ocIf is too far off from the sum opcode.
6630 if (nToksTillIf > MAXDIST_IF)
6631 return;
6633 switch (eCurrOp)
6635 case ocDiv:
6636 case ocMul:
6637 if (!bTillClose)
6638 return;
6639 break;
6640 case ocPush:
6642 break;
6643 case ocClose:
6644 if (bTillClose)
6646 bTillClose = false;
6647 bCloseTillIf = true;
6649 else
6650 return;
6651 break;
6652 case ocIf:
6654 if (!bCloseTillIf)
6655 return;
6657 if (!pTok->IsInForceArray())
6658 return;
6660 const short nJumpCount = pTok->GetJump()[0];
6661 if (nJumpCount != 2) // Should have THEN but no ELSE.
6662 return;
6664 OpCode eCompOp = (*(ppTok - 1))->GetOpCode();
6665 if (eCompOp != ocEqual)
6666 return;
6668 FormulaToken* pLHS = *(ppTok - 2);
6669 FormulaToken* pRHS = *(ppTok - 3);
6670 if (((pLHS->GetType() == svSingleRef || pLHS->GetType() == svDouble) && pRHS->GetType() == svDoubleRef) ||
6671 ((pRHS->GetType() == svSingleRef || pRHS->GetType() == svDouble) && pLHS->GetType() == svDoubleRef))
6673 if (pLHS->GetType() == svDoubleRef)
6674 pLHS->GetDoubleRef()->SetTrimToData(true);
6675 else
6676 pRHS->GetDoubleRef()->SetTrimToData(true);
6677 return;
6680 break;
6681 default:
6682 return;
6684 --ppTok;
6687 else if (eOpCode == ocSumProduct)
6689 FormulaToken** ppTok = pCode - 2; // exclude the root operator.
6690 // The following loop runs till a "pattern" is found or there is a mismatch
6691 // and marks the push DoubleRef arguments as trimmable when there is a match.
6692 // The pattern is
6693 // SUMPRODUCT(IF(<reference|double>=<reference|double>, <then-clause>)<a some operands with operators / or *>)
6694 // such that one of the operands of ocEqual is a double-ref.
6695 // Examples of formula that matches this are:
6696 // SUMPRODUCT(IF($A:$A=$L12;$D:$D*G:G))
6697 // Also in case of DoubleRef arguments around other Binary operators can be trimmable inside one parameter
6698 // of the root operator:
6699 // SUMPRODUCT(($D:$D>M47:M47)*($D:$D<M48:M48)*($I:$I=N$41))
6700 bool bTillClose = true;
6701 bool bCloseTillIf = false;
6702 sal_Int16 nToksTillIf = 0;
6703 constexpr sal_Int16 MAXDIST_IF = 15;
6704 while (*ppTok)
6706 FormulaToken* pTok = *ppTok;
6707 OpCode eCurrOp = pTok->GetOpCode();
6708 ++nToksTillIf;
6710 // TODO : Is there a better way to handle this ?
6711 // ocIf is too far off from the sum opcode.
6712 if (nToksTillIf > MAXDIST_IF)
6713 return;
6715 switch (eCurrOp)
6717 case ocDiv:
6718 case ocMul:
6720 if (!pTok->IsInForceArray())
6721 break;
6722 FormulaToken* pLHS = *(ppTok - 1);
6723 FormulaToken* pRHS = *(ppTok - 2);
6724 if (pLHS && pRHS)
6726 StackVar lhsType = pLHS->GetType();
6727 StackVar rhsType = pRHS->GetType();
6728 if (lhsType == svDoubleRef && rhsType == svDoubleRef)
6730 pLHS->GetDoubleRef()->SetTrimToData(true);
6731 pRHS->GetDoubleRef()->SetTrimToData(true);
6735 break;
6736 case ocEqual:
6737 case ocAdd:
6738 case ocSub:
6739 case ocAmpersand:
6740 case ocPow:
6741 case ocNotEqual:
6742 case ocLess:
6743 case ocGreater:
6744 case ocLessEqual:
6745 case ocGreaterEqual:
6746 case ocAnd:
6747 case ocOr:
6748 case ocXor:
6749 case ocIntersect:
6751 // tdf#160616: Double refs with these operators only
6752 // trimmable in case of one parameter
6753 if (!pTok->IsInForceArray() || nRootParam > 1)
6754 break;
6755 FormulaToken* pLHS = *(ppTok - 1);
6756 FormulaToken* pRHS = *(ppTok - 2);
6757 if (pLHS && pRHS)
6759 StackVar lhsType = pLHS->GetType();
6760 StackVar rhsType = pRHS->GetType();
6761 if (lhsType == svDoubleRef && (rhsType == svSingleRef || rhsType == svDoubleRef))
6763 pLHS->GetDoubleRef()->SetTrimToData(true);
6765 if (rhsType == svDoubleRef && (lhsType == svSingleRef || lhsType == svDoubleRef))
6767 pRHS->GetDoubleRef()->SetTrimToData(true);
6771 break;
6772 case ocPush:
6773 break;
6774 case ocClose:
6775 if (bTillClose)
6777 bTillClose = false;
6778 bCloseTillIf = true;
6780 else
6781 return;
6782 break;
6783 case ocIf:
6785 if (!bCloseTillIf)
6786 return;
6788 if (!pTok->IsInForceArray())
6789 return;
6791 const short nJumpCount = pTok->GetJump()[0];
6792 if (nJumpCount != 2) // Should have THEN but no ELSE.
6793 return;
6795 OpCode eCompOp = (*(ppTok - 1))->GetOpCode();
6796 if (eCompOp != ocEqual)
6797 return;
6799 FormulaToken* pLHS = *(ppTok - 2);
6800 FormulaToken* pRHS = *(ppTok - 3);
6801 StackVar lhsType = pLHS->GetType();
6802 StackVar rhsType = pRHS->GetType();
6803 if (lhsType == svDoubleRef && (rhsType == svSingleRef || rhsType == svDouble))
6805 pLHS->GetDoubleRef()->SetTrimToData(true);
6807 if ((lhsType == svSingleRef || lhsType == svDouble) && rhsType == svDoubleRef)
6809 pRHS->GetDoubleRef()->SetTrimToData(true);
6811 return;
6813 break;
6814 default:
6815 return;
6817 --ppTok;
6822 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */