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