1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 <validat.hxx>
23 #include <com/sun/star/sheet/TableValidationVisibility.hpp>
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>
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>
40 #include <document.hxx>
42 #include <formulacell.hxx>
43 #include <patattr.hxx>
44 #include <globstr.hrc>
45 #include <scresid.hxx>
46 #include <rangenam.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>
58 using namespace formula
;
60 // Entries for validation (with only one condition)
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
)
74 , eErrorStyle( SC_VALERR_STOP
)
75 , mnListType( css::sheet::TableValidationVisibility::UNSORTED
)
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
)
87 , eErrorStyle( SC_VALERR_STOP
)
88 , mnListType( css::sheet::TableValidationVisibility::UNSORTED
)
92 ScValidationData::ScValidationData( const ScValidationData
& r
)
93 : ScConditionEntry( r
)
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
)
105 // Formulae copied by RefCount
108 ScValidationData::ScValidationData( ScDocument
& rDocument
, const ScValidationData
& r
)
109 : ScConditionEntry( rDocument
, r
)
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
)
121 // Formulae really copied
124 ScValidationData::~ScValidationData()
128 bool ScValidationData::IsEmpty() const
130 ScValidationData
aDefault( SC_VALID_ANY
, ScConditionMode::Equal
, u
""_ustr
, u
""_ustr
, *GetDocument(), ScAddress() );
131 return EqualEntries( aDefault
);
134 bool ScValidationData::EqualEntries( const ScValidationData
& r
) const
136 // test same parameters (excluding Key)
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
;
150 void ScValidationData::ResetInput()
155 void ScValidationData::ResetError()
160 void ScValidationData::SetInput( const OUString
& rTitle
, const OUString
& rMsg
)
163 aInputTitle
= rTitle
;
164 aInputMessage
= rMsg
;
167 void ScValidationData::SetError( const OUString
& rTitle
, const OUString
& rMsg
,
168 ScValidErrorStyle eStyle
)
171 eErrorStyle
= eStyle
;
172 aErrorTitle
= rTitle
;
173 aErrorMessage
= rMsg
;
176 bool ScValidationData::GetErrMsg( OUString
& rTitle
, OUString
& rMsg
,
177 ScValidErrorStyle
& rStyle
) const
179 rTitle
= aErrorTitle
;
180 rMsg
= aErrorMessage
;
181 rStyle
= eErrorStyle
;
185 bool ScValidationData::DoScript( const ScAddress
& rPos
, const OUString
& rInput
,
186 ScFormulaCell
* pCell
, weld::Window
* pParent
) const
188 ScDocument
* pDocument
= GetDocument();
189 ScDocShell
* pDocSh
= pDocument
->GetDocumentShell();
193 bool bScriptReturnedFalse
= false; // default: do not abort
195 // 1) entered or calculated value
196 css::uno::Any
aParam0(rInput
);
197 if ( pCell
) // if cell exists, call interpret
199 if ( pCell
->IsValue() )
200 aParam0
<<= pCell
->GetValue();
202 aParam0
<<= pCell
->GetString().getString();
205 // 2) Position of the cell
206 OUString
aPosStr(rPos
.Format(ScRefFlags::VALID
| ScRefFlags::TAB_3D
, pDocument
, pDocument
->GetAddressConvention()));
209 css::uno::Sequence
< css::uno::Any
> aParams
{ aParam0
, css::uno::Any(aPosStr
) };
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 );
218 pDocument
->LockTable( rPos
.Tab() );
221 css::uno::Sequence
< sal_Int16
> aOutArgsIndex
;
222 css::uno::Sequence
< css::uno::Any
> aOutArgs
;
224 ErrCode eRet
= pDocSh
->CallXScript(
225 aErrorTitle
, aParams
, aRet
, aOutArgsIndex
, aOutArgs
);
228 pDocument
->UnlockTable( rPos
.Tab() );
230 if ( !bWasInLinkUpdate
)
231 pDocument
->SetInLinkUpdate( false );
233 // Check the return value from the script
234 // The contents of the cell get reset if the script returns false
236 if ( eRet
== ERRCODE_NONE
&&
237 aRet
.getValueType() == cppu::UnoType
<bool>::get() &&
241 bScriptReturnedFalse
= true;
244 if ( eRet
== ERRCODE_BASIC_METHOD_NOT_FOUND
&& !pCell
)
245 // Macro not found (only with input)
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
)));
254 return bScriptReturnedFalse
;
259 bool ScValidationData::DoMacro( const ScAddress
& rPos
, const OUString
& rInput
,
260 ScFormulaCell
* pCell
, weld::Window
* pParent
) const
262 if ( SfxApplication::IsXScriptURL( aErrorTitle
) )
264 return DoScript( rPos
, rInput
, pCell
, pParent
);
267 ScDocument
* pDocument
= GetDocument();
268 ScDocShell
* pDocSh
= pDocument
->GetDocumentShell();
273 bool bRet
= false; // default: do not abort
275 // If the Doc was loaded during a Basic-Calls,
276 // the Sbx-object may not be created (?)
277 // pDocSh->GetSbxObject();
279 #if HAVE_FEATURE_SCRIPTING
280 // no security check ahead (only CheckMacroWarn), that happens in CallBasic
282 // Function search by their simple name,
283 // then assemble aBasicStr, aMacroStr for SfxObjectShell::CallBasic
285 StarBASIC
* pRoot
= pDocSh
->GetBasic();
286 SbxVariable
* pVar
= pRoot
->Find( aErrorTitle
, SbxClassType::Method
);
287 if (SbMethod
* pMethod
= dynamic_cast<SbMethod
*>(pVar
))
289 SbModule
* pModule
= pMethod
->GetModule();
290 SbxObject
* pObject
= pModule
->GetParent();
292 pObject
->GetName() + "." + pModule
->GetName() + "." + pMethod
->GetName());
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.
300 if ( pObject
->GetParent() )
301 aBasicStr
= pObject
->GetParent()->GetName(); // Basic of document
303 aBasicStr
= SfxGetpApp()->GetName(); // Basic of application
305 // Parameter for Macro
306 SbxArrayRef refPar
= new SbxArray
;
308 // 1) entered or calculated value
309 OUString aValStr
= rInput
;
311 bool bIsValue
= false;
312 if ( pCell
) // if cell set, called from interpret
314 bIsValue
= pCell
->IsValue();
316 nValue
= pCell
->GetValue();
318 aValStr
= pCell
->GetString().getString();
321 refPar
->Get(1)->PutDouble(nValue
);
323 refPar
->Get(1)->PutString(aValStr
);
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
);
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 );
336 pDocument
->LockTable( rPos
.Tab() );
337 SbxVariableRef refRes
= new SbxVariable
;
338 ErrCode eRet
= pDocSh
->CallBasic( aMacroStr
, aBasicStr
, refPar
.get(), refRes
.get() );
340 pDocument
->UnlockTable( rPos
.Tab() );
342 if ( !bWasInLinkUpdate
)
343 pDocument
->SetInLinkUpdate( false );
345 // Interrupt input if Basic macro returns false
346 if ( eRet
== ERRCODE_NONE
&& refRes
->GetType() == SbxBOOL
&& !refRes
->GetBool() )
351 if ( !bDone
&& !pCell
) // Macro not found (only with input)
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
)));
363 void ScValidationData::DoCalcError( ScFormulaCell
* pCell
) const
365 if ( eErrorStyle
== SC_VALERR_MACRO
)
366 DoMacro( pCell
->aPos
, OUString(), pCell
, nullptr );
369 IMPL_STATIC_LINK_NOARG(ScValidationData
, InstallLOKNotifierHdl
, void*, vcl::ILibreOfficeKitNotifier
*)
371 return SfxViewShell::Current();
376 bool ScValidationData::DoError(weld::Window
* pParent
, const OUString
& rInput
,
377 const ScAddress
& rPos
) const
379 if ( eErrorStyle
== SC_VALERR_MACRO
)
380 return DoMacro(rPos
, rInput
, nullptr, pParent
);
385 // Output error message
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
);
394 VclButtonsType eStyle
= VclButtonsType::Ok
;
395 VclMessageType eType
= VclMessageType::Error
;
399 eType
= VclMessageType::Info
;
400 eStyle
= VclButtonsType::OkCancel
;
402 case SC_VALERR_WARNING
:
403 eType
= VclMessageType::Warning
;
404 eStyle
= VclButtonsType::OkCancel
;
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
));
418 xBox
->set_default_response(RET_OK
);
420 case SC_VALERR_WARNING
:
421 xBox
->set_default_response(RET_CANCEL
);
427 short nRet
= xBox
->run();
429 return ( eErrorStyle
== SC_VALERR_STOP
|| nRet
== RET_CANCEL
);
432 bool ScValidationData::IsDataValidCustom(
433 const OUString
& rTest
,
434 const ScPatternAttr
& rPattern
,
435 const ScAddress
& rPos
,
436 const CustomValidationPrivateAccess
& ) const
438 OSL_ENSURE(GetDataMode() == SC_VALID_CUSTOM
,
439 "ScValidationData::IsDataValidCustom invoked for a non-custom validation");
441 if (rTest
.isEmpty()) // check whether empty cells are allowed
442 return IsIgnoreBlank();
444 SvNumberFormatter
* pFormatter
= nullptr;
445 sal_uInt32 nFormat
= 0;
447 OUString rStrResult
= u
""_ustr
;
452 if (!isFormulaResultsValidatable(rTest
, rPos
, pFormatter
, rStrResult
, nVal
, nFormat
, bIsVal
))
455 // check whether empty cells are allowed
456 if (rStrResult
.isEmpty())
457 return IsIgnoreBlank();
461 pFormatter
= GetDocument()->GetFormatTable();
463 // get the value if any
464 nFormat
= rPattern
.GetNumberFormat(pFormatter
);
465 bIsVal
= pFormatter
->IsNumberFormat(rTest
, nFormat
, nVal
);
469 ScRefCellValue aTmpCell
;
470 svl::SharedString aSS
;
473 aTmpCell
= ScRefCellValue(nVal
);
477 aSS
= mpDoc
->GetSharedStringPool().intern(rStrResult
);
478 aTmpCell
= ScRefCellValue(&aSS
);
481 ScCellValue
aOriginalCellValue(ScRefCellValue(*GetDocument(), rPos
));
483 aTmpCell
.commit(*GetDocument(), rPos
);
484 bool bRet
= IsCellValid(aTmpCell
, rPos
);
485 aOriginalCellValue
.commit(*GetDocument(), rPos
);
490 /** To test numeric data text length in IsDataValidTextLen().
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.
495 struct ScValidationDataIsNumeric
497 SvNumberFormatter
* mpFormatter
;
501 ScValidationDataIsNumeric( double fVal
, SvNumberFormatter
* pFormatter
= nullptr, sal_uInt32 nFormat
= 0 )
502 : mpFormatter(pFormatter
), mfVal(fVal
), mnFormat(nFormat
)
506 void init( const ScDocument
& rDoc
, const ScAddress
& rPos
)
508 const ScPatternAttr
* pPattern
= rDoc
.GetPattern( rPos
.Col(), rPos
.Row(), rPos
.Tab());
509 mpFormatter
= rDoc
.GetFormatTable();
510 mnFormat
= pPattern
->GetNumberFormat( mpFormatter
);
514 bool ScValidationData::IsDataValidTextLen( std::u16string_view rTest
, const ScAddress
& rPos
,
515 ScValidationDataIsNumeric
* pDataNumeric
) const
522 if (!pDataNumeric
->mpFormatter
)
523 pDataNumeric
->init( *GetDocument(), rPos
);
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
= pDataNumeric
->mpFormatter
->GetInputLineString( pDataNumeric
->mfVal
, pDataNumeric
->mnFormat
);
530 nLen
= aStr
.getLength();
532 ScRefCellValue
aTmpCell( static_cast<double>(nLen
));
533 return IsCellValid( aTmpCell
, rPos
);
536 bool ScValidationData::IsDataValid(
537 const OUString
& rTest
, const ScPatternAttr
& rPattern
, const ScAddress
& rPos
) const
539 if ( eDataMode
== SC_VALID_ANY
) // check if any cell content is allowed
542 if (rTest
.isEmpty()) // check whether empty cells are allowed
543 return IsIgnoreBlank();
545 SvNumberFormatter
* pFormatter
= nullptr;
546 sal_uInt32 nFormat
= 0;
548 OUString rStrResult
= u
""_ustr
;
553 if (!isFormulaResultsValidatable(rTest
, rPos
, pFormatter
, rStrResult
, nVal
, nFormat
, bIsVal
))
556 // check whether empty cells are allowed
557 if (rStrResult
.isEmpty())
558 return IsIgnoreBlank();
562 pFormatter
= GetDocument()->GetFormatTable();
564 // get the value if any
565 nFormat
= rPattern
.GetNumberFormat(pFormatter
);
566 bIsVal
= pFormatter
->IsNumberFormat(rTest
, nFormat
, nVal
);
571 if (SC_VALID_TEXTLEN
== eDataMode
)
574 bRet
= IsDataValidTextLen( rStrResult
, rPos
, nullptr);
577 ScValidationDataIsNumeric
aDataNumeric( nVal
, pFormatter
, nFormat
);
578 bRet
= IsDataValidTextLen( rStrResult
, rPos
, &aDataNumeric
);
585 ScRefCellValue
aTmpCell(nVal
);
586 bRet
= IsDataValid(aTmpCell
, rPos
);
590 svl::SharedString aSS
= mpDoc
->GetSharedStringPool().intern( rStrResult
);
591 ScRefCellValue
aTmpCell(&aSS
);
592 bRet
= IsDataValid(aTmpCell
, rPos
);
599 bool ScValidationData::IsDataValid( ScRefCellValue
& rCell
, const ScAddress
& rPos
) const
601 if( eDataMode
== SC_VALID_LIST
)
602 return IsListValid(rCell
, rPos
);
604 if ( eDataMode
== SC_VALID_CUSTOM
)
605 return IsCellValid(rCell
, rPos
);
611 switch (rCell
.getType())
614 nVal
= rCell
.getDouble();
616 case CELLTYPE_STRING
:
617 aString
= rCell
.getSharedString()->getString();
621 if (rCell
.getEditText())
622 aString
= ScEditUtil::GetString(*rCell
.getEditText(), GetDocument());
625 case CELLTYPE_FORMULA
:
627 ScFormulaCell
* pFCell
= rCell
.getFormula();
628 bIsVal
= pFCell
->IsValue();
630 nVal
= pFCell
->GetValue();
632 aString
= pFCell
->GetString().getString();
635 default: // Notes, Broadcaster
636 return IsIgnoreBlank(); // as set
642 // SC_VALID_ANY already above
645 case SC_VALID_DECIMAL
:
646 case SC_VALID_DATE
: // Date/Time is only formatting
649 if ( bOk
&& eDataMode
== SC_VALID_WHOLE
)
650 bOk
= ::rtl::math::approxEqual( nVal
, floor(nVal
+0.5) ); // integers
652 bOk
= IsCellValid(rCell
, rPos
);
655 case SC_VALID_TEXTLEN
:
657 bOk
= IsDataValidTextLen( aString
, rPos
, nullptr);
660 ScValidationDataIsNumeric
aDataNumeric( nVal
);
661 bOk
= IsDataValidTextLen( aString
, rPos
, &aDataNumeric
);
666 OSL_FAIL("not yet done");
673 bool ScValidationData::isFormulaResultsValidatable(const OUString
& rTest
, const ScAddress
& rPos
, SvNumberFormatter
* pFormatter
,
674 OUString
& rStrResult
, double& nVal
, sal_uInt32
& nFormat
, bool& bIsVal
) const
676 std::optional
<ScSimpleFormulaCalculator
> pFCell(std::in_place
, *mpDoc
, rPos
, rTest
, true);
677 pFCell
->SetLimitString(true);
679 bool bColRowName
= pFCell
->HasColRowName();
682 // ColRowName from RPN-Code?
683 if (pFCell
->GetCode()->GetCodeLen() <= 1)
685 // ==0: would be an area if...
686 OUString aBraced
= "(" + rTest
+ ")";
687 pFCell
.emplace(*mpDoc
, rPos
, aBraced
, true);
688 pFCell
->SetLimitString(true);
694 FormulaError nErrCode
= pFCell
->GetErrCode();
695 if (nErrCode
== FormulaError::NONE
|| pFCell
->IsMatrix())
697 pFormatter
= mpDoc
->GetFormatTable();
699 if (pFCell
->IsMatrix())
701 rStrResult
= pFCell
->GetString().getString();
703 else if (pFCell
->IsValue())
705 nVal
= pFCell
->GetValue();
706 nFormat
= pFormatter
->GetStandardFormat(nVal
, 0,
707 pFCell
->GetFormatType(), ScGlobal::eLnge
);
708 pFormatter
->GetOutputString(nVal
, nFormat
, rStrResult
, &pColor
);
713 nFormat
= pFormatter
->GetStandardFormat(
714 pFCell
->GetFormatType(), ScGlobal::eLnge
);
715 pFormatter
->GetOutputString(pFCell
->GetString().getString(), nFormat
,
716 rStrResult
, &pColor
);
717 // Indicate it's a string, so a number string doesn't look numeric.
718 // Escape embedded quotation marks first by doubling them, as
719 // usual. Actually the result can be copy-pasted from the result
720 // box as literal into a formula expression.
721 rStrResult
= "\"" + rStrResult
.replaceAll("\"", "\"\"") + "\"";
725 if (bColRowName
|| (aTestRange
.Parse(rTest
, *mpDoc
) & ScRefFlags::VALID
))
726 rStrResult
+= " ...";
739 /** Token array helper. Iterates over all string tokens.
740 @descr The token array must contain separated string tokens only.
741 @param bSkipEmpty true = Ignores string tokens with empty strings. */
742 class ScStringTokenIterator
745 explicit ScStringTokenIterator( const ScTokenArray
& rTokArr
) :
746 maIter( rTokArr
), mbOk( true ) {}
748 /** Returns the string of the first string token or NULL on error or empty token array. */
749 rtl_uString
* First();
750 /** Returns the string of the next string token or NULL on error or end of token array. */
753 /** Returns false, if a wrong token has been found. Does NOT return false on end of token array. */
754 bool Ok() const { return mbOk
; }
757 svl::SharedString maCurString
; /// Current string.
758 FormulaTokenArrayPlainIterator maIter
;
759 bool mbOk
; /// true = correct token or end of token array.
762 rtl_uString
* ScStringTokenIterator::First()
769 rtl_uString
* ScStringTokenIterator::Next()
774 // seek to next non-separator token
775 const FormulaToken
* pToken
= maIter
.NextNoSpaces();
776 while( pToken
&& (pToken
->GetOpCode() == ocSep
) )
777 pToken
= maIter
.NextNoSpaces();
779 mbOk
= !pToken
|| (pToken
->GetType() == formula::svString
);
781 maCurString
= svl::SharedString(); // start with invalid string.
783 maCurString
= pToken
->GetString();
785 // string found but empty -> get next token; otherwise return it
786 return (maCurString
.isValid() && maCurString
.isEmpty()) ? Next() : maCurString
.getData();
789 /** Returns the number format of the passed cell, or the standard format. */
790 sal_uInt32
lclGetCellFormat( const ScDocument
& rDoc
, const ScAddress
& rPos
)
792 const ScPatternAttr
* pPattern
= rDoc
.GetPattern( rPos
.Col(), rPos
.Row(), rPos
.Tab() );
794 pPattern
= &rDoc
.getCellAttributeHelper().getDefaultCellAttribute();
795 return pPattern
->GetNumberFormat( rDoc
.GetFormatTable() );
800 bool ScValidationData::HasSelectionList() const
802 return (eDataMode
== SC_VALID_LIST
) && (mnListType
!= css::sheet::TableValidationVisibility::INVISIBLE
);
805 bool ScValidationData::GetSelectionFromFormula(
806 std::vector
<ScTypedStrData
>* pStrings
, ScRefCellValue
& rCell
, const ScAddress
& rPos
,
807 const ScTokenArray
& rTokArr
, int& rMatch
) const
811 // pDoc is private in condition, use an accessor and a long winded name.
812 ScDocument
* pDocument
= GetDocument();
813 if( nullptr == pDocument
)
816 ScFormulaCell
aValidationSrc(
817 *pDocument
, rPos
, rTokArr
, formula::FormulaGrammar::GRAM_DEFAULT
, ScMatrixMode::Formula
);
819 // Make sure the formula gets interpreted and a result is delivered,
820 // regardless of the AutoCalc setting.
821 aValidationSrc
.Interpret();
824 const ScMatrix
*pValues
= aValidationSrc
.GetMatrix();
827 // The somewhat nasty case of either an error occurred, or the
828 // dereferenced value of a single cell reference or an immediate result
829 // is stored as a single value.
831 // Use an interim matrix to create the TypedStrData below.
832 xMatRef
= new ScMatrix(1, 1, 0.0);
834 FormulaError nErrCode
= aValidationSrc
.GetErrCode();
835 if (nErrCode
!= FormulaError::NONE
)
837 /* TODO : to use later in an alert box?
838 * OUString rStrResult = "...";
839 * rStrResult += ScGlobal::GetLongErrorString(nErrCode);
842 xMatRef
->PutError( nErrCode
, 0, 0);
845 else if (aValidationSrc
.IsValue())
846 xMatRef
->PutDouble( aValidationSrc
.GetValue(), 0);
849 svl::SharedString aStr
= aValidationSrc
.GetString();
850 xMatRef
->PutString(aStr
, 0);
853 pValues
= xMatRef
.get();
856 // which index matched. We will want it eventually to pre-select that item.
859 SvNumberFormatter
* pFormatter
= GetDocument()->GetFormatTable();
860 sal_uInt32 nDestFormat
= pDocument
->GetNumberFormat(rPos
.Col(), rPos
.Row(), rPos
.Tab());
862 SCSIZE nCol
, nRow
, nCols
, nRows
, n
= 0;
863 pValues
->GetDimensions( nCols
, nRows
);
868 ScTokenArray
* pArr
= const_cast<ScTokenArray
*>(&rTokArr
);
869 if (pArr
->GetLen() == 1)
871 formula::FormulaTokenArrayPlainIterator
aIter(*pArr
);
872 formula::FormulaToken
* t
= aIter
.GetNextReferenceOrName();
875 OpCode eOpCode
= t
->GetOpCode();
876 if (eOpCode
== ocDBArea
|| eOpCode
== ocTableRef
)
878 if (const ScDBData
* pDBData
= pDocument
->GetDBCollection()->getNamedDBs().findByIndex(t
->GetIndex()))
880 pDBData
->GetArea(aRange
);
884 else if (eOpCode
== ocName
)
886 const ScRangeData
* pName
= pDocument
->FindRangeNameBySheetAndIndex( t
->GetSheet(), t
->GetIndex());
887 if (pName
&& pName
->IsReference(aRange
))
892 else if (t
->GetType() != svIndex
)
894 if (pArr
->IsValidReference(aRange
, rPos
))
902 bool bHaveEmpty
= false;
903 svl::SharedStringPool
& rSPool
= pDocument
->GetSharedStringPool();
905 /* XL artificially limits things to a single col or row in the UI but does
906 * not list the constraint in MOOXml. If a defined name or INDIRECT
907 * resulting in 1D is entered in the UI and the definition later modified
908 * to 2D, it is evaluated fine and also stored and loaded. Let's get ahead
909 * of the curve and support 2d. In XL, values are listed row-wise, do the
911 for( nRow
= 0; nRow
< nRows
; nRow
++ )
913 for( nCol
= 0; nCol
< nCols
; nCol
++ )
915 ScTokenArray
aCondTokArr(*pDocument
);
916 std::unique_ptr
<ScTypedStrData
> pEntry
;
918 ScMatrixValue nMatVal
= pValues
->Get( nCol
, nRow
);
920 // strings and empties
921 if( ScMatrix::IsNonValueType( nMatVal
.nType
) )
923 aValStr
= nMatVal
.GetString().getString();
925 // Do not add multiple empty strings to the validation list,
926 // especially not if they'd bloat the tail with a million empty
927 // entries for a column range, fdo#61520
928 if (aValStr
.isEmpty())
935 if( nullptr != pStrings
)
936 pEntry
.reset(new ScTypedStrData(aValStr
, 0.0, 0.0, ScTypedStrData::Standard
));
938 if (!rCell
.isEmpty() && rMatch
< 0)
939 aCondTokArr
.AddString(rSPool
.intern(aValStr
));
943 FormulaError nErr
= nMatVal
.GetError();
945 if( FormulaError::NONE
!= nErr
)
947 aValStr
= ScGlobal::GetErrorString( nErr
);
952 // Feature regression. Date formats are lost passing through the matrix
953 //pFormatter->GetInputLineString( pMatVal->fVal, 0, aValStr );
954 //For external reference and a formula that results in an area or array, date formats are still lost.
957 aValStr
= pDocument
->GetInputString(static_cast<SCCOL
>(nCol
+aRange
.aStart
.Col()),
958 static_cast<SCROW
>(nRow
+aRange
.aStart
.Row()), aRange
.aStart
.Tab());
962 aValStr
= pFormatter
->GetInputLineString( nMatVal
.fVal
, nDestFormat
);
966 if (!rCell
.isEmpty() && rMatch
< 0)
968 // I am not sure errors will work here, but a user can no
969 // manually enter an error yet so the point is somewhat moot.
970 aCondTokArr
.AddDouble( nMatVal
.fVal
);
972 if( nullptr != pStrings
)
973 pEntry
.reset(new ScTypedStrData(aValStr
, nMatVal
.fVal
, nMatVal
.fVal
, ScTypedStrData::Value
));
976 if (rMatch
< 0 && !rCell
.isEmpty() && IsEqualToTokenArray(rCell
, rPos
, aCondTokArr
))
979 // short circuit on the first match if not filling the list
980 if( nullptr == pStrings
)
987 pStrings
->push_back(*pEntry
);
993 // In case of no match needed and an error occurred, return that error
994 // entry as valid instead of silently failing.
995 return bOk
|| rCell
.isEmpty();
998 bool ScValidationData::FillSelectionList(std::vector
<ScTypedStrData
>& rStrColl
, const ScAddress
& rPos
) const
1002 if( HasSelectionList() )
1004 std::unique_ptr
<ScTokenArray
> pTokArr( CreateFlatCopiedTokenArray(0) );
1006 // *** try if formula is a string list ***
1008 sal_uInt32 nFormat
= lclGetCellFormat( *GetDocument(), rPos
);
1009 ScStringTokenIterator
aIt( *pTokArr
);
1010 for (rtl_uString
* pString
= aIt
.First(); pString
&& aIt
.Ok(); pString
= aIt
.Next())
1013 OUString
aStr(pString
);
1014 bool bIsValue
= GetDocument()->GetFormatTable()->IsNumberFormat(aStr
, nFormat
, fValue
);
1015 rStrColl
.emplace_back(
1016 aStr
, fValue
, fValue
, bIsValue
? ScTypedStrData::Value
: ScTypedStrData::Standard
);
1020 // *** if not a string list, try if formula results in a cell range or
1021 // anything else we recognize as valid ***
1026 ScRefCellValue aEmptyCell
;
1027 bOk
= GetSelectionFromFormula(&rStrColl
, aEmptyCell
, rPos
, *pTokArr
, nMatch
);
1034 bool ScValidationData::IsEqualToTokenArray( ScRefCellValue
& rCell
, const ScAddress
& rPos
, const ScTokenArray
& rTokArr
) const
1036 // create a condition entry that tests on equality and set the passed token array
1037 ScConditionEntry
aCondEntry( ScConditionMode::Equal
, &rTokArr
, nullptr, *GetDocument(), rPos
);
1038 aCondEntry
.SetCaseSensitive(IsCaseSensitive());
1040 return aCondEntry
.IsCellValid(rCell
, rPos
);
1043 bool ScValidationData::IsListValid( ScRefCellValue
& rCell
, const ScAddress
& rPos
) const
1045 bool bIsValid
= false;
1047 /* Compare input cell with all supported tokens from the formula.
1048 Currently a formula may contain:
1049 1) A list of strings (at least one string).
1050 2) A single cell or range reference.
1051 3) A single defined name (must contain a cell/range reference, another
1052 name, or DB range, or a formula resulting in a cell/range reference
1054 4) A single database range.
1055 5) A formula resulting in a cell/range reference or matrix/array.
1058 std::unique_ptr
< ScTokenArray
> pTokArr( CreateFlatCopiedTokenArray( 0 ) );
1060 // *** try if formula is a string list ***
1062 svl::SharedStringPool
& rSPool
= GetDocument()->GetSharedStringPool();
1063 sal_uInt32 nFormat
= lclGetCellFormat( *GetDocument(), rPos
);
1064 ScStringTokenIterator
aIt( *pTokArr
);
1065 for (rtl_uString
* pString
= aIt
.First(); pString
&& aIt
.Ok(); pString
= aIt
.Next())
1067 /* Do not break the loop, if a valid string has been found.
1068 This is to find invalid tokens following in the formula. */
1071 // create a formula containing a single string or number
1072 ScTokenArray
aCondTokArr(*GetDocument());
1074 OUString
aStr(pString
);
1075 if (GetDocument()->GetFormatTable()->IsNumberFormat(aStr
, nFormat
, fValue
))
1076 aCondTokArr
.AddDouble( fValue
);
1078 aCondTokArr
.AddString(rSPool
.intern(aStr
));
1080 bIsValid
= IsEqualToTokenArray(rCell
, rPos
, aCondTokArr
);
1087 // *** if not a string list, try if formula results in a cell range or
1088 // anything else we recognize as valid ***
1093 bIsValid
= GetSelectionFromFormula(nullptr, rCell
, rPos
, *pTokArr
, nMatch
);
1094 bIsValid
= bIsValid
&& nMatch
>= 0;
1100 ScValidationDataList::ScValidationDataList(const ScValidationDataList
& rList
)
1102 // for Ref-Undo - real copy with new tokens!
1104 for (const auto& rxItem
: rList
)
1106 InsertNew( std::unique_ptr
<ScValidationData
>(rxItem
->Clone()) );
1109 //TODO: faster insert for sorted entries from rList ???
1112 ScValidationDataList::ScValidationDataList(ScDocument
& rNewDoc
,
1113 const ScValidationDataList
& rList
)
1115 // for new documents - real copy with new tokens!
1117 for (const auto& rxItem
: rList
)
1119 InsertNew( std::unique_ptr
<ScValidationData
>(rxItem
->Clone(&rNewDoc
)) );
1122 //TODO: faster insert for sorted entries from rList ???
1125 ScValidationData
* ScValidationDataList::GetData( sal_uInt32 nKey
)
1127 //TODO: binary search
1129 for( iterator it
= begin(); it
!= end(); ++it
)
1130 if( (*it
)->GetKey() == nKey
)
1133 OSL_FAIL("ScValidationDataList: Entry not found");
1137 void ScValidationDataList::CompileXML()
1139 for( iterator it
= begin(); it
!= end(); ++it
)
1140 (*it
)->CompileXML();
1143 void ScValidationDataList::UpdateReference( sc::RefUpdateContext
& rCxt
)
1145 for( iterator it
= begin(); it
!= end(); ++it
)
1146 (*it
)->UpdateReference(rCxt
);
1149 void ScValidationDataList::UpdateInsertTab( sc::RefUpdateInsertTabContext
& rCxt
)
1151 for (iterator it
= begin(); it
!= end(); ++it
)
1152 (*it
)->UpdateInsertTab(rCxt
);
1155 void ScValidationDataList::UpdateDeleteTab( sc::RefUpdateDeleteTabContext
& rCxt
)
1157 for (iterator it
= begin(); it
!= end(); ++it
)
1158 (*it
)->UpdateDeleteTab(rCxt
);
1161 void ScValidationDataList::UpdateMoveTab( sc::RefUpdateMoveTabContext
& rCxt
)
1163 for (iterator it
= begin(); it
!= end(); ++it
)
1164 (*it
)->UpdateMoveTab(rCxt
);
1167 ScValidationDataList::iterator
ScValidationDataList::begin()
1169 return maData
.begin();
1172 ScValidationDataList::const_iterator
ScValidationDataList::begin() const
1174 return maData
.begin();
1177 ScValidationDataList::iterator
ScValidationDataList::end()
1179 return maData
.end();
1182 ScValidationDataList::const_iterator
ScValidationDataList::end() const
1184 return maData
.end();
1187 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */