summaryrefslogtreecommitdiffstats
path: root/sc/source/core/tool/interpr7.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'sc/source/core/tool/interpr7.cxx')
-rw-r--r--sc/source/core/tool/interpr7.cxx556
1 files changed, 556 insertions, 0 deletions
diff --git a/sc/source/core/tool/interpr7.cxx b/sc/source/core/tool/interpr7.cxx
new file mode 100644
index 000000000..352c7cf70
--- /dev/null
+++ b/sc/source/core/tool/interpr7.cxx
@@ -0,0 +1,556 @@
+/* -*- 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>
+
+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: */