diff options
Diffstat (limited to 'sc/source/core/tool/interpr7.cxx')
-rw-r--r-- | sc/source/core/tool/interpr7.cxx | 556 |
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: */ |