557 lines
18 KiB
C++
557 lines
18 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* This file is part of the LibreOffice project.
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
#include <interpre.hxx>
|
|
#include <jumpmatrix.hxx>
|
|
#include <formulacell.hxx>
|
|
#include <scmatrix.hxx>
|
|
#include <rtl/strbuf.hxx>
|
|
#include <rtl/character.hxx>
|
|
#include <formula/errorcodes.hxx>
|
|
#include <sfx2/bindings.hxx>
|
|
#include <sfx2/linkmgr.hxx>
|
|
#include <tools/urlobj.hxx>
|
|
|
|
#include <officecfg/Office/Common.hxx>
|
|
#include <libxml/xpath.h>
|
|
#include <datastreamgettime.hxx>
|
|
#include <dpobject.hxx>
|
|
#include <document.hxx>
|
|
#include <tokenarray.hxx>
|
|
#include <webservicelink.hxx>
|
|
|
|
#include <sc.hrc>
|
|
|
|
#include <cstring>
|
|
#include <memory>
|
|
#include <string_view>
|
|
#include <libxml/parser.h>
|
|
|
|
using namespace com::sun::star;
|
|
|
|
// TODO: Add new methods for ScInterpreter here.
|
|
|
|
void ScInterpreter::ScFilterXML()
|
|
{
|
|
sal_uInt8 nParamCount = GetByte();
|
|
if (!MustHaveParamCount( nParamCount, 2 ) )
|
|
return;
|
|
|
|
SCSIZE nMatCols = 1, nMatRows = 1, nNode = 0;
|
|
// In array/matrix context node elements' results are to be
|
|
// subsequently stored. Check this before obtaining any argument from
|
|
// the stack so the stack type can be used.
|
|
if (pJumpMatrix || IsInArrayContext())
|
|
{
|
|
if (pJumpMatrix)
|
|
{
|
|
// Single result, GetString() will retrieve the corresponding
|
|
// argument and JumpMatrix() will store it at the proper
|
|
// position. Note that nMatCols and nMatRows are still 1.
|
|
SCSIZE nCurCol = 0, nCurRow = 0;
|
|
pJumpMatrix->GetPos( nCurCol, nCurRow);
|
|
nNode = nCurRow;
|
|
}
|
|
else if (bMatrixFormula)
|
|
{
|
|
// If there is no formula cell then continue with a single
|
|
// result.
|
|
if (pMyFormulaCell)
|
|
{
|
|
SCCOL nCols;
|
|
SCROW nRows;
|
|
pMyFormulaCell->GetMatColsRows( nCols, nRows);
|
|
nMatCols = nCols;
|
|
nMatRows = nRows;
|
|
}
|
|
}
|
|
else if (GetStackType() == formula::svMatrix)
|
|
{
|
|
const ScMatrix* pPathMatrix = pStack[sp-1]->GetMatrix();
|
|
if (!pPathMatrix)
|
|
{
|
|
PushIllegalParameter();
|
|
return;
|
|
}
|
|
pPathMatrix->GetDimensions( nMatCols, nMatRows);
|
|
|
|
/* TODO: it is unclear what should happen if there are
|
|
* different path arguments in matrix elements. We may have to
|
|
* evaluate each, and for repeated identical paths use
|
|
* subsequent nodes. As is, the path at 0,0 is used as obtained
|
|
* by GetString(). */
|
|
|
|
}
|
|
}
|
|
if (!nMatCols || !nMatRows)
|
|
{
|
|
PushNoValue();
|
|
return;
|
|
}
|
|
|
|
OUString aXPathExpression = GetString().getString();
|
|
OUString aString = GetString().getString();
|
|
if(aString.isEmpty() || aXPathExpression.isEmpty())
|
|
{
|
|
PushError( FormulaError::NoValue );
|
|
return;
|
|
}
|
|
|
|
OString aOXPathExpression = OUStringToOString( aXPathExpression, RTL_TEXTENCODING_UTF8 );
|
|
const char* pXPathExpr = aOXPathExpression.getStr();
|
|
OString aOString = OUStringToOString( aString, RTL_TEXTENCODING_UTF8 );
|
|
const char* pXML = aOString.getStr();
|
|
|
|
std::shared_ptr<xmlParserCtxt> pContext(
|
|
xmlNewParserCtxt(), xmlFreeParserCtxt );
|
|
|
|
std::shared_ptr<xmlDoc> pDoc( xmlParseMemory( pXML, aOString.getLength() ),
|
|
xmlFreeDoc );
|
|
|
|
if(!pDoc)
|
|
{
|
|
PushError( FormulaError::NoValue );
|
|
return;
|
|
}
|
|
|
|
std::shared_ptr<xmlXPathContext> pXPathCtx( xmlXPathNewContext(pDoc.get()),
|
|
xmlXPathFreeContext );
|
|
|
|
std::shared_ptr<xmlXPathObject> pXPathObj( xmlXPathEvalExpression(BAD_CAST(pXPathExpr), pXPathCtx.get()),
|
|
xmlXPathFreeObject );
|
|
|
|
if(!pXPathObj)
|
|
{
|
|
PushError( FormulaError::NoValue );
|
|
return;
|
|
}
|
|
|
|
switch(pXPathObj->type)
|
|
{
|
|
case XPATH_UNDEFINED:
|
|
PushNoValue();
|
|
break;
|
|
case XPATH_NODESET:
|
|
{
|
|
xmlNodeSetPtr pNodeSet = pXPathObj->nodesetval;
|
|
if(!pNodeSet)
|
|
{
|
|
PushError( FormulaError::NoValue );
|
|
return;
|
|
}
|
|
|
|
const size_t nSize = pNodeSet->nodeNr;
|
|
if (nNode >= nSize)
|
|
{
|
|
// For pJumpMatrix
|
|
PushError( FormulaError::NotAvailable);
|
|
return;
|
|
}
|
|
|
|
/* TODO: for nMatCols>1 IF stack type is svMatrix, i.e.
|
|
* pPathMatrix!=nullptr, we may want a result matrix with
|
|
* nMatCols columns as well, but clarify first how to treat
|
|
* differing path elements. */
|
|
|
|
ScMatrixRef xResMat;
|
|
if (nMatRows > 1)
|
|
{
|
|
xResMat = GetNewMat( 1, nMatRows, true);
|
|
if (!xResMat)
|
|
{
|
|
PushError( FormulaError::CodeOverflow);
|
|
return;
|
|
}
|
|
}
|
|
|
|
for ( ; nNode < nMatRows; ++nNode)
|
|
{
|
|
if( nSize > nNode )
|
|
{
|
|
OUString aResult;
|
|
if(pNodeSet->nodeTab[nNode]->type == XML_NAMESPACE_DECL)
|
|
{
|
|
xmlNsPtr ns = reinterpret_cast<xmlNsPtr>(pNodeSet->nodeTab[nNode]);
|
|
xmlNodePtr cur = reinterpret_cast<xmlNodePtr>(ns->next);
|
|
std::shared_ptr<xmlChar> pChar2(xmlNodeGetContent(cur), xmlFree);
|
|
aResult = OStringToOUString(std::string_view(reinterpret_cast<char*>(pChar2.get())), RTL_TEXTENCODING_UTF8);
|
|
}
|
|
else
|
|
{
|
|
xmlNodePtr cur = pNodeSet->nodeTab[nNode];
|
|
std::shared_ptr<xmlChar> pChar2(xmlNodeGetContent(cur), xmlFree);
|
|
aResult = OStringToOUString(std::string_view(reinterpret_cast<char*>(pChar2.get())), RTL_TEXTENCODING_UTF8);
|
|
}
|
|
if (xResMat)
|
|
xResMat->PutString( mrStrPool.intern( aResult), 0, nNode);
|
|
else
|
|
PushString(aResult);
|
|
}
|
|
else
|
|
{
|
|
if (xResMat)
|
|
xResMat->PutError( FormulaError::NotAvailable, 0, nNode);
|
|
else
|
|
PushError( FormulaError::NotAvailable );
|
|
}
|
|
}
|
|
if (xResMat)
|
|
PushMatrix( xResMat);
|
|
}
|
|
break;
|
|
case XPATH_BOOLEAN:
|
|
{
|
|
bool bVal = pXPathObj->boolval != 0;
|
|
PushDouble(double(bVal));
|
|
}
|
|
break;
|
|
case XPATH_NUMBER:
|
|
{
|
|
double fVal = pXPathObj->floatval;
|
|
PushDouble(fVal);
|
|
}
|
|
break;
|
|
case XPATH_STRING:
|
|
PushString(OUString::createFromAscii(reinterpret_cast<char*>(pXPathObj->stringval)));
|
|
break;
|
|
#if LIBXML_VERSION < 21000 || defined(LIBXML_XPTR_LOCS_ENABLED)
|
|
case XPATH_POINT:
|
|
PushNoValue();
|
|
break;
|
|
case XPATH_RANGE:
|
|
PushNoValue();
|
|
break;
|
|
case XPATH_LOCATIONSET:
|
|
PushNoValue();
|
|
break;
|
|
#endif
|
|
case XPATH_USERS:
|
|
PushNoValue();
|
|
break;
|
|
case XPATH_XSLT_TREE:
|
|
PushNoValue();
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
static ScWebServiceLink* lcl_GetWebServiceLink(const sfx2::LinkManager* pLinkMgr, std::u16string_view rURL)
|
|
{
|
|
size_t nCount = pLinkMgr->GetLinks().size();
|
|
for (size_t i=0; i<nCount; ++i)
|
|
{
|
|
::sfx2::SvBaseLink* pBase = pLinkMgr->GetLinks()[i].get();
|
|
if (ScWebServiceLink* pLink = dynamic_cast<ScWebServiceLink*>(pBase))
|
|
{
|
|
if (pLink->GetURL() == rURL)
|
|
return pLink;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
static bool lcl_FunctionAccessLoadWebServiceLink( OUString& rResult, ScDocument* pDoc, const OUString& rURI )
|
|
{
|
|
// For FunctionAccess service always force a changed data update.
|
|
ScWebServiceLink aLink( pDoc, rURI);
|
|
if (aLink.DataChanged( OUString(), css::uno::Any()) != sfx2::SvBaseLink::UpdateResult::SUCCESS)
|
|
return false;
|
|
|
|
if (!aLink.HasResult())
|
|
return false;
|
|
|
|
rResult = aLink.GetResult();
|
|
|
|
return true;
|
|
}
|
|
|
|
void ScInterpreter::ScWebservice()
|
|
{
|
|
sal_uInt8 nParamCount = GetByte();
|
|
if (!MustHaveParamCount( nParamCount, 1 ) )
|
|
return;
|
|
|
|
OUString aURI = GetString().getString();
|
|
|
|
if (aURI.isEmpty())
|
|
{
|
|
PushError( FormulaError::NoValue );
|
|
return;
|
|
}
|
|
|
|
INetURLObject aObj(aURI, INetProtocol::File);
|
|
INetProtocol eProtocol = aObj.GetProtocol();
|
|
if (eProtocol != INetProtocol::Http && eProtocol != INetProtocol::Https)
|
|
{
|
|
PushError(FormulaError::NoValue);
|
|
return;
|
|
}
|
|
|
|
if (!mpLinkManager)
|
|
{
|
|
if (!mrDoc.IsFunctionAccess() || mrDoc.HasLinkFormulaNeedingCheck())
|
|
{
|
|
PushError( FormulaError::NoValue);
|
|
}
|
|
else
|
|
{
|
|
OUString aResult;
|
|
if (lcl_FunctionAccessLoadWebServiceLink( aResult, &mrDoc, aURI))
|
|
PushString( aResult);
|
|
else
|
|
PushError( FormulaError::NoValue);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Need to reinterpret after loading (build links)
|
|
pArr->AddRecalcMode( ScRecalcMode::ONLOAD_LENIENT );
|
|
|
|
// while the link is not evaluated, idle must be disabled (to avoid circular references)
|
|
bool bOldEnabled = mrDoc.IsIdleEnabled();
|
|
mrDoc.EnableIdle(false);
|
|
|
|
// Get/ Create link object
|
|
ScWebServiceLink* pLink = lcl_GetWebServiceLink(mpLinkManager, aURI);
|
|
|
|
bool bWasError = (pMyFormulaCell && pMyFormulaCell->GetRawError() != FormulaError::NONE);
|
|
|
|
if (!pLink)
|
|
{
|
|
pLink = new ScWebServiceLink(&mrDoc, aURI);
|
|
mpLinkManager->InsertFileLink(*pLink, sfx2::SvBaseLinkObjectType::ClientFile, aURI);
|
|
if ( mpLinkManager->GetLinks().size() == 1 ) // the first one?
|
|
{
|
|
SfxBindings* pBindings = mrDoc.GetViewBindings();
|
|
if (pBindings)
|
|
pBindings->Invalidate( SID_LINKS ); // Link-Manager enabled
|
|
}
|
|
|
|
//if the document was just loaded, but the ScDdeLink entry was missing, then
|
|
//don't update this link until the links are updated in response to the users
|
|
//decision
|
|
if (!mrDoc.HasLinkFormulaNeedingCheck())
|
|
{
|
|
pLink->Update();
|
|
}
|
|
|
|
if (pMyFormulaCell)
|
|
{
|
|
// StartListening after the Update to avoid circular references
|
|
pMyFormulaCell->StartListening(*pLink);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (pMyFormulaCell)
|
|
pMyFormulaCell->StartListening(*pLink);
|
|
}
|
|
|
|
// If a new Error from Reschedule appears when the link is executed then reset the errorflag
|
|
if (pMyFormulaCell && pMyFormulaCell->GetRawError() != FormulaError::NONE && !bWasError)
|
|
pMyFormulaCell->SetErrCode(FormulaError::NONE);
|
|
|
|
// check the value
|
|
if (pLink->HasResult())
|
|
PushString(pLink->GetResult());
|
|
else if (mrDoc.HasLinkFormulaNeedingCheck())
|
|
{
|
|
// If this formula cell is recalculated just after load and the
|
|
// expression is exactly WEBSERVICE("literal_URI") (i.e. no other
|
|
// calculation involved, not even a cell reference) and a cached
|
|
// result is set as hybrid string then use that as result value to
|
|
// prevent a #VALUE! result due to the "Automatic update of
|
|
// external links has been disabled."
|
|
// This will work only once, as the new formula cell result won't
|
|
// be a hybrid anymore.
|
|
/* TODO: the FormulaError::LinkFormulaNeedingCheck could be used as
|
|
* a signal for the formula cell to keep the hybrid string as
|
|
* result of the overall formula *iff* no higher prioritized
|
|
* ScRecalcMode than ONLOAD_LENIENT is present in the entire
|
|
* document (i.e. the formula result could not be influenced by an
|
|
* ONLOAD_MUST or ALWAYS recalc, necessary as we don't track
|
|
* interim results of subexpressions that could be compared), which
|
|
* also means to track setting ScRecalcMode somehow... note this is
|
|
* just a vague idea so far and might or might not work. */
|
|
if (pMyFormulaCell && pMyFormulaCell->HasHybridStringResult())
|
|
{
|
|
if (pMyFormulaCell->GetCode()->GetCodeLen() == 2)
|
|
{
|
|
formula::FormulaToken const * const * pRPN = pMyFormulaCell->GetCode()->GetCode();
|
|
if (pRPN[0]->GetType() == formula::svString && pRPN[1]->GetOpCode() == ocWebservice)
|
|
PushString( pMyFormulaCell->GetResultString());
|
|
else
|
|
PushError(FormulaError::LinkFormulaNeedingCheck);
|
|
}
|
|
else
|
|
PushError(FormulaError::LinkFormulaNeedingCheck);
|
|
}
|
|
else
|
|
PushError(FormulaError::LinkFormulaNeedingCheck);
|
|
}
|
|
else
|
|
PushError(FormulaError::NoValue);
|
|
|
|
mrDoc.EnableIdle(bOldEnabled);
|
|
mpLinkManager->CloseCachedComps();
|
|
}
|
|
|
|
/**
|
|
Returns a string in which all non-alphanumeric characters except stroke and
|
|
underscore (-_) have been replaced with a percent (%) sign
|
|
followed by hex digits.
|
|
It is encoded the same way that the posted data from a WWW form is encoded,
|
|
that is the same way as in application/x-www-form-urlencoded media type and
|
|
as per RFC 3986.
|
|
|
|
@see fdo#76870
|
|
*/
|
|
void ScInterpreter::ScEncodeURL()
|
|
{
|
|
sal_uInt8 nParamCount = GetByte();
|
|
if ( !MustHaveParamCount( nParamCount, 1 ) )
|
|
return;
|
|
|
|
OUString aStr = GetString().getString();
|
|
if ( aStr.isEmpty() )
|
|
{
|
|
PushError( FormulaError::NoValue );
|
|
return;
|
|
}
|
|
|
|
OString aUtf8Str( aStr.toUtf8());
|
|
const sal_Int32 nLen = aUtf8Str.getLength();
|
|
OStringBuffer aUrlBuf( nLen );
|
|
for ( int i = 0; i < nLen; i++ )
|
|
{
|
|
char c = aUtf8Str[ i ];
|
|
if ( rtl::isAsciiAlphanumeric( static_cast<unsigned char>( c ) ) || c == '-' || c == '_' )
|
|
aUrlBuf.append( c );
|
|
else
|
|
{
|
|
aUrlBuf.append( '%' );
|
|
OString convertedChar = OString::number( static_cast<unsigned char>( c ), 16 ).toAsciiUpperCase();
|
|
// RFC 3986 indicates:
|
|
// "A percent-encoded octet is encoded as a character triplet,
|
|
// consisting of the percent character "%" followed by the two hexadecimal digits
|
|
// representing that octet's numeric value"
|
|
if (convertedChar.getLength() == 1)
|
|
aUrlBuf.append("0");
|
|
aUrlBuf.append(convertedChar);
|
|
}
|
|
}
|
|
PushString( OUString::fromUtf8( aUrlBuf ) );
|
|
}
|
|
|
|
void ScInterpreter::ScDebugVar()
|
|
{
|
|
// This is to be used by developers only! Never document this for end
|
|
// users. This is a convenient way to extract arbitrary internal state to
|
|
// a cell for easier debugging.
|
|
|
|
if (!officecfg::Office::Common::Misc::ExperimentalMode::get())
|
|
{
|
|
PushError(FormulaError::NoName);
|
|
return;
|
|
}
|
|
|
|
if (!MustHaveParamCount(GetByte(), 1))
|
|
return;
|
|
|
|
rtl_uString* p = GetString().getDataIgnoreCase();
|
|
if (!p)
|
|
{
|
|
PushIllegalParameter();
|
|
return;
|
|
}
|
|
|
|
OUString aStrUpper(p);
|
|
|
|
if (aStrUpper == "PIVOTCOUNT")
|
|
{
|
|
// Set the number of pivot tables in the document.
|
|
|
|
double fVal = 0.0;
|
|
if (mrDoc.HasPivotTable())
|
|
{
|
|
const ScDPCollection* pDPs = mrDoc.GetDPCollection();
|
|
fVal = pDPs->GetCount();
|
|
}
|
|
PushDouble(fVal);
|
|
}
|
|
else if (aStrUpper == "DATASTREAM_IMPORT")
|
|
PushDouble( sc::datastream_get_time( sc::DebugTime::Import ) );
|
|
else if (aStrUpper == "DATASTREAM_RECALC")
|
|
PushDouble( sc::datastream_get_time( sc::DebugTime::Recalc ) );
|
|
else if (aStrUpper == "DATASTREAM_RENDER")
|
|
PushDouble( sc::datastream_get_time( sc::DebugTime::Render ) );
|
|
else
|
|
PushIllegalParameter();
|
|
}
|
|
|
|
void ScInterpreter::ScErf()
|
|
{
|
|
sal_uInt8 nParamCount = GetByte();
|
|
if (MustHaveParamCount( nParamCount, 1 ) )
|
|
PushDouble( std::erf( GetDouble() ) );
|
|
}
|
|
|
|
void ScInterpreter::ScErfc()
|
|
{
|
|
sal_uInt8 nParamCount = GetByte();
|
|
if (MustHaveParamCount( nParamCount, 1 ) )
|
|
PushDouble( std::erfc( GetDouble() ) );
|
|
}
|
|
|
|
void ScInterpreter::ScColor()
|
|
{
|
|
sal_uInt8 nParamCount = GetByte();
|
|
if(!MustHaveParamCount(nParamCount, 3, 4))
|
|
return;
|
|
|
|
double nAlpha = 0;
|
|
if(nParamCount == 4)
|
|
nAlpha = rtl::math::approxFloor(GetDouble());
|
|
|
|
if(nAlpha < 0 || nAlpha > 255)
|
|
{
|
|
PushIllegalArgument();
|
|
return;
|
|
}
|
|
|
|
double nBlue = rtl::math::approxFloor(GetDouble());
|
|
|
|
if(nBlue < 0 || nBlue > 255)
|
|
{
|
|
PushIllegalArgument();
|
|
return;
|
|
}
|
|
|
|
double nGreen = rtl::math::approxFloor(GetDouble());
|
|
|
|
if(nGreen < 0 || nGreen > 255)
|
|
{
|
|
PushIllegalArgument();
|
|
return;
|
|
}
|
|
|
|
double nRed = rtl::math::approxFloor(GetDouble());
|
|
|
|
if(nRed < 0 || nRed > 255)
|
|
{
|
|
PushIllegalArgument();
|
|
return;
|
|
}
|
|
|
|
double nVal = 256*256*256*nAlpha + 256*256*nRed + 256*nGreen + nBlue;
|
|
PushDouble(nVal);
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|