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/.
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>
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>
33 #include <string_view>
34 #include <libxml/parser.h>
36 using namespace com::sun::star
;
38 // TODO: Add new methods for ScInterpreter here.
40 void ScInterpreter::ScFilterXML()
42 sal_uInt8 nParamCount
= GetByte();
43 if (!MustHaveParamCount( nParamCount
, 2 ) )
46 SCSIZE nMatCols
= 1, nMatRows
= 1, nNode
= 0;
47 // In array/matrix context node elements' results are to be
48 // subsequently stored. Check this before obtaining any argument from
49 // the stack so the stack type can be used.
50 if (pJumpMatrix
|| IsInArrayContext())
54 // Single result, GetString() will retrieve the corresponding
55 // argument and JumpMatrix() will store it at the proper
56 // position. Note that nMatCols and nMatRows are still 1.
57 SCSIZE nCurCol
= 0, nCurRow
= 0;
58 pJumpMatrix
->GetPos( nCurCol
, nCurRow
);
61 else if (bMatrixFormula
)
63 // If there is no formula cell then continue with a single
69 pMyFormulaCell
->GetMatColsRows( nCols
, nRows
);
74 else if (GetStackType() == formula::svMatrix
)
76 const ScMatrix
* pPathMatrix
= pStack
[sp
-1]->GetMatrix();
79 PushIllegalParameter();
82 pPathMatrix
->GetDimensions( nMatCols
, nMatRows
);
84 /* TODO: it is unclear what should happen if there are
85 * different path arguments in matrix elements. We may have to
86 * evaluate each, and for repeated identical paths use
87 * subsequent nodes. As is, the path at 0,0 is used as obtained
92 if (!nMatCols
|| !nMatRows
)
98 OUString aXPathExpression
= GetString().getString();
99 OUString aString
= GetString().getString();
100 if(aString
.isEmpty() || aXPathExpression
.isEmpty())
102 PushError( FormulaError::NoValue
);
106 OString aOXPathExpression
= OUStringToOString( aXPathExpression
, RTL_TEXTENCODING_UTF8
);
107 const char* pXPathExpr
= aOXPathExpression
.getStr();
108 OString aOString
= OUStringToOString( aString
, RTL_TEXTENCODING_UTF8
);
109 const char* pXML
= aOString
.getStr();
111 std::shared_ptr
<xmlParserCtxt
> pContext(
112 xmlNewParserCtxt(), xmlFreeParserCtxt
);
114 std::shared_ptr
<xmlDoc
> pDoc( xmlParseMemory( pXML
, aOString
.getLength() ),
119 PushError( FormulaError::NoValue
);
123 std::shared_ptr
<xmlXPathContext
> pXPathCtx( xmlXPathNewContext(pDoc
.get()),
124 xmlXPathFreeContext
);
126 std::shared_ptr
<xmlXPathObject
> pXPathObj( xmlXPathEvalExpression(BAD_CAST(pXPathExpr
), pXPathCtx
.get()),
127 xmlXPathFreeObject
);
131 PushError( FormulaError::NoValue
);
135 switch(pXPathObj
->type
)
137 case XPATH_UNDEFINED
:
142 xmlNodeSetPtr pNodeSet
= pXPathObj
->nodesetval
;
145 PushError( FormulaError::NoValue
);
149 const size_t nSize
= pNodeSet
->nodeNr
;
153 PushError( FormulaError::NotAvailable
);
157 /* TODO: for nMatCols>1 IF stack type is svMatrix, i.e.
158 * pPathMatrix!=nullptr, we may want a result matrix with
159 * nMatCols columns as well, but clarify first how to treat
160 * differing path elements. */
165 xResMat
= GetNewMat( 1, nMatRows
, true);
168 PushError( FormulaError::CodeOverflow
);
173 for ( ; nNode
< nMatRows
; ++nNode
)
178 if(pNodeSet
->nodeTab
[nNode
]->type
== XML_NAMESPACE_DECL
)
180 xmlNsPtr ns
= reinterpret_cast<xmlNsPtr
>(pNodeSet
->nodeTab
[nNode
]);
181 xmlNodePtr cur
= reinterpret_cast<xmlNodePtr
>(ns
->next
);
182 std::shared_ptr
<xmlChar
> pChar2(xmlNodeGetContent(cur
), xmlFree
);
183 aResult
= OStringToOUString(std::string_view(reinterpret_cast<char*>(pChar2
.get())), RTL_TEXTENCODING_UTF8
);
187 xmlNodePtr cur
= pNodeSet
->nodeTab
[nNode
];
188 std::shared_ptr
<xmlChar
> pChar2(xmlNodeGetContent(cur
), xmlFree
);
189 aResult
= OStringToOUString(std::string_view(reinterpret_cast<char*>(pChar2
.get())), RTL_TEXTENCODING_UTF8
);
192 xResMat
->PutString( mrStrPool
.intern( aResult
), 0, nNode
);
199 xResMat
->PutError( FormulaError::NotAvailable
, 0, nNode
);
201 PushError( FormulaError::NotAvailable
);
205 PushMatrix( xResMat
);
210 bool bVal
= pXPathObj
->boolval
!= 0;
211 PushDouble(double(bVal
));
216 double fVal
= pXPathObj
->floatval
;
221 PushString(OUString::createFromAscii(reinterpret_cast<char*>(pXPathObj
->stringval
)));
223 #if LIBXML_VERSION < 21000 || defined(LIBXML_XPTR_LOCS_ENABLED)
230 case XPATH_LOCATIONSET
:
237 case XPATH_XSLT_TREE
:
244 static ScWebServiceLink
* lcl_GetWebServiceLink(const sfx2::LinkManager
* pLinkMgr
, std::u16string_view rURL
)
246 size_t nCount
= pLinkMgr
->GetLinks().size();
247 for (size_t i
=0; i
<nCount
; ++i
)
249 ::sfx2::SvBaseLink
* pBase
= pLinkMgr
->GetLinks()[i
].get();
250 if (ScWebServiceLink
* pLink
= dynamic_cast<ScWebServiceLink
*>(pBase
))
252 if (pLink
->GetURL() == rURL
)
260 static bool lcl_FunctionAccessLoadWebServiceLink( OUString
& rResult
, ScDocument
* pDoc
, const OUString
& rURI
)
262 // For FunctionAccess service always force a changed data update.
263 ScWebServiceLink
aLink( pDoc
, rURI
);
264 if (aLink
.DataChanged( OUString(), css::uno::Any()) != sfx2::SvBaseLink::UpdateResult::SUCCESS
)
267 if (!aLink
.HasResult())
270 rResult
= aLink
.GetResult();
275 void ScInterpreter::ScWebservice()
277 sal_uInt8 nParamCount
= GetByte();
278 if (!MustHaveParamCount( nParamCount
, 1 ) )
281 OUString aURI
= GetString().getString();
285 PushError( FormulaError::NoValue
);
289 INetURLObject
aObj(aURI
, INetProtocol::File
);
290 INetProtocol eProtocol
= aObj
.GetProtocol();
291 if (eProtocol
!= INetProtocol::Http
&& eProtocol
!= INetProtocol::Https
)
293 PushError(FormulaError::NoValue
);
299 if (!mrDoc
.IsFunctionAccess() || mrDoc
.HasLinkFormulaNeedingCheck())
301 PushError( FormulaError::NoValue
);
306 if (lcl_FunctionAccessLoadWebServiceLink( aResult
, &mrDoc
, aURI
))
307 PushString( aResult
);
309 PushError( FormulaError::NoValue
);
314 // Need to reinterpret after loading (build links)
315 pArr
->AddRecalcMode( ScRecalcMode::ONLOAD_LENIENT
);
317 // while the link is not evaluated, idle must be disabled (to avoid circular references)
318 bool bOldEnabled
= mrDoc
.IsIdleEnabled();
319 mrDoc
.EnableIdle(false);
321 // Get/ Create link object
322 ScWebServiceLink
* pLink
= lcl_GetWebServiceLink(mpLinkManager
, aURI
);
324 bool bWasError
= (pMyFormulaCell
&& pMyFormulaCell
->GetRawError() != FormulaError::NONE
);
328 pLink
= new ScWebServiceLink(&mrDoc
, aURI
);
329 mpLinkManager
->InsertFileLink(*pLink
, sfx2::SvBaseLinkObjectType::ClientFile
, aURI
);
330 if ( mpLinkManager
->GetLinks().size() == 1 ) // the first one?
332 SfxBindings
* pBindings
= mrDoc
.GetViewBindings();
334 pBindings
->Invalidate( SID_LINKS
); // Link-Manager enabled
337 //if the document was just loaded, but the ScDdeLink entry was missing, then
338 //don't update this link until the links are updated in response to the users
340 if (!mrDoc
.HasLinkFormulaNeedingCheck())
347 // StartListening after the Update to avoid circular references
348 pMyFormulaCell
->StartListening(*pLink
);
354 pMyFormulaCell
->StartListening(*pLink
);
357 // If a new Error from Reschedule appears when the link is executed then reset the errorflag
358 if (pMyFormulaCell
&& pMyFormulaCell
->GetRawError() != FormulaError::NONE
&& !bWasError
)
359 pMyFormulaCell
->SetErrCode(FormulaError::NONE
);
362 if (pLink
->HasResult())
363 PushString(pLink
->GetResult());
364 else if (mrDoc
.HasLinkFormulaNeedingCheck())
366 // If this formula cell is recalculated just after load and the
367 // expression is exactly WEBSERVICE("literal_URI") (i.e. no other
368 // calculation involved, not even a cell reference) and a cached
369 // result is set as hybrid string then use that as result value to
370 // prevent a #VALUE! result due to the "Automatic update of
371 // external links has been disabled."
372 // This will work only once, as the new formula cell result won't
373 // be a hybrid anymore.
374 /* TODO: the FormulaError::LinkFormulaNeedingCheck could be used as
375 * a signal for the formula cell to keep the hybrid string as
376 * result of the overall formula *iff* no higher prioritized
377 * ScRecalcMode than ONLOAD_LENIENT is present in the entire
378 * document (i.e. the formula result could not be influenced by an
379 * ONLOAD_MUST or ALWAYS recalc, necessary as we don't track
380 * interim results of subexpressions that could be compared), which
381 * also means to track setting ScRecalcMode somehow... note this is
382 * just a vague idea so far and might or might not work. */
383 if (pMyFormulaCell
&& pMyFormulaCell
->HasHybridStringResult())
385 if (pMyFormulaCell
->GetCode()->GetCodeLen() == 2)
387 formula::FormulaToken
const * const * pRPN
= pMyFormulaCell
->GetCode()->GetCode();
388 if (pRPN
[0]->GetType() == formula::svString
&& pRPN
[1]->GetOpCode() == ocWebservice
)
389 PushString( pMyFormulaCell
->GetResultString());
391 PushError(FormulaError::LinkFormulaNeedingCheck
);
394 PushError(FormulaError::LinkFormulaNeedingCheck
);
397 PushError(FormulaError::LinkFormulaNeedingCheck
);
400 PushError(FormulaError::NoValue
);
402 mrDoc
.EnableIdle(bOldEnabled
);
403 mpLinkManager
->CloseCachedComps();
407 Returns a string in which all non-alphanumeric characters except stroke and
408 underscore (-_) have been replaced with a percent (%) sign
409 followed by hex digits.
410 It is encoded the same way that the posted data from a WWW form is encoded,
411 that is the same way as in application/x-www-form-urlencoded media type and
416 void ScInterpreter::ScEncodeURL()
418 sal_uInt8 nParamCount
= GetByte();
419 if ( !MustHaveParamCount( nParamCount
, 1 ) )
422 OUString aStr
= GetString().getString();
423 if ( aStr
.isEmpty() )
425 PushError( FormulaError::NoValue
);
429 OString
aUtf8Str( aStr
.toUtf8());
430 const sal_Int32 nLen
= aUtf8Str
.getLength();
431 OStringBuffer
aUrlBuf( nLen
);
432 for ( int i
= 0; i
< nLen
; i
++ )
434 char c
= aUtf8Str
[ i
];
435 if ( rtl::isAsciiAlphanumeric( static_cast<unsigned char>( c
) ) || c
== '-' || c
== '_' )
439 aUrlBuf
.append( '%' );
440 OString convertedChar
= OString::number( static_cast<unsigned char>( c
), 16 ).toAsciiUpperCase();
441 // RFC 3986 indicates:
442 // "A percent-encoded octet is encoded as a character triplet,
443 // consisting of the percent character "%" followed by the two hexadecimal digits
444 // representing that octet's numeric value"
445 if (convertedChar
.getLength() == 1)
447 aUrlBuf
.append(convertedChar
);
450 PushString( OUString::fromUtf8( aUrlBuf
) );
453 void ScInterpreter::ScDebugVar()
455 // This is to be used by developers only! Never document this for end
456 // users. This is a convenient way to extract arbitrary internal state to
457 // a cell for easier debugging.
459 if (!officecfg::Office::Common::Misc::ExperimentalMode::get())
461 PushError(FormulaError::NoName
);
465 if (!MustHaveParamCount(GetByte(), 1))
468 rtl_uString
* p
= GetString().getDataIgnoreCase();
471 PushIllegalParameter();
475 OUString
aStrUpper(p
);
477 if (aStrUpper
== "PIVOTCOUNT")
479 // Set the number of pivot tables in the document.
482 if (mrDoc
.HasPivotTable())
484 const ScDPCollection
* pDPs
= mrDoc
.GetDPCollection();
485 fVal
= pDPs
->GetCount();
489 else if (aStrUpper
== "DATASTREAM_IMPORT")
490 PushDouble( sc::datastream_get_time( sc::DebugTime::Import
) );
491 else if (aStrUpper
== "DATASTREAM_RECALC")
492 PushDouble( sc::datastream_get_time( sc::DebugTime::Recalc
) );
493 else if (aStrUpper
== "DATASTREAM_RENDER")
494 PushDouble( sc::datastream_get_time( sc::DebugTime::Render
) );
496 PushIllegalParameter();
499 void ScInterpreter::ScErf()
501 sal_uInt8 nParamCount
= GetByte();
502 if (MustHaveParamCount( nParamCount
, 1 ) )
503 PushDouble( std::erf( GetDouble() ) );
506 void ScInterpreter::ScErfc()
508 sal_uInt8 nParamCount
= GetByte();
509 if (MustHaveParamCount( nParamCount
, 1 ) )
510 PushDouble( std::erfc( GetDouble() ) );
513 void ScInterpreter::ScColor()
515 sal_uInt8 nParamCount
= GetByte();
516 if(!MustHaveParamCount(nParamCount
, 3, 4))
521 nAlpha
= rtl::math::approxFloor(GetDouble());
523 if(nAlpha
< 0 || nAlpha
> 255)
525 PushIllegalArgument();
529 double nBlue
= rtl::math::approxFloor(GetDouble());
531 if(nBlue
< 0 || nBlue
> 255)
533 PushIllegalArgument();
537 double nGreen
= rtl::math::approxFloor(GetDouble());
539 if(nGreen
< 0 || nGreen
> 255)
541 PushIllegalArgument();
545 double nRed
= rtl::math::approxFloor(GetDouble());
547 if(nRed
< 0 || nRed
> 255)
549 PushIllegalArgument();
553 double nVal
= 256*256*256*nAlpha
+ 256*256*nRed
+ 256*nGreen
+ nBlue
;
557 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */