xref: /core/sc/source/core/data/validat.cxx (revision 36d898b3)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
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 .
18  */
19 
20 #include <config_features.h>
21 
22 #include <validat.hxx>
23 #include <com/sun/star/sheet/TableValidationVisibility.hpp>
24 
25 #include <sfx2/app.hxx>
26 #include <sfx2/viewsh.hxx>
27 #include <basic/sbmeth.hxx>
28 #include <basic/sbmod.hxx>
29 #include <basic/sbstar.hxx>
30 #include <basic/sberrors.hxx>
31 
32 #include <basic/sbx.hxx>
33 #include <svl/numformat.hxx>
34 #include <svl/sharedstringpool.hxx>
35 #include <vcl/svapp.hxx>
36 #include <vcl/weld.hxx>
37 #include <rtl/math.hxx>
38 #include <osl/diagnose.h>
39 
40 #include <document.hxx>
41 #include <docsh.hxx>
42 #include <formulacell.hxx>
43 #include <patattr.hxx>
44 #include <globstr.hrc>
45 #include <scresid.hxx>
46 #include <rangenam.hxx>
47 #include <dbdata.hxx>
48 #include <typedstrdata.hxx>
49 #include <editutil.hxx>
50 #include <tokenarray.hxx>
51 #include <scmatrix.hxx>
52 #include <cellvalue.hxx>
53 #include <simpleformulacalc.hxx>
54 
55 #include <math.h>
56 #include <memory>
57 
58 using namespace formula;
59 
60 //  Entries for validation (with only one condition)
61 
ScValidationData(ScValidationMode eMode,ScConditionMode eOper,const OUString & rExpr1,const OUString & rExpr2,ScDocument & rDocument,const ScAddress & rPos,const OUString & rExprNmsp1,const OUString & rExprNmsp2,FormulaGrammar::Grammar eGrammar1,FormulaGrammar::Grammar eGrammar2)62 ScValidationData::ScValidationData( ScValidationMode eMode, ScConditionMode eOper,
63                                     const OUString& rExpr1, const OUString& rExpr2,
64                                     ScDocument& rDocument, const ScAddress& rPos,
65                                     const OUString& rExprNmsp1, const OUString& rExprNmsp2,
66                                     FormulaGrammar::Grammar eGrammar1,
67                                     FormulaGrammar::Grammar eGrammar2 )
68     : ScConditionEntry( eOper, rExpr1, rExpr2, rDocument, rPos, rExprNmsp1,
69                         rExprNmsp2, eGrammar1, eGrammar2 )
70     , nKey( 0 )
71     , eDataMode( eMode )
72     , bShowInput(false)
73     , bShowError(false)
74     , eErrorStyle( SC_VALERR_STOP )
75     , mnListType( css::sheet::TableValidationVisibility::UNSORTED )
76 {
77 }
78 
ScValidationData(ScValidationMode eMode,ScConditionMode eOper,const ScTokenArray * pArr1,const ScTokenArray * pArr2,ScDocument & rDocument,const ScAddress & rPos)79 ScValidationData::ScValidationData( ScValidationMode eMode, ScConditionMode eOper,
80                                     const ScTokenArray* pArr1, const ScTokenArray* pArr2,
81                                     ScDocument& rDocument, const ScAddress& rPos )
82     : ScConditionEntry( eOper, pArr1, pArr2, rDocument, rPos )
83     , nKey( 0 )
84     , eDataMode( eMode )
85     , bShowInput(false)
86     , bShowError(false)
87     , eErrorStyle( SC_VALERR_STOP )
88     , mnListType( css::sheet::TableValidationVisibility::UNSORTED )
89 {
90 }
91 
ScValidationData(const ScValidationData & r)92 ScValidationData::ScValidationData( const ScValidationData& r )
93     : ScConditionEntry( r )
94     , nKey( r.nKey )
95     , eDataMode( r.eDataMode )
96     , bShowInput( r.bShowInput )
97     , bShowError( r.bShowError )
98     , eErrorStyle( r.eErrorStyle )
99     , mnListType( r.mnListType )
100     , aInputTitle( r.aInputTitle )
101     , aInputMessage( r.aInputMessage )
102     , aErrorTitle( r.aErrorTitle )
103     , aErrorMessage( r.aErrorMessage )
104 {
105     //  Formulae copied by RefCount
106 }
107 
ScValidationData(ScDocument & rDocument,const ScValidationData & r)108 ScValidationData::ScValidationData( ScDocument& rDocument, const ScValidationData& r )
109     : ScConditionEntry( rDocument, r )
110     , nKey( r.nKey )
111     , eDataMode( r.eDataMode )
112     , bShowInput( r.bShowInput )
113     , bShowError( r.bShowError )
114     , eErrorStyle( r.eErrorStyle )
115     , mnListType( r.mnListType )
116     , aInputTitle( r.aInputTitle )
117     , aInputMessage( r.aInputMessage )
118     , aErrorTitle( r.aErrorTitle )
119     , aErrorMessage( r.aErrorMessage )
120 {
121     //  Formulae really copied
122 }
123 
~ScValidationData()124 ScValidationData::~ScValidationData()
125 {
126 }
127 
IsEmpty() const128 bool ScValidationData::IsEmpty() const
129 {
130     ScValidationData aDefault( SC_VALID_ANY, ScConditionMode::Equal, u""_ustr, u""_ustr, *GetDocument(), ScAddress() );
131     return EqualEntries( aDefault );
132 }
133 
EqualEntries(const ScValidationData & r) const134 bool ScValidationData::EqualEntries( const ScValidationData& r ) const
135 {
136         //  test same parameters (excluding Key)
137 
138     return ScConditionEntry::operator==(r) &&
139             eDataMode       == r.eDataMode &&
140             bShowInput      == r.bShowInput &&
141             bShowError      == r.bShowError &&
142             eErrorStyle     == r.eErrorStyle &&
143             mnListType      == r.mnListType &&
144             aInputTitle     == r.aInputTitle &&
145             aInputMessage   == r.aInputMessage &&
146             aErrorTitle     == r.aErrorTitle &&
147             aErrorMessage   == r.aErrorMessage;
148 }
149 
ResetInput()150 void ScValidationData::ResetInput()
151 {
152     bShowInput = false;
153 }
154 
ResetError()155 void ScValidationData::ResetError()
156 {
157     bShowError = false;
158 }
159 
SetInput(const OUString & rTitle,const OUString & rMsg)160 void ScValidationData::SetInput( const OUString& rTitle, const OUString& rMsg )
161 {
162     bShowInput = true;
163     aInputTitle = rTitle;
164     aInputMessage = rMsg;
165 }
166 
SetError(const OUString & rTitle,const OUString & rMsg,ScValidErrorStyle eStyle)167 void ScValidationData::SetError( const OUString& rTitle, const OUString& rMsg,
168                                     ScValidErrorStyle eStyle )
169 {
170     bShowError = true;
171     eErrorStyle = eStyle;
172     aErrorTitle = rTitle;
173     aErrorMessage = rMsg;
174 }
175 
GetErrMsg(OUString & rTitle,OUString & rMsg,ScValidErrorStyle & rStyle) const176 bool ScValidationData::GetErrMsg( OUString& rTitle, OUString& rMsg,
177                                     ScValidErrorStyle& rStyle ) const
178 {
179     rTitle = aErrorTitle;
180     rMsg   = aErrorMessage;
181     rStyle = eErrorStyle;
182     return bShowError;
183 }
184 
DoScript(const ScAddress & rPos,const OUString & rInput,ScFormulaCell * pCell,weld::Window * pParent) const185 bool ScValidationData::DoScript( const ScAddress& rPos, const OUString& rInput,
186                                 ScFormulaCell* pCell, weld::Window* pParent ) const
187 {
188     ScDocument* pDocument = GetDocument();
189     ScDocShell* pDocSh = pDocument->GetDocumentShell();
190     if ( !pDocSh )
191         return false;
192 
193     bool bScriptReturnedFalse = false;  // default: do not abort
194 
195     //  1) entered or calculated value
196     css::uno::Any aParam0(rInput);
197     if ( pCell )                // if cell exists, call interpret
198     {
199         if ( pCell->IsValue() )
200             aParam0 <<= pCell->GetValue();
201         else
202             aParam0 <<= pCell->GetString().getString();
203     }
204 
205     //  2) Position of the cell
206     OUString aPosStr(rPos.Format(ScRefFlags::VALID | ScRefFlags::TAB_3D, pDocument, pDocument->GetAddressConvention()));
207 
208     // Set up parameters
209     css::uno::Sequence< css::uno::Any > aParams{ aParam0, css::uno::Any(aPosStr) };
210 
211     //  use link-update flag to prevent closing the document
212     //  while the macro is running
213     bool bWasInLinkUpdate = pDocument->IsInLinkUpdate();
214     if ( !bWasInLinkUpdate )
215         pDocument->SetInLinkUpdate( true );
216 
217     if ( pCell )
218         pDocument->LockTable( rPos.Tab() );
219 
220     css::uno::Any aRet;
221     css::uno::Sequence< sal_Int16 > aOutArgsIndex;
222     css::uno::Sequence< css::uno::Any > aOutArgs;
223 
224     ErrCode eRet = pDocSh->CallXScript(
225         aErrorTitle, aParams, aRet, aOutArgsIndex, aOutArgs );
226 
227     if ( pCell )
228         pDocument->UnlockTable( rPos.Tab() );
229 
230     if ( !bWasInLinkUpdate )
231         pDocument->SetInLinkUpdate( false );
232 
233     // Check the return value from the script
234     // The contents of the cell get reset if the script returns false
235     bool bTmp = false;
236     if ( eRet == ERRCODE_NONE &&
237              aRet.getValueType() == cppu::UnoType<bool>::get() &&
238              ( aRet >>= bTmp ) &&
239              !bTmp )
240     {
241         bScriptReturnedFalse =  true;
242     }
243 
244     if ( eRet == ERRCODE_BASIC_METHOD_NOT_FOUND && !pCell )
245     // Macro not found (only with input)
246     {
247         //TODO: different error message, if found, but not bAllowed ??
248         std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pParent,
249                                                   VclMessageType::Warning, VclButtonsType::Ok,
250                                                   ScResId(STR_VALID_MACRONOTFOUND)));
251         xBox->run();
252     }
253 
254     return bScriptReturnedFalse;
255 }
256 
257     // true -> abort
258 
DoMacro(const ScAddress & rPos,const OUString & rInput,ScFormulaCell * pCell,weld::Window * pParent) const259 bool ScValidationData::DoMacro( const ScAddress& rPos, const OUString& rInput,
260                                 ScFormulaCell* pCell, weld::Window* pParent ) const
261 {
262     if ( SfxApplication::IsXScriptURL( aErrorTitle ) )
263     {
264         return DoScript( rPos, rInput, pCell, pParent );
265     }
266 
267     ScDocument* pDocument = GetDocument();
268     ScDocShell* pDocSh = pDocument->GetDocumentShell();
269     if ( !pDocSh )
270         return false;
271 
272     bool bDone = false;
273     bool bRet = false;                      // default: do not abort
274 
275     //  If the Doc was loaded during a Basic-Calls,
276     //  the Sbx-object may not be created (?)
277 //  pDocSh->GetSbxObject();
278 
279 #if HAVE_FEATURE_SCRIPTING
280     //  no security check ahead (only CheckMacroWarn), that happens in CallBasic
281 
282     //  Function search by their simple name,
283     //  then assemble aBasicStr, aMacroStr for SfxObjectShell::CallBasic
284 
285     StarBASIC* pRoot = pDocSh->GetBasic();
286     SbxVariable* pVar = pRoot->Find( aErrorTitle, SbxClassType::Method );
287     if (SbMethod* pMethod = dynamic_cast<SbMethod*>(pVar))
288     {
289         SbModule* pModule = pMethod->GetModule();
290         SbxObject* pObject = pModule->GetParent();
291         OUString aMacroStr(
292             pObject->GetName() + "." + pModule->GetName() + "." + pMethod->GetName());
293         OUString aBasicStr;
294 
295         //  the distinction between document- and app-basic has to be done
296         //  by checking the parent (as in ScInterpreter::ScMacro), not by looping
297         //  over all open documents, because this may be called from within loading,
298         //  when SfxObjectShell::GetFirst/GetNext won't find the document.
299 
300         if ( pObject->GetParent() )
301             aBasicStr = pObject->GetParent()->GetName();    // Basic of document
302         else
303             aBasicStr = SfxGetpApp()->GetName();            // Basic of application
304 
305         //  Parameter for Macro
306         SbxArrayRef refPar = new SbxArray;
307 
308         //  1) entered or calculated value
309         OUString aValStr = rInput;
310         double nValue = 0.0;
311         bool bIsValue = false;
312         if ( pCell )                // if cell set, called from interpret
313         {
314             bIsValue = pCell->IsValue();
315             if ( bIsValue )
316                 nValue  = pCell->GetValue();
317             else
318                 aValStr = pCell->GetString().getString();
319         }
320         if ( bIsValue )
321             refPar->Get(1)->PutDouble(nValue);
322         else
323             refPar->Get(1)->PutString(aValStr);
324 
325         //  2) Position of the cell
326         OUString aPosStr(rPos.Format(ScRefFlags::VALID | ScRefFlags::TAB_3D, pDocument, pDocument->GetAddressConvention()));
327         refPar->Get(2)->PutString(aPosStr);
328 
329         //  use link-update flag to prevent closing the document
330         //  while the macro is running
331         bool bWasInLinkUpdate = pDocument->IsInLinkUpdate();
332         if ( !bWasInLinkUpdate )
333             pDocument->SetInLinkUpdate( true );
334 
335         if ( pCell )
336             pDocument->LockTable( rPos.Tab() );
337         SbxVariableRef refRes = new SbxVariable;
338         ErrCode eRet = pDocSh->CallBasic( aMacroStr, aBasicStr, refPar.get(), refRes.get() );
339         if ( pCell )
340             pDocument->UnlockTable( rPos.Tab() );
341 
342         if ( !bWasInLinkUpdate )
343             pDocument->SetInLinkUpdate( false );
344 
345         //  Interrupt input if Basic macro returns false
346         if ( eRet == ERRCODE_NONE && refRes->GetType() == SbxBOOL && !refRes->GetBool() )
347             bRet = true;
348         bDone = true;
349     }
350 #endif
351     if ( !bDone && !pCell )         // Macro not found (only with input)
352     {
353         //TODO: different error message, if found, but not bAllowed ??
354         std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pParent,
355                                                   VclMessageType::Warning, VclButtonsType::Ok,
356                                                   ScResId(STR_VALID_MACRONOTFOUND)));
357         xBox->run();
358     }
359 
360     return bRet;
361 }
362 
DoCalcError(ScFormulaCell * pCell) const363 void ScValidationData::DoCalcError( ScFormulaCell* pCell ) const
364 {
365     if ( eErrorStyle == SC_VALERR_MACRO )
366         DoMacro( pCell->aPos, OUString(), pCell, nullptr );
367 }
368 
IMPL_STATIC_LINK_NOARG(ScValidationData,InstallLOKNotifierHdl,void *,vcl::ILibreOfficeKitNotifier *)369 IMPL_STATIC_LINK_NOARG(ScValidationData, InstallLOKNotifierHdl, void*, vcl::ILibreOfficeKitNotifier*)
370 {
371     return SfxViewShell::Current();
372 }
373 
374     // true -> abort
375 
DoError(weld::Window * pParent,const OUString & rInput,const ScAddress & rPos) const376 bool ScValidationData::DoError(weld::Window* pParent, const OUString& rInput,
377                                const ScAddress& rPos) const
378 {
379     if ( eErrorStyle == SC_VALERR_MACRO )
380         return DoMacro(rPos, rInput, nullptr, pParent);
381 
382     if (!bShowError)
383         return true;
384 
385     //  Output error message
386 
387     OUString aTitle = aErrorTitle;
388     if (aTitle.isEmpty())
389         aTitle = ScResId( STR_MSSG_DOSUBTOTALS_0 );  // application title
390     OUString aMessage = aErrorMessage;
391     if (aMessage.isEmpty())
392         aMessage = ScResId( STR_VALID_DEFERROR );
393 
394     VclButtonsType eStyle = VclButtonsType::Ok;
395     VclMessageType eType = VclMessageType::Error;
396     switch (eErrorStyle)
397     {
398         case SC_VALERR_INFO:
399             eType = VclMessageType::Info;
400             eStyle = VclButtonsType::OkCancel;
401             break;
402         case SC_VALERR_WARNING:
403             eType = VclMessageType::Warning;
404             eStyle = VclButtonsType::OkCancel;
405             break;
406         default:
407             break;
408     }
409 
410     std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pParent, eType,
411                                               eStyle, aMessage, SfxViewShell::Current()));
412     xBox->set_title(aTitle);
413     xBox->SetInstallLOKNotifierHdl(LINK(nullptr, ScValidationData, InstallLOKNotifierHdl));
414 
415     switch (eErrorStyle)
416     {
417         case SC_VALERR_INFO:
418             xBox->set_default_response(RET_OK);
419             break;
420         case SC_VALERR_WARNING:
421             xBox->set_default_response(RET_CANCEL);
422             break;
423         default:
424             break;
425     }
426 
427     short nRet = xBox->run();
428 
429     return ( eErrorStyle == SC_VALERR_STOP || nRet == RET_CANCEL );
430 }
431 
IsDataValidCustom(const OUString & rTest,const ScPatternAttr & rPattern,const ScAddress & rPos,const CustomValidationPrivateAccess &) const432 bool ScValidationData::IsDataValidCustom(
433         const OUString& rTest,
434         const ScPatternAttr& rPattern,
435         const ScAddress& rPos,
436         const CustomValidationPrivateAccess& ) const
437 {
438     OSL_ENSURE(GetDataMode() == SC_VALID_CUSTOM,
439             "ScValidationData::IsDataValidCustom invoked for a non-custom validation");
440 
441     if (rTest.isEmpty())              // check whether empty cells are allowed
442         return IsIgnoreBlank();
443 
444     SvNumberFormatter* pFormatter = nullptr;
445     sal_uInt32 nFormat = 0;
446     double nVal = 0.0;
447     OUString rStrResult = u""_ustr;
448     bool bIsVal = false;
449 
450     if (rTest[0] == '=')
451     {
452         if (!isFormulaResultsValidatable(rTest, rPos, pFormatter, rStrResult, nVal, nFormat, bIsVal))
453             return false;
454 
455         // check whether empty cells are allowed
456         if (rStrResult.isEmpty())
457             return IsIgnoreBlank();
458     }
459     else
460     {
461         pFormatter = GetDocument()->GetFormatTable();
462 
463         // get the value if any
464         nFormat = rPattern.GetNumberFormat(pFormatter);
465         bIsVal = pFormatter->IsNumberFormat(rTest, nFormat, nVal);
466         rStrResult = rTest;
467     }
468 
469     ScRefCellValue aTmpCell;
470     svl::SharedString aSS;
471     if (bIsVal)
472     {
473         aTmpCell = ScRefCellValue(nVal);
474     }
475     else
476     {
477         aSS = mpDoc->GetSharedStringPool().intern(rStrResult);
478         aTmpCell = ScRefCellValue(&aSS);
479     }
480 
481     ScCellValue aOriginalCellValue(ScRefCellValue(*GetDocument(), rPos));
482 
483     aTmpCell.commit(*GetDocument(), rPos);
484     bool bRet = IsCellValid(aTmpCell, rPos);
485     aOriginalCellValue.commit(*GetDocument(), rPos);
486 
487     return bRet;
488 }
489 
490 /** To test numeric data text length in IsDataValidTextLen().
491 
492     If mpFormatter is not set, it is obtained from the document and the format
493     key is determined from the cell position's attribute pattern.
494  */
495 struct ScValidationDataIsNumeric
496 {
497     SvNumberFormatter*  mpFormatter;
498     double              mfVal;
499     sal_uInt32          mnFormat;
500 
ScValidationDataIsNumericScValidationDataIsNumeric501     ScValidationDataIsNumeric( double fVal, SvNumberFormatter* pFormatter = nullptr, sal_uInt32 nFormat = 0 )
502         : mpFormatter(pFormatter), mfVal(fVal), mnFormat(nFormat)
503     {
504     }
505 
initScValidationDataIsNumeric506     void init( const ScDocument& rDoc, const ScAddress& rPos )
507     {
508         const ScPatternAttr* pPattern = rDoc.GetPattern( rPos.Col(), rPos.Row(), rPos.Tab());
509         mpFormatter = rDoc.GetFormatTable();
510         mnFormat = pPattern->GetNumberFormat( mpFormatter);
511     }
512 };
513 
IsDataValidTextLen(std::u16string_view rTest,const ScAddress & rPos,ScValidationDataIsNumeric * pDataNumeric) const514 bool ScValidationData::IsDataValidTextLen( std::u16string_view rTest, const ScAddress& rPos,
515         ScValidationDataIsNumeric* pDataNumeric ) const
516 {
517     sal_Int32 nLen;
518     if (!pDataNumeric)
519         nLen = rTest.size();
520     else
521     {
522         if (!pDataNumeric->mpFormatter)
523             pDataNumeric->init( *GetDocument(), rPos);
524 
525         // For numeric values use the resulting input line string to
526         // determine length, otherwise an once accepted value maybe could
527         // not be edited again, for example abbreviated dates or leading
528         // zeros or trailing zeros after decimal separator change length.
529         OUString aStr;
530         pDataNumeric->mpFormatter->GetInputLineString( pDataNumeric->mfVal, pDataNumeric->mnFormat, aStr);
531         nLen = aStr.getLength();
532     }
533     ScRefCellValue aTmpCell( static_cast<double>(nLen));
534     return IsCellValid( aTmpCell, rPos);
535 }
536 
IsDataValid(const OUString & rTest,const ScPatternAttr & rPattern,const ScAddress & rPos) const537 bool ScValidationData::IsDataValid(
538     const OUString& rTest, const ScPatternAttr& rPattern, const ScAddress& rPos ) const
539 {
540     if ( eDataMode == SC_VALID_ANY ) // check if any cell content is allowed
541         return true;
542 
543     if (rTest.isEmpty())              // check whether empty cells are allowed
544         return IsIgnoreBlank();
545 
546     SvNumberFormatter* pFormatter = nullptr;
547     sal_uInt32 nFormat = 0;
548     double nVal = 0.0;
549     OUString rStrResult = u""_ustr;
550     bool bIsVal = false;
551 
552     if (rTest[0] == '=')
553     {
554         if (!isFormulaResultsValidatable(rTest, rPos, pFormatter, rStrResult, nVal, nFormat, bIsVal))
555             return false;
556 
557         // check whether empty cells are allowed
558         if (rStrResult.isEmpty())
559             return IsIgnoreBlank();
560     }
561     else
562     {
563         pFormatter = GetDocument()->GetFormatTable();
564 
565         // get the value if any
566         nFormat = rPattern.GetNumberFormat(pFormatter);
567         bIsVal = pFormatter->IsNumberFormat(rTest, nFormat, nVal);
568         rStrResult = rTest;
569     }
570 
571     bool bRet;
572     if (SC_VALID_TEXTLEN == eDataMode)
573     {
574         if (!bIsVal)
575             bRet = IsDataValidTextLen( rStrResult, rPos, nullptr);
576         else
577         {
578             ScValidationDataIsNumeric aDataNumeric( nVal, pFormatter, nFormat);
579             bRet = IsDataValidTextLen( rStrResult, rPos, &aDataNumeric);
580         }
581     }
582     else
583     {
584         if (bIsVal)
585         {
586             ScRefCellValue aTmpCell(nVal);
587             bRet = IsDataValid(aTmpCell, rPos);
588         }
589         else
590         {
591             svl::SharedString aSS = mpDoc->GetSharedStringPool().intern( rStrResult );
592             ScRefCellValue aTmpCell(&aSS);
593             bRet = IsDataValid(aTmpCell, rPos);
594         }
595     }
596 
597     return bRet;
598 }
599 
IsDataValid(ScRefCellValue & rCell,const ScAddress & rPos) const600 bool ScValidationData::IsDataValid( ScRefCellValue& rCell, const ScAddress& rPos ) const
601 {
602     if( eDataMode == SC_VALID_LIST )
603         return IsListValid(rCell, rPos);
604 
605     if ( eDataMode == SC_VALID_CUSTOM )
606         return IsCellValid(rCell, rPos);
607 
608     double nVal = 0.0;
609     OUString aString;
610     bool bIsVal = true;
611 
612     switch (rCell.getType())
613     {
614         case CELLTYPE_VALUE:
615             nVal = rCell.getDouble();
616         break;
617         case CELLTYPE_STRING:
618             aString = rCell.getSharedString()->getString();
619             bIsVal = false;
620         break;
621         case CELLTYPE_EDIT:
622             if (rCell.getEditText())
623                 aString = ScEditUtil::GetString(*rCell.getEditText(), GetDocument());
624             bIsVal = false;
625         break;
626         case CELLTYPE_FORMULA:
627         {
628             ScFormulaCell* pFCell = rCell.getFormula();
629             bIsVal = pFCell->IsValue();
630             if ( bIsVal )
631                 nVal  = pFCell->GetValue();
632             else
633                 aString = pFCell->GetString().getString();
634         }
635         break;
636         default:                        // Notes, Broadcaster
637             return IsIgnoreBlank();     // as set
638     }
639 
640     bool bOk = true;
641     switch (eDataMode)
642     {
643         // SC_VALID_ANY already above
644 
645         case SC_VALID_WHOLE:
646         case SC_VALID_DECIMAL:
647         case SC_VALID_DATE:         // Date/Time is only formatting
648         case SC_VALID_TIME:
649             bOk = bIsVal;
650             if ( bOk && eDataMode == SC_VALID_WHOLE )
651                 bOk = ::rtl::math::approxEqual( nVal, floor(nVal+0.5) );        // integers
652             if ( bOk )
653                 bOk = IsCellValid(rCell, rPos);
654             break;
655 
656         case SC_VALID_TEXTLEN:
657             if (!bIsVal)
658                 bOk = IsDataValidTextLen( aString, rPos, nullptr);
659             else
660             {
661                 ScValidationDataIsNumeric aDataNumeric( nVal);
662                 bOk = IsDataValidTextLen( aString, rPos, &aDataNumeric);
663             }
664         break;
665 
666         default:
667             OSL_FAIL("not yet done");
668             break;
669     }
670 
671     return bOk;
672 }
673 
isFormulaResultsValidatable(const OUString & rTest,const ScAddress & rPos,SvNumberFormatter * pFormatter,OUString & rStrResult,double & nVal,sal_uInt32 & nFormat,bool & bIsVal) const674 bool ScValidationData::isFormulaResultsValidatable(const OUString& rTest, const ScAddress& rPos, SvNumberFormatter* pFormatter,
675     OUString& rStrResult, double& nVal, sal_uInt32& nFormat, bool& bIsVal) const
676 {
677     std::optional<ScSimpleFormulaCalculator> pFCell(std::in_place, *mpDoc, rPos, rTest, true);
678     pFCell->SetLimitString(true);
679 
680     bool bColRowName = pFCell->HasColRowName();
681     if (bColRowName)
682     {
683         // ColRowName from RPN-Code?
684         if (pFCell->GetCode()->GetCodeLen() <= 1)
685         {   // ==1: area
686             // ==0: would be an area if...
687             OUString aBraced = "(" + rTest + ")";
688             pFCell.emplace(*mpDoc, rPos, aBraced, true);
689             pFCell->SetLimitString(true);
690         }
691         else
692             bColRowName = false;
693     }
694 
695     FormulaError nErrCode = pFCell->GetErrCode();
696     if (nErrCode == FormulaError::NONE || pFCell->IsMatrix())
697     {
698         pFormatter = mpDoc->GetFormatTable();
699         const Color* pColor;
700         if (pFCell->IsMatrix())
701         {
702             rStrResult = pFCell->GetString().getString();
703         }
704         else if (pFCell->IsValue())
705         {
706             nVal = pFCell->GetValue();
707             nFormat = pFormatter->GetStandardFormat(nVal, 0,
708                 pFCell->GetFormatType(), ScGlobal::eLnge);
709             pFormatter->GetOutputString(nVal, nFormat, rStrResult, &pColor);
710             bIsVal = true;
711         }
712         else
713         {
714             nFormat = pFormatter->GetStandardFormat(
715                 pFCell->GetFormatType(), ScGlobal::eLnge);
716             pFormatter->GetOutputString(pFCell->GetString().getString(), nFormat,
717                 rStrResult, &pColor);
718             // Indicate it's a string, so a number string doesn't look numeric.
719             // Escape embedded quotation marks first by doubling them, as
720             // usual. Actually the result can be copy-pasted from the result
721             // box as literal into a formula expression.
722             rStrResult = "\"" + rStrResult.replaceAll("\"", "\"\"") + "\"";
723         }
724 
725         ScRange aTestRange;
726         if (bColRowName || (aTestRange.Parse(rTest, *mpDoc) & ScRefFlags::VALID))
727             rStrResult += " ...";
728         // area
729 
730         return true;
731     }
732     else
733     {
734         return false;
735     }
736 }
737 
738 namespace {
739 
740 /** Token array helper. Iterates over all string tokens.
741     @descr  The token array must contain separated string tokens only.
742     @param bSkipEmpty  true = Ignores string tokens with empty strings. */
743 class ScStringTokenIterator
744 {
745 public:
ScStringTokenIterator(const ScTokenArray & rTokArr)746     explicit             ScStringTokenIterator( const ScTokenArray& rTokArr ) :
747         maIter( rTokArr ), mbOk( true ) {}
748 
749     /** Returns the string of the first string token or NULL on error or empty token array. */
750     rtl_uString* First();
751     /** Returns the string of the next string token or NULL on error or end of token array. */
752     rtl_uString* Next();
753 
754     /** Returns false, if a wrong token has been found. Does NOT return false on end of token array. */
Ok() const755     bool                 Ok() const { return mbOk; }
756 
757 private:
758     svl::SharedString maCurString; /// Current string.
759     FormulaTokenArrayPlainIterator maIter;
760     bool                        mbOk;           /// true = correct token or end of token array.
761 };
762 
First()763 rtl_uString* ScStringTokenIterator::First()
764 {
765     maIter.Reset();
766     mbOk = true;
767     return Next();
768 }
769 
Next()770 rtl_uString* ScStringTokenIterator::Next()
771 {
772     if( !mbOk )
773         return nullptr;
774 
775     // seek to next non-separator token
776     const FormulaToken* pToken = maIter.NextNoSpaces();
777     while( pToken && (pToken->GetOpCode() == ocSep) )
778         pToken = maIter.NextNoSpaces();
779 
780     mbOk = !pToken || (pToken->GetType() == formula::svString);
781 
782     maCurString = svl::SharedString(); // start with invalid string.
783     if (mbOk && pToken)
784         maCurString = pToken->GetString();
785 
786     // string found but empty -> get next token; otherwise return it
787     return (maCurString.isValid() && maCurString.isEmpty()) ? Next() : maCurString.getData();
788 }
789 
790 /** Returns the number format of the passed cell, or the standard format. */
lclGetCellFormat(const ScDocument & rDoc,const ScAddress & rPos)791 sal_uInt32 lclGetCellFormat( const ScDocument& rDoc, const ScAddress& rPos )
792 {
793     const ScPatternAttr* pPattern = rDoc.GetPattern( rPos.Col(), rPos.Row(), rPos.Tab() );
794     if( !pPattern )
795         pPattern = &rDoc.getCellAttributeHelper().getDefaultCellAttribute();
796     return pPattern->GetNumberFormat( rDoc.GetFormatTable() );
797 }
798 
799 } // namespace
800 
HasSelectionList() const801 bool ScValidationData::HasSelectionList() const
802 {
803     return (eDataMode == SC_VALID_LIST) && (mnListType != css::sheet::TableValidationVisibility::INVISIBLE);
804 }
805 
GetSelectionFromFormula(std::vector<ScTypedStrData> * pStrings,ScRefCellValue & rCell,const ScAddress & rPos,const ScTokenArray & rTokArr,int & rMatch) const806 bool ScValidationData::GetSelectionFromFormula(
807     std::vector<ScTypedStrData>* pStrings, ScRefCellValue& rCell, const ScAddress& rPos,
808     const ScTokenArray& rTokArr, int& rMatch) const
809 {
810     bool bOk = true;
811 
812     // pDoc is private in condition, use an accessor and a long winded name.
813     ScDocument* pDocument = GetDocument();
814     if( nullptr == pDocument )
815         return false;
816 
817     ScFormulaCell aValidationSrc(
818         *pDocument, rPos, rTokArr, formula::FormulaGrammar::GRAM_DEFAULT, ScMatrixMode::Formula);
819 
820     // Make sure the formula gets interpreted and a result is delivered,
821     // regardless of the AutoCalc setting.
822     aValidationSrc.Interpret();
823 
824     ScMatrixRef xMatRef;
825     const ScMatrix *pValues = aValidationSrc.GetMatrix();
826     if (!pValues)
827     {
828         // The somewhat nasty case of either an error occurred, or the
829         // dereferenced value of a single cell reference or an immediate result
830         // is stored as a single value.
831 
832         // Use an interim matrix to create the TypedStrData below.
833         xMatRef = new ScMatrix(1, 1, 0.0);
834 
835         FormulaError nErrCode = aValidationSrc.GetErrCode();
836         if (nErrCode != FormulaError::NONE)
837         {
838             /* TODO : to use later in an alert box?
839              * OUString rStrResult = "...";
840              * rStrResult += ScGlobal::GetLongErrorString(nErrCode);
841              */
842 
843             xMatRef->PutError( nErrCode, 0, 0);
844             bOk = false;
845         }
846         else if (aValidationSrc.IsValue())
847             xMatRef->PutDouble( aValidationSrc.GetValue(), 0);
848         else
849         {
850             svl::SharedString aStr = aValidationSrc.GetString();
851             xMatRef->PutString(aStr, 0);
852         }
853 
854         pValues = xMatRef.get();
855     }
856 
857     // which index matched.  We will want it eventually to pre-select that item.
858     rMatch = -1;
859 
860     SvNumberFormatter* pFormatter = GetDocument()->GetFormatTable();
861     sal_uInt32 nDestFormat = pDocument->GetNumberFormat(rPos.Col(), rPos.Row(), rPos.Tab());
862 
863     SCSIZE  nCol, nRow, nCols, nRows, n = 0;
864     pValues->GetDimensions( nCols, nRows );
865 
866     bool bRef = false;
867     ScRange aRange;
868 
869     ScTokenArray* pArr = const_cast<ScTokenArray*>(&rTokArr);
870     if (pArr->GetLen() == 1)
871     {
872         formula::FormulaTokenArrayPlainIterator aIter(*pArr);
873         formula::FormulaToken* t = aIter.GetNextReferenceOrName();
874         if (t)
875         {
876             OpCode eOpCode = t->GetOpCode();
877             if (eOpCode == ocDBArea || eOpCode == ocTableRef)
878             {
879                 if (const ScDBData* pDBData = pDocument->GetDBCollection()->getNamedDBs().findByIndex(t->GetIndex()))
880                 {
881                     pDBData->GetArea(aRange);
882                     bRef = true;
883                 }
884             }
885             else if (eOpCode == ocName)
886             {
887                 const ScRangeData* pName = pDocument->FindRangeNameBySheetAndIndex( t->GetSheet(), t->GetIndex());
888                 if (pName && pName->IsReference(aRange))
889                 {
890                     bRef = true;
891                 }
892             }
893             else if (t->GetType() != svIndex)
894             {
895                 if (pArr->IsValidReference(aRange, rPos))
896                 {
897                     bRef = true;
898                 }
899             }
900         }
901     }
902 
903     bool bHaveEmpty = false;
904     svl::SharedStringPool& rSPool = pDocument->GetSharedStringPool();
905 
906     /* XL artificially limits things to a single col or row in the UI but does
907      * not list the constraint in MOOXml. If a defined name or INDIRECT
908      * resulting in 1D is entered in the UI and the definition later modified
909      * to 2D, it is evaluated fine and also stored and loaded. Let's get ahead
910      * of the curve and support 2d. In XL, values are listed row-wise, do the
911      * same. */
912     for( nRow = 0; nRow < nRows ; nRow++ )
913     {
914         for( nCol = 0; nCol < nCols ; nCol++ )
915         {
916             ScTokenArray         aCondTokArr(*pDocument);
917             std::unique_ptr<ScTypedStrData> pEntry;
918             OUString               aValStr;
919             ScMatrixValue nMatVal = pValues->Get( nCol, nRow);
920 
921             // strings and empties
922             if( ScMatrix::IsNonValueType( nMatVal.nType ) )
923             {
924                 aValStr = nMatVal.GetString().getString();
925 
926                 // Do not add multiple empty strings to the validation list,
927                 // especially not if they'd bloat the tail with a million empty
928                 // entries for a column range, fdo#61520
929                 if (aValStr.isEmpty())
930                 {
931                     if (bHaveEmpty)
932                         continue;
933                     bHaveEmpty = true;
934                 }
935 
936                 if( nullptr != pStrings )
937                     pEntry.reset(new ScTypedStrData(aValStr, 0.0, 0.0, ScTypedStrData::Standard));
938 
939                 if (!rCell.isEmpty() && rMatch < 0)
940                     aCondTokArr.AddString(rSPool.intern(aValStr));
941             }
942             else
943             {
944                 FormulaError nErr = nMatVal.GetError();
945 
946                 if( FormulaError::NONE != nErr )
947                 {
948                     aValStr = ScGlobal::GetErrorString( nErr );
949                 }
950                 else
951                 {
952                     // FIXME FIXME FIXME
953                     // Feature regression.  Date formats are lost passing through the matrix
954                     //pFormatter->GetInputLineString( pMatVal->fVal, 0, aValStr );
955                     //For external reference and a formula that results in an area or array, date formats are still lost.
956                     if ( bRef )
957                     {
958                         aValStr = pDocument->GetInputString(static_cast<SCCOL>(nCol+aRange.aStart.Col()),
959                             static_cast<SCROW>(nRow+aRange.aStart.Row()), aRange.aStart.Tab());
960                     }
961                     else
962                     {
963                         pFormatter->GetInputLineString( nMatVal.fVal, nDestFormat, aValStr );
964                     }
965                 }
966 
967                 if (!rCell.isEmpty() && rMatch < 0)
968                 {
969                     // I am not sure errors will work here, but a user can no
970                     // manually enter an error yet so the point is somewhat moot.
971                     aCondTokArr.AddDouble( nMatVal.fVal );
972                 }
973                 if( nullptr != pStrings )
974                     pEntry.reset(new ScTypedStrData(aValStr, nMatVal.fVal, nMatVal.fVal, ScTypedStrData::Value));
975             }
976 
977             if (rMatch < 0 && !rCell.isEmpty() && IsEqualToTokenArray(rCell, rPos, aCondTokArr))
978             {
979                 rMatch = n;
980                 // short circuit on the first match if not filling the list
981                 if( nullptr == pStrings )
982                     return true;
983             }
984 
985             if( pEntry )
986             {
987                 assert(pStrings);
988                 pStrings->push_back(*pEntry);
989                 n++;
990             }
991         }
992     }
993 
994     // In case of no match needed and an error occurred, return that error
995     // entry as valid instead of silently failing.
996     return bOk || rCell.isEmpty();
997 }
998 
FillSelectionList(std::vector<ScTypedStrData> & rStrColl,const ScAddress & rPos) const999 bool ScValidationData::FillSelectionList(std::vector<ScTypedStrData>& rStrColl, const ScAddress& rPos) const
1000 {
1001     bool bOk = false;
1002 
1003     if( HasSelectionList() )
1004     {
1005         std::unique_ptr<ScTokenArray> pTokArr( CreateFlatCopiedTokenArray(0) );
1006 
1007         // *** try if formula is a string list ***
1008 
1009         sal_uInt32 nFormat = lclGetCellFormat( *GetDocument(), rPos );
1010         ScStringTokenIterator aIt( *pTokArr );
1011         for (rtl_uString* pString = aIt.First(); pString && aIt.Ok(); pString = aIt.Next())
1012         {
1013             double fValue;
1014             OUString aStr(pString);
1015             bool bIsValue = GetDocument()->GetFormatTable()->IsNumberFormat(aStr, nFormat, fValue);
1016             rStrColl.emplace_back(
1017                     aStr, fValue, fValue, bIsValue ? ScTypedStrData::Value : ScTypedStrData::Standard);
1018         }
1019         bOk = aIt.Ok();
1020 
1021         // *** if not a string list, try if formula results in a cell range or
1022         // anything else we recognize as valid ***
1023 
1024         if (!bOk)
1025         {
1026             int nMatch;
1027             ScRefCellValue aEmptyCell;
1028             bOk = GetSelectionFromFormula(&rStrColl, aEmptyCell, rPos, *pTokArr, nMatch);
1029         }
1030     }
1031 
1032     return bOk;
1033 }
1034 
IsEqualToTokenArray(ScRefCellValue & rCell,const ScAddress & rPos,const ScTokenArray & rTokArr) const1035 bool ScValidationData::IsEqualToTokenArray( ScRefCellValue& rCell, const ScAddress& rPos, const ScTokenArray& rTokArr ) const
1036 {
1037     // create a condition entry that tests on equality and set the passed token array
1038     ScConditionEntry aCondEntry( ScConditionMode::Equal, &rTokArr, nullptr, *GetDocument(), rPos );
1039     aCondEntry.SetCaseSensitive(IsCaseSensitive());
1040 
1041     return aCondEntry.IsCellValid(rCell, rPos);
1042 }
1043 
IsListValid(ScRefCellValue & rCell,const ScAddress & rPos) const1044 bool ScValidationData::IsListValid( ScRefCellValue& rCell, const ScAddress& rPos ) const
1045 {
1046     bool bIsValid = false;
1047 
1048     /*  Compare input cell with all supported tokens from the formula.
1049         Currently a formula may contain:
1050         1)  A list of strings (at least one string).
1051         2)  A single cell or range reference.
1052         3)  A single defined name (must contain a cell/range reference, another
1053             name, or DB range, or a formula resulting in a cell/range reference
1054             or matrix/array).
1055         4)  A single database range.
1056         5)  A formula resulting in a cell/range reference or matrix/array.
1057     */
1058 
1059     std::unique_ptr< ScTokenArray > pTokArr( CreateFlatCopiedTokenArray( 0 ) );
1060 
1061     // *** try if formula is a string list ***
1062 
1063     svl::SharedStringPool& rSPool = GetDocument()->GetSharedStringPool();
1064     sal_uInt32 nFormat = lclGetCellFormat( *GetDocument(), rPos );
1065     ScStringTokenIterator aIt( *pTokArr );
1066     for (rtl_uString* pString = aIt.First(); pString && aIt.Ok(); pString = aIt.Next())
1067     {
1068         /*  Do not break the loop, if a valid string has been found.
1069             This is to find invalid tokens following in the formula. */
1070         if( !bIsValid )
1071         {
1072             // create a formula containing a single string or number
1073             ScTokenArray aCondTokArr(*GetDocument());
1074             double fValue;
1075             OUString aStr(pString);
1076             if (GetDocument()->GetFormatTable()->IsNumberFormat(aStr, nFormat, fValue))
1077                 aCondTokArr.AddDouble( fValue );
1078             else
1079                 aCondTokArr.AddString(rSPool.intern(aStr));
1080 
1081             bIsValid = IsEqualToTokenArray(rCell, rPos, aCondTokArr);
1082         }
1083     }
1084 
1085     if( !aIt.Ok() )
1086         bIsValid = false;
1087 
1088     // *** if not a string list, try if formula results in a cell range or
1089     // anything else we recognize as valid ***
1090 
1091     if (!bIsValid)
1092     {
1093         int nMatch;
1094         bIsValid = GetSelectionFromFormula(nullptr, rCell, rPos, *pTokArr, nMatch);
1095         bIsValid = bIsValid && nMatch >= 0;
1096     }
1097 
1098     return bIsValid;
1099 }
1100 
ScValidationDataList(const ScValidationDataList & rList)1101 ScValidationDataList::ScValidationDataList(const ScValidationDataList& rList)
1102 {
1103     //  for Ref-Undo - real copy with new tokens!
1104 
1105     for (const auto& rxItem : rList)
1106     {
1107         InsertNew( std::unique_ptr<ScValidationData>(rxItem->Clone()) );
1108     }
1109 
1110     //TODO: faster insert for sorted entries from rList ???
1111 }
1112 
ScValidationDataList(ScDocument & rNewDoc,const ScValidationDataList & rList)1113 ScValidationDataList::ScValidationDataList(ScDocument& rNewDoc,
1114                                             const ScValidationDataList& rList)
1115 {
1116     //  for new documents - real copy with new tokens!
1117 
1118     for (const auto& rxItem : rList)
1119     {
1120         InsertNew( std::unique_ptr<ScValidationData>(rxItem->Clone(&rNewDoc)) );
1121     }
1122 
1123     //TODO: faster insert for sorted entries from rList ???
1124 }
1125 
GetData(sal_uInt32 nKey)1126 ScValidationData* ScValidationDataList::GetData( sal_uInt32 nKey )
1127 {
1128     //TODO: binary search
1129 
1130     for( iterator it = begin(); it != end(); ++it )
1131         if( (*it)->GetKey() == nKey )
1132             return it->get();
1133 
1134     OSL_FAIL("ScValidationDataList: Entry not found");
1135     return nullptr;
1136 }
1137 
CompileXML()1138 void ScValidationDataList::CompileXML()
1139 {
1140     for( iterator it = begin(); it != end(); ++it )
1141         (*it)->CompileXML();
1142 }
1143 
UpdateReference(sc::RefUpdateContext & rCxt)1144 void ScValidationDataList::UpdateReference( sc::RefUpdateContext& rCxt )
1145 {
1146     for( iterator it = begin(); it != end(); ++it )
1147         (*it)->UpdateReference(rCxt);
1148 }
1149 
UpdateInsertTab(sc::RefUpdateInsertTabContext & rCxt)1150 void ScValidationDataList::UpdateInsertTab( sc::RefUpdateInsertTabContext& rCxt )
1151 {
1152     for (iterator it = begin(); it != end(); ++it)
1153         (*it)->UpdateInsertTab(rCxt);
1154 }
1155 
UpdateDeleteTab(sc::RefUpdateDeleteTabContext & rCxt)1156 void ScValidationDataList::UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt )
1157 {
1158     for (iterator it = begin(); it != end(); ++it)
1159         (*it)->UpdateDeleteTab(rCxt);
1160 }
1161 
UpdateMoveTab(sc::RefUpdateMoveTabContext & rCxt)1162 void ScValidationDataList::UpdateMoveTab( sc::RefUpdateMoveTabContext& rCxt )
1163 {
1164     for (iterator it = begin(); it != end(); ++it)
1165         (*it)->UpdateMoveTab(rCxt);
1166 }
1167 
begin()1168 ScValidationDataList::iterator ScValidationDataList::begin()
1169 {
1170     return maData.begin();
1171 }
1172 
begin() const1173 ScValidationDataList::const_iterator ScValidationDataList::begin() const
1174 {
1175     return maData.begin();
1176 }
1177 
end()1178 ScValidationDataList::iterator ScValidationDataList::end()
1179 {
1180     return maData.end();
1181 }
1182 
end() const1183 ScValidationDataList::const_iterator ScValidationDataList::end() const
1184 {
1185     return maData.end();
1186 }
1187 
1188 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1189