Branch data Line data Source code
1 : : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : : /*************************************************************************
3 : : *
4 : : * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 : : *
6 : : * Copyright 2000, 2010 Oracle and/or its affiliates.
7 : : *
8 : : * OpenOffice.org - a multi-platform office productivity suite
9 : : *
10 : : * This file is part of OpenOffice.org.
11 : : *
12 : : * OpenOffice.org is free software: you can redistribute it and/or modify
13 : : * it under the terms of the GNU Lesser General Public License version 3
14 : : * only, as published by the Free Software Foundation.
15 : : *
16 : : * OpenOffice.org is distributed in the hope that it will be useful,
17 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 : : * GNU Lesser General Public License version 3 for more details
20 : : * (a copy is included in the LICENSE file that accompanied this code).
21 : : *
22 : : * You should have received a copy of the GNU Lesser General Public License
23 : : * version 3 along with OpenOffice.org. If not, see
24 : : * <http://www.openoffice.org/license.html>
25 : : * for a copy of the LGPLv3 License.
26 : : *
27 : : ************************************************************************/
28 : :
29 : : #include "scitems.hxx"
30 : : #include <sfx2/app.hxx>
31 : : #include <sfx2/docfile.hxx>
32 : : #include <sfx2/objsh.hxx>
33 : : #include <basic/sbmeth.hxx>
34 : : #include <basic/sbmod.hxx>
35 : : #include <basic/sbstar.hxx>
36 : : #include <basic/basmgr.hxx>
37 : :
38 : : #include <basic/sbx.hxx>
39 : : #include <svl/zforlist.hxx>
40 : : #include <vcl/msgbox.hxx>
41 : : #include <rtl/math.hxx>
42 : :
43 : : #include "validat.hxx"
44 : : #include "document.hxx"
45 : : #include "cell.hxx"
46 : : #include "patattr.hxx"
47 : : #include "rechead.hxx"
48 : : #include "globstr.hrc"
49 : : #include "rangenam.hxx"
50 : : #include "dbdata.hxx"
51 : : #include "typedstrdata.hxx"
52 : :
53 : : #include <math.h>
54 : : #include <memory>
55 : :
56 : : using namespace formula;
57 : :
58 : : //
59 : : // Eintrag fuer Gueltigkeit (es gibt nur eine Bedingung)
60 : : //
61 : :
62 : 111 : ScValidationData::ScValidationData( ScValidationMode eMode, ScConditionMode eOper,
63 : : const String& rExpr1, const String& rExpr2,
64 : : ScDocument* pDocument, const ScAddress& rPos,
65 : : const String& rExprNmsp1, const String& rExprNmsp2,
66 : : FormulaGrammar::Grammar eGrammar1, FormulaGrammar::Grammar eGrammar2 ) :
67 : : ScConditionEntry( eOper, rExpr1, rExpr2, pDocument, rPos, rExprNmsp1, rExprNmsp2, eGrammar1, eGrammar2 ),
68 : : nKey( 0 ),
69 : : eDataMode( eMode ),
70 : : eErrorStyle( SC_VALERR_STOP ),
71 [ + - ][ + - ]: 111 : mnListType( ValidListType::UNSORTED )
[ + - ][ + - ]
72 : : {
73 : 111 : bShowInput = bShowError = false;
74 : 111 : }
75 : :
76 : 3 : ScValidationData::ScValidationData( ScValidationMode eMode, ScConditionMode eOper,
77 : : const ScTokenArray* pArr1, const ScTokenArray* pArr2,
78 : : ScDocument* pDocument, const ScAddress& rPos ) :
79 : : ScConditionEntry( eOper, pArr1, pArr2, pDocument, rPos ),
80 : : nKey( 0 ),
81 : : eDataMode( eMode ),
82 : : eErrorStyle( SC_VALERR_STOP ),
83 [ + - ][ + - ]: 3 : mnListType( ValidListType::UNSORTED )
[ + - ][ + - ]
84 : : {
85 : 3 : bShowInput = bShowError = false;
86 : 3 : }
87 : :
88 : 3 : ScValidationData::ScValidationData( const ScValidationData& r ) :
89 : : ScConditionEntry( r ),
90 : : nKey( r.nKey ),
91 : : eDataMode( r.eDataMode ),
92 : : bShowInput( r.bShowInput ),
93 : : bShowError( r.bShowError ),
94 : : eErrorStyle( r.eErrorStyle ),
95 : : mnListType( r.mnListType ),
96 : : aInputTitle( r.aInputTitle ),
97 : : aInputMessage( r.aInputMessage ),
98 : : aErrorTitle( r.aErrorTitle ),
99 [ + - ][ + - ]: 3 : aErrorMessage( r.aErrorMessage )
[ + - ][ + - ]
100 : : {
101 : : // Formeln per RefCount kopiert
102 : 3 : }
103 : :
104 : 12 : ScValidationData::ScValidationData( ScDocument* pDocument, const ScValidationData& r ) :
105 : : ScConditionEntry( pDocument, r ),
106 : : nKey( r.nKey ),
107 : : eDataMode( r.eDataMode ),
108 : : bShowInput( r.bShowInput ),
109 : : bShowError( r.bShowError ),
110 : : eErrorStyle( r.eErrorStyle ),
111 : : mnListType( r.mnListType ),
112 : : aInputTitle( r.aInputTitle ),
113 : : aInputMessage( r.aInputMessage ),
114 : : aErrorTitle( r.aErrorTitle ),
115 [ + - ][ + - ]: 12 : aErrorMessage( r.aErrorMessage )
[ + - ][ + - ]
116 : : {
117 : : // Formeln wirklich kopiert
118 : 12 : }
119 : :
120 [ + - ][ + - ]: 129 : ScValidationData::~ScValidationData()
[ + - ][ + - ]
121 : : {
122 [ - + ]: 150 : }
123 : :
124 : 54 : sal_Bool ScValidationData::IsEmpty() const
125 : : {
126 [ + - ]: 54 : String aEmpty;
127 [ + - ]: 54 : ScValidationData aDefault( SC_VALID_ANY, SC_COND_EQUAL, aEmpty, aEmpty, GetDocument(), ScAddress() );
128 [ + - ][ + - ]: 54 : return EqualEntries( aDefault );
[ + - ]
129 : : }
130 : :
131 : 132 : sal_Bool ScValidationData::EqualEntries( const ScValidationData& r ) const
132 : : {
133 : : // gleiche Parameter eingestellt (ohne Key)
134 : :
135 : 132 : return ScConditionEntry::operator==(r) &&
136 : : eDataMode == r.eDataMode &&
137 : : bShowInput == r.bShowInput &&
138 : : bShowError == r.bShowError &&
139 : : eErrorStyle == r.eErrorStyle &&
140 : : mnListType == r.mnListType &&
141 : 48 : aInputTitle == r.aInputTitle &&
142 : 48 : aInputMessage == r.aInputMessage &&
143 : 48 : aErrorTitle == r.aErrorTitle &&
144 [ + - ][ + - ]: 276 : aErrorMessage == r.aErrorMessage;
[ + - ][ + - ]
[ + - + -
+ - + - ]
[ + - ][ + + ]
145 : : }
146 : :
147 : 57 : void ScValidationData::ResetInput()
148 : : {
149 : 57 : bShowInput = false;
150 : 57 : }
151 : :
152 : 9 : void ScValidationData::ResetError()
153 : : {
154 : 9 : bShowError = false;
155 : 9 : }
156 : :
157 : 51 : void ScValidationData::SetInput( const String& rTitle, const String& rMsg )
158 : : {
159 : 51 : bShowInput = sal_True;
160 : 51 : aInputTitle = rTitle;
161 : 51 : aInputMessage = rMsg;
162 : 51 : }
163 : :
164 : 60 : void ScValidationData::SetError( const String& rTitle, const String& rMsg,
165 : : ScValidErrorStyle eStyle )
166 : : {
167 : 60 : bShowError = sal_True;
168 : 60 : eErrorStyle = eStyle;
169 : 60 : aErrorTitle = rTitle;
170 : 60 : aErrorMessage = rMsg;
171 : 60 : }
172 : :
173 : 45 : sal_Bool ScValidationData::GetErrMsg( String& rTitle, String& rMsg,
174 : : ScValidErrorStyle& rStyle ) const
175 : : {
176 : 45 : rTitle = aErrorTitle;
177 : 45 : rMsg = aErrorMessage;
178 : 45 : rStyle = eErrorStyle;
179 : 45 : return bShowError;
180 : : }
181 : :
182 : 0 : sal_Bool ScValidationData::DoScript( const ScAddress& rPos, const String& rInput,
183 : : ScFormulaCell* pCell, Window* pParent ) const
184 : : {
185 : 0 : ScDocument* pDocument = GetDocument();
186 : 0 : SfxObjectShell* pDocSh = pDocument->GetDocumentShell();
187 [ # # ][ # # ]: 0 : if ( !pDocSh || !pDocument->CheckMacroWarn() )
[ # # ][ # # ]
188 : 0 : return false;
189 : :
190 : 0 : sal_Bool bScriptReturnedFalse = false; // Standard: kein Abbruch
191 : :
192 : : // Set up parameters
193 [ # # ]: 0 : ::com::sun::star::uno::Sequence< ::com::sun::star::uno::Any > aParams(2);
194 : :
195 : : // 1) eingegebener / berechneter Wert
196 [ # # ]: 0 : String aValStr = rInput;
197 : : double nValue;
198 : 0 : sal_Bool bIsValue = false;
199 [ # # ]: 0 : if ( pCell ) // wenn Zelle gesetzt, aus Interpret gerufen
200 : : {
201 [ # # ]: 0 : bIsValue = pCell->IsValue();
202 [ # # ]: 0 : if ( bIsValue )
203 [ # # ]: 0 : nValue = pCell->GetValue();
204 : : else
205 [ # # ][ # # ]: 0 : aValStr = pCell->GetString();
206 : : }
207 [ # # ]: 0 : if ( bIsValue )
208 [ # # ][ # # ]: 0 : aParams[0] = ::com::sun::star::uno::makeAny( nValue );
209 : : else
210 [ # # ][ # # ]: 0 : aParams[0] = ::com::sun::star::uno::makeAny( ::rtl::OUString( aValStr ) );
[ # # ]
211 : :
212 : : // 2) Position der Zelle
213 [ # # ]: 0 : String aPosStr;
214 [ # # ][ # # ]: 0 : rPos.Format( aPosStr, SCA_VALID | SCA_TAB_3D, pDocument, pDocument->GetAddressConvention() );
215 [ # # ][ # # ]: 0 : aParams[1] = ::com::sun::star::uno::makeAny( ::rtl::OUString( aPosStr ) );
[ # # ]
216 : :
217 : : // use link-update flag to prevent closing the document
218 : : // while the macro is running
219 [ # # ]: 0 : sal_Bool bWasInLinkUpdate = pDocument->IsInLinkUpdate();
220 [ # # ]: 0 : if ( !bWasInLinkUpdate )
221 [ # # ]: 0 : pDocument->SetInLinkUpdate( sal_True );
222 : :
223 [ # # ]: 0 : if ( pCell )
224 [ # # ]: 0 : pDocument->LockTable( rPos.Tab() );
225 : :
226 : 0 : ::com::sun::star::uno::Any aRet;
227 [ # # ]: 0 : ::com::sun::star::uno::Sequence< sal_Int16 > aOutArgsIndex;
228 [ # # ]: 0 : ::com::sun::star::uno::Sequence< ::com::sun::star::uno::Any > aOutArgs;
229 : :
230 : : ErrCode eRet = pDocSh->CallXScript(
231 [ # # ]: 0 : aErrorTitle, aParams, aRet, aOutArgsIndex, aOutArgs );
232 : :
233 [ # # ]: 0 : if ( pCell )
234 [ # # ]: 0 : pDocument->UnlockTable( rPos.Tab() );
235 : :
236 [ # # ]: 0 : if ( !bWasInLinkUpdate )
237 [ # # ]: 0 : pDocument->SetInLinkUpdate( false );
238 : :
239 : : // Check the return value from the script
240 : : // The contents of the cell get reset if the script returns false
241 : 0 : sal_Bool bTmp = false;
242 [ # # # # : 0 : if ( eRet == ERRCODE_NONE &&
# # ][ # # ]
[ # # ]
243 [ # # ]: 0 : aRet.getValueType() == getCppuBooleanType() &&
244 : 0 : sal_True == ( aRet >>= bTmp ) &&
245 : : bTmp == false )
246 : : {
247 : 0 : bScriptReturnedFalse = sal_True;
248 : : }
249 : :
250 [ # # ][ # # ]: 0 : if ( eRet == ERRCODE_BASIC_METHOD_NOT_FOUND && !pCell )
251 : : // Makro nicht gefunden (nur bei Eingabe)
252 : : {
253 : : //! andere Fehlermeldung, wenn gefunden, aber nicht bAllowed ??
254 : :
255 : : ErrorBox aBox( pParent, WinBits(WB_OK),
256 [ # # ][ # # ]: 0 : ScGlobal::GetRscString( STR_VALID_MACRONOTFOUND ) );
257 [ # # ][ # # ]: 0 : aBox.Execute();
258 : : }
259 : :
260 [ # # ][ # # ]: 0 : return bScriptReturnedFalse;
[ # # ][ # # ]
[ # # ]
261 : : }
262 : :
263 : : // sal_True -> Abbruch
264 : :
265 : 0 : sal_Bool ScValidationData::DoMacro( const ScAddress& rPos, const String& rInput,
266 : : ScFormulaCell* pCell, Window* pParent ) const
267 : : {
268 [ # # ]: 0 : if ( SfxApplication::IsXScriptURL( aErrorTitle ) )
269 : : {
270 : 0 : return DoScript( rPos, rInput, pCell, pParent );
271 : : }
272 : :
273 : 0 : ScDocument* pDocument = GetDocument();
274 : 0 : SfxObjectShell* pDocSh = pDocument->GetDocumentShell();
275 [ # # ][ # # ]: 0 : if ( !pDocSh || !pDocument->CheckMacroWarn() )
[ # # ]
276 : 0 : return false;
277 : :
278 : 0 : sal_Bool bDone = false;
279 : 0 : sal_Bool bRet = false; // Standard: kein Abbruch
280 : :
281 : : // Wenn das Dok waehrend eines Basic-Calls geladen wurde,
282 : : // ist das Sbx-Objekt evtl. nicht angelegt (?)
283 : : // pDocSh->GetSbxObject();
284 : :
285 : : #ifndef DISABLE_SCRIPTING
286 : : // keine Sicherheitsabfrage mehr vorneweg (nur CheckMacroWarn), das passiert im CallBasic
287 : :
288 : : // Funktion ueber den einfachen Namen suchen,
289 : : // dann aBasicStr, aMacroStr fuer SfxObjectShell::CallBasic zusammenbauen
290 : :
291 : 0 : StarBASIC* pRoot = pDocSh->GetBasic();
292 [ # # ]: 0 : SbxVariable* pVar = pRoot->Find( aErrorTitle, SbxCLASS_METHOD );
293 [ # # ][ # # ]: 0 : if ( pVar && pVar->ISA(SbMethod) )
[ # # ]
294 : : {
295 : 0 : SbMethod* pMethod = (SbMethod*)pVar;
296 : 0 : SbModule* pModule = pMethod->GetModule();
297 [ # # ]: 0 : SbxObject* pObject = pModule->GetParent();
298 [ # # ][ # # ]: 0 : String aMacroStr = pObject->GetName();
299 [ # # ]: 0 : aMacroStr += '.';
300 [ # # ][ # # ]: 0 : aMacroStr += pModule->GetName();
301 [ # # ]: 0 : aMacroStr += '.';
302 [ # # ][ # # ]: 0 : aMacroStr += pMethod->GetName();
303 [ # # ]: 0 : String aBasicStr;
304 : :
305 : : // the distinction between document- and app-basic has to be done
306 : : // by checking the parent (as in ScInterpreter::ScMacro), not by looping
307 : : // over all open documents, because this may be called from within loading,
308 : : // when SfxObjectShell::GetFirst/GetNext won't find the document.
309 : :
310 [ # # ][ # # ]: 0 : if ( pObject->GetParent() )
311 [ # # ][ # # ]: 0 : aBasicStr = pObject->GetParent()->GetName(); // Dokumentenbasic
[ # # ]
312 : : else
313 [ # # ][ # # ]: 0 : aBasicStr = SFX_APP()->GetName(); // Applikationsbasic
[ # # ]
314 : :
315 : : // Parameter fuer Makro
316 [ # # ][ # # ]: 0 : SbxArrayRef refPar = new SbxArray;
317 : :
318 : : // 1) eingegebener / berechneter Wert
319 [ # # ]: 0 : String aValStr = rInput;
320 : 0 : double nValue = 0.0;
321 : 0 : sal_Bool bIsValue = false;
322 [ # # ]: 0 : if ( pCell ) // wenn Zelle gesetzt, aus Interpret gerufen
323 : : {
324 [ # # ]: 0 : bIsValue = pCell->IsValue();
325 [ # # ]: 0 : if ( bIsValue )
326 [ # # ]: 0 : nValue = pCell->GetValue();
327 : : else
328 [ # # ][ # # ]: 0 : aValStr = pCell->GetString();
329 : : }
330 [ # # ]: 0 : if ( bIsValue )
331 [ # # ][ # # ]: 0 : refPar->Get(1)->PutDouble( nValue );
332 : : else
333 [ # # ][ # # ]: 0 : refPar->Get(1)->PutString( aValStr );
[ # # ]
334 : :
335 : : // 2) Position der Zelle
336 [ # # ]: 0 : String aPosStr;
337 [ # # ][ # # ]: 0 : rPos.Format( aPosStr, SCA_VALID | SCA_TAB_3D, pDocument, pDocument->GetAddressConvention() );
338 [ # # ][ # # ]: 0 : refPar->Get(2)->PutString( aPosStr );
[ # # ]
339 : :
340 : : // use link-update flag to prevent closing the document
341 : : // while the macro is running
342 [ # # ]: 0 : sal_Bool bWasInLinkUpdate = pDocument->IsInLinkUpdate();
343 [ # # ]: 0 : if ( !bWasInLinkUpdate )
344 [ # # ]: 0 : pDocument->SetInLinkUpdate( sal_True );
345 : :
346 [ # # ]: 0 : if ( pCell )
347 [ # # ]: 0 : pDocument->LockTable( rPos.Tab() );
348 [ # # ][ # # ]: 0 : SbxVariableRef refRes = new SbxVariable;
349 [ # # ]: 0 : ErrCode eRet = pDocSh->CallBasic( aMacroStr, aBasicStr, refPar, refRes );
350 [ # # ]: 0 : if ( pCell )
351 [ # # ]: 0 : pDocument->UnlockTable( rPos.Tab() );
352 : :
353 [ # # ]: 0 : if ( !bWasInLinkUpdate )
354 [ # # ]: 0 : pDocument->SetInLinkUpdate( false );
355 : :
356 : : // Eingabe abbrechen, wenn Basic-Makro sal_False zurueckgibt
357 [ # # ][ # # ]: 0 : if ( eRet == ERRCODE_NONE && refRes->GetType() == SbxBOOL && refRes->GetBool() == false )
[ # # ][ # # ]
[ # # ][ # # ]
358 : 0 : bRet = sal_True;
359 [ # # ][ # # ]: 0 : bDone = sal_True;
[ # # ][ # # ]
[ # # ][ # # ]
360 : : }
361 : : #endif
362 [ # # ][ # # ]: 0 : if ( !bDone && !pCell ) // Makro nicht gefunden (nur bei Eingabe)
363 : : {
364 : : //! andere Fehlermeldung, wenn gefunden, aber nicht bAllowed ??
365 : :
366 : : ErrorBox aBox( pParent, WinBits(WB_OK),
367 [ # # ][ # # ]: 0 : ScGlobal::GetRscString( STR_VALID_MACRONOTFOUND ) );
368 [ # # ][ # # ]: 0 : aBox.Execute();
369 : : }
370 : :
371 : 0 : return bRet;
372 : : }
373 : :
374 : 0 : void ScValidationData::DoCalcError( ScFormulaCell* pCell ) const
375 : : {
376 [ # # ]: 0 : if ( eErrorStyle == SC_VALERR_MACRO )
377 : 0 : DoMacro( pCell->aPos, EMPTY_STRING, pCell, NULL );
378 : 0 : }
379 : :
380 : : // sal_True -> Abbruch
381 : :
382 : 0 : sal_Bool ScValidationData::DoError( Window* pParent, const String& rInput,
383 : : const ScAddress& rPos ) const
384 : : {
385 [ # # ]: 0 : if ( eErrorStyle == SC_VALERR_MACRO )
386 [ # # ]: 0 : return DoMacro( rPos, rInput, NULL, pParent );
387 : :
388 : : // Fehlermeldung ausgeben
389 : :
390 [ # # ]: 0 : String aTitle = aErrorTitle;
391 [ # # ]: 0 : if (!aTitle.Len())
392 [ # # ][ # # ]: 0 : aTitle = ScGlobal::GetRscString( STR_MSSG_DOSUBTOTALS_0 ); // application title
393 [ # # ]: 0 : String aMessage = aErrorMessage;
394 [ # # ]: 0 : if (!aMessage.Len())
395 [ # # ][ # # ]: 0 : aMessage = ScGlobal::GetRscString( STR_VALID_DEFERROR );
396 : :
397 : : //! ErrorBox / WarningBox / InfoBox ?
398 : : //! (bei InfoBox immer nur OK-Button)
399 : :
400 : 0 : WinBits nStyle = 0;
401 [ # # # # ]: 0 : switch (eErrorStyle)
402 : : {
403 : : case SC_VALERR_STOP:
404 : 0 : nStyle = WB_OK | WB_DEF_OK;
405 : 0 : break;
406 : : case SC_VALERR_WARNING:
407 : 0 : nStyle = WB_OK_CANCEL | WB_DEF_CANCEL;
408 : 0 : break;
409 : : case SC_VALERR_INFO:
410 : 0 : nStyle = WB_OK_CANCEL | WB_DEF_OK;
411 : 0 : break;
412 : : default:
413 : : {
414 : : // added to avoid warnings
415 : : }
416 : : }
417 : :
418 [ # # ]: 0 : MessBox aBox( pParent, WinBits(nStyle), aTitle, aMessage );
419 [ # # ]: 0 : sal_uInt16 nRet = aBox.Execute();
420 : :
421 [ # # ][ # # ]: 0 : return ( eErrorStyle == SC_VALERR_STOP || nRet == RET_CANCEL );
[ # # ][ # # ]
[ # # ]
422 : : }
423 : :
424 : :
425 : 0 : sal_Bool ScValidationData::IsDataValid( const String& rTest, const ScPatternAttr& rPattern,
426 : : const ScAddress& rPos ) const
427 : : {
428 [ # # ]: 0 : if ( eDataMode == SC_VALID_ANY )
429 : 0 : return sal_True; // alles erlaubt
430 : :
431 [ # # ]: 0 : if ( rTest.GetChar(0) == '=' )
432 : 0 : return false; // Formeln sind sonst immer ungueltig
433 : :
434 [ # # ]: 0 : if ( !rTest.Len() )
435 : 0 : return IsIgnoreBlank(); // leer: wie eingestellt
436 : :
437 [ # # ]: 0 : SvNumberFormatter* pFormatter = GetDocument()->GetFormatTable();
438 : :
439 : : // Test, was es denn ist - wie in ScColumn::SetString
440 : :
441 [ # # ]: 0 : sal_uInt32 nFormat = rPattern.GetNumberFormat( pFormatter );
442 : :
443 : : double nVal;
444 [ # # ]: 0 : sal_Bool bIsVal = pFormatter->IsNumberFormat( rTest, nFormat, nVal );
445 : : ScBaseCell* pCell;
446 [ # # ]: 0 : if (bIsVal)
447 [ # # ][ # # ]: 0 : pCell = new ScValueCell( nVal );
448 : : else
449 [ # # ][ # # ]: 0 : pCell = new ScStringCell( rTest );
[ # # ]
450 : :
451 [ # # ]: 0 : sal_Bool bRet = IsDataValid( pCell, rPos );
452 : :
453 [ # # ]: 0 : pCell->Delete();
454 : 0 : return bRet;
455 : : }
456 : :
457 : 0 : sal_Bool ScValidationData::IsDataValid( ScBaseCell* pCell, const ScAddress& rPos ) const
458 : : {
459 [ # # ]: 0 : if( eDataMode == SC_VALID_LIST )
460 [ # # ]: 0 : return IsListValid( pCell, rPos );
461 : :
462 : 0 : double nVal = 0.0;
463 [ # # ]: 0 : String aString;
464 : 0 : sal_Bool bIsVal = sal_True;
465 : :
466 [ # # # # : 0 : switch (pCell->GetCellType())
# ]
467 : : {
468 : : case CELLTYPE_VALUE:
469 : 0 : nVal = ((ScValueCell*)pCell)->GetValue();
470 : 0 : break;
471 : : case CELLTYPE_STRING:
472 [ # # ]: 0 : aString = ((ScStringCell*)pCell)->GetString();
473 : 0 : bIsVal = false;
474 : 0 : break;
475 : : case CELLTYPE_EDIT:
476 [ # # ][ # # ]: 0 : aString = ((ScEditCell*)pCell)->GetString();
477 : 0 : bIsVal = false;
478 : 0 : break;
479 : : case CELLTYPE_FORMULA:
480 : : {
481 [ # # ]: 0 : ScFormulaCell* pFCell = (ScFormulaCell*)pCell;
482 [ # # ]: 0 : bIsVal = pFCell->IsValue();
483 [ # # ]: 0 : if ( bIsVal )
484 [ # # ]: 0 : nVal = pFCell->GetValue();
485 : : else
486 [ # # ][ # # ]: 0 : aString = pFCell->GetString();
487 : : }
488 : 0 : break;
489 : : default: // Notizen, Broadcaster
490 : 0 : return IsIgnoreBlank(); // wie eingestellt
491 : : }
492 : :
493 : 0 : sal_Bool bOk = sal_True;
494 [ # # # # ]: 0 : switch (eDataMode)
495 : : {
496 : : // SC_VALID_ANY schon oben
497 : :
498 : : case SC_VALID_WHOLE:
499 : : case SC_VALID_DECIMAL:
500 : : case SC_VALID_DATE: // Date/Time ist nur Formatierung
501 : : case SC_VALID_TIME:
502 : 0 : bOk = bIsVal;
503 [ # # ][ # # ]: 0 : if ( bOk && eDataMode == SC_VALID_WHOLE )
504 : 0 : bOk = ::rtl::math::approxEqual( nVal, floor(nVal+0.5) ); // ganze Zahlen
505 [ # # ]: 0 : if ( bOk )
506 [ # # ]: 0 : bOk = IsCellValid( pCell, rPos );
507 : 0 : break;
508 : :
509 : : case SC_VALID_CUSTOM:
510 : : // fuer Custom muss eOp == SC_COND_DIRECT sein
511 : : //! der Wert muss im Dokument stehen !!!!!!!!!!!!!!!!!!!!
512 [ # # ]: 0 : bOk = IsCellValid( pCell, rPos );
513 : 0 : break;
514 : :
515 : : case SC_VALID_TEXTLEN:
516 : 0 : bOk = !bIsVal; // nur Text
517 [ # # ]: 0 : if ( bOk )
518 : : {
519 : 0 : double nLenVal = (double) aString.Len();
520 [ # # ][ # # ]: 0 : ScValueCell* pTmpCell = new ScValueCell( nLenVal );
521 [ # # ]: 0 : bOk = IsCellValid( pTmpCell, rPos );
522 [ # # ]: 0 : pTmpCell->Delete();
523 : : }
524 : 0 : break;
525 : :
526 : : default:
527 : : OSL_FAIL("hammanochnich");
528 : 0 : break;
529 : : }
530 : :
531 [ # # ]: 0 : return bOk;
532 : : }
533 : :
534 : : // ----------------------------------------------------------------------------
535 : :
536 : : namespace {
537 : :
538 : : /** Token array helper. Iterates over all string tokens.
539 : : @descr The token array must contain separated string tokens only.
540 : : @param bSkipEmpty true = Ignores string tokens with empty strings. */
541 : : class ScStringTokenIterator
542 : : {
543 : : public:
544 : 0 : inline explicit ScStringTokenIterator( ScTokenArray& rTokArr, bool bSkipEmpty = true ) :
545 : 0 : mrTokArr( rTokArr ), mbSkipEmpty( bSkipEmpty ), mbOk( true ) {}
546 : :
547 : : /** Returns the string of the first string token or NULL on error or empty token array. */
548 : : const String* First();
549 : : /** Returns the string of the next string token or NULL on error or end of token array. */
550 : : const String* Next();
551 : :
552 : : /** Returns false, if a wrong token has been found. Does NOT return false on end of token array. */
553 : 0 : inline bool Ok() const { return mbOk; }
554 : :
555 : : private:
556 : : ScTokenArray& mrTokArr; /// The token array for iteration.
557 : : bool mbSkipEmpty; /// Ignore empty strings.
558 : : bool mbOk; /// true = correct token or end of token array.
559 : : };
560 : :
561 : 0 : const String* ScStringTokenIterator::First()
562 : : {
563 : 0 : mrTokArr.Reset();
564 : 0 : mbOk = true;
565 : 0 : return Next();
566 : : }
567 : :
568 : 0 : const String* ScStringTokenIterator::Next()
569 : : {
570 [ # # ]: 0 : if( !mbOk )
571 : 0 : return NULL;
572 : :
573 : : // seek to next non-separator token
574 : 0 : const FormulaToken* pToken = mrTokArr.NextNoSpaces();
575 [ # # ][ # # ]: 0 : while( pToken && (pToken->GetOpCode() == ocSep) )
[ # # ]
576 : 0 : pToken = mrTokArr.NextNoSpaces();
577 : :
578 [ # # ][ # # ]: 0 : mbOk = !pToken || (pToken->GetType() == formula::svString);
579 [ # # ][ # # ]: 0 : const String* pString = (mbOk && pToken) ? &pToken->GetString() : NULL;
580 : : // string found but empty -> get next token; otherwise return it
581 [ # # ][ # # ]: 0 : return (mbSkipEmpty && pString && !pString->Len()) ? Next() : pString;
[ # # ]
582 : : }
583 : :
584 : : // ----------------------------------------------------------------------------
585 : :
586 : : /** Returns the number format of the passed cell, or the standard format. */
587 : 0 : sal_uLong lclGetCellFormat( ScDocument& rDoc, const ScAddress& rPos )
588 : : {
589 : 0 : const ScPatternAttr* pPattern = rDoc.GetPattern( rPos.Col(), rPos.Row(), rPos.Tab() );
590 [ # # ]: 0 : if( !pPattern )
591 : 0 : pPattern = rDoc.GetDefPattern();
592 : 0 : return pPattern->GetNumberFormat( rDoc.GetFormatTable() );
593 : : }
594 : :
595 : : } // namespace
596 : :
597 : : // ----------------------------------------------------------------------------
598 : :
599 : 0 : bool ScValidationData::HasSelectionList() const
600 : : {
601 [ # # ][ # # ]: 0 : return (eDataMode == SC_VALID_LIST) && (mnListType != ValidListType::INVISIBLE);
602 : : }
603 : :
604 : 0 : bool ScValidationData::GetSelectionFromFormula(
605 : : std::vector<ScTypedStrData>* pStrings, ScBaseCell* pCell, const ScAddress& rPos,
606 : : const ScTokenArray& rTokArr, int& rMatch) const
607 : : {
608 : 0 : bool bOk = true;
609 : :
610 : : // pDoc is private in condition, use an accessor and a long winded name.
611 : 0 : ScDocument* pDocument = GetDocument();
612 [ # # ]: 0 : if( NULL == pDocument )
613 : 0 : return false;
614 : :
615 : : ScFormulaCell aValidationSrc( pDocument, rPos, &rTokArr,
616 [ # # ]: 0 : formula::FormulaGrammar::GRAM_DEFAULT, MM_FORMULA);
617 : :
618 : : // Make sure the formula gets interpreted and a result is delivered,
619 : : // regardless of the AutoCalc setting.
620 [ # # ]: 0 : aValidationSrc.Interpret();
621 : :
622 : 0 : ScMatrixRef xMatRef;
623 [ # # ]: 0 : const ScMatrix *pValues = aValidationSrc.GetMatrix();
624 [ # # ]: 0 : if (!pValues)
625 : : {
626 : : // The somewhat nasty case of either an error occurred, or the
627 : : // dereferenced value of a single cell reference or an immediate result
628 : : // is stored as a single value.
629 : :
630 : : // Use an interim matrix to create the TypedStrData below.
631 [ # # ][ # # ]: 0 : xMatRef = new ScMatrix(1, 1, 0.0);
[ # # ]
632 : :
633 [ # # ]: 0 : sal_uInt16 nErrCode = aValidationSrc.GetErrCode();
634 [ # # ]: 0 : if (nErrCode)
635 : : {
636 : : /* TODO : to use later in an alert box?
637 : : * String rStrResult = "...";
638 : : * rStrResult += ScGlobal::GetLongErrorString(nErrCode);
639 : : */
640 : :
641 [ # # ]: 0 : xMatRef->PutError( nErrCode, 0, 0);
642 : 0 : bOk = false;
643 : : }
644 [ # # ][ # # ]: 0 : else if (aValidationSrc.HasValueData())
645 [ # # ][ # # ]: 0 : xMatRef->PutDouble( aValidationSrc.GetValue(), 0);
646 : : else
647 : : {
648 [ # # ][ # # ]: 0 : String aStr = aValidationSrc.GetString();
649 [ # # ][ # # ]: 0 : xMatRef->PutString( aStr, 0);
[ # # ]
650 : : }
651 : :
652 : 0 : pValues = xMatRef.get();
653 : : }
654 : :
655 : : // which index matched. We will want it eventually to pre-select that item.
656 : 0 : rMatch = -1;
657 : :
658 [ # # ]: 0 : SvNumberFormatter* pFormatter = GetDocument()->GetFormatTable();
659 : :
660 : 0 : SCSIZE nCol, nRow, nCols, nRows, n = 0;
661 [ # # ]: 0 : pValues->GetDimensions( nCols, nRows );
662 : :
663 : 0 : bool bRef = false;
664 : 0 : ScRange aRange;
665 : :
666 : 0 : ScTokenArray* pArr = (ScTokenArray*) &rTokArr;
667 : 0 : pArr->Reset();
668 : 0 : ScToken* t = NULL;
669 [ # # ][ # # ]: 0 : if (pArr->GetLen() == 1 && (t = static_cast<ScToken*>(pArr->GetNextReferenceOrName())) != NULL)
[ # # ][ # # ]
670 : : {
671 [ # # ]: 0 : if (t->GetOpCode() == ocDBArea)
672 : : {
673 [ # # ][ # # ]: 0 : if (const ScDBData* pDBData = pDocument->GetDBCollection()->getNamedDBs().findByIndex(t->GetIndex()))
[ # # ][ # # ]
[ # # ]
674 : : {
675 [ # # ]: 0 : pDBData->GetArea(aRange);
676 : 0 : bRef = true;
677 : : }
678 : : }
679 [ # # ]: 0 : else if (t->GetOpCode() == ocName)
680 : : {
681 [ # # ][ # # ]: 0 : ScRangeData* pName = pDocument->GetRangeName()->findByIndex( t->GetIndex() );
[ # # ]
682 [ # # ][ # # ]: 0 : if (pName && pName->IsReference(aRange))
[ # # ][ # # ]
683 : : {
684 : 0 : bRef = true;
685 : : }
686 : : }
687 [ # # ]: 0 : else if (t->GetType() != svIndex)
688 : : {
689 [ # # ]: 0 : t->CalcAbsIfRel(rPos);
690 [ # # ][ # # ]: 0 : if (pArr->IsValidReference(aRange))
691 : : {
692 : 0 : bRef = true;
693 : : }
694 : : }
695 : : }
696 : :
697 : : /* XL artificially limits things to a single col or row in the UI but does
698 : : * not list the constraint in MOOXml. If a defined name or INDIRECT
699 : : * resulting in 1D is entered in the UI and the definition later modified
700 : : * to 2D, it is evaluated fine and also stored and loaded. Lets get ahead
701 : : * of the curve and support 2d. In XL, values are listed row-wise, do the
702 : : * same. */
703 [ # # ]: 0 : for( nRow = 0; nRow < nRows ; nRow++ )
704 : : {
705 [ # # ]: 0 : for( nCol = 0; nCol < nCols ; nCol++ )
706 : : {
707 [ # # ]: 0 : ScTokenArray aCondTokArr;
708 : 0 : ScTypedStrData* pEntry = NULL;
709 [ # # ]: 0 : String aValStr;
710 [ # # ]: 0 : ScMatrixValue nMatVal = pValues->Get( nCol, nRow);
711 : :
712 : : // strings and empties
713 [ # # ]: 0 : if( ScMatrix::IsNonValueType( nMatVal.nType ) )
714 : : {
715 [ # # ]: 0 : aValStr = nMatVal.GetString();
716 : :
717 [ # # ]: 0 : if( NULL != pStrings )
718 [ # # ][ # # ]: 0 : pEntry = new ScTypedStrData( aValStr, 0.0, ScTypedStrData::Standard);
[ # # ]
719 : :
720 [ # # ][ # # ]: 0 : if( pCell && rMatch < 0 )
721 [ # # ]: 0 : aCondTokArr.AddString( aValStr );
722 : : }
723 : : else
724 : : {
725 : 0 : sal_uInt16 nErr = nMatVal.GetError();
726 : :
727 [ # # ]: 0 : if( 0 != nErr )
728 : : {
729 [ # # ][ # # ]: 0 : aValStr = ScGlobal::GetErrorString( nErr );
[ # # ]
730 : : }
731 : : else
732 : : {
733 : : // FIXME FIXME FIXME
734 : : // Feature regression. Date formats are lost passing through the matrix
735 : : //pFormatter->GetInputLineString( pMatVal->fVal, 0, aValStr );
736 : : //For external reference and a formula that results in an area or array, date formats are still lost.
737 [ # # ]: 0 : if ( bRef )
738 : : {
739 : 0 : pDocument->GetInputString((SCCOL)(nCol+aRange.aStart.Col()),
740 [ # # ]: 0 : (SCROW)(nRow+aRange.aStart.Row()), aRange.aStart.Tab() , aValStr);
741 : : }
742 : : else
743 [ # # ]: 0 : pFormatter->GetInputLineString( nMatVal.fVal, 0, aValStr );
744 : : }
745 : :
746 [ # # ][ # # ]: 0 : if( pCell && rMatch < 0 )
747 : : {
748 : : // I am not sure errors will work here, but a user can no
749 : : // manually enter an error yet so the point is somewhat moot.
750 [ # # ]: 0 : aCondTokArr.AddDouble( nMatVal.fVal );
751 : : }
752 [ # # ]: 0 : if( NULL != pStrings )
753 [ # # ][ # # ]: 0 : pEntry = new ScTypedStrData( aValStr, nMatVal.fVal, ScTypedStrData::Value);
[ # # ]
754 : : }
755 : :
756 [ # # ][ # # ]: 0 : if( rMatch < 0 && NULL != pCell && IsEqualToTokenArray( pCell, rPos, aCondTokArr ) )
[ # # ][ # # ]
[ # # ]
757 : : {
758 : 0 : rMatch = n;
759 : : // short circuit on the first match if not filling the list
760 [ # # ]: 0 : if( NULL == pStrings )
761 : 0 : return true;
762 : : }
763 : :
764 [ # # ]: 0 : if( NULL != pEntry )
765 : : {
766 [ # # ]: 0 : pStrings->push_back(*pEntry);
767 [ # # ]: 0 : delete pEntry;
768 : 0 : n++;
769 : : }
770 [ # # ][ # # ]: 0 : }
[ # # ][ # # ]
[ # # ]
771 : : }
772 : :
773 : : // In case of no match needed and an error occurred, return that error
774 : : // entry as valid instead of silently failing.
775 [ # # ][ # # ]: 0 : return bOk || NULL == pCell;
[ # # ][ # # ]
776 : : }
777 : :
778 : 0 : bool ScValidationData::FillSelectionList(std::vector<ScTypedStrData>& rStrColl, const ScAddress& rPos) const
779 : : {
780 : 0 : bool bOk = false;
781 : :
782 [ # # ]: 0 : if( HasSelectionList() )
783 : : {
784 [ # # ]: 0 : boost::scoped_ptr<ScTokenArray> pTokArr( CreateTokenArry(0) );
785 : :
786 : : // *** try if formula is a string list ***
787 : :
788 [ # # ]: 0 : sal_uInt32 nFormat = lclGetCellFormat( *GetDocument(), rPos );
789 : 0 : ScStringTokenIterator aIt( *pTokArr );
790 [ # # ][ # # ]: 0 : for( const String* pString = aIt.First(); pString && aIt.Ok(); pString = aIt.Next() )
[ # # ][ # # ]
[ # # ]
791 : : {
792 : : double fValue;
793 [ # # ][ # # ]: 0 : bool bIsValue = GetDocument()->GetFormatTable()->IsNumberFormat( *pString, nFormat, fValue );
794 : : rStrColl.push_back(
795 : : ScTypedStrData(
796 [ # # ][ # # ]: 0 : *pString, fValue, bIsValue ? ScTypedStrData::Value : ScTypedStrData::Standard));
[ # # ][ # # ]
797 : : }
798 : 0 : bOk = aIt.Ok();
799 : :
800 : : // *** if not a string list, try if formula results in a cell range or
801 : : // anything else we recognize as valid ***
802 : :
803 [ # # ]: 0 : if (!bOk)
804 : : {
805 : : int nMatch;
806 [ # # ]: 0 : bOk = GetSelectionFromFormula( &rStrColl, NULL, rPos, *pTokArr, nMatch );
807 [ # # ]: 0 : }
808 : : }
809 : :
810 : 0 : return bOk;
811 : : }
812 : :
813 : : // ----------------------------------------------------------------------------
814 : :
815 : 0 : bool ScValidationData::IsEqualToTokenArray( ScBaseCell* pCell, const ScAddress& rPos, const ScTokenArray& rTokArr ) const
816 : : {
817 : : // create a condition entry that tests on equality and set the passed token array
818 [ # # ]: 0 : ScConditionEntry aCondEntry( SC_COND_EQUAL, &rTokArr, NULL, GetDocument(), rPos );
819 [ # # ][ # # ]: 0 : return aCondEntry.IsCellValid( pCell, rPos );
820 : : }
821 : :
822 : 0 : bool ScValidationData::IsListValid( ScBaseCell* pCell, const ScAddress& rPos ) const
823 : : {
824 : 0 : bool bIsValid = false;
825 : :
826 : : /* Compare input cell with all supported tokens from the formula.
827 : : Currently a formula may contain:
828 : : 1) A list of strings (at least one string).
829 : : 2) A single cell or range reference.
830 : : 3) A single defined name (must contain a cell/range reference, another
831 : : name, or DB range, or a formula resulting in a cell/range reference
832 : : or matrix/array).
833 : : 4) A single database range.
834 : : 5) A formula resulting in a cell/range reference or matrix/array.
835 : : */
836 : :
837 : : SAL_WNODEPRECATED_DECLARATIONS_PUSH
838 [ # # ]: 0 : ::std::auto_ptr< ScTokenArray > pTokArr( CreateTokenArry( 0 ) );
839 : : SAL_WNODEPRECATED_DECLARATIONS_POP
840 : :
841 : : // *** try if formula is a string list ***
842 : :
843 [ # # ]: 0 : sal_uInt32 nFormat = lclGetCellFormat( *GetDocument(), rPos );
844 : 0 : ScStringTokenIterator aIt( *pTokArr );
845 [ # # ][ # # ]: 0 : for( const String* pString = aIt.First(); pString && aIt.Ok(); pString = aIt.Next() )
[ # # ][ # # ]
[ # # ]
846 : : {
847 : : /* Do not break the loop, if a valid string has been found.
848 : : This is to find invalid tokens following in the formula. */
849 [ # # ]: 0 : if( !bIsValid )
850 : : {
851 : : // create a formula containing a single string or number
852 [ # # ]: 0 : ScTokenArray aCondTokArr;
853 : : double fValue;
854 [ # # ][ # # ]: 0 : if( GetDocument()->GetFormatTable()->IsNumberFormat( *pString, nFormat, fValue ) )
[ # # ]
855 [ # # ]: 0 : aCondTokArr.AddDouble( fValue );
856 : : else
857 [ # # ]: 0 : aCondTokArr.AddString( *pString );
858 : :
859 [ # # ][ # # ]: 0 : bIsValid = IsEqualToTokenArray( pCell, rPos, aCondTokArr );
860 : : }
861 : : }
862 : :
863 [ # # ]: 0 : if( !aIt.Ok() )
864 : 0 : bIsValid = false;
865 : :
866 : : // *** if not a string list, try if formula results in a cell range or
867 : : // anything else we recognize as valid ***
868 : :
869 [ # # ]: 0 : if (!bIsValid)
870 : : {
871 : : int nMatch;
872 [ # # ]: 0 : bIsValid = GetSelectionFromFormula( NULL, pCell, rPos, *pTokArr, nMatch );
873 [ # # ][ # # ]: 0 : bIsValid = bIsValid && nMatch >= 0;
874 : : }
875 : :
876 [ # # ]: 0 : return bIsValid;
877 : : }
878 : :
879 : : // ============================================================================
880 : : // ============================================================================
881 : :
882 : 0 : ScValidationDataList::ScValidationDataList(const ScValidationDataList& rList) :
883 : 0 : std::set<ScValidationData*, CompareScValidationDataPtr>()
884 : : {
885 : : // fuer Ref-Undo - echte Kopie mit neuen Tokens!
886 : :
887 [ # # ][ # # ]: 0 : for (const_iterator it = rList.begin(); it != rList.end(); ++it)
[ # # ]
888 : : {
889 [ # # ][ # # ]: 0 : InsertNew( (*it)->Clone() );
[ # # ]
890 : : }
891 : :
892 : : //! sortierte Eintraege aus rList schneller einfuegen ???
893 : 0 : }
894 : :
895 : 0 : ScValidationDataList::ScValidationDataList(ScDocument* pNewDoc,
896 : 0 : const ScValidationDataList& rList)
897 : : {
898 : : // fuer neues Dokument - echte Kopie mit neuen Tokens!
899 : :
900 [ # # ][ # # ]: 0 : for (const_iterator it = rList.begin(); it != rList.end(); ++it)
[ # # ]
901 : : {
902 [ # # ][ # # ]: 0 : InsertNew( (*it)->Clone(pNewDoc) );
[ # # ]
903 : : }
904 : :
905 : : //! sortierte Eintraege aus rList schneller einfuegen ???
906 : 0 : }
907 : :
908 : 147 : ScValidationData* ScValidationDataList::GetData( sal_uInt32 nKey )
909 : : {
910 : : //! binaer suchen
911 : :
912 [ + - ][ + - ]: 228 : for( iterator it = begin(); it != end(); ++it )
[ + - ]
913 [ + - ][ + + ]: 228 : if( (*it)->GetKey() == nKey )
914 [ + - ]: 147 : return *it;
915 : :
916 : : OSL_FAIL("ScValidationDataList: Eintrag nicht gefunden");
917 : 147 : return NULL;
918 : : }
919 : :
920 : 3 : void ScValidationDataList::CompileXML()
921 : : {
922 [ + - ][ + - ]: 9 : for( iterator it = begin(); it != end(); ++it )
[ + + ]
923 [ + - ][ + - ]: 6 : (*it)->CompileXML();
924 : 3 : }
925 : :
926 : 0 : void ScValidationDataList::UpdateReference( UpdateRefMode eUpdateRefMode,
927 : : const ScRange& rRange, SCsCOL nDx, SCsROW nDy, SCsTAB nDz )
928 : : {
929 [ # # ][ # # ]: 0 : for( iterator it = begin(); it != end(); ++it )
[ # # ]
930 [ # # ][ # # ]: 0 : (*it)->UpdateReference( eUpdateRefMode, rRange, nDx, nDy, nDz);
931 : 0 : }
932 : :
933 : 0 : void ScValidationDataList::UpdateMoveTab( SCTAB nOldPos, SCTAB nNewPos )
934 : : {
935 [ # # ][ # # ]: 0 : for( iterator it = begin(); it != end(); ++it )
[ # # ]
936 [ # # ][ # # ]: 0 : (*it)->UpdateMoveTab( nOldPos, nNewPos );
937 : 0 : }
938 : :
939 : 0 : sal_Bool ScValidationDataList::operator==( const ScValidationDataList& r ) const
940 : : {
941 : : // fuer Ref-Undo - interne Variablen werden nicht verglichen
942 : :
943 : 0 : sal_uInt16 nCount = size();
944 : 0 : sal_Bool bEqual = ( nCount == r.size() );
945 [ # # ][ # # ]: 0 : for( const_iterator it1 = begin(), it2 = r.begin(); it1 != end() && bEqual; ++it1, ++it2 ) // Eintraege sind sortiert
[ # # ][ # # ]
[ # # ][ # # ]
[ # # # # ]
946 [ # # ][ # # ]: 0 : if ( !(*it1)->EqualEntries(**it2) ) // Eintraege unterschiedlich ?
[ # # ][ # # ]
947 : 0 : bEqual = sal_False;
948 : :
949 : 0 : return bEqual;
950 : : }
951 : :
952 : : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|