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 <memory>
21 #include <tokenuno.hxx>
22
23 #include <sal/macros.h>
24 #include <sal/log.hxx>
25
26 #include <com/sun/star/sheet/ComplexReference.hpp>
27 #include <com/sun/star/sheet/ExternalReference.hpp>
28 #include <com/sun/star/sheet/ReferenceFlags.hpp>
29 #include <com/sun/star/sheet/AddressConvention.hpp>
30 #include <com/sun/star/sheet/NameToken.hpp>
31 #include <com/sun/star/table/CellAddress.hpp>
32
33 #include <svl/itemprop.hxx>
34 #include <vcl/svapp.hxx>
35
36 #include <miscuno.hxx>
37 #include <convuno.hxx>
38 #include <unonames.hxx>
39 #include <compiler.hxx>
40 #include <tokenarray.hxx>
41 #include <docsh.hxx>
42 #include <rangeseq.hxx>
43 #include <externalrefmgr.hxx>
44
45 using namespace ::formula;
46 using namespace ::com::sun::star;
47
lcl_GetFormulaParserMap()48 static const SfxItemPropertyMapEntry* lcl_GetFormulaParserMap()
49 {
50 static const SfxItemPropertyMapEntry aFormulaParserMap_Impl[] =
51 {
52 {u"" SC_UNO_COMPILEFAP, 0, cppu::UnoType<bool>::get(), 0, 0 },
53 {u"" SC_UNO_COMPILEENGLISH, 0, cppu::UnoType<bool>::get(), 0, 0 },
54 {u"" SC_UNO_IGNORELEADING, 0, cppu::UnoType<bool>::get(), 0, 0 },
55 {u"" SC_UNO_FORMULACONVENTION, 0, cppu::UnoType<decltype(sheet::AddressConvention::UNSPECIFIED)>::get(), 0, 0 },
56 {u"" SC_UNO_OPCODEMAP, 0, cppu::UnoType<uno::Sequence< sheet::FormulaOpCodeMapEntry >>::get(), 0, 0 },
57 { u"", 0, css::uno::Type(), 0, 0 }
58 };
59 return aFormulaParserMap_Impl;
60 }
61
62 const formula::FormulaGrammar::AddressConvention aConvMap[] = {
63 formula::FormulaGrammar::CONV_OOO, // <- AddressConvention::OOO
64 formula::FormulaGrammar::CONV_XL_A1, // <- AddressConvention::XL_A1
65 formula::FormulaGrammar::CONV_XL_R1C1, // <- AddressConvention::XL_R1C1
66 formula::FormulaGrammar::CONV_XL_OOX, // <- AddressConvention::XL_OOX
67 formula::FormulaGrammar::CONV_LOTUS_A1 // <- AddressConvention::LOTUS_A1
68 };
69 // sal_Int16 because of comparison of integer expressions below.
70 constexpr sal_Int16 nConvMapCount = SAL_N_ELEMENTS(aConvMap);
71
72
73 SC_SIMPLE_SERVICE_INFO( ScFormulaParserObj, "ScFormulaParserObj", SC_SERVICENAME_FORMULAPARS )
74
ScFormulaParserObj(ScDocShell * pDocSh)75 ScFormulaParserObj::ScFormulaParserObj(ScDocShell* pDocSh) :
76 mpDocShell( pDocSh ),
77 mnConv( sheet::AddressConvention::UNSPECIFIED ),
78 mbEnglish( false ),
79 mbIgnoreSpaces( true ),
80 mbCompileFAP( false )
81 {
82 mpDocShell->GetDocument().AddUnoObject(*this);
83 }
84
~ScFormulaParserObj()85 ScFormulaParserObj::~ScFormulaParserObj()
86 {
87 SolarMutexGuard g;
88
89 if (mpDocShell)
90 mpDocShell->GetDocument().RemoveUnoObject(*this);
91 }
92
Notify(SfxBroadcaster &,const SfxHint & rHint)93 void ScFormulaParserObj::Notify( SfxBroadcaster&, const SfxHint& rHint )
94 {
95 if ( rHint.GetId() == SfxHintId::Dying )
96 mpDocShell = nullptr;
97 }
98
99 // XFormulaParser
100
SetCompilerFlags(ScCompiler & rCompiler) const101 void ScFormulaParserObj::SetCompilerFlags( ScCompiler& rCompiler ) const
102 {
103 formula::FormulaGrammar::AddressConvention eConv = formula::FormulaGrammar::CONV_UNSPECIFIED;
104 if (mnConv >= 0 && mnConv < nConvMapCount)
105 eConv = aConvMap[mnConv];
106
107 // If mxOpCodeMap is not empty it overrides mbEnglish, and vice versa. We
108 // don't need to initialize things twice.
109 if (mxOpCodeMap)
110 rCompiler.SetFormulaLanguage( mxOpCodeMap );
111 else
112 {
113 const sal_Int32 nFormulaLanguage = (eConv == formula::FormulaGrammar::CONV_XL_OOX ?
114 sheet::FormulaLanguage::OOXML :
115 (mbEnglish ? sheet::FormulaLanguage::ENGLISH : sheet::FormulaLanguage::NATIVE));
116 ScCompiler::OpCodeMapPtr xMap = rCompiler.GetOpCodeMap( nFormulaLanguage);
117 rCompiler.SetFormulaLanguage( xMap);
118 }
119
120 rCompiler.SetRefConvention( eConv );
121 rCompiler.EnableJumpCommandReorder(!mbCompileFAP);
122 rCompiler.EnableStopOnError(!mbCompileFAP);
123
124 rCompiler.SetExternalLinks( maExternalLinks);
125 }
126
parseFormula(const OUString & aFormula,const table::CellAddress & rReferencePos)127 uno::Sequence<sheet::FormulaToken> SAL_CALL ScFormulaParserObj::parseFormula(
128 const OUString& aFormula, const table::CellAddress& rReferencePos )
129 {
130 SolarMutexGuard aGuard;
131 uno::Sequence<sheet::FormulaToken> aRet;
132
133 if (mpDocShell)
134 {
135 ScDocument& rDoc = mpDocShell->GetDocument();
136 ScExternalRefManager::ApiGuard aExtRefGuard(rDoc);
137
138 ScAddress aRefPos( ScAddress::UNINITIALIZED );
139 ScUnoConversion::FillScAddress( aRefPos, rReferencePos );
140 ScCompiler aCompiler( rDoc, aRefPos, rDoc.GetGrammar());
141 SetCompilerFlags( aCompiler );
142
143 std::unique_ptr<ScTokenArray> pCode = aCompiler.CompileString( aFormula );
144 ScTokenConversion::ConvertToTokenSequence( rDoc, aRet, *pCode );
145 }
146
147 return aRet;
148 }
149
printFormula(const uno::Sequence<sheet::FormulaToken> & aTokens,const table::CellAddress & rReferencePos)150 OUString SAL_CALL ScFormulaParserObj::printFormula(
151 const uno::Sequence<sheet::FormulaToken>& aTokens, const table::CellAddress& rReferencePos )
152 {
153 SolarMutexGuard aGuard;
154 OUString aRet;
155
156 if (mpDocShell)
157 {
158 ScDocument& rDoc = mpDocShell->GetDocument();
159 ScTokenArray aCode(rDoc);
160 (void)ScTokenConversion::ConvertToTokenArray( rDoc, aCode, aTokens );
161 ScAddress aRefPos( ScAddress::UNINITIALIZED );
162 ScUnoConversion::FillScAddress( aRefPos, rReferencePos );
163 ScCompiler aCompiler(rDoc, aRefPos, aCode, rDoc.GetGrammar());
164 SetCompilerFlags( aCompiler );
165
166 OUStringBuffer aBuffer;
167 aCompiler.CreateStringFromTokenArray( aBuffer );
168 aRet = aBuffer.makeStringAndClear();
169 }
170
171 return aRet;
172 }
173
174 // XPropertySet
175
getPropertySetInfo()176 uno::Reference<beans::XPropertySetInfo> SAL_CALL ScFormulaParserObj::getPropertySetInfo()
177 {
178 SolarMutexGuard aGuard;
179 static uno::Reference< beans::XPropertySetInfo > aRef(new SfxItemPropertySetInfo( lcl_GetFormulaParserMap() ));
180 return aRef;
181 }
182
setPropertyValue(const OUString & aPropertyName,const uno::Any & aValue)183 void SAL_CALL ScFormulaParserObj::setPropertyValue(
184 const OUString& aPropertyName, const uno::Any& aValue )
185 {
186 SolarMutexGuard aGuard;
187 if ( aPropertyName == SC_UNO_COMPILEFAP )
188 {
189 aValue >>= mbCompileFAP;
190 }
191 else if ( aPropertyName == SC_UNO_COMPILEENGLISH )
192 {
193 bool bOldEnglish = mbEnglish;
194 if (!(aValue >>= mbEnglish))
195 throw lang::IllegalArgumentException();
196
197 // Need to recreate the symbol map to change English property
198 // because the map is const. So for performance reasons set
199 // CompileEnglish _before_ OpCodeMap!
200 if (mxOpCodeMap && mbEnglish != bOldEnglish)
201 {
202 ScDocument& rDoc = mpDocShell->GetDocument();
203 ScCompiler aCompiler( rDoc, ScAddress(), rDoc.GetGrammar());
204 mxOpCodeMap = formula::FormulaCompiler::CreateOpCodeMap( maOpCodeMapping, mbEnglish);
205 }
206
207 }
208 else if ( aPropertyName == SC_UNO_FORMULACONVENTION )
209 {
210 aValue >>= mnConv;
211
212 bool bOldEnglish = mbEnglish;
213 if (mnConv >= 0 && mnConv < nConvMapCount
214 && aConvMap[mnConv] == formula::FormulaGrammar::CONV_XL_OOX)
215 mbEnglish = true;
216
217 // Same as for SC_UNO_COMPILEENGLISH, though an OpCodeMap should not
218 // had been set for CONV_XL_OOX.
219 if (mxOpCodeMap && mbEnglish != bOldEnglish)
220 {
221 ScDocument& rDoc = mpDocShell->GetDocument();
222 ScCompiler aCompiler( rDoc, ScAddress(), rDoc.GetGrammar());
223 mxOpCodeMap = formula::FormulaCompiler::CreateOpCodeMap( maOpCodeMapping, mbEnglish);
224 }
225 }
226 else if ( aPropertyName == SC_UNO_IGNORELEADING )
227 {
228 aValue >>= mbIgnoreSpaces;
229 }
230 else if ( aPropertyName == SC_UNO_OPCODEMAP )
231 {
232 if (!(aValue >>= maOpCodeMapping))
233 throw lang::IllegalArgumentException();
234
235 ScDocument& rDoc = mpDocShell->GetDocument();
236 ScCompiler aCompiler(rDoc, ScAddress(), rDoc.GetGrammar());
237 mxOpCodeMap = formula::FormulaCompiler::CreateOpCodeMap( maOpCodeMapping, mbEnglish);
238
239 }
240 else if ( aPropertyName == SC_UNO_EXTERNALLINKS )
241 {
242 if (!(aValue >>= maExternalLinks))
243 throw lang::IllegalArgumentException();
244 }
245 else
246 throw beans::UnknownPropertyException(aPropertyName);
247 }
248
getPropertyValue(const OUString & aPropertyName)249 uno::Any SAL_CALL ScFormulaParserObj::getPropertyValue( const OUString& aPropertyName )
250 {
251 SolarMutexGuard aGuard;
252 uno::Any aRet;
253 if ( aPropertyName == SC_UNO_COMPILEFAP )
254 {
255 aRet <<= mbCompileFAP;
256 }
257 else if ( aPropertyName == SC_UNO_COMPILEENGLISH )
258 {
259 aRet <<= mbEnglish;
260 }
261 else if ( aPropertyName == SC_UNO_FORMULACONVENTION )
262 {
263 aRet <<= mnConv;
264 }
265 else if ( aPropertyName == SC_UNO_IGNORELEADING )
266 {
267 aRet <<= mbIgnoreSpaces;
268 }
269 else if ( aPropertyName == SC_UNO_OPCODEMAP )
270 {
271 aRet <<= maOpCodeMapping;
272 }
273 else if ( aPropertyName == SC_UNO_EXTERNALLINKS )
274 {
275 aRet <<= maExternalLinks;
276 }
277 else
278 throw beans::UnknownPropertyException(aPropertyName);
279 return aRet;
280 }
281
SC_IMPL_DUMMY_PROPERTY_LISTENER(ScFormulaParserObj)282 SC_IMPL_DUMMY_PROPERTY_LISTENER( ScFormulaParserObj )
283
284 static void lcl_ExternalRefToApi( sheet::SingleReference& rAPI, const ScSingleRefData& rRef )
285 {
286 rAPI.Column = 0;
287 rAPI.Row = 0;
288 rAPI.Sheet = 0;
289 rAPI.RelativeColumn = 0;
290 rAPI.RelativeRow = 0;
291 rAPI.RelativeSheet = 0;
292
293 sal_Int32 nFlags = 0;
294 if ( rRef.IsColRel() )
295 {
296 nFlags |= sheet::ReferenceFlags::COLUMN_RELATIVE;
297 rAPI.RelativeColumn = rRef.Col();
298 }
299 else
300 rAPI.Column = rRef.Col();
301
302 if ( rRef.IsRowRel() )
303 {
304 nFlags |= sheet::ReferenceFlags::ROW_RELATIVE;
305 rAPI.RelativeRow = rRef.Row();
306 }
307 else
308 rAPI.Row = rRef.Row();
309
310 if ( rRef.IsColDeleted() ) nFlags |= sheet::ReferenceFlags::COLUMN_DELETED;
311 if ( rRef.IsRowDeleted() ) nFlags |= sheet::ReferenceFlags::ROW_DELETED;
312 if ( rRef.IsFlag3D() ) nFlags |= sheet::ReferenceFlags::SHEET_3D;
313 if ( rRef.IsRelName() ) nFlags |= sheet::ReferenceFlags::RELATIVE_NAME;
314 rAPI.Flags = nFlags;
315 }
316
lcl_SingleRefToApi(sheet::SingleReference & rAPI,const ScSingleRefData & rRef)317 static void lcl_SingleRefToApi( sheet::SingleReference& rAPI, const ScSingleRefData& rRef )
318 {
319 sal_Int32 nFlags = 0;
320 if ( rRef.IsColRel() )
321 {
322 nFlags |= sheet::ReferenceFlags::COLUMN_RELATIVE;
323 rAPI.RelativeColumn = rRef.Col();
324 rAPI.Column = 0;
325 }
326 else
327 {
328 rAPI.RelativeColumn = 0;
329 rAPI.Column = rRef.Col();
330 }
331
332 if ( rRef.IsRowRel() )
333 {
334 nFlags |= sheet::ReferenceFlags::ROW_RELATIVE;
335 rAPI.RelativeRow = rRef.Row();
336 rAPI.Row = 0;
337 }
338 else
339 {
340 rAPI.RelativeRow = 0;
341 rAPI.Row = rRef.Row();
342 }
343
344 if ( rRef.IsTabRel() )
345 {
346 nFlags |= sheet::ReferenceFlags::SHEET_RELATIVE;
347 rAPI.RelativeSheet = rRef.Tab();
348 rAPI.Sheet = 0;
349 }
350 else
351 {
352 rAPI.RelativeSheet = 0;
353 rAPI.Sheet = rRef.Tab();
354 }
355
356 if ( rRef.IsColDeleted() ) nFlags |= sheet::ReferenceFlags::COLUMN_DELETED;
357 if ( rRef.IsRowDeleted() ) nFlags |= sheet::ReferenceFlags::ROW_DELETED;
358 if ( rRef.IsTabDeleted() ) nFlags |= sheet::ReferenceFlags::SHEET_DELETED;
359 if ( rRef.IsFlag3D() ) nFlags |= sheet::ReferenceFlags::SHEET_3D;
360 if ( rRef.IsRelName() ) nFlags |= sheet::ReferenceFlags::RELATIVE_NAME;
361 rAPI.Flags = nFlags;
362 }
363
ConvertToTokenArray(ScDocument & rDoc,ScTokenArray & rTokenArray,const uno::Sequence<sheet::FormulaToken> & rSequence)364 bool ScTokenConversion::ConvertToTokenArray( ScDocument& rDoc,
365 ScTokenArray& rTokenArray, const uno::Sequence<sheet::FormulaToken>& rSequence )
366 {
367 return !rTokenArray.Fill(rSequence, rDoc.GetSharedStringPool(), rDoc.GetExternalRefManager());
368 }
369
ConvertToTokenSequence(const ScDocument & rDoc,uno::Sequence<sheet::FormulaToken> & rSequence,const ScTokenArray & rTokenArray)370 void ScTokenConversion::ConvertToTokenSequence( const ScDocument& rDoc,
371 uno::Sequence<sheet::FormulaToken>& rSequence, const ScTokenArray& rTokenArray )
372 {
373 sal_Int32 nLen = static_cast<sal_Int32>(rTokenArray.GetLen());
374 formula::FormulaToken** pTokens = rTokenArray.GetArray();
375 if ( pTokens )
376 {
377 rSequence.realloc(nLen);
378 for (sal_Int32 nPos=0; nPos<nLen; nPos++)
379 {
380 const formula::FormulaToken& rToken = *pTokens[nPos];
381 sheet::FormulaToken& rAPI = rSequence[nPos];
382
383 OpCode eOpCode = rToken.GetOpCode();
384 // eOpCode may be changed in the following switch/case
385 switch ( rToken.GetType() )
386 {
387 case svByte:
388 // Only the count of spaces is stored as "long". Parameter count is ignored.
389 if ( eOpCode == ocSpaces )
390 rAPI.Data <<= static_cast<sal_Int32>(rToken.GetByte());
391 else
392 rAPI.Data.clear(); // no data
393 break;
394 case formula::svDouble:
395 rAPI.Data <<= rToken.GetDouble();
396 break;
397 case formula::svString:
398 rAPI.Data <<= rToken.GetString().getString();
399 break;
400 case svExternal:
401 // Function name is stored as string.
402 // Byte (parameter count) is ignored.
403 rAPI.Data <<= rToken.GetExternal();
404 break;
405 case svSingleRef:
406 {
407 sheet::SingleReference aSingleRef;
408 lcl_SingleRefToApi( aSingleRef, *rToken.GetSingleRef() );
409 rAPI.Data <<= aSingleRef;
410 }
411 break;
412 case formula::svDoubleRef:
413 {
414 sheet::ComplexReference aCompRef;
415 lcl_SingleRefToApi( aCompRef.Reference1, *rToken.GetSingleRef() );
416 lcl_SingleRefToApi( aCompRef.Reference2, *rToken.GetSingleRef2() );
417 rAPI.Data <<= aCompRef;
418 }
419 break;
420 case svIndex:
421 {
422 sheet::NameToken aNameToken;
423 aNameToken.Index = static_cast<sal_Int32>( rToken.GetIndex() );
424 aNameToken.Sheet = rToken.GetSheet();
425 rAPI.Data <<= aNameToken;
426 }
427 break;
428 case svMatrix:
429 if (!ScRangeToSequence::FillMixedArray( rAPI.Data, rToken.GetMatrix(), true))
430 rAPI.Data.clear();
431 break;
432 case svExternalSingleRef:
433 {
434 sheet::SingleReference aSingleRef;
435 lcl_ExternalRefToApi( aSingleRef, *rToken.GetSingleRef() );
436 size_t nCacheId;
437 rDoc.GetExternalRefManager()->getCacheTable(
438 rToken.GetIndex(), rToken.GetString().getString(), false, &nCacheId);
439 aSingleRef.Sheet = static_cast< sal_Int32 >( nCacheId );
440 sheet::ExternalReference aExtRef;
441 aExtRef.Index = rToken.GetIndex();
442 aExtRef.Reference <<= aSingleRef;
443 rAPI.Data <<= aExtRef;
444 eOpCode = ocPush;
445 }
446 break;
447 case svExternalDoubleRef:
448 {
449 sheet::ComplexReference aComplRef;
450 lcl_ExternalRefToApi( aComplRef.Reference1, *rToken.GetSingleRef() );
451 lcl_ExternalRefToApi( aComplRef.Reference2, *rToken.GetSingleRef2() );
452 size_t nCacheId;
453 rDoc.GetExternalRefManager()->getCacheTable(
454 rToken.GetIndex(), rToken.GetString().getString(), false, &nCacheId);
455 aComplRef.Reference1.Sheet = static_cast< sal_Int32 >( nCacheId );
456 // NOTE: This assumes that cached sheets are in consecutive order!
457 aComplRef.Reference2.Sheet =
458 aComplRef.Reference1.Sheet +
459 (rToken.GetSingleRef2()->Tab() - rToken.GetSingleRef()->Tab());
460 sheet::ExternalReference aExtRef;
461 aExtRef.Index = rToken.GetIndex();
462 aExtRef.Reference <<= aComplRef;
463 rAPI.Data <<= aExtRef;
464 eOpCode = ocPush;
465 }
466 break;
467 case svExternalName:
468 {
469 sheet::ExternalReference aExtRef;
470 aExtRef.Index = rToken.GetIndex();
471 aExtRef.Reference <<= rToken.GetString().getString();
472 rAPI.Data <<= aExtRef;
473 eOpCode = ocPush;
474 }
475 break;
476 default:
477 SAL_WARN("sc", "ScTokenConversion::ConvertToTokenSequence: unhandled token type " << StackVarEnumToString(rToken.GetType()));
478 [[fallthrough]];
479 case svJump: // occurs with ocIf, ocChoose
480 case svError: // seems to be fairly common, and probably not exceptional and not worth a warning?
481 case svMissing: // occurs with ocMissing
482 case svSep: // occurs with ocSep, ocOpen, ocClose, ocArray*
483 rAPI.Data.clear(); // no data
484 }
485 rAPI.OpCode = static_cast<sal_Int32>(eOpCode); //! assuming equal values for the moment
486 }
487 }
488 else
489 rSequence.realloc(0);
490 }
491
ScFormulaOpCodeMapperObj(::std::unique_ptr<formula::FormulaCompiler> && _pCompiler)492 ScFormulaOpCodeMapperObj::ScFormulaOpCodeMapperObj(::std::unique_ptr<formula::FormulaCompiler> && _pCompiler)
493 : formula::FormulaOpCodeMapperObj(std::move(_pCompiler))
494 {
495 }
496
497 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
498