LibreOffice Module sc (master)  1
interpr7.cxx
Go to the documentation of this file.
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 
10 #include <interpre.hxx>
11 #include <jumpmatrix.hxx>
12 #include <formulacell.hxx>
13 #include <scmatrix.hxx>
14 #include <rtl/strbuf.hxx>
15 #include <rtl/character.hxx>
16 #include <formula/errorcodes.hxx>
17 #include <sfx2/bindings.hxx>
18 #include <sfx2/linkmgr.hxx>
19 #include <tools/urlobj.hxx>
20 
21 #include <officecfg/Office/Common.hxx>
22 #include <libxml/xpath.h>
23 #include <datastreamgettime.hxx>
24 #include <dpobject.hxx>
25 #include <document.hxx>
26 #include <tokenarray.hxx>
27 #include <webservicelink.hxx>
28 
29 #include <sc.hrc>
30 
31 #include <cstring>
32 #include <memory>
33 #include <string_view>
34 
35 using namespace com::sun::star;
36 
37 // TODO: Add new methods for ScInterpreter here.
38 
40 {
41  sal_uInt8 nParamCount = GetByte();
42  if (!MustHaveParamCount( nParamCount, 2 ) )
43  return;
44 
45  SCSIZE nMatCols = 1, nMatRows = 1, nNode = 0;
46  // In array/matrix context node elements' results are to be
47  // subsequently stored. Check this before obtaining any argument from
48  // the stack so the stack type can be used.
49  if (pJumpMatrix || IsInArrayContext())
50  {
51  if (pJumpMatrix)
52  {
53  // Single result, GetString() will retrieve the corresponding
54  // argument and JumpMatrix() will store it at the proper
55  // position. Note that nMatCols and nMatRows are still 1.
56  SCSIZE nCurCol = 0, nCurRow = 0;
57  pJumpMatrix->GetPos( nCurCol, nCurRow);
58  nNode = nCurRow;
59  }
60  else if (bMatrixFormula)
61  {
62  // If there is no formula cell then continue with a single
63  // result.
64  if (pMyFormulaCell)
65  {
66  SCCOL nCols;
67  SCROW nRows;
68  pMyFormulaCell->GetMatColsRows( nCols, nRows);
69  nMatCols = nCols;
70  nMatRows = nRows;
71  }
72  }
73  else if (GetStackType() == formula::svMatrix)
74  {
75  const ScMatrix* pPathMatrix = pStack[sp-1]->GetMatrix();
76  if (!pPathMatrix)
77  {
78  PushIllegalParameter();
79  return;
80  }
81  pPathMatrix->GetDimensions( nMatCols, nMatRows);
82 
83  /* TODO: it is unclear what should happen if there are
84  * different path arguments in matrix elements. We may have to
85  * evaluate each, and for repeated identical paths use
86  * subsequent nodes. As is, the path at 0,0 is used as obtained
87  * by GetString(). */
88 
89  }
90  }
91  if (!nMatCols || !nMatRows)
92  {
93  PushNoValue();
94  return;
95  }
96 
97  OUString aXPathExpression = GetString().getString();
98  OUString aString = GetString().getString();
99  if(aString.isEmpty() || aXPathExpression.isEmpty())
100  {
101  PushError( FormulaError::NoValue );
102  return;
103  }
104 
105  OString aOXPathExpression = OUStringToOString( aXPathExpression, RTL_TEXTENCODING_UTF8 );
106  const char* pXPathExpr = aOXPathExpression.getStr();
107  OString aOString = OUStringToOString( aString, RTL_TEXTENCODING_UTF8 );
108  const char* pXML = aOString.getStr();
109 
110  std::shared_ptr<xmlParserCtxt> pContext(
111  xmlNewParserCtxt(), xmlFreeParserCtxt );
112 
113  std::shared_ptr<xmlDoc> pDoc( xmlParseMemory( pXML, aOString.getLength() ),
114  xmlFreeDoc );
115 
116  if(!pDoc)
117  {
118  PushError( FormulaError::NoValue );
119  return;
120  }
121 
122  std::shared_ptr<xmlXPathContext> pXPathCtx( xmlXPathNewContext(pDoc.get()),
123  xmlXPathFreeContext );
124 
125  std::shared_ptr<xmlXPathObject> pXPathObj( xmlXPathEvalExpression(BAD_CAST(pXPathExpr), pXPathCtx.get()),
126  xmlXPathFreeObject );
127 
128  if(!pXPathObj)
129  {
130  PushError( FormulaError::NoValue );
131  return;
132  }
133 
134  switch(pXPathObj->type)
135  {
136  case XPATH_UNDEFINED:
137  PushNoValue();
138  break;
139  case XPATH_NODESET:
140  {
141  xmlNodeSetPtr pNodeSet = pXPathObj->nodesetval;
142  if(!pNodeSet)
143  {
144  PushError( FormulaError::NoValue );
145  return;
146  }
147 
148  const size_t nSize = pNodeSet->nodeNr;
149  if (nNode >= nSize)
150  {
151  // For pJumpMatrix
152  PushError( FormulaError::NotAvailable);
153  return;
154  }
155 
156  /* TODO: for nMatCols>1 IF stack type is svMatrix, i.e.
157  * pPathMatrix!=nullptr, we may want a result matrix with
158  * nMatCols columns as well, but clarify first how to treat
159  * differing path elements. */
160 
161  ScMatrixRef xResMat;
162  if (nMatRows > 1)
163  {
164  xResMat = GetNewMat( 1, nMatRows, true);
165  if (!xResMat)
166  {
167  PushError( FormulaError::CodeOverflow);
168  return;
169  }
170  }
171 
172  for ( ; nNode < nMatRows; ++nNode)
173  {
174  if( nSize > nNode )
175  {
176  OUString aResult;
177  if(pNodeSet->nodeTab[nNode]->type == XML_NAMESPACE_DECL)
178  {
179  xmlNsPtr ns = reinterpret_cast<xmlNsPtr>(pNodeSet->nodeTab[nNode]);
180  xmlNodePtr cur = reinterpret_cast<xmlNodePtr>(ns->next);
181  std::shared_ptr<xmlChar> pChar2(xmlNodeGetContent(cur), xmlFree);
182  aResult = OStringToOUString(std::string_view(reinterpret_cast<char*>(pChar2.get())), RTL_TEXTENCODING_UTF8);
183  }
184  else
185  {
186  xmlNodePtr cur = pNodeSet->nodeTab[nNode];
187  std::shared_ptr<xmlChar> pChar2(xmlNodeGetContent(cur), xmlFree);
188  aResult = OStringToOUString(std::string_view(reinterpret_cast<char*>(pChar2.get())), RTL_TEXTENCODING_UTF8);
189  }
190  if (xResMat)
191  xResMat->PutString( mrStrPool.intern( aResult), 0, nNode);
192  else
193  PushString(aResult);
194  }
195  else
196  {
197  if (xResMat)
198  xResMat->PutError( FormulaError::NotAvailable, 0, nNode);
199  else
200  PushError( FormulaError::NotAvailable );
201  }
202  }
203  if (xResMat)
204  PushMatrix( xResMat);
205  }
206  break;
207  case XPATH_BOOLEAN:
208  {
209  bool bVal = pXPathObj->boolval != 0;
210  PushDouble(double(bVal));
211  }
212  break;
213  case XPATH_NUMBER:
214  {
215  double fVal = pXPathObj->floatval;
216  PushDouble(fVal);
217  }
218  break;
219  case XPATH_STRING:
220  PushString(OUString::createFromAscii(reinterpret_cast<char*>(pXPathObj->stringval)));
221  break;
222  case XPATH_POINT:
223  PushNoValue();
224  break;
225  case XPATH_RANGE:
226  PushNoValue();
227  break;
228  case XPATH_LOCATIONSET:
229  PushNoValue();
230  break;
231  case XPATH_USERS:
232  PushNoValue();
233  break;
234  case XPATH_XSLT_TREE:
235  PushNoValue();
236  break;
237 
238  }
239 }
240 
241 static ScWebServiceLink* lcl_GetWebServiceLink(const sfx2::LinkManager* pLinkMgr, std::u16string_view rURL)
242 {
243  size_t nCount = pLinkMgr->GetLinks().size();
244  for (size_t i=0; i<nCount; ++i)
245  {
246  ::sfx2::SvBaseLink* pBase = pLinkMgr->GetLinks()[i].get();
247  if (ScWebServiceLink* pLink = dynamic_cast<ScWebServiceLink*>(pBase))
248  {
249  if (pLink->GetURL() == rURL)
250  return pLink;
251  }
252  }
253 
254  return nullptr;
255 }
256 
257 static bool lcl_FunctionAccessLoadWebServiceLink( OUString& rResult, ScDocument* pDoc, const OUString& rURI )
258 {
259  // For FunctionAccess service always force a changed data update.
260  ScWebServiceLink aLink( pDoc, rURI);
261  if (aLink.DataChanged( OUString(), css::uno::Any()) != sfx2::SvBaseLink::UpdateResult::SUCCESS)
262  return false;
263 
264  if (!aLink.HasResult())
265  return false;
266 
267  rResult = aLink.GetResult();
268 
269  return true;
270 }
271 
273 {
274  sal_uInt8 nParamCount = GetByte();
275  if (!MustHaveParamCount( nParamCount, 1 ) )
276  return;
277 
278  OUString aURI = GetString().getString();
279 
280  if (aURI.isEmpty())
281  {
282  PushError( FormulaError::NoValue );
283  return;
284  }
285 
286  INetURLObject aObj(aURI, INetProtocol::File);
287  INetProtocol eProtocol = aObj.GetProtocol();
288  if (eProtocol != INetProtocol::Http && eProtocol != INetProtocol::Https)
289  {
290  PushError(FormulaError::NoValue);
291  return;
292  }
293 
294  if (!mpLinkManager)
295  {
296  if (!mrDoc.IsFunctionAccess() || mrDoc.HasLinkFormulaNeedingCheck())
297  {
298  PushError( FormulaError::NoValue);
299  }
300  else
301  {
302  OUString aResult;
303  if (lcl_FunctionAccessLoadWebServiceLink( aResult, &mrDoc, aURI))
304  PushString( aResult);
305  else
306  PushError( FormulaError::NoValue);
307  }
308  return;
309  }
310 
311  // Need to reinterpret after loading (build links)
312  pArr->AddRecalcMode( ScRecalcMode::ONLOAD_LENIENT );
313 
314  // while the link is not evaluated, idle must be disabled (to avoid circular references)
315  bool bOldEnabled = mrDoc.IsIdleEnabled();
316  mrDoc.EnableIdle(false);
317 
318  // Get/ Create link object
319  ScWebServiceLink* pLink = lcl_GetWebServiceLink(mpLinkManager, aURI);
320 
321  bool bWasError = (pMyFormulaCell && pMyFormulaCell->GetRawError() != FormulaError::NONE);
322 
323  if (!pLink)
324  {
325  pLink = new ScWebServiceLink(&mrDoc, aURI);
326  mpLinkManager->InsertFileLink(*pLink, sfx2::SvBaseLinkObjectType::ClientFile, aURI);
327  if ( mpLinkManager->GetLinks().size() == 1 ) // the first one?
328  {
329  SfxBindings* pBindings = mrDoc.GetViewBindings();
330  if (pBindings)
331  pBindings->Invalidate( SID_LINKS ); // Link-Manager enabled
332  }
333 
334  //if the document was just loaded, but the ScDdeLink entry was missing, then
335  //don't update this link until the links are updated in response to the users
336  //decision
337  if (!mrDoc.HasLinkFormulaNeedingCheck())
338  {
339  pLink->Update();
340  }
341 
342  if (pMyFormulaCell)
343  {
344  // StartListening after the Update to avoid circular references
345  pMyFormulaCell->StartListening(*pLink);
346  }
347  }
348  else
349  {
350  if (pMyFormulaCell)
351  pMyFormulaCell->StartListening(*pLink);
352  }
353 
354  // If a new Error from Reschedule appears when the link is executed then reset the errorflag
355  if (pMyFormulaCell && pMyFormulaCell->GetRawError() != FormulaError::NONE && !bWasError)
356  pMyFormulaCell->SetErrCode(FormulaError::NONE);
357 
358  // check the value
359  if (pLink->HasResult())
360  PushString(pLink->GetResult());
361  else if (mrDoc.HasLinkFormulaNeedingCheck())
362  {
363  // If this formula cell is recalculated just after load and the
364  // expression is exactly WEBSERVICE("literal_URI") (i.e. no other
365  // calculation involved, not even a cell reference) and a cached
366  // result is set as hybrid string then use that as result value to
367  // prevent a #VALUE! result due to the "Automatic update of
368  // external links has been disabled."
369  // This will work only once, as the new formula cell result won't
370  // be a hybrid anymore.
371  /* TODO: the FormulaError::LinkFormulaNeedingCheck could be used as
372  * a signal for the formula cell to keep the hybrid string as
373  * result of the overall formula *iff* no higher prioritized
374  * ScRecalcMode than ONLOAD_LENIENT is present in the entire
375  * document (i.e. the formula result could not be influenced by an
376  * ONLOAD_MUST or ALWAYS recalc, necessary as we don't track
377  * interim results of subexpressions that could be compared), which
378  * also means to track setting ScRecalcMode somehow... note this is
379  * just a vague idea so far and might or might not work. */
380  if (pMyFormulaCell && pMyFormulaCell->HasHybridStringResult())
381  {
382  if (pMyFormulaCell->GetCode()->GetCodeLen() == 2)
383  {
384  formula::FormulaToken const * const * pRPN = pMyFormulaCell->GetCode()->GetCode();
385  if (pRPN[0]->GetType() == formula::svString && pRPN[1]->GetOpCode() == ocWebservice)
386  PushString( pMyFormulaCell->GetResultString());
387  else
388  PushError(FormulaError::LinkFormulaNeedingCheck);
389  }
390  else
391  PushError(FormulaError::LinkFormulaNeedingCheck);
392  }
393  else
394  PushError(FormulaError::LinkFormulaNeedingCheck);
395  }
396  else
397  PushError(FormulaError::NoValue);
398 
399  mrDoc.EnableIdle(bOldEnabled);
400  mpLinkManager->CloseCachedComps();
401 }
402 
414 {
415  sal_uInt8 nParamCount = GetByte();
416  if ( !MustHaveParamCount( nParamCount, 1 ) )
417  return;
418 
419  OUString aStr = GetString().getString();
420  if ( aStr.isEmpty() )
421  {
422  PushError( FormulaError::NoValue );
423  return;
424  }
425 
426  OString aUtf8Str( aStr.toUtf8());
427  const sal_Int32 nLen = aUtf8Str.getLength();
428  OStringBuffer aUrlBuf( nLen );
429  for ( int i = 0; i < nLen; i++ )
430  {
431  char c = aUtf8Str[ i ];
432  if ( rtl::isAsciiAlphanumeric( static_cast<unsigned char>( c ) ) || c == '-' || c == '_' )
433  aUrlBuf.append( c );
434  else
435  {
436  aUrlBuf.append( '%' );
437  OString convertedChar = OString::number( static_cast<unsigned char>( c ), 16 ).toAsciiUpperCase();
438  // RFC 3986 indicates:
439  // "A percent-encoded octet is encoded as a character triplet,
440  // consisting of the percent character "%" followed by the two hexadecimal digits
441  // representing that octet's numeric value"
442  if (convertedChar.getLength() == 1)
443  aUrlBuf.append("0");
444  aUrlBuf.append(convertedChar);
445  }
446  }
447  PushString( OUString::fromUtf8( aUrlBuf.makeStringAndClear() ) );
448 }
449 
451 {
452  // This is to be used by developers only! Never document this for end
453  // users. This is a convenient way to extract arbitrary internal state to
454  // a cell for easier debugging.
455 
456  if (!officecfg::Office::Common::Misc::ExperimentalMode::get())
457  {
458  PushError(FormulaError::NoName);
459  return;
460  }
461 
462  if (!MustHaveParamCount(GetByte(), 1))
463  return;
464 
465  rtl_uString* p = GetString().getDataIgnoreCase();
466  if (!p)
467  {
468  PushIllegalParameter();
469  return;
470  }
471 
472  OUString aStrUpper(p);
473 
474  if (aStrUpper == "PIVOTCOUNT")
475  {
476  // Set the number of pivot tables in the document.
477 
478  double fVal = 0.0;
479  if (mrDoc.HasPivotTable())
480  {
481  const ScDPCollection* pDPs = mrDoc.GetDPCollection();
482  fVal = pDPs->GetCount();
483  }
484  PushDouble(fVal);
485  }
486  else if (aStrUpper == "DATASTREAM_IMPORT")
488  else if (aStrUpper == "DATASTREAM_RECALC")
490  else if (aStrUpper == "DATASTREAM_RENDER")
492  else
493  PushIllegalParameter();
494 }
495 
497 {
498  sal_uInt8 nParamCount = GetByte();
499  if (MustHaveParamCount( nParamCount, 1 ) )
500  PushDouble( std::erf( GetDouble() ) );
501 }
502 
504 {
505  sal_uInt8 nParamCount = GetByte();
506  if (MustHaveParamCount( nParamCount, 1 ) )
507  PushDouble( std::erfc( GetDouble() ) );
508 }
509 
511 {
512  sal_uInt8 nParamCount = GetByte();
513  if(!MustHaveParamCount(nParamCount, 3, 4))
514  return;
515 
516  double nAlpha = 0;
517  if(nParamCount == 4)
518  nAlpha = rtl::math::approxFloor(GetDouble());
519 
520  if(nAlpha < 0 || nAlpha > 255)
521  {
522  PushIllegalArgument();
523  return;
524  }
525 
526  double nBlue = rtl::math::approxFloor(GetDouble());
527 
528  if(nBlue < 0 || nBlue > 255)
529  {
530  PushIllegalArgument();
531  return;
532  }
533 
534  double nGreen = rtl::math::approxFloor(GetDouble());
535 
536  if(nGreen < 0 || nGreen > 255)
537  {
538  PushIllegalArgument();
539  return;
540  }
541 
542  double nRed = rtl::math::approxFloor(GetDouble());
543 
544  if(nRed < 0 || nRed > 255)
545  {
546  PushIllegalArgument();
547  return;
548  }
549 
550  double nVal = 256*256*256*nAlpha + 256*256*nRed + 256*nGreen + nBlue;
551  PushDouble(nVal);
552 }
553 
554 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
Matrix data type that can store values of mixed types.
Definition: scmatrix.hxx:112
void ScErf()
Definition: interpr7.cxx:496
void ScErfc()
Definition: interpr7.cxx:503
void ScDebugVar()
Definition: interpr7.cxx:450
SC_DLLPUBLIC size_t GetCount() const
Definition: dpobject.cxx:3674
tuple ns
void Invalidate(sal_uInt16 nId)
void ScFilterXML()
Definition: interpr7.cxx:39
OUString GetString(int nId)
char sal_uInt16 & nParamCount
Definition: callform.cxx:53
size_t SCSIZE
size_t typedef to be able to find places where code was changed from USHORT to size_t and is used to ...
Definition: address.hxx:44
int nCount
::boost::intrusive_ptr< ScMatrix > ScMatrixRef
Definition: types.hxx:25
void ScWebservice()
Definition: interpr7.cxx:272
OString OUStringToOString(std::u16string_view str, ConnectionSettings const *settings)
INetProtocol
const SvBaseLinks & GetLinks() const
static bool lcl_FunctionAccessLoadWebServiceLink(OUString &rResult, ScDocument *pDoc, const OUString &rURI)
Definition: interpr7.cxx:257
int i
void GetDimensions(SCSIZE &rC, SCSIZE &rR) const
Definition: scmatrix.cxx:2929
double datastream_get_time(DebugTime nIdx)
Definition: datastream.cxx:39
sal_Int16 SCCOL
Definition: types.hxx:21
static ScWebServiceLink * lcl_GetWebServiceLink(const sfx2::LinkManager *pLinkMgr, std::u16string_view rURL)
Definition: interpr7.cxx:241
ocWebservice
void ScEncodeURL()
Returns a string in which all non-alphanumeric characters except stroke and underscore (-_) have been...
Definition: interpr7.cxx:413
sal_Int32 SCROW
Definition: types.hxx:17
SvBaseLink * pLink
unsigned char sal_uInt8
INetProtocol GetProtocol() const
void * p
aStr
void ScColor()
Definition: interpr7.cxx:510