/* -*- 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/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace com::sun::star; #define SC_CALLERPOS_NONE (-1) ScUnoAddInFuncData::ScUnoAddInFuncData( const OUString& rNam, const OUString& rLoc, OUString aDesc, sal_uInt16 nCat, OUString sHelp, uno::Reference xFunc, uno::Any aO, sal_Int32 nAC, const ScAddInArgDesc* pAD, sal_Int32 nCP ) : aOriginalName( rNam ), aLocalName( rLoc ), aUpperName( rNam ), aUpperLocal( rLoc ), aDescription(std::move( aDesc )), xFunction(std::move( xFunc )), aObject(std::move( aO )), nArgCount( nAC ), nCallerPos( nCP ), nCategory( nCat ), sHelpId(std::move( sHelp )), bCompInitialized( false ) { if ( nArgCount ) { pArgDescs.reset( new ScAddInArgDesc[nArgCount] ); for (sal_Int32 i=0; i& ScUnoAddInFuncData::GetCompNames() const { if ( !bCompInitialized ) { // read sequence of compatibility names on demand uno::Reference xAddIn; if ( aObject >>= xAddIn ) { uno::Reference xComp( xAddIn, uno::UNO_QUERY ); if ( xComp.is() && xFunction.is() ) { OUString aMethodName = xFunction->getName(); const uno::Sequence< sheet::LocalizedName> aCompNames( xComp->getCompatibilityNames( aMethodName )); maCompNames.clear(); for (const sheet::LocalizedName& rCompName : aCompNames) { maCompNames.emplace_back( LanguageTag::convertToBcp47( rCompName.Locale, false), rCompName.Name); } } } bCompInitialized = true; // also if not successful } return maCompNames; } void ScUnoAddInFuncData::SetCompNames( ::std::vector< ScUnoAddInFuncData::LocalizedName >&& rNew ) { OSL_ENSURE( !bCompInitialized, "SetCompNames after initializing" ); maCompNames = std::move(rNew); bCompInitialized = true; } void ScUnoAddInFuncData::SetEnglishName( const OUString& rEnglishName ) { if (!rEnglishName.isEmpty()) aUpperEnglish = ScCompiler::GetCharClassEnglish()->uppercase(rEnglishName); else { // A dumb fallback to not have an empty name, mainly just for the // assignment to ScFuncDesc::mxFuncName for the Function Wizard and // formula input tooltips. aUpperEnglish = aUpperLocal; } } bool ScUnoAddInFuncData::GetExcelName( const LanguageTag& rDestLang, OUString& rRetExcelName, bool bFallbackToAny ) const { const ::std::vector& rCompNames = GetCompNames(); if ( !rCompNames.empty() ) { const OUString& aSearch( rDestLang.getBcp47()); // First, check exact match without fallback overhead. ::std::vector::const_iterator itNames = std::find_if(rCompNames.begin(), rCompNames.end(), [&aSearch](const LocalizedName& rName) { return rName.maLocale == aSearch; }); if (itNames != rCompNames.end()) { rRetExcelName = (*itNames).maName; return true; } // For "en-US" try the most likely fallback of "en". if (aSearch == "en-US") { itNames = std::find_if(rCompNames.begin(), rCompNames.end(), [](const LocalizedName& rName) { return rName.maLocale == "en"; }); if (itNames != rCompNames.end()) { rRetExcelName = (*itNames).maName; return true; } } // Try match of fallback search with fallback locales, // appending also 'en-US' and 'en' to search if not queried. ::std::vector< OUString > aFallbackSearch( rDestLang.getFallbackStrings( true)); if (aSearch != "en-US") { aFallbackSearch.emplace_back("en-US"); if (aSearch != "en") { aFallbackSearch.emplace_back("en"); } } for (const auto& rSearch : aFallbackSearch) { for (const auto& rCompName : rCompNames) { ::std::vector< OUString > aFallbackLocales( LanguageTag( rCompName.maLocale).getFallbackStrings(true)); if (std::find(aFallbackLocales.begin(), aFallbackLocales.end(), rSearch) != aFallbackLocales.end()) { rRetExcelName = rCompName.maName; return true; } } } if (bFallbackToAny) { // Last resort, use first (default) entry. rRetExcelName = rCompNames[0].maName; return true; } } return false; } void ScUnoAddInFuncData::SetFunction( const uno::Reference< reflection::XIdlMethod>& rNewFunc, const uno::Any& rNewObj ) { xFunction = rNewFunc; aObject = rNewObj; } void ScUnoAddInFuncData::SetArguments( sal_Int32 nNewCount, const ScAddInArgDesc* pNewDescs ) { nArgCount = nNewCount; if ( nArgCount ) { pArgDescs.reset( new ScAddInArgDesc[nArgCount] ); for (sal_Int32 i=0; i xManager = comphelper::getProcessServiceFactory(); uno::Reference xEnAc( xManager, uno::UNO_QUERY ); if ( xEnAc.is() ) { uno::Reference xEnum = xEnAc->createContentEnumeration( u"com.sun.star.sheet.AddIn"_ustr ); if ( xEnum.is() ) { // loop through all AddIns while ( xEnum->hasMoreElements() ) { uno::Any aAddInAny = xEnum->nextElement(); try { uno::Reference xIntFac; aAddInAny >>= xIntFac; if ( xIntFac.is() ) { // #i59984# try XSingleComponentFactory in addition to (old) XSingleServiceFactory, // passing the context to the component uno::Reference xInterface; uno::Reference xCtx( comphelper::getComponentContext(xManager)); uno::Reference xCFac( xIntFac, uno::UNO_QUERY ); if (xCFac.is()) { xInterface = xCFac->createInstanceWithContext(xCtx); if (xInterface.is()) ReadFromAddIn( xInterface ); } if (!xInterface.is()) { uno::Reference xFac( xIntFac, uno::UNO_QUERY ); if ( xFac.is() ) { xInterface = xFac->createInstance(); if (xInterface.is()) ReadFromAddIn( xInterface ); } } } } catch ( const uno::Exception& ) { SAL_WARN ( "sc", "Failed to initialize create instance of sheet.AddIn" ); } } } } // ReadConfiguration is called after looking at the AddIn implementations. // Duplicated are skipped (by using the service information, they don't have to be updated again // when argument information is needed). ReadConfiguration(); bInitialized = true; // with or without functions } static sal_uInt16 lcl_GetCategory( std::u16string_view rName ) { static const char* aFuncNames[SC_FUNCGROUP_COUNT] = { // array index = ID - 1 (ID starts at 1) // all upper case "Database", // ID_FUNCTION_GRP_DATABASE "Date&Time", // ID_FUNCTION_GRP_DATETIME "Financial", // ID_FUNCTION_GRP_FINANCIAL "Information", // ID_FUNCTION_GRP_INFO "Logical", // ID_FUNCTION_GRP_LOGIC "Mathematical", // ID_FUNCTION_GRP_MATH "Matrix", // ID_FUNCTION_GRP_MATRIX "Statistical", // ID_FUNCTION_GRP_STATISTIC "Spreadsheet", // ID_FUNCTION_GRP_TABLE "Text", // ID_FUNCTION_GRP_TEXT "Add-In" // ID_FUNCTION_GRP_ADDINS }; for (sal_uInt16 i=0; iGetAddInCfg(); // Additional, temporary config item for the display names and // compatibility names. ScLinkConfigItem aAllLocalesConfig( CFGPATH_ADDINS, ConfigItemMode::AllLocales ); // CommitLink is not used (only reading values) const OUString sSlash('/'); // get the list of add-ins (services) const uno::Sequence aServiceNames = rAddInConfig.GetNodeNames( u""_ustr ); for ( const OUString& aServiceName : aServiceNames ) { ScUnoAddInHelpIdGenerator aHelpIdGenerator( aServiceName ); OUString aFunctionsPath(aServiceName + sSlash + CFGSTR_ADDINFUNCTIONS); uno::Sequence aFunctionNames = rAddInConfig.GetNodeNames( aFunctionsPath ); sal_Int32 nNewCount = aFunctionNames.getLength(); // allocate pointers sal_Int32 nOld = nFuncCount; nFuncCount = nNewCount+nOld; if ( nOld ) { std::unique_ptr[]> ppNew(new std::unique_ptr[nFuncCount]); for (sal_Int32 i=0; i[nFuncCount] ); //TODO: adjust bucket count? if ( !pExactHashMap ) pExactHashMap.reset( new ScAddInHashMap ); if ( !pNameHashMap ) pNameHashMap.reset( new ScAddInHashMap ); if ( !pLocalHashMap ) pLocalHashMap.reset( new ScAddInHashMap ); if ( !pEnglishHashMap ) pEnglishHashMap.reset( new ScAddInHashMap ); //TODO: get the function information in a single call for all functions? const OUString* pFuncNameArray = aFunctionNames.getConstArray(); for ( sal_Int32 nFuncPos = 0; nFuncPos < nNewCount; nFuncPos++ ) { ppFuncData[nFuncPos+nOld] = nullptr; // stored function name: (service name).(function) OUString aFuncName = aServiceName + "." + pFuncNameArray[nFuncPos]; // skip the function if already known (read from old AddIn service) if ( pExactHashMap->find( aFuncName ) == pExactHashMap->end() ) { OUString aEnglishName; OUString aLocalName; OUString aDescription; sal_uInt16 nCategory = ID_FUNCTION_GRP_ADDINS; // get direct information on the function OUString aFuncPropPath = aFunctionsPath + sSlash + pFuncNameArray[nFuncPos] + sSlash; uno::Sequence aFuncPropNames{ (aFuncPropPath + CFGSTR_DISPLAYNAME), // CFG_FUNCPROP_DISPLAYNAME (aFuncPropPath + CFGSTR_DESCRIPTION), // CFG_FUNCPROP_DESCRIPTION (aFuncPropPath + CFGSTR_CATEGORY)}; // CFG_FUNCPROP_CATEGORY uno::Sequence aFuncProperties = rAddInConfig.GetProperties( aFuncPropNames ); if ( aFuncProperties.getLength() == CFG_FUNCPROP_COUNT ) { aFuncProperties[CFG_FUNCPROP_DISPLAYNAME] >>= aLocalName; aFuncProperties[CFG_FUNCPROP_DESCRIPTION] >>= aDescription; OUString aCategoryName; aFuncProperties[CFG_FUNCPROP_CATEGORY] >>= aCategoryName; nCategory = lcl_GetCategory( aCategoryName ); } // get English display name OUString aDisplayNamePath(aFuncPropPath + CFGSTR_DISPLAYNAME); uno::Sequence aDisplayNamePropNames( &aDisplayNamePath, 1 ); uno::Sequence aDisplayNameProperties = aAllLocalesConfig.GetProperties( aDisplayNamePropNames ); if ( aDisplayNameProperties.getLength() == 1 ) { uno::Sequence aLocalEntries; if ( aDisplayNameProperties[0] >>= aLocalEntries ) { for (const beans::PropertyValue& rConfig : aLocalEntries) { // PropertyValue name is the locale ("convert" from // string to canonicalize). OUString aLocale( LanguageTag( rConfig.Name, true).getBcp47( false)); // PropertyValue value is the localized value (string in this case). OUString aName; rConfig.Value >>= aName; // Accept 'en' and 'en-...' but prefer 'en-US'. if (aLocale == "en-US" && !aName.isEmpty()) aEnglishName = aName; else if (aEnglishName.isEmpty() && (aLocale == "en" || aLocale.startsWith("en-"))) aEnglishName = aName; } } } bool bNeedEnglish = aEnglishName.isEmpty(); // get compatibility names ::std::vector aCompNames; OUString aCompPath(aFuncPropPath + CFGSTR_COMPATIBILITYNAME); uno::Sequence aCompPropNames( &aCompPath, 1 ); uno::Sequence aCompProperties = aAllLocalesConfig.GetProperties( aCompPropNames ); if ( aCompProperties.getLength() == 1 ) { uno::Sequence aLocalEntries; if ( aCompProperties[0] >>= aLocalEntries ) { for (const beans::PropertyValue& rConfig : aLocalEntries) { // PropertyValue name is the locale ("convert" from // string to canonicalize). OUString aLocale( LanguageTag( rConfig.Name, true).getBcp47( false)); // PropertyValue value is the localized value (string in this case). OUString aName; rConfig.Value >>= aName; if (!aName.isEmpty()) { aCompNames.emplace_back( aLocale, aName); if (bNeedEnglish) { // Accept 'en' and 'en-...' but prefer 'en-US'. if (aLocale == "en-US") aEnglishName = aName; else if (aEnglishName.isEmpty() && (aLocale == "en" || aLocale.startsWith("en-"))) aEnglishName = aName; } } } } } // get argument info std::unique_ptr pVisibleArgs; sal_Int32 nVisibleCount = 0; OUString aArgumentsPath(aFuncPropPath + CFGSTR_PARAMETERS); const uno::Sequence aArgumentNames = rAddInConfig.GetNodeNames( aArgumentsPath ); sal_Int32 nArgumentCount = aArgumentNames.getLength(); if ( nArgumentCount ) { // get DisplayName and Description for each argument uno::Sequence aArgPropNames( nArgumentCount * 2 ); OUString* pPropNameArray = aArgPropNames.getArray(); sal_Int32 nIndex = 0; for ( const OUString& rArgName : aArgumentNames ) { OUString aOneArgPath = aArgumentsPath + sSlash + rArgName + sSlash; pPropNameArray[nIndex++] = aOneArgPath + CFGSTR_DISPLAYNAME; pPropNameArray[nIndex++] = aOneArgPath + CFGSTR_DESCRIPTION; } uno::Sequence aArgProperties = rAddInConfig.GetProperties( aArgPropNames ); if ( aArgProperties.getLength() == aArgPropNames.getLength() ) { const OUString* pArgNameArray = aArgumentNames.getConstArray(); const uno::Any* pPropArray = aArgProperties.getConstArray(); OUString sDisplayName; OUString sDescription; ScAddInArgDesc aDesc; aDesc.eType = SC_ADDINARG_NONE; // arg type is not in configuration aDesc.bOptional = false; nVisibleCount = nArgumentCount; pVisibleArgs.reset(new ScAddInArgDesc[nVisibleCount]); nIndex = 0; for ( sal_Int32 nArgument = 0; nArgument < nArgumentCount; nArgument++ ) { pPropArray[nIndex++] >>= sDisplayName; pPropArray[nIndex++] >>= sDescription; aDesc.aInternalName = pArgNameArray[nArgument]; aDesc.aName = sDisplayName; aDesc.aDescription = sDescription; pVisibleArgs[nArgument] = aDesc; } } } OUString sHelpId = aHelpIdGenerator.GetHelpId( pFuncNameArray[nFuncPos] ); uno::Reference xFunc; // remains empty uno::Any aObject; // also empty // create and insert into the array ScUnoAddInFuncData* pData = new ScUnoAddInFuncData( aFuncName, aLocalName, aDescription, nCategory, sHelpId, xFunc, aObject, nVisibleCount, pVisibleArgs.get(), SC_CALLERPOS_NONE ); pData->SetCompNames( std::move(aCompNames) ); ppFuncData[nFuncPos+nOld].reset(pData); pExactHashMap->emplace( pData->GetOriginalName(), pData ); pNameHashMap->emplace( pData->GetUpperName(), pData ); pLocalHashMap->emplace( pData->GetUpperLocal(), pData ); if (aEnglishName.isEmpty()) SAL_WARN("sc.core", "no English name for " << aLocalName << " " << aFuncName); else { pEnglishHashMap->emplace( ScCompiler::GetCharClassEnglish()->uppercase(aEnglishName), pData ); } pData->SetEnglishName(aEnglishName); // takes care of handling empty } } } } void ScUnoAddInCollection::LoadComponent( const ScUnoAddInFuncData& rFuncData ) { const OUString& aFullName = rFuncData.GetOriginalName(); sal_Int32 nPos = aFullName.lastIndexOf( '.' ); if ( nPos <= 0 ) return; OUString aServiceName = aFullName.copy( 0, nPos ); try { uno::Reference xServiceFactory = comphelper::getProcessServiceFactory(); uno::Reference xInterface( xServiceFactory->createInstance( aServiceName ) ); if (xInterface.is()) UpdateFromAddIn( xInterface, aServiceName ); } catch (const uno::Exception &) { SAL_WARN ("sc", "Failed to create addin component '" << aServiceName << "'"); } } bool ScUnoAddInCollection::GetExcelName( const OUString& rCalcName, LanguageType eDestLang, OUString& rRetExcelName ) { const ScUnoAddInFuncData* pFuncData = GetFuncData( rCalcName ); if ( pFuncData ) return pFuncData->GetExcelName( LanguageTag( eDestLang), rRetExcelName); return false; } bool ScUnoAddInCollection::GetCalcName( const OUString& rExcelName, OUString& rRetCalcName ) { if (!bInitialized) Initialize(); OUString aUpperCmp = ScGlobal::getCharClass().uppercase(rExcelName); for (sal_Int32 i=0; i& rNames = pFuncData->GetCompNames(); auto bFound = std::any_of(rNames.begin(), rNames.end(), [&aUpperCmp](const ScUnoAddInFuncData::LocalizedName& rName) { return ScGlobal::getCharClass().uppercase( rName.maName ) == aUpperCmp; }); if (bFound) { //TODO: store upper case for comparing? // use the first function that has this name for any language rRetCalcName = pFuncData->GetOriginalName(); return true; } } } return false; } static bool IsTypeName( std::u16string_view rName, const uno::Type& rType ) { return rName == rType.getTypeName(); } static bool lcl_ValidReturnType( const uno::Reference& xClass ) { // this must match with ScUnoAddInCall::SetResult if ( !xClass.is() ) return false; switch (xClass->getTypeClass()) { case uno::TypeClass_ANY: // variable type case uno::TypeClass_ENUM: //TODO: ??? case uno::TypeClass_BOOLEAN: case uno::TypeClass_CHAR: case uno::TypeClass_BYTE: case uno::TypeClass_SHORT: case uno::TypeClass_UNSIGNED_SHORT: case uno::TypeClass_LONG: case uno::TypeClass_UNSIGNED_LONG: case uno::TypeClass_FLOAT: case uno::TypeClass_DOUBLE: case uno::TypeClass_STRING: return true; // values or string case uno::TypeClass_INTERFACE: { // return type XInterface may contain a XVolatileResult //TODO: XIdlClass needs getType() method! OUString sName = xClass->getName(); return ( IsTypeName( sName, cppu::UnoType::get()) || IsTypeName( sName, cppu::UnoType::get()) ); } default: { // nested sequences for arrays //TODO: XIdlClass needs getType() method! OUString sName = xClass->getName(); return ( IsTypeName( sName, cppu::UnoType >>::get() ) || IsTypeName( sName, cppu::UnoType >>::get() ) || IsTypeName( sName, cppu::UnoType >>::get() ) || IsTypeName( sName, cppu::UnoType >>::get() ) ); } } } static ScAddInArgumentType lcl_GetArgType( const uno::Reference& xClass ) { if (!xClass.is()) return SC_ADDINARG_NONE; uno::TypeClass eType = xClass->getTypeClass(); if ( eType == uno::TypeClass_LONG ) //TODO: other integer types? return SC_ADDINARG_INTEGER; if ( eType == uno::TypeClass_DOUBLE ) return SC_ADDINARG_DOUBLE; if ( eType == uno::TypeClass_STRING ) return SC_ADDINARG_STRING; //TODO: XIdlClass needs getType() method! OUString sName = xClass->getName(); if (IsTypeName( sName, cppu::UnoType >>::get() )) return SC_ADDINARG_INTEGER_ARRAY; if (IsTypeName( sName, cppu::UnoType >>::get() )) return SC_ADDINARG_DOUBLE_ARRAY; if (IsTypeName( sName, cppu::UnoType >>::get() )) return SC_ADDINARG_STRING_ARRAY; if (IsTypeName( sName, cppu::UnoType >>::get() )) return SC_ADDINARG_MIXED_ARRAY; if (IsTypeName( sName, cppu::UnoType::get())) return SC_ADDINARG_VALUE_OR_ARRAY; if (IsTypeName( sName, cppu::UnoType::get())) return SC_ADDINARG_CELLRANGE; if (IsTypeName( sName, cppu::UnoType::get())) return SC_ADDINARG_CALLER; if (IsTypeName( sName, cppu::UnoType>::get() )) return SC_ADDINARG_VARARGS; return SC_ADDINARG_NONE; } void ScUnoAddInCollection::ReadFromAddIn( const uno::Reference& xInterface ) { uno::Reference xAddIn( xInterface, uno::UNO_QUERY ); uno::Reference xName( xInterface, uno::UNO_QUERY ); if ( !(xAddIn.is() && xName.is()) ) return; // Even if GetUseEnglishFunctionName() would return true, do not set the // locale to en-US to get English function names as that also would mix in // English descriptions and parameter names. Also, setting a locale will // reinitialize the Add-In completely, so switching back and forth isn't a // good idea either. xAddIn->setLocale( Application::GetSettings().GetUILanguageTag().getLocale()); // Instead, in a second run with 'en-US' obtain English names. struct FuncNameData { OUString aFuncU; ScUnoAddInFuncData* pData; }; std::vector aFuncNameData; OUString aServiceName( xName->getServiceName() ); ScUnoAddInHelpIdGenerator aHelpIdGenerator( aServiceName ); //TODO: pass XIntrospection to ReadFromAddIn const uno::Reference& xContext = comphelper::getProcessComponentContext(); uno::Reference xIntro = beans::theIntrospection::get( xContext ); uno::Any aObject; aObject <<= xAddIn; uno::Reference xAcc = xIntro->inspect(aObject); if (!xAcc.is()) return; uno::Sequence< uno::Reference > aMethods = xAcc->getMethods( beans::MethodConcept::ALL ); sal_Int32 nNewCount = aMethods.getLength(); if ( !nNewCount ) return; sal_Int32 nOld = nFuncCount; nFuncCount = nNewCount+nOld; if ( nOld ) { std::unique_ptr[]> ppNew(new std::unique_ptr[nFuncCount]); for (sal_Int32 i=0; i[nFuncCount]); //TODO: adjust bucket count? if ( !pExactHashMap ) pExactHashMap.reset( new ScAddInHashMap ); if ( !pNameHashMap ) pNameHashMap.reset( new ScAddInHashMap ); if ( !pLocalHashMap ) pLocalHashMap.reset( new ScAddInHashMap ); if ( !pEnglishHashMap ) pEnglishHashMap.reset( new ScAddInHashMap ); const uno::Reference* pArray = aMethods.getConstArray(); for (sal_Int32 nFuncPos=0; nFuncPos xFunc = pArray[nFuncPos]; if (xFunc.is()) { // leave out internal functions uno::Reference xClass = xFunc->getDeclaringClass(); bool bSkip = true; if ( xClass.is() ) { //TODO: XIdlClass needs getType() method! OUString sName = xClass->getName(); bSkip = ( IsTypeName( sName, cppu::UnoType::get()) || IsTypeName( sName, cppu::UnoType::get()) || IsTypeName( sName, cppu::UnoType::get()) || IsTypeName( sName, cppu::UnoType::get()) ); } if (!bSkip) { uno::Reference xReturn = xFunc->getReturnType(); if ( !lcl_ValidReturnType( xReturn ) ) bSkip = true; } if (!bSkip) { OUString aFuncU = xFunc->getName(); // stored function name: (service name).(function) OUString aFuncName = aServiceName + "." + aFuncU; bool bValid = true; sal_Int32 nVisibleCount = 0; sal_Int32 nCallerPos = SC_CALLERPOS_NONE; uno::Sequence aParams = xFunc->getParameterInfos(); sal_Int32 nParamCount = aParams.getLength(); const reflection::ParamInfo* pParArr = aParams.getConstArray(); sal_Int32 nParamPos; for (nParamPos=0; nParamPos xParClass = pParArr[nParamPos].aType; ScAddInArgumentType eArgType = lcl_GetArgType( xParClass ); if ( eArgType == SC_ADDINARG_NONE ) bValid = false; else if ( eArgType == SC_ADDINARG_CALLER ) nCallerPos = nParamPos; else ++nVisibleCount; } if (bValid) { sal_uInt16 nCategory = lcl_GetCategory( xAddIn->getProgrammaticCategoryName( aFuncU ) ); OUString sHelpId = aHelpIdGenerator.GetHelpId( aFuncU ); OUString aLocalName; try { aLocalName = xAddIn-> getDisplayFunctionName( aFuncU ); } catch(uno::Exception&) { aLocalName = "###"; } OUString aDescription; try { aDescription = xAddIn-> getFunctionDescription( aFuncU ); } catch(uno::Exception&) { aDescription = "###"; } std::unique_ptr pVisibleArgs; if ( nVisibleCount > 0 ) { ScAddInArgDesc aDesc; pVisibleArgs.reset(new ScAddInArgDesc[nVisibleCount]); sal_Int32 nDestPos = 0; for (nParamPos=0; nParamPos xParClass = pParArr[nParamPos].aType; ScAddInArgumentType eArgType = lcl_GetArgType( xParClass ); if ( eArgType != SC_ADDINARG_CALLER ) { OUString aArgName; try { aArgName = xAddIn-> getDisplayArgumentName( aFuncU, nParamPos ); } catch(uno::Exception&) { aArgName = "###"; } OUString aArgDesc; try { aArgDesc = xAddIn-> getArgumentDescription( aFuncU, nParamPos ); } catch(uno::Exception&) { aArgDesc = "###"; } bool bOptional = ( eArgType == SC_ADDINARG_VALUE_OR_ARRAY || eArgType == SC_ADDINARG_VARARGS ); aDesc.eType = eArgType; aDesc.aName = aArgName; aDesc.aDescription = aArgDesc; aDesc.bOptional = bOptional; //TODO: initialize aInternalName only from config? aDesc.aInternalName = pParArr[nParamPos].aName; pVisibleArgs[nDestPos++] = aDesc; } } OSL_ENSURE( nDestPos==nVisibleCount, "wrong count" ); } ppFuncData[nFuncPos+nOld].reset( new ScUnoAddInFuncData( aFuncName, aLocalName, aDescription, nCategory, sHelpId, xFunc, aObject, nVisibleCount, pVisibleArgs.get(), nCallerPos ) ); ScUnoAddInFuncData* pData = ppFuncData[nFuncPos+nOld].get(); pExactHashMap->emplace( pData->GetOriginalName(), pData ); pNameHashMap->emplace( pData->GetUpperName(), pData ); pLocalHashMap->emplace( pData->GetUpperLocal(), pData ); aFuncNameData.push_back({aFuncU, pData}); } } } } const LanguageTag aEnglishLanguageTag(LANGUAGE_ENGLISH_US); xAddIn->setLocale( aEnglishLanguageTag.getLocale()); for (const auto& rFunc : aFuncNameData) { OUString aEnglishName; try { aEnglishName = xAddIn->getDisplayFunctionName( rFunc.aFuncU ); } catch(uno::Exception&) { } if (aEnglishName.isEmpty() && rFunc.pData->GetExcelName( aEnglishLanguageTag, aEnglishName, false /*bFallbackToAny*/)) { // Check our known suffixes and append if not present. Note this // depends on localization (that should not add such suffix, but..) // and is really only a last resort. if (rFunc.pData->GetLocalName().endsWith("_ADD") && !aEnglishName.endsWith("_ADD")) aEnglishName += "_ADD"; else if (rFunc.pData->GetLocalName().endsWith("_EXCEL2003") && !aEnglishName.endsWith("_EXCEL2003")) aEnglishName += "_EXCEL2003"; SAL_WARN("sc.core", "obtaining English name for " << rFunc.pData->GetLocalName() << " " << rFunc.pData->GetOriginalName() << " as ExcelName '" << aEnglishName << "'"); } SAL_WARN_IF(aEnglishName.isEmpty(), "sc.core", "no English name for " << rFunc.pData->GetLocalName() << " " << rFunc.pData->GetOriginalName()); rFunc.pData->SetEnglishName(aEnglishName); // takes care of handling empty pEnglishHashMap->emplace( rFunc.pData->GetUpperEnglish(), rFunc.pData); } } static void lcl_UpdateFunctionList( const ScFunctionList& rFunctionList, const ScUnoAddInFuncData& rFuncData, bool bEnglishFunctionNames ) { // as used in FillFunctionDescFromData const OUString& aCompare = (bEnglishFunctionNames ? rFuncData.GetUpperEnglish() : rFuncData.GetUpperLocal()); sal_uLong nCount = rFunctionList.GetCount(); for (sal_uLong nPos=0; nPosmxFuncName && *pDesc->mxFuncName == aCompare ) { ScUnoAddInCollection::FillFunctionDescFromData( rFuncData, *const_cast(pDesc), bEnglishFunctionNames); break; } } } static const ScAddInArgDesc* lcl_FindArgDesc( const ScUnoAddInFuncData& rFuncData, std::u16string_view rArgIntName ) { sal_Int32 nArgCount = rFuncData.GetArgumentCount(); const ScAddInArgDesc* pArguments = rFuncData.GetArguments(); for (sal_Int32 nPos=0; nPos& xInterface, std::u16string_view rServiceName ) { const bool bEnglishFunctionNames = ScModule::get()->GetFormulaOptions().GetUseEnglishFuncName(); uno::Reference xLoc( xInterface, uno::UNO_QUERY ); if ( xLoc.is() ) // optional in new add-ins xLoc->setLocale( Application::GetSettings().GetUILanguageTag().getLocale()); // if function list was already initialized, it must be updated ScFunctionList* pFunctionList = nullptr; if ( ScGlobal::HasStarCalcFunctionList() ) pFunctionList = ScGlobal::GetStarCalcFunctionList(); // only get the function information from Introspection const uno::Reference& xContext = comphelper::getProcessComponentContext(); uno::Reference xIntro = beans::theIntrospection::get(xContext); uno::Any aObject; aObject <<= xInterface; uno::Reference xAcc = xIntro->inspect(aObject); if (!xAcc.is()) return; const uno::Sequence< uno::Reference > aMethods = xAcc->getMethods( beans::MethodConcept::ALL ); for (const uno::Reference& xFunc : aMethods) { if (xFunc.is()) { OUString aFuncU = xFunc->getName(); // stored function name: (service name).(function) OUString aFuncName = OUString::Concat(rServiceName) + "." + aFuncU; // internal names are skipped because no FuncData exists ScUnoAddInFuncData* pOldData = const_cast( GetFuncData( aFuncName ) ); if ( pOldData ) { // Create new (complete) argument info. // As in ReadFromAddIn, the reflection information is authoritative. // Local names and descriptions from pOldData are looked up using the // internal argument name. bool bValid = true; sal_Int32 nVisibleCount = 0; sal_Int32 nCallerPos = SC_CALLERPOS_NONE; const uno::Sequence aParams = xFunc->getParameterInfos(); sal_Int32 nParamCount = aParams.getLength(); const reflection::ParamInfo* pParArr = aParams.getConstArray(); for (sal_Int32 nParamPos=0; nParamPos xParClass = pParArr[nParamPos].aType; ScAddInArgumentType eArgType = lcl_GetArgType( xParClass ); if ( eArgType == SC_ADDINARG_NONE ) bValid = false; else if ( eArgType == SC_ADDINARG_CALLER ) nCallerPos = nParamPos; else ++nVisibleCount; } if (bValid) { std::unique_ptr pVisibleArgs; if ( nVisibleCount > 0 ) { ScAddInArgDesc aDesc; pVisibleArgs.reset(new ScAddInArgDesc[nVisibleCount]); sal_Int32 nDestPos = 0; for (const auto& rParam : aParams) { uno::Reference xParClass = rParam.aType; ScAddInArgumentType eArgType = lcl_GetArgType( xParClass ); if ( eArgType != SC_ADDINARG_CALLER ) { const ScAddInArgDesc* pOldArgDesc = lcl_FindArgDesc( *pOldData, rParam.aName ); if ( pOldArgDesc ) { aDesc.aName = pOldArgDesc->aName; aDesc.aDescription = pOldArgDesc->aDescription; } else aDesc.aName = aDesc.aDescription = "###"; bool bOptional = ( eArgType == SC_ADDINARG_VALUE_OR_ARRAY || eArgType == SC_ADDINARG_VARARGS ); aDesc.eType = eArgType; aDesc.bOptional = bOptional; //TODO: initialize aInternalName only from config? aDesc.aInternalName = rParam.aName; pVisibleArgs[nDestPos++] = aDesc; } } OSL_ENSURE( nDestPos==nVisibleCount, "wrong count" ); } pOldData->SetFunction( xFunc, aObject ); pOldData->SetArguments( nVisibleCount, pVisibleArgs.get() ); pOldData->SetCallerPos( nCallerPos ); if ( pFunctionList ) lcl_UpdateFunctionList( *pFunctionList, *pOldData, bEnglishFunctionNames ); } } } } } OUString ScUnoAddInCollection::FindFunction( const OUString& rUpperName, bool bLocalFirst ) { if (!bInitialized) Initialize(); if (nFuncCount == 0) return OUString(); if ( bLocalFirst ) { // Only scan local names (used for entering formulas). ScAddInHashMap::const_iterator iLook( pLocalHashMap->find( rUpperName ) ); if ( iLook != pLocalHashMap->end() ) return iLook->second->GetOriginalName(); } else { // First scan international programmatic names (used when calling a // function). ScAddInHashMap::const_iterator iLook( pNameHashMap->find( rUpperName ) ); if ( iLook != pNameHashMap->end() ) return iLook->second->GetOriginalName(); // Then scan English names (as FunctionAccess API could expect). iLook = pEnglishHashMap->find( rUpperName ); if ( iLook != pEnglishHashMap->end() ) return iLook->second->GetOriginalName(); // After that, scan all local names; either to allow replacing old // AddIns with Uno, or for functions where the AddIn did not provide an // English name. iLook = pLocalHashMap->find( rUpperName ); if ( iLook != pLocalHashMap->end() ) return iLook->second->GetOriginalName(); } return OUString(); } const ScUnoAddInFuncData* ScUnoAddInCollection::GetFuncData( const OUString& rName, bool bComplete ) { if (!bInitialized) Initialize(); // rName must be the exact internal name ScAddInHashMap::const_iterator iLook( pExactHashMap->find( rName ) ); if ( iLook != pExactHashMap->end() ) { const ScUnoAddInFuncData* pFuncData = iLook->second; if ( bComplete && !pFuncData->GetFunction().is() ) //TODO: extra flag? LoadComponent( *pFuncData ); return pFuncData; } return nullptr; } const ScUnoAddInFuncData* ScUnoAddInCollection::GetFuncData( sal_Int32 nIndex ) { if (!bInitialized) Initialize(); if (nIndex < nFuncCount) return ppFuncData[nIndex].get(); return nullptr; } void ScUnoAddInCollection::LocalizeString( OUString& rName ) { if (!bInitialized) Initialize(); // modify rName - input: exact name ScAddInHashMap::const_iterator iLook( pExactHashMap->find( rName ) ); if ( iLook != pExactHashMap->end() ) rName = iLook->second->GetUpperLocal(); //TODO: upper? } sal_Int32 ScUnoAddInCollection::GetFuncCount() { if (!bInitialized) Initialize(); return nFuncCount; } bool ScUnoAddInCollection::FillFunctionDesc( sal_Int32 nFunc, ScFuncDesc& rDesc, bool bEnglishFunctionNames ) { if (!bInitialized) Initialize(); if (nFunc >= nFuncCount || !ppFuncData[nFunc]) return false; const ScUnoAddInFuncData& rFuncData = *ppFuncData[nFunc]; return FillFunctionDescFromData( rFuncData, rDesc, bEnglishFunctionNames ); } bool ScUnoAddInCollection::FillFunctionDescFromData( const ScUnoAddInFuncData& rFuncData, ScFuncDesc& rDesc, bool bEnglishFunctionNames ) { rDesc.Clear(); bool bIncomplete = !rFuncData.GetFunction().is(); //TODO: extra flag? sal_Int32 nArgCount = rFuncData.GetArgumentCount(); if ( nArgCount > SAL_MAX_UINT16 ) return false; if ( bIncomplete ) nArgCount = 0; // if incomplete, fill without argument info (no wrong order) // nFIndex is set from outside rDesc.mxFuncName = (bEnglishFunctionNames ? rFuncData.GetUpperEnglish() : rFuncData.GetUpperLocal()); rDesc.nCategory = rFuncData.GetCategory(); rDesc.sHelpId = rFuncData.GetHelpId(); OUString aDesc = rFuncData.GetDescription(); if (aDesc.isEmpty()) aDesc = rFuncData.GetLocalName(); // use name if no description is available rDesc.mxFuncDesc = aDesc ; // AddInArgumentType_CALLER is already left out in FuncData rDesc.nArgCount = static_cast(nArgCount); if ( nArgCount ) { bool bMultiple = false; const ScAddInArgDesc* pArgs = rFuncData.GetArguments(); rDesc.maDefArgNames.clear(); rDesc.maDefArgNames.resize(nArgCount); rDesc.maDefArgDescs.clear(); rDesc.maDefArgDescs.resize(nArgCount); rDesc.pDefArgFlags = new ScFuncDesc::ParameterFlags[nArgCount]; for ( sal_Int32 nArg=0; nArgGetArgumentCount(); const ScAddInArgDesc* pArgs = pFuncData->GetArguments(); // is aVarArg sequence needed? if ( nParamCount >= nDescCount && nDescCount > 0 && pArgs[nDescCount-1].eType == SC_ADDINARG_VARARGS ) { sal_Int32 nVarCount = nParamCount - ( nDescCount - 1 ); // size of last argument aVarArg.realloc( nVarCount ); bValidCount = true; } else if ( nParamCount <= nDescCount ) { // all args behind nParamCount must be optional bValidCount = true; for (sal_Int32 i=nParamCount; iGetArgumentCount(); const ScAddInArgDesc* pArgs = pFuncData->GetArguments(); // if last arg is sequence, use "any" type if ( nCount > 0 && nPos >= nCount-1 && pArgs[nCount-1].eType == SC_ADDINARG_VARARGS ) return SC_ADDINARG_VALUE_OR_ARRAY; if ( nPos < nCount ) return pArgs[nPos].eType; } return SC_ADDINARG_VALUE_OR_ARRAY; //TODO: error code !!!! } bool ScUnoAddInCall::NeedsCaller() const { return pFuncData && pFuncData->GetCallerPos() != SC_CALLERPOS_NONE; } void ScUnoAddInCall::SetCaller( const uno::Reference& rInterface ) { xCaller = rInterface; } void ScUnoAddInCall::SetCallerFromObjectShell( const SfxObjectShell* pObjSh ) { if (pObjSh) { uno::Reference xInt( pObjSh->GetBaseModel(), uno::UNO_QUERY ); SetCaller( xInt ); } } void ScUnoAddInCall::SetParam( sal_Int32 nPos, const uno::Any& rValue ) { if ( !pFuncData ) return; sal_Int32 nCount = pFuncData->GetArgumentCount(); const ScAddInArgDesc* pArgs = pFuncData->GetArguments(); if ( nCount > 0 && nPos >= nCount-1 && pArgs[nCount-1].eType == SC_ADDINARG_VARARGS ) { sal_Int32 nVarPos = nPos-(nCount-1); if ( nVarPos < aVarArg.getLength() ) aVarArg.getArray()[nVarPos] = rValue; else { OSL_FAIL("wrong argument number"); } } else if ( nPos < aArgs.getLength() ) aArgs.getArray()[nPos] = rValue; else { OSL_FAIL("wrong argument number"); } } void ScUnoAddInCall::ExecuteCall() { if ( !pFuncData ) return; sal_Int32 nCount = pFuncData->GetArgumentCount(); const ScAddInArgDesc* pArgs = pFuncData->GetArguments(); if ( nCount > 0 && pArgs[nCount-1].eType == SC_ADDINARG_VARARGS ) { // insert aVarArg as last argument //TODO: after inserting caller (to prevent copying twice)? OSL_ENSURE( aArgs.getLength() == nCount, "wrong argument count" ); aArgs.getArray()[nCount-1] <<= aVarArg; } if ( pFuncData->GetCallerPos() != SC_CALLERPOS_NONE ) { uno::Any aCallerAny; aCallerAny <<= xCaller; sal_Int32 nUserLen = aArgs.getLength(); sal_Int32 nCallPos = pFuncData->GetCallerPos(); if (nCallPos>nUserLen) // should not happen { OSL_FAIL("wrong CallPos"); nCallPos = nUserLen; } sal_Int32 nDestLen = nUserLen + 1; uno::Sequence aRealArgs( nDestLen ); uno::Any* pDest = aRealArgs.getArray(); pDest = std::copy_n(std::cbegin(aArgs), nCallPos, pDest); *pDest = std::move(aCallerAny); std::copy(std::next(std::cbegin(aArgs), nCallPos), std::cend(aArgs), std::next(pDest)); ExecuteCallWithArgs( aRealArgs ); } else ExecuteCallWithArgs( aArgs ); } void ScUnoAddInCall::ExecuteCallWithArgs(uno::Sequence& rCallArgs) { // rCallArgs may not match argument descriptions (because of caller) uno::Reference xFunction; uno::Any aObject; if ( pFuncData ) { xFunction = pFuncData->GetFunction(); aObject = pFuncData->GetObject(); } if ( !xFunction.is() ) return; uno::Any aAny; nErrCode = FormulaError::NONE; try { aAny = xFunction->invoke( aObject, rCallArgs ); } catch(lang::IllegalArgumentException&) { nErrCode = FormulaError::IllegalArgument; } catch(const reflection::InvocationTargetException& rWrapped) { if ( rWrapped.TargetException.getValueType().equals( cppu::UnoType::get()) ) nErrCode = FormulaError::IllegalArgument; else if ( rWrapped.TargetException.getValueType().equals( cppu::UnoType::get()) ) nErrCode = FormulaError::NoConvergence; else nErrCode = FormulaError::NoValue; } catch(uno::Exception&) { nErrCode = FormulaError::NoValue; } if (nErrCode == FormulaError::NONE) SetResult( aAny ); // convert result to Calc types } template static sal_Int32 lcl_GetMaxColCount(const uno::Sequence< uno::Sequence >* pRowSeq) { if (!pRowSeq->hasElements()) return 0; auto pRow = std::max_element(pRowSeq->begin(), pRowSeq->end(), [](const uno::Sequence& a, const uno::Sequence& b) { return a.getLength() < b.getLength(); }); return pRow->getLength(); } void ScUnoAddInCall::SetResult( const uno::Any& rNewRes ) { nErrCode = FormulaError::NONE; xVarRes = nullptr; // Reflection* pRefl = rNewRes.getReflection(); uno::TypeClass eClass = rNewRes.getValueTypeClass(); const uno::Type& aType = rNewRes.getValueType(); switch (eClass) { case uno::TypeClass_VOID: nErrCode = FormulaError::NotAvailable; // #NA break; case uno::TypeClass_ENUM: case uno::TypeClass_BOOLEAN: case uno::TypeClass_CHAR: case uno::TypeClass_BYTE: case uno::TypeClass_SHORT: case uno::TypeClass_UNSIGNED_SHORT: case uno::TypeClass_LONG: case uno::TypeClass_UNSIGNED_LONG: case uno::TypeClass_FLOAT: case uno::TypeClass_DOUBLE: { uno::TypeClass eMyClass; ScApiTypeConversion::ConvertAnyToDouble( fValue, eMyClass, rNewRes); bHasString = false; } break; case uno::TypeClass_STRING: { rNewRes >>= aString; bHasString = true; } break; case uno::TypeClass_INTERFACE: { //TODO: directly extract XVolatileResult from any? uno::Reference xInterface; rNewRes >>= xInterface; if ( xInterface.is() ) xVarRes.set( xInterface, uno::UNO_QUERY ); if (!xVarRes.is()) nErrCode = FormulaError::NoValue; // unknown interface } break; default: if ( aType.equals( cppu::UnoType >>::get() ) ) { const uno::Sequence< uno::Sequence >* pRowSeq = nullptr; //TODO: use pointer from any! uno::Sequence< uno::Sequence > aSequence; if ( rNewRes >>= aSequence ) pRowSeq = &aSequence; if ( pRowSeq ) { sal_Int32 nRowCount = pRowSeq->getLength(); sal_Int32 nMaxColCount = lcl_GetMaxColCount(pRowSeq); if ( nMaxColCount && nRowCount ) { const uno::Sequence* pRowArr = pRowSeq->getConstArray(); xMatrix = new ScMatrix( static_cast(nMaxColCount), static_cast(nRowCount), 0.0); for (sal_Int32 nRow=0; nRowPutDouble( pColArr[nCol], static_cast(nCol), static_cast(nRow) ); for (sal_Int32 nCol=nColCount; nColPutDouble( 0.0, static_cast(nCol), static_cast(nRow) ); } } } } else if ( aType.equals( cppu::UnoType >>::get() ) ) { const uno::Sequence< uno::Sequence >* pRowSeq = nullptr; //TODO: use pointer from any! uno::Sequence< uno::Sequence > aSequence; if ( rNewRes >>= aSequence ) pRowSeq = &aSequence; if ( pRowSeq ) { sal_Int32 nRowCount = pRowSeq->getLength(); sal_Int32 nMaxColCount = lcl_GetMaxColCount(pRowSeq); if ( nMaxColCount && nRowCount ) { const uno::Sequence* pRowArr = pRowSeq->getConstArray(); xMatrix = new ScMatrix( static_cast(nMaxColCount), static_cast(nRowCount), 0.0); for (sal_Int32 nRow=0; nRowPutDouble( pColArr[nCol], static_cast(nCol), static_cast(nRow) ); for (sal_Int32 nCol=nColCount; nColPutDouble( 0.0, static_cast(nCol), static_cast(nRow) ); } } } } else if ( aType.equals( cppu::UnoType >>::get() ) ) { const uno::Sequence< uno::Sequence >* pRowSeq = nullptr; //TODO: use pointer from any! uno::Sequence< uno::Sequence > aSequence; if ( rNewRes >>= aSequence ) pRowSeq = &aSequence; if ( pRowSeq ) { sal_Int32 nRowCount = pRowSeq->getLength(); sal_Int32 nMaxColCount = lcl_GetMaxColCount(pRowSeq); if ( nMaxColCount && nRowCount ) { const uno::Sequence* pRowArr = pRowSeq->getConstArray(); xMatrix = new ScMatrix( static_cast(nMaxColCount), static_cast(nRowCount), 0.0); for (sal_Int32 nRow=0; nRowPutString( mrDoc.GetSharedStringPool().intern(pColArr[nCol]), static_cast(nCol), static_cast(nRow)); } for (sal_Int32 nCol=nColCount; nColPutString( svl::SharedString::getEmptyString(), static_cast(nCol), static_cast(nRow)); } } } } } else if ( aType.equals( cppu::UnoType >>::get() ) ) { xMatrix = ScSequenceToMatrix::CreateMixedMatrix( rNewRes ); } if (!xMatrix) // no array found nErrCode = FormulaError::NoValue; //TODO: code for error in return type??? } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */