diff options
Diffstat (limited to 'scripting/source')
46 files changed, 13293 insertions, 0 deletions
diff --git a/scripting/source/basprov/baslibnode.cxx b/scripting/source/basprov/baslibnode.cxx new file mode 100644 index 000000000..33942c6b2 --- /dev/null +++ b/scripting/source/basprov/baslibnode.cxx @@ -0,0 +1,130 @@ +/* -*- 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 "baslibnode.hxx" +#include "basmodnode.hxx" +#include <com/sun/star/script/browse/BrowseNodeTypes.hpp> +#include <comphelper/sequence.hxx> +#include <vcl/svapp.hxx> +#include <basic/basmgr.hxx> +#include <basic/sbstar.hxx> + + +using namespace ::com::sun::star; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::script; + + +namespace basprov +{ + + + // BasicLibraryNodeImpl + + + BasicLibraryNodeImpl::BasicLibraryNodeImpl( const Reference< XComponentContext >& rxContext, + const OUString& sScriptingContext, BasicManager* pBasicManager, + const Reference< script::XLibraryContainer >& xLibContainer, const OUString& sLibName, bool isAppScript ) + :m_xContext( rxContext ) + ,m_sScriptingContext( sScriptingContext ) + ,m_pBasicManager( pBasicManager ) + ,m_xLibContainer( xLibContainer ) + ,m_sLibName( sLibName ) + ,m_bIsAppScript( isAppScript ) + { + if ( m_xLibContainer.is() ) + { + Any aElement = m_xLibContainer->getByName( m_sLibName ); + aElement >>= m_xLibrary; + } + } + + + BasicLibraryNodeImpl::~BasicLibraryNodeImpl() + { + } + + + // XBrowseNode + + + OUString BasicLibraryNodeImpl::getName( ) + { + SolarMutexGuard aGuard; + + return m_sLibName; + } + + + Sequence< Reference< browse::XBrowseNode > > BasicLibraryNodeImpl::getChildNodes( ) + { + SolarMutexGuard aGuard; + + std::vector< Reference< browse::XBrowseNode > > aChildNodes; + + if ( m_xLibContainer.is() && m_xLibContainer->hasByName( m_sLibName ) && !m_xLibContainer->isLibraryLoaded( m_sLibName ) ) + m_xLibContainer->loadLibrary( m_sLibName ); + + if ( m_pBasicManager ) + { + StarBASIC* pBasic = m_pBasicManager->GetLib( m_sLibName ); + if ( pBasic && m_xLibrary.is() ) + { + Sequence< OUString > aNames = m_xLibrary->getElementNames(); + sal_Int32 nCount = aNames.getLength(); + const OUString* pNames = aNames.getConstArray(); + aChildNodes.resize( nCount ); + + for ( sal_Int32 i = 0 ; i < nCount ; ++i ) + { + SbModule* pModule = pBasic->FindModule( pNames[i] ); + if ( pModule ) + aChildNodes[i] = new BasicModuleNodeImpl(m_xContext, m_sScriptingContext, + pModule, m_bIsAppScript); + } + } + } + + return comphelper::containerToSequence(aChildNodes); + } + + + sal_Bool BasicLibraryNodeImpl::hasChildNodes( ) + { + SolarMutexGuard aGuard; + + bool bReturn = false; + if ( m_xLibrary.is() ) + bReturn = m_xLibrary->hasElements(); + + return bReturn; + } + + + sal_Int16 BasicLibraryNodeImpl::getType( ) + { + return browse::BrowseNodeTypes::CONTAINER; + } + + +} // namespace basprov + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/scripting/source/basprov/baslibnode.hxx b/scripting/source/basprov/baslibnode.hxx new file mode 100644 index 000000000..46d31aae5 --- /dev/null +++ b/scripting/source/basprov/baslibnode.hxx @@ -0,0 +1,70 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/script/XLibraryContainer.hpp> +#include <com/sun/star/script/browse/XBrowseNode.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <cppuhelper/implbase.hxx> + +class BasicManager; + + +namespace basprov +{ + + + + + typedef ::cppu::WeakImplHelper< + css::script::browse::XBrowseNode > BasicLibraryNodeImpl_BASE; + + + class BasicLibraryNodeImpl : public BasicLibraryNodeImpl_BASE + { + private: + css::uno::Reference< css::uno::XComponentContext > m_xContext; + OUString m_sScriptingContext; + BasicManager* m_pBasicManager; + css::uno::Reference< css::script::XLibraryContainer > m_xLibContainer; + css::uno::Reference< css::container::XNameContainer > m_xLibrary; + OUString m_sLibName; + bool m_bIsAppScript; + + public: + BasicLibraryNodeImpl( const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const OUString& sScriptingContext, + BasicManager* pBasicManager, + const css::uno::Reference< css::script::XLibraryContainer >& xLibContainer, + const OUString& sLibName, bool isAppScript ); + virtual ~BasicLibraryNodeImpl() override; + + // XBrowseNode + virtual OUString SAL_CALL getName( ) override; + virtual css::uno::Sequence< css::uno::Reference< css::script::browse::XBrowseNode > > SAL_CALL getChildNodes( ) override; + virtual sal_Bool SAL_CALL hasChildNodes( ) override; + virtual sal_Int16 SAL_CALL getType( ) override; + }; + + +} // namespace basprov + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/scripting/source/basprov/basmethnode.cxx b/scripting/source/basprov/basmethnode.cxx new file mode 100644 index 000000000..fca2cafe8 --- /dev/null +++ b/scripting/source/basprov/basmethnode.cxx @@ -0,0 +1,291 @@ +/* -*- 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 "basmethnode.hxx" +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/frame/DispatchHelper.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/script/browse/BrowseNodeTypes.hpp> + +#include <comphelper/propertyvalue.hxx> +#include <vcl/svapp.hxx> +#include <basic/sbstar.hxx> +#include <basic/sbmeth.hxx> +#include <basic/sbmod.hxx> + +#include <util/MiscUtils.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::comphelper; +using namespace ::com::sun::star::script; +using namespace ::sf_misc; + +#define BASPROV_PROPERTY_ID_URI 1 +#define BASPROV_PROPERTY_ID_EDITABLE 2 + +constexpr OUStringLiteral BASPROV_PROPERTY_URI = u"URI"; +constexpr OUStringLiteral BASPROV_PROPERTY_EDITABLE = u"Editable"; + +#define BASPROV_DEFAULT_ATTRIBS() PropertyAttribute::BOUND | PropertyAttribute::TRANSIENT | PropertyAttribute::READONLY + + +namespace basprov +{ + + + // BasicMethodNodeImpl + + + BasicMethodNodeImpl::BasicMethodNodeImpl( const Reference< XComponentContext >& rxContext, + const OUString& sScriptingContext, SbMethod* pMethod, bool isAppScript ) + : ::scripting_helper::OBroadcastHelperHolder( m_aMutex ) + ,OPropertyContainer( GetBroadcastHelper() ) + ,m_xContext( rxContext ) + ,m_sScriptingContext( sScriptingContext ) + ,m_pMethod( pMethod ) + ,m_bIsAppScript( isAppScript ) + ,m_bEditable( true ) + { + if ( m_pMethod ) + { + SbModule* pModule = m_pMethod->GetModule(); + if ( pModule ) + { + StarBASIC* pBasic = static_cast< StarBASIC* >( pModule->GetParent() ); + if ( pBasic ) + { + m_sURI = "vnd.sun.star.script:"; + m_sURI += pBasic->GetName(); + m_sURI += "."; + m_sURI += pModule->GetName(); + m_sURI += "."; + m_sURI += m_pMethod->GetName(); + m_sURI += "?language=Basic&location="; + if ( m_bIsAppScript ) + m_sURI += "application"; + else + m_sURI += "document"; + } + } + } + + registerProperty( BASPROV_PROPERTY_URI, BASPROV_PROPERTY_ID_URI, BASPROV_DEFAULT_ATTRIBS(), &m_sURI, cppu::UnoType<decltype(m_sURI)>::get() ); + registerProperty( BASPROV_PROPERTY_EDITABLE, BASPROV_PROPERTY_ID_EDITABLE, BASPROV_DEFAULT_ATTRIBS(), &m_bEditable, cppu::UnoType<decltype(m_bEditable)>::get() ); + } + + + BasicMethodNodeImpl::~BasicMethodNodeImpl() + { + } + + + // XInterface + + + IMPLEMENT_FORWARD_XINTERFACE2( BasicMethodNodeImpl, BasicMethodNodeImpl_BASE, OPropertyContainer ) + + + // XTypeProvider + + + IMPLEMENT_FORWARD_XTYPEPROVIDER2( BasicMethodNodeImpl, BasicMethodNodeImpl_BASE, OPropertyContainer ) + + + // XBrowseNode + + + OUString BasicMethodNodeImpl::getName( ) + { + SolarMutexGuard aGuard; + + OUString sMethodName; + if ( m_pMethod ) + sMethodName = m_pMethod->GetName(); + + return sMethodName; + } + + + Sequence< Reference< browse::XBrowseNode > > BasicMethodNodeImpl::getChildNodes( ) + { + return Sequence< Reference< browse::XBrowseNode > >(); + } + + + sal_Bool BasicMethodNodeImpl::hasChildNodes( ) + { + return false; + } + + + sal_Int16 BasicMethodNodeImpl::getType( ) + { + return browse::BrowseNodeTypes::SCRIPT; + } + + + // OPropertySetHelper + + + ::cppu::IPropertyArrayHelper& BasicMethodNodeImpl::getInfoHelper( ) + { + return *getArrayHelper(); + } + + + // OPropertyArrayUsageHelper + + + ::cppu::IPropertyArrayHelper* BasicMethodNodeImpl::createArrayHelper( ) const + { + Sequence< Property > aProps; + describeProperties( aProps ); + return new ::cppu::OPropertyArrayHelper( aProps ); + } + + + // XPropertySet + + + Reference< XPropertySetInfo > BasicMethodNodeImpl::getPropertySetInfo( ) + { + Reference< XPropertySetInfo > xInfo( createPropertySetInfo( getInfoHelper() ) ); + return xInfo; + } + + + // XInvocation + + + Reference< XIntrospectionAccess > BasicMethodNodeImpl::getIntrospection( ) + { + return Reference< XIntrospectionAccess >(); + } + + + Any BasicMethodNodeImpl::invoke( const OUString& aFunctionName, const Sequence< Any >&, + Sequence< sal_Int16 >&, Sequence< Any >& ) + { + if ( aFunctionName != BASPROV_PROPERTY_EDITABLE ) + { + throw IllegalArgumentException( + "BasicMethodNodeImpl::invoke: function name not supported!", + Reference< XInterface >(), 1 ); + } + + OUString sDocURL, sLibName, sModName; + sal_uInt16 nLine1 = 0; + + if ( !m_bIsAppScript ) + { + Reference< frame::XModel > xModel = MiscUtils::tDocUrlToModel( m_sScriptingContext ); + + if ( xModel.is() ) + { + sDocURL = xModel->getURL(); + if ( sDocURL.isEmpty() ) + { + const Sequence < PropertyValue > aProps = xModel->getArgs(); + // TODO: according to MBA the property 'Title' may change in future + const PropertyValue* pProp = std::find_if(aProps.begin(), aProps.end(), + [](const PropertyValue& rProp) { return rProp.Name == "Title"; }); + if (pProp != aProps.end()) + pProp->Value >>= sDocURL; + } + } + } + + if ( m_pMethod ) + { + sal_uInt16 nLine2; + m_pMethod->GetLineRange( nLine1, nLine2 ); + SbModule* pModule = m_pMethod->GetModule(); + if ( pModule ) + { + sModName = pModule->GetName(); + StarBASIC* pBasic = static_cast< StarBASIC* >( pModule->GetParent() ); + if ( pBasic ) + sLibName = pBasic->GetName(); + } + } + + if ( m_xContext.is() ) + { + Reference< frame::XDesktop2 > xDesktop = frame::Desktop::create( m_xContext ); + + Reference < frame::XDispatchProvider > xProv( xDesktop->getCurrentFrame(), UNO_QUERY ); + + if ( xProv.is() ) + { + Reference< frame::XDispatchHelper > xHelper( frame::DispatchHelper::create( m_xContext ) ); + + Sequence < PropertyValue > aArgs{ + comphelper::makePropertyValue("Document", sDocURL), + comphelper::makePropertyValue("LibName", sLibName), + comphelper::makePropertyValue("Name", sModName), + comphelper::makePropertyValue("Type", OUString("Module")), + comphelper::makePropertyValue("Line", static_cast< sal_uInt32 >( nLine1 )) + }; + xHelper->executeDispatch( xProv, ".uno:BasicIDEAppear", OUString(), 0, aArgs ); + } + } + + + return Any(); + } + + + void BasicMethodNodeImpl::setValue( const OUString&, const Any& ) + { + throw UnknownPropertyException( + "BasicMethodNodeImpl::setValue: property name is unknown!" ); + } + + + Any BasicMethodNodeImpl::getValue( const OUString& ) + { + throw UnknownPropertyException( + "BasicMethodNodeImpl::getValue: property name is unknown!" ); + } + + + sal_Bool BasicMethodNodeImpl::hasMethod( const OUString& aName ) + { + bool bReturn = false; + if ( aName == BASPROV_PROPERTY_EDITABLE ) + bReturn = true; + + return bReturn; + } + + + sal_Bool BasicMethodNodeImpl::hasProperty( const OUString& ) + { + return false; + } + + +} // namespace basprov + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/scripting/source/basprov/basmethnode.hxx b/scripting/source/basprov/basmethnode.hxx new file mode 100644 index 000000000..d1eddada0 --- /dev/null +++ b/scripting/source/basprov/basmethnode.hxx @@ -0,0 +1,108 @@ +/* -*- 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 . + */ + +#pragma once + +#include <bcholder.hxx> +#include <com/sun/star/beans/XPropertySetInfo.hpp> +#include <com/sun/star/script/XInvocation.hpp> +#include <com/sun/star/script/browse/XBrowseNode.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <comphelper/proparrhlp.hxx> +#include <comphelper/propertycontainer.hxx> +#include <comphelper/uno3.hxx> +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/implbase.hxx> + + +class SbMethod; + + +namespace basprov +{ + + + + + typedef ::cppu::WeakImplHelper< + css::script::browse::XBrowseNode, + css::script::XInvocation > BasicMethodNodeImpl_BASE; + + class BasicMethodNodeImpl : public BasicMethodNodeImpl_BASE, + public cppu::BaseMutex, + public ::scripting_helper::OBroadcastHelperHolder, + public ::comphelper::OPropertyContainer, + public ::comphelper::OPropertyArrayUsageHelper< BasicMethodNodeImpl > + { + private: + css::uno::Reference< css::uno::XComponentContext > m_xContext; + OUString m_sScriptingContext; + SbMethod* m_pMethod; + bool m_bIsAppScript; + + // properties + OUString m_sURI; + bool m_bEditable; + + protected: + // OPropertySetHelper + virtual ::cppu::IPropertyArrayHelper& SAL_CALL getInfoHelper( ) override; + + // OPropertyArrayUsageHelper + virtual ::cppu::IPropertyArrayHelper* createArrayHelper( ) const override; + + public: + BasicMethodNodeImpl( const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const OUString& sScriptingContext, + SbMethod* pMethod, bool isAppScript ); + virtual ~BasicMethodNodeImpl() override; + + // XInterface + DECLARE_XINTERFACE() + + // XTypeProvider + DECLARE_XTYPEPROVIDER() + + // XBrowseNode + virtual OUString SAL_CALL getName( ) override; + virtual css::uno::Sequence< css::uno::Reference< css::script::browse::XBrowseNode > > SAL_CALL getChildNodes( ) override; + virtual sal_Bool SAL_CALL hasChildNodes( ) override; + virtual sal_Int16 SAL_CALL getType( ) override; + + // XPropertySet + virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo( ) override; + + // XInvocation + virtual css::uno::Reference< css::beans::XIntrospectionAccess > SAL_CALL getIntrospection( ) override; + virtual css::uno::Any SAL_CALL invoke( + const OUString& aFunctionName, + const css::uno::Sequence< css::uno::Any >& aParams, + css::uno::Sequence< sal_Int16 >& aOutParamIndex, + css::uno::Sequence< css::uno::Any >& aOutParam ) override; + virtual void SAL_CALL setValue( const OUString& aPropertyName, const css::uno::Any& aValue ) override; + virtual css::uno::Any SAL_CALL getValue( const OUString& aPropertyName ) override; + virtual sal_Bool SAL_CALL hasMethod( const OUString& aName ) override; + virtual sal_Bool SAL_CALL hasProperty( const OUString& aName ) override; + }; + + +} // namespace basprov + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/scripting/source/basprov/basmodnode.cxx b/scripting/source/basprov/basmodnode.cxx new file mode 100644 index 000000000..986e3062d --- /dev/null +++ b/scripting/source/basprov/basmodnode.cxx @@ -0,0 +1,134 @@ +/* -*- 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 "basmodnode.hxx" +#include "basmethnode.hxx" +#include <com/sun/star/script/browse/BrowseNodeTypes.hpp> +#include <vcl/svapp.hxx> +#include <basic/sbx.hxx> +#include <basic/sbmod.hxx> +#include <basic/sbmeth.hxx> + + +using namespace ::com::sun::star; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::script; + + +namespace basprov +{ + + + // BasicModuleNodeImpl + + + BasicModuleNodeImpl::BasicModuleNodeImpl( const Reference< XComponentContext >& rxContext, + const OUString& sScriptingContext, SbModule* pModule, bool isAppScript ) + :m_xContext( rxContext ) + ,m_sScriptingContext( sScriptingContext ) + ,m_pModule( pModule ) + ,m_bIsAppScript( isAppScript ) + { + } + + + BasicModuleNodeImpl::~BasicModuleNodeImpl() + { + } + + + // XBrowseNode + + + OUString BasicModuleNodeImpl::getName( ) + { + SolarMutexGuard aGuard; + + OUString sModuleName; + if ( m_pModule ) + sModuleName = m_pModule->GetName(); + + return sModuleName; + } + + + Sequence< Reference< browse::XBrowseNode > > BasicModuleNodeImpl::getChildNodes( ) + { + SolarMutexGuard aGuard; + + Sequence< Reference< browse::XBrowseNode > > aChildNodes; + + if ( m_pModule ) + { + SbxArray* pMethods = m_pModule->GetMethods().get(); + if ( pMethods ) + { + sal_uInt32 nCount = pMethods->Count(); + sal_Int32 nRealCount = 0; + for ( sal_uInt32 i = 0; i < nCount; ++i ) + { + SbMethod* pMethod = static_cast<SbMethod*>(pMethods->Get(i)); + if ( pMethod && !pMethod->IsHidden() ) + ++nRealCount; + } + aChildNodes.realloc( nRealCount ); + Reference< browse::XBrowseNode >* pChildNodes = aChildNodes.getArray(); + + sal_Int32 iTarget = 0; + for ( sal_uInt32 i = 0; i < nCount; ++i ) + { + SbMethod* pMethod = static_cast<SbMethod*>(pMethods->Get(i)); + if ( pMethod && !pMethod->IsHidden() ) + pChildNodes[iTarget++] = new BasicMethodNodeImpl( + m_xContext, m_sScriptingContext, pMethod, m_bIsAppScript); + } + } + } + + return aChildNodes; + } + + + sal_Bool BasicModuleNodeImpl::hasChildNodes( ) + { + SolarMutexGuard aGuard; + + bool bReturn = false; + if ( m_pModule ) + { + SbxArray* pMethods = m_pModule->GetMethods().get(); + if (pMethods && pMethods->Count() > 0) + bReturn = true; + } + + return bReturn; + } + + + sal_Int16 BasicModuleNodeImpl::getType( ) + { + return browse::BrowseNodeTypes::CONTAINER; + } + + +} // namespace basprov + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/scripting/source/basprov/basmodnode.hxx b/scripting/source/basprov/basmodnode.hxx new file mode 100644 index 000000000..ebf0957ef --- /dev/null +++ b/scripting/source/basprov/basmodnode.hxx @@ -0,0 +1,64 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/script/browse/XBrowseNode.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <cppuhelper/implbase.hxx> + +class SbModule; + + +namespace basprov +{ + + + + + typedef ::cppu::WeakImplHelper< + css::script::browse::XBrowseNode > BasicModuleNodeImpl_BASE; + + + class BasicModuleNodeImpl : public BasicModuleNodeImpl_BASE + { + private: + css::uno::Reference< css::uno::XComponentContext > m_xContext; + OUString m_sScriptingContext; + SbModule* m_pModule; + bool m_bIsAppScript; + + public: + BasicModuleNodeImpl( const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const OUString& sScriptingContext, + SbModule* pModule, bool isAppScript ); + virtual ~BasicModuleNodeImpl() override; + + // XBrowseNode + virtual OUString SAL_CALL getName( ) override; + virtual css::uno::Sequence< css::uno::Reference< css::script::browse::XBrowseNode > > SAL_CALL getChildNodes( ) override; + virtual sal_Bool SAL_CALL hasChildNodes( ) override; + virtual sal_Int16 SAL_CALL getType( ) override; + }; + + +} // namespace basprov + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/scripting/source/basprov/basprov.component b/scripting/source/basprov/basprov.component new file mode 100644 index 000000000..d0d6bf974 --- /dev/null +++ b/scripting/source/basprov/basprov.component @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.scripting.ScriptProviderForBasic" + constructor="scripting_BasicProviderImpl_get_implementation"> + <service name="com.sun.star.script.browse.BrowseNode"/> + <service name="com.sun.star.script.provider.LanguageScriptProvider"/> + <service name="com.sun.star.script.provider.ScriptProvider"/> + <service name="com.sun.star.script.provider.ScriptProviderForBasic"/> + </implementation> +</component> diff --git a/scripting/source/basprov/basprov.cxx b/scripting/source/basprov/basprov.cxx new file mode 100644 index 000000000..d4afb41ea --- /dev/null +++ b/scripting/source/basprov/basprov.cxx @@ -0,0 +1,477 @@ +/* -*- 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 "basprov.hxx" +#include "basscript.hxx" +#include "baslibnode.hxx" +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/script/browse/BrowseNodeTypes.hpp> +#include <com/sun/star/script/provider/ScriptFrameworkErrorException.hpp> +#include <com/sun/star/script/provider/ScriptFrameworkErrorType.hpp> +#include <com/sun/star/document/XEmbeddedScripts.hpp> +#include <com/sun/star/uri/UriReferenceFactory.hpp> + +#include <cppuhelper/supportsservice.hxx> +#include <rtl/uri.hxx> +#include <sal/log.hxx> +#include <osl/file.hxx> +#include <vcl/svapp.hxx> +#include <basic/basmgr.hxx> +#include <basic/basicmanagerrepository.hxx> +#include <basic/sbstar.hxx> +#include <basic/sbmod.hxx> +#include <basic/sbmeth.hxx> +#include <sfx2/app.hxx> + +#include <com/sun/star/util/theMacroExpander.hpp> +#include <com/sun/star/script/XLibraryContainer2.hpp> +#include <com/sun/star/uri/XUriReference.hpp> +#include <com/sun/star/uri/XUriReferenceFactory.hpp> +#include <com/sun/star/uri/XVndSunStarScriptUrl.hpp> + +#include <util/MiscUtils.hxx> + + +using namespace ::com::sun::star; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::script; +using namespace ::com::sun::star::document; +using namespace ::sf_misc; + + +namespace basprov +{ + + // BasicProviderImpl + + + BasicProviderImpl::BasicProviderImpl( const Reference< XComponentContext >& xContext ) + :m_pAppBasicManager( nullptr ) + ,m_pDocBasicManager( nullptr ) + ,m_xContext( xContext ) + ,m_bIsAppScriptCtx( true ) + ,m_bIsUserCtx(true) + { + } + + + BasicProviderImpl::~BasicProviderImpl() + { + SolarMutexGuard aGuard; + EndListeningAll(); + } + + + bool BasicProviderImpl::isLibraryShared( const Reference< script::XLibraryContainer >& rxLibContainer, const OUString& rLibName ) + { + bool bIsShared = false; + + Reference< script::XLibraryContainer2 > xLibContainer( rxLibContainer, UNO_QUERY ); + if ( xLibContainer.is() && xLibContainer->hasByName( rLibName ) && xLibContainer->isLibraryLink( rLibName ) ) + { + OUString aFileURL; + if ( m_xContext.is() ) + { + Reference< uri::XUriReferenceFactory > xUriFac( uri::UriReferenceFactory::create( m_xContext ) ); + + OUString aLinkURL( xLibContainer->getLibraryLinkURL( rLibName ) ); + Reference< uri::XUriReference > xUriRef = xUriFac->parse( aLinkURL ); + + if ( xUriRef.is() ) + { + OUString aScheme = xUriRef->getScheme(); + if ( aScheme.equalsIgnoreAsciiCase("file") ) + { + aFileURL = aLinkURL; + } + else if ( aScheme.equalsIgnoreAsciiCase("vnd.sun.star.pkg") ) + { + OUString aAuthority = xUriRef->getAuthority(); + if ( aAuthority.matchIgnoreAsciiCase( "vnd.sun.star.expand:" ) ) + { + OUString aDecodedURL( aAuthority.copy( sizeof ( "vnd.sun.star.expand:" ) - 1 ) ); + aDecodedURL = ::rtl::Uri::decode( aDecodedURL, rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8 ); + Reference<util::XMacroExpander> xMacroExpander = + util::theMacroExpander::get(m_xContext); + aFileURL = xMacroExpander->expandMacros( aDecodedURL ); + } + } + } + } + + if ( !aFileURL.isEmpty() ) + { + osl::DirectoryItem aFileItem; + osl::FileStatus aFileStatus( osl_FileStatus_Mask_FileURL ); + OSL_VERIFY( osl::DirectoryItem::get( aFileURL, aFileItem ) == osl::FileBase::E_None ); + OSL_VERIFY( aFileItem.getFileStatus( aFileStatus ) == osl::FileBase::E_None ); + OUString aCanonicalFileURL( aFileStatus.getFileURL() ); + + if( aCanonicalFileURL.indexOf( "share/basic" ) != -1 + || aCanonicalFileURL.indexOf( "share/uno_packages" ) != -1 ) + bIsShared = true; + } + } + + return bIsShared; + } + + // SfxListener + void BasicProviderImpl::Notify(SfxBroadcaster& rBC, const SfxHint& rHint) + { + if (auto pManager = dynamic_cast<const BasicManager*>(&rBC)) + if (pManager == m_pAppBasicManager && rHint.GetId() == SfxHintId::Dying) + { + EndListening(*m_pAppBasicManager); + m_pAppBasicManager = nullptr; + } + } + + // XServiceInfo + OUString BasicProviderImpl::getImplementationName( ) + { + return "com.sun.star.comp.scripting.ScriptProviderForBasic"; + } + + sal_Bool BasicProviderImpl::supportsService( const OUString& rServiceName ) + { + return cppu::supportsService(this, rServiceName); + } + + Sequence< OUString > BasicProviderImpl::getSupportedServiceNames( ) + { + return { + "com.sun.star.script.provider.ScriptProviderForBasic", + "com.sun.star.script.provider.LanguageScriptProvider", + "com.sun.star.script.provider.ScriptProvider", + "com.sun.star.script.browse.BrowseNode"}; + } + + + // XInitialization + + + void BasicProviderImpl::initialize( const Sequence< Any >& aArguments ) + { + // TODO + + SolarMutexGuard aGuard; + + if ( aArguments.getLength() != 1 ) + { + throw IllegalArgumentException( + "BasicProviderImpl::initialize: incorrect argument count.", + *this, + 1 + ); + } + + Reference< frame::XModel > xModel; + + m_xInvocationContext.set( aArguments[0], UNO_QUERY ); + if ( m_xInvocationContext.is() ) + { + xModel.set( m_xInvocationContext->getScriptContainer(), UNO_QUERY ); + if ( !xModel.is() ) + { + throw IllegalArgumentException( + "BasicProviderImpl::initialize: unable to determine the document model from the script invocation context.", + *this, + 1 + ); + } + } + else + { + if ( !( aArguments[0] >>= m_sScriptingContext ) ) + { + throw IllegalArgumentException( + "BasicProviderImpl::initialize: incorrect argument type " + aArguments[0].getValueTypeName(), + *this, + 1 + ); + } + + if ( m_sScriptingContext.startsWith( "vnd.sun.star.tdoc" ) ) + { + xModel = MiscUtils::tDocUrlToModel( m_sScriptingContext ); + } + } + + if ( xModel.is() ) + { + Reference< XEmbeddedScripts > xDocumentScripts( xModel, UNO_QUERY ); + if ( xDocumentScripts.is() ) + { + m_pDocBasicManager = ::basic::BasicManagerRepository::getDocumentBasicManager( xModel ); + m_xLibContainerDoc = xDocumentScripts->getBasicLibraries(); + OSL_ENSURE( m_pDocBasicManager && m_xLibContainerDoc.is(), + "BasicProviderImpl::initialize: invalid BasicManager, or invalid script container!" ); + } + m_bIsAppScriptCtx = false; + } + else + { + // Provider has been created with application context for user + // or share + if ( m_sScriptingContext != "user" ) + { + m_bIsUserCtx = false; + } + else + { + /* + throw RuntimeException( + "BasicProviderImpl::initialize: no scripting context!" ); + */ + } + } + + // TODO + if ( !m_pAppBasicManager ) + { + m_pAppBasicManager = SfxApplication::GetBasicManager(); + if (m_pAppBasicManager) + StartListening(*m_pAppBasicManager); + } + + if ( !m_xLibContainerApp.is() ) + m_xLibContainerApp = SfxGetpApp()->GetBasicContainer(); + } + + + // XScriptProvider + + + Reference < provider::XScript > BasicProviderImpl::getScript( const OUString& scriptURI ) + { + // TODO + + SolarMutexGuard aGuard; + + Reference< provider::XScript > xScript; + Reference< uri::XUriReferenceFactory > xFac ( uri::UriReferenceFactory::create( m_xContext ) ); + + Reference< uri::XUriReference > uriRef = xFac->parse( scriptURI ); + + Reference < uri::XVndSunStarScriptUrl > sfUri( uriRef, UNO_QUERY ); + + if ( !uriRef.is() || !sfUri.is() ) + { + throw provider::ScriptFrameworkErrorException( + "BasicProviderImpl::getScript: failed to parse URI: " + scriptURI, + Reference< XInterface >(), + scriptURI, "Basic", + provider::ScriptFrameworkErrorType::MALFORMED_URL ); + } + + + OUString aDescription = sfUri->getName(); + OUString aLocation = sfUri->getParameter( "location" ); + + sal_Int32 nIndex = 0; + // In some strange circumstances the Library name can have an + // apparently illegal '.' in it ( in imported VBA ) + + BasicManager* pBasicMgr = nullptr; + if ( aLocation == "document" ) + { + pBasicMgr = m_pDocBasicManager; + } + else if ( aLocation == "application" ) + { + pBasicMgr = m_pAppBasicManager; + } + OUString sProjectName; + if ( pBasicMgr ) + sProjectName = pBasicMgr->GetName(); + + OUString aLibrary; + if ( !sProjectName.isEmpty() && aDescription.match( sProjectName ) ) + { + SAL_WARN("scripting", "LibraryName " << sProjectName << " is part of the url " << aDescription ); + aLibrary = sProjectName; + nIndex = sProjectName.getLength() + 1; + } + else + aLibrary = aDescription.getToken( 0, '.', nIndex ); + OUString aModule; + if ( nIndex != -1 ) + aModule = aDescription.getToken( 0, '.', nIndex ); + OUString aMethod; + if ( nIndex != -1 ) + aMethod = aDescription.getToken( 0, '.', nIndex ); + + if ( !aLibrary.isEmpty() && !aModule.isEmpty() && !aMethod.isEmpty() && !aLocation.isEmpty() ) + { + + if ( pBasicMgr ) + { + StarBASIC* pBasic = pBasicMgr->GetLib( aLibrary ); + if ( !pBasic ) + { + sal_uInt16 nId = pBasicMgr->GetLibId( aLibrary ); + if ( nId != LIB_NOTFOUND ) + { + pBasicMgr->LoadLib( nId ); + pBasic = pBasicMgr->GetLib( aLibrary ); + } + } + if ( pBasic ) + { + SbModule* pModule = pBasic->FindModule( aModule ); + if ( pModule ) + { + SbMethod* pMethod = pModule->FindMethod( aMethod, SbxClassType::Method ); + if ( pMethod && !pMethod->IsHidden() ) + { + if ( m_pDocBasicManager == pBasicMgr ) + xScript = new BasicScriptImpl( aDescription, pMethod, *m_pDocBasicManager, m_xInvocationContext ); + else + xScript = new BasicScriptImpl( aDescription, pMethod ); + } + } + } + } + } + + if ( !xScript.is() ) + { + throw provider::ScriptFrameworkErrorException( + "The following Basic script could not be found:\n" + "library: '" + aLibrary + "'\n" + "module: '" + aModule + "'\n" + "method: '" + aMethod + "'\n" + "location: '" + aLocation + "'\n", + Reference< XInterface >(), + scriptURI, "Basic", + provider::ScriptFrameworkErrorType::NO_SUCH_SCRIPT ); + } + + return xScript; + } + + + // XBrowseNode + + + OUString BasicProviderImpl::getName( ) + { + return "Basic"; + } + + + Sequence< Reference< browse::XBrowseNode > > BasicProviderImpl::getChildNodes( ) + { + SolarMutexGuard aGuard; + + Reference< script::XLibraryContainer > xLibContainer; + BasicManager* pBasicManager = nullptr; + + if ( m_bIsAppScriptCtx ) + { + xLibContainer = m_xLibContainerApp; + pBasicManager = m_pAppBasicManager; + } + else + { + xLibContainer = m_xLibContainerDoc; + pBasicManager = m_pDocBasicManager; + } + + Sequence< Reference< browse::XBrowseNode > > aChildNodes; + + if ( pBasicManager && xLibContainer.is() ) + { + const Sequence< OUString > aLibNames = xLibContainer->getElementNames(); + sal_Int32 nLibCount = aLibNames.getLength(); + aChildNodes.realloc( nLibCount ); + Reference< browse::XBrowseNode >* pChildNodes = aChildNodes.getArray(); + sal_Int32 childrenFound = 0; + + for ( const OUString& rLibName : aLibNames ) + { + bool bCreate = false; + if ( m_bIsAppScriptCtx ) + { + const bool bShared = isLibraryShared( xLibContainer, rLibName ); + if (m_bIsUserCtx != bShared) + bCreate = true; + } + else + { + bCreate = true; + } + if ( bCreate ) + { + pChildNodes[childrenFound++] + = new BasicLibraryNodeImpl(m_xContext, m_sScriptingContext, pBasicManager, + xLibContainer, rLibName, m_bIsAppScriptCtx); + } + } + + if ( childrenFound != nLibCount ) + aChildNodes.realloc( childrenFound ); + } + + return aChildNodes; + } + + + sal_Bool BasicProviderImpl::hasChildNodes( ) + { + SolarMutexGuard aGuard; + + bool bReturn = false; + Reference< script::XLibraryContainer > xLibContainer; + if ( m_bIsAppScriptCtx ) + { + xLibContainer = m_xLibContainerApp; + } + else + { + xLibContainer = m_xLibContainerDoc; + } + if ( xLibContainer.is() ) + bReturn = xLibContainer->hasElements(); + + return bReturn; + } + + + sal_Int16 BasicProviderImpl::getType( ) + { + return browse::BrowseNodeTypes::CONTAINER; + } + + + // component operations + + extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* + scripting_BasicProviderImpl_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&) + { + return cppu::acquire(new BasicProviderImpl(context)); + } + + +} // namespace basprov + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/scripting/source/basprov/basprov.hxx b/scripting/source/basprov/basprov.hxx new file mode 100644 index 000000000..994113b62 --- /dev/null +++ b/scripting/source/basprov/basprov.hxx @@ -0,0 +1,97 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/script/XLibraryContainer.hpp> +#include <com/sun/star/script/browse/XBrowseNode.hpp> +#include <com/sun/star/script/provider/XScriptProvider.hpp> +#include <com/sun/star/document/XScriptInvocationContext.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <cppuhelper/implbase.hxx> +#include <svl/lstner.hxx> + +class BasicManager; + + +namespace basprov +{ + + + + + typedef ::cppu::WeakImplHelper< + css::lang::XServiceInfo, + css::lang::XInitialization, + css::script::provider::XScriptProvider, + css::script::browse::XBrowseNode > BasicProviderImpl_BASE; + + + class BasicProviderImpl : public BasicProviderImpl_BASE, public SfxListener + { + private: + BasicManager* m_pAppBasicManager; + BasicManager* m_pDocBasicManager; + css::uno::Reference< css::script::XLibraryContainer > m_xLibContainerApp; + css::uno::Reference< css::script::XLibraryContainer > m_xLibContainerDoc; + css::uno::Reference< css::uno::XComponentContext > m_xContext; + css::uno::Reference< css::document::XScriptInvocationContext > m_xInvocationContext; + OUString m_sScriptingContext; + bool m_bIsAppScriptCtx; + bool m_bIsUserCtx; + + bool isLibraryShared( + const css::uno::Reference< css::script::XLibraryContainer >& rxLibContainer, + const OUString& rLibName ); + + public: + explicit BasicProviderImpl( + const css::uno::Reference< css::uno::XComponentContext >& xContext ); + virtual ~BasicProviderImpl() override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + + // XInitialization + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override; + + // XScriptProvider + virtual css::uno::Reference < css::script::provider::XScript > SAL_CALL getScript( + const OUString& scriptURI ) override; + + // XBrowseNode + virtual OUString SAL_CALL getName( ) override; + virtual css::uno::Sequence< css::uno::Reference< css::script::browse::XBrowseNode > > SAL_CALL getChildNodes( ) override; + virtual sal_Bool SAL_CALL hasChildNodes( ) override; + virtual sal_Int16 SAL_CALL getType( ) override; + + protected: + // SfxListener + virtual void Notify(SfxBroadcaster& rBC, const SfxHint& rHint) override; + }; + + +} // namespace basprov + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/scripting/source/basprov/basscript.cxx b/scripting/source/basprov/basscript.cxx new file mode 100644 index 000000000..b5a3f6136 --- /dev/null +++ b/scripting/source/basprov/basscript.cxx @@ -0,0 +1,314 @@ +/* -*- 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 "basscript.hxx" +#include <vcl/svapp.hxx> +#include <basic/sbx.hxx> +#include <basic/sbmod.hxx> +#include <basic/sbmeth.hxx> +#include <basic/sbuno.hxx> +#include <basic/basmgr.hxx> +#include <com/sun/star/script/provider/ScriptFrameworkErrorException.hpp> +#include <com/sun/star/script/provider/ScriptFrameworkErrorType.hpp> +#include <bcholder.hxx> +#include <comphelper/propertycontainer.hxx> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <map> + + +using namespace ::com::sun::star; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::script; +using namespace ::com::sun::star::document; +using namespace ::com::sun::star::beans; + + +namespace basprov +{ + +#define BASSCRIPT_PROPERTY_ID_CALLER 1 +constexpr OUStringLiteral BASSCRIPT_PROPERTY_CALLER = u"Caller"; + +#define BASSCRIPT_DEFAULT_ATTRIBS() PropertyAttribute::BOUND | PropertyAttribute::TRANSIENT + + typedef ::std::map< sal_Int16, Any > OutParamMap; + + + // BasicScriptImpl + + + BasicScriptImpl::BasicScriptImpl( const OUString& funcName, SbMethodRef const & xMethod ) + : ::scripting_helper::OBroadcastHelperHolder( m_aMutex ) + ,OPropertyContainer( GetBroadcastHelper() ) + ,m_xMethod( xMethod ) + ,m_funcName( funcName ) + ,m_documentBasicManager( nullptr ) + ,m_xDocumentScriptContext() + { + registerProperty( BASSCRIPT_PROPERTY_CALLER, BASSCRIPT_PROPERTY_ID_CALLER, BASSCRIPT_DEFAULT_ATTRIBS(), &m_caller, cppu::UnoType<decltype(m_caller)>::get() ); + } + + + BasicScriptImpl::BasicScriptImpl( const OUString& funcName, SbMethodRef const & xMethod, + BasicManager& documentBasicManager, const Reference< XScriptInvocationContext >& documentScriptContext ) : ::scripting_helper::OBroadcastHelperHolder( m_aMutex ) + ,OPropertyContainer( GetBroadcastHelper() ) + ,m_xMethod( xMethod ) + ,m_funcName( funcName ) + ,m_documentBasicManager( &documentBasicManager ) + ,m_xDocumentScriptContext( documentScriptContext ) + { + StartListening( *m_documentBasicManager ); + registerProperty( BASSCRIPT_PROPERTY_CALLER, BASSCRIPT_PROPERTY_ID_CALLER, BASSCRIPT_DEFAULT_ATTRIBS(), &m_caller, cppu::UnoType<decltype(m_caller)>::get() ); + } + + + BasicScriptImpl::~BasicScriptImpl() + { + SolarMutexGuard g; + + if ( m_documentBasicManager ) + EndListening( *m_documentBasicManager ); + } + + + // SfxListener + + void BasicScriptImpl::Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) + { + if ( &rBC != m_documentBasicManager ) + { + OSL_ENSURE( false, "BasicScriptImpl::Notify: where does this come from?" ); + // not interested in + return; + } + if ( rHint.GetId() == SfxHintId::Dying ) + { + m_documentBasicManager = nullptr; + EndListening( rBC ); // prevent multiple notifications + } + } + + + // XInterface + + + IMPLEMENT_FORWARD_XINTERFACE2( BasicScriptImpl, BasicScriptImpl_BASE, OPropertyContainer ) + + + // XTypeProvider + + + IMPLEMENT_FORWARD_XTYPEPROVIDER2( BasicScriptImpl, BasicScriptImpl_BASE, OPropertyContainer ) + + + // OPropertySetHelper + + + ::cppu::IPropertyArrayHelper& BasicScriptImpl::getInfoHelper( ) + { + return *getArrayHelper(); + } + + + // OPropertyArrayUsageHelper + + + ::cppu::IPropertyArrayHelper* BasicScriptImpl::createArrayHelper( ) const + { + Sequence< Property > aProps; + describeProperties( aProps ); + return new ::cppu::OPropertyArrayHelper( aProps ); + } + + + // XPropertySet + + + Reference< XPropertySetInfo > BasicScriptImpl::getPropertySetInfo( ) + { + Reference< XPropertySetInfo > xInfo( createPropertySetInfo( getInfoHelper() ) ); + return xInfo; + } + + + // XScript + + + Any BasicScriptImpl::invoke( const Sequence< Any >& aParams, Sequence< sal_Int16 >& aOutParamIndex, Sequence< Any >& aOutParam ) + { + // TODO: throw CannotConvertException + // TODO: check length of aOutParamIndex, aOutParam + + SolarMutexGuard aGuard; + + Any aReturn; + + if ( m_xMethod.is() ) + { + // check if compiled + SbModule* pModule = static_cast< SbModule* >( m_xMethod->GetParent() ); + if ( pModule && !pModule->IsCompiled() ) + pModule->Compile(); + + // check number of parameters + sal_Int32 nParamsCount = aParams.getLength(); + SbxInfo* pInfo = m_xMethod->GetInfo(); + if ( pInfo ) + { + sal_Int32 nSbxOptional = 0; + sal_uInt16 n = 1; + for ( const SbxParamInfo* pParamInfo = pInfo->GetParam( n ); pParamInfo; pParamInfo = pInfo->GetParam( ++n ) ) + { + if ( pParamInfo->nFlags & SbxFlagBits::Optional ) + ++nSbxOptional; + else + nSbxOptional = 0; + } + sal_Int32 nSbxCount = n - 1; + if ( nParamsCount < nSbxCount - nSbxOptional ) + { + throw provider::ScriptFrameworkErrorException( + "wrong number of parameters!", + Reference< XInterface >(), + m_funcName, + "Basic", + provider::ScriptFrameworkErrorType::NO_SUCH_SCRIPT ); + } + } + + // set parameters + SbxArrayRef xSbxParams; + if ( nParamsCount > 0 ) + { + xSbxParams = new SbxArray; + const Any* pParams = aParams.getConstArray(); + for ( sal_Int32 i = 0; i < nParamsCount; ++i ) + { + SbxVariableRef xSbxVar = new SbxVariable( SbxVARIANT ); + unoToSbxValue( xSbxVar.get(), pParams[i] ); + xSbxParams->Put(xSbxVar.get(), static_cast<sal_uInt32>(i) + 1); + + if (pInfo) + { + if (auto* p = pInfo->GetParam(static_cast<sal_uInt16>(i) + 1)) + { + SbxDataType t = static_cast<SbxDataType>(p->eType & 0x0FFF); + // tdf#133889 Revert the downcasting performed in sbxToUnoValueImpl + // to allow passing by reference. + SbxDataType a = xSbxVar->GetType(); + if (t == SbxSINGLE && (a == SbxINTEGER || a == SbxLONG)) + { + sal_Int32 val = xSbxVar->GetLong(); + if (val >= -16777216 && val <= 16777215) + xSbxVar->SetType(t); + } + else if (t == SbxDOUBLE && (a == SbxINTEGER || a == SbxLONG)) + xSbxVar->SetType(t); + else if (t == SbxLONG && a == SbxINTEGER) + xSbxVar->SetType(t); + else if (t == SbxULONG && a == SbxUSHORT) + xSbxVar->SetType(t); + // Enable passing by ref + if (t != SbxVARIANT) + xSbxVar->SetFlag(SbxFlagBits::Fixed); + } + } + } + } + if ( xSbxParams.is() ) + m_xMethod->SetParameters( xSbxParams.get() ); + + // call method + SbxVariableRef xReturn = new SbxVariable; + ErrCode nErr = ERRCODE_NONE; + + // if it's a document-based script, temporarily reset ThisComponent to the script invocation context + Any aOldThisComponent; + if ( m_documentBasicManager && m_xDocumentScriptContext.is() ) + m_documentBasicManager->SetGlobalUNOConstant( "ThisComponent", Any( m_xDocumentScriptContext ), &aOldThisComponent ); + + if ( m_caller.hasElements() && m_caller[ 0 ].hasValue() ) + { + SbxVariableRef xCallerVar = new SbxVariable( SbxVARIANT ); + unoToSbxValue( xCallerVar.get(), m_caller[ 0 ] ); + nErr = m_xMethod->Call( xReturn.get(), xCallerVar.get() ); + } + else + nErr = m_xMethod->Call( xReturn.get() ); + + if ( m_documentBasicManager && m_xDocumentScriptContext.is() ) + m_documentBasicManager->SetGlobalUNOConstant( "ThisComponent", aOldThisComponent ); + + if ( nErr != ERRCODE_NONE ) + { + // TODO: throw InvocationTargetException ? + } + + // get output parameters + if ( xSbxParams.is() ) + { + SbxInfo* pInfo_ = m_xMethod->GetInfo(); + if ( pInfo_ ) + { + OutParamMap aOutParamMap; + for (sal_uInt32 n = 1, nCount = xSbxParams->Count(); n < nCount; ++n) + { + assert(nCount <= std::numeric_limits<sal_uInt16>::max()); + const SbxParamInfo* pParamInfo = pInfo_->GetParam( sal::static_int_cast<sal_uInt16>(n) ); + if ( pParamInfo && ( pParamInfo->eType & SbxBYREF ) != 0 ) + { + SbxVariable* pVar = xSbxParams->Get(n); + if ( pVar ) + { + SbxVariableRef xVar = pVar; + aOutParamMap.emplace( n - 1, sbxToUnoValue( xVar.get() ) ); + } + } + } + sal_Int32 nOutParamCount = aOutParamMap.size(); + aOutParamIndex.realloc( nOutParamCount ); + aOutParam.realloc( nOutParamCount ); + sal_Int16* pOutParamIndex = aOutParamIndex.getArray(); + Any* pOutParam = aOutParam.getArray(); + for ( const auto& rEntry : aOutParamMap ) + { + *pOutParamIndex = rEntry.first; + ++pOutParamIndex; + *pOutParam = rEntry.second; + ++pOutParam; + } + } + } + + // get return value + aReturn = sbxToUnoValue( xReturn.get() ); + + // reset parameters + m_xMethod->SetParameters( nullptr ); + } + + return aReturn; + } + + +} // namespace basprov + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/scripting/source/basprov/basscript.hxx b/scripting/source/basprov/basscript.hxx new file mode 100644 index 000000000..d0f9e6e7a --- /dev/null +++ b/scripting/source/basprov/basscript.hxx @@ -0,0 +1,103 @@ +/* -*- 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 . + */ + +#pragma once + +#include <bcholder.hxx> +#include <com/sun/star/script/provider/XScript.hpp> +#include <com/sun/star/document/XScriptInvocationContext.hpp> +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/implbase.hxx> +#include <comphelper/proparrhlp.hxx> +#include <comphelper/propertycontainer.hxx> +#include <basic/sbmeth.hxx> +#include <svl/lstner.hxx> + +class BasicManager; + + +namespace basprov +{ + + + + + typedef ::cppu::WeakImplHelper< + css::script::provider::XScript > BasicScriptImpl_BASE; + + + class BasicScriptImpl : public BasicScriptImpl_BASE, public SfxListener, + public cppu::BaseMutex, + public ::scripting_helper::OBroadcastHelperHolder, + public ::comphelper::OPropertyContainer, + public ::comphelper::OPropertyArrayUsageHelper< BasicScriptImpl > + { + private: + SbMethodRef m_xMethod; + OUString m_funcName; + BasicManager* m_documentBasicManager; + css::uno::Reference< css::document::XScriptInvocationContext > + m_xDocumentScriptContext; + // hack, OPropertyContainer doesn't allow you to define a property of unknown + // type ( I guess because an Any can't contain an Any... I've always wondered why? + // as it's not unusual to do that in corba ) + css::uno::Sequence< css::uno::Any > m_caller; + protected: + // OPropertySetHelper + virtual ::cppu::IPropertyArrayHelper& SAL_CALL getInfoHelper( ) override; + + // OPropertyArrayUsageHelper + virtual ::cppu::IPropertyArrayHelper* createArrayHelper( ) const override; + + public: + BasicScriptImpl( + const OUString& funcName, + SbMethodRef const & xMethod + ); + BasicScriptImpl( + const OUString& funcName, + SbMethodRef const & xMethod, + BasicManager& documentBasicManager, + const css::uno::Reference< css::document::XScriptInvocationContext >& documentScriptContext + ); + virtual ~BasicScriptImpl() override; + + // XInterface + DECLARE_XINTERFACE() + + // XTypeProvider + DECLARE_XTYPEPROVIDER() + + // XScript + virtual css::uno::Any SAL_CALL invoke( + const css::uno::Sequence< css::uno::Any >& aParams, + css::uno::Sequence< sal_Int16 >& aOutParamIndex, + css::uno::Sequence< css::uno::Any >& aOutParam ) override; + // XPropertySet + virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo( ) override; + + // SfxListener + virtual void Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) override; + }; + + +} // namespace basprov + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/scripting/source/dlgprov/DialogModelProvider.cxx b/scripting/source/dlgprov/DialogModelProvider.cxx new file mode 100644 index 000000000..e49ed058d --- /dev/null +++ b/scripting/source/dlgprov/DialogModelProvider.cxx @@ -0,0 +1,166 @@ +/* -*- 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 "DialogModelProvider.hxx" +#include "dlgprov.hxx" +#include <com/sun/star/resource/XStringResourceManager.hpp> +#include <com/sun/star/ucb/SimpleFileAccess.hpp> + +#include <cppuhelper/supportsservice.hxx> + +/// anonymous implementation namespace +namespace dlgprov { + +using namespace ::com::sun::star; +using namespace awt; +using namespace lang; +using namespace uno; +using namespace script; +using namespace beans; + + +DialogModelProvider::DialogModelProvider(Reference< XComponentContext > const & context) : + m_xContext(context) +{} + +// lang::XInitialization: +void SAL_CALL DialogModelProvider::initialize(const css::uno::Sequence< uno::Any > & aArguments) +{ + if ( aArguments.getLength() != 1 ) + return; + + OUString sURL; + if ( !( aArguments[ 0 ] >>= sURL )) + throw css::lang::IllegalArgumentException(); + // Try any other URL with SimpleFileAccess + Reference< ucb::XSimpleFileAccess3 > xSFI = ucb::SimpleFileAccess::create(m_xContext); + + try + { + Reference< io::XInputStream > xInput = xSFI->openFileRead( sURL ); + Reference< resource::XStringResourceManager > xStringResourceManager; + if ( xInput.is() ) + { + xStringResourceManager = dlgprov::lcl_getStringResourceManager(m_xContext,sURL); + Any aDialogSourceURLAny; + aDialogSourceURLAny <<= sURL; + + Reference< frame::XModel > xModel; + m_xDialogModel.set( dlgprov::lcl_createDialogModel( m_xContext, xInput , xModel, xStringResourceManager, aDialogSourceURLAny ), UNO_SET_THROW); + m_xDialogModelProp.set(m_xDialogModel, UNO_QUERY_THROW); + } + } + catch( Exception& ) + {} + //m_sURL = sURL; +} + +// container::XElementAccess: +uno::Type SAL_CALL DialogModelProvider::getElementType() +{ + return m_xDialogModel->getElementType(); +} + +sal_Bool SAL_CALL DialogModelProvider::hasElements() +{ + return m_xDialogModel->hasElements(); +} + +// container::XNameAccess: +uno::Any SAL_CALL DialogModelProvider::getByName(const OUString & aName) +{ + return m_xDialogModel->getByName(aName); +} + +css::uno::Sequence< OUString > SAL_CALL DialogModelProvider::getElementNames() +{ + return m_xDialogModel->getElementNames(); +} + +sal_Bool SAL_CALL DialogModelProvider::hasByName(const OUString & aName) +{ + return m_xDialogModel->hasByName(aName); +} + +// container::XNameReplace: +void SAL_CALL DialogModelProvider::replaceByName(const OUString & aName, const uno::Any & aElement) +{ + m_xDialogModel->replaceByName(aName,aElement); +} + +// container::XNameContainer: +void SAL_CALL DialogModelProvider::insertByName(const OUString & aName, const uno::Any & aElement) +{ + m_xDialogModel->insertByName(aName,aElement); +} + +void SAL_CALL DialogModelProvider::removeByName(const OUString & aName) +{ + m_xDialogModel->removeByName(aName); +} +uno::Reference< beans::XPropertySetInfo > SAL_CALL DialogModelProvider::getPropertySetInfo( ) +{ + return m_xDialogModelProp->getPropertySetInfo(); +} +void SAL_CALL DialogModelProvider::setPropertyValue( const OUString&, const uno::Any& ) +{ +} +uno::Any SAL_CALL DialogModelProvider::getPropertyValue( const OUString& PropertyName ) +{ + return m_xDialogModelProp->getPropertyValue(PropertyName); +} +void SAL_CALL DialogModelProvider::addPropertyChangeListener( const OUString& , const uno::Reference< beans::XPropertyChangeListener >& ) +{ +} +void SAL_CALL DialogModelProvider::removePropertyChangeListener( const OUString& , const uno::Reference< beans::XPropertyChangeListener >& ) +{ +} +void SAL_CALL DialogModelProvider::addVetoableChangeListener( const OUString& , const uno::Reference< beans::XVetoableChangeListener >& ) +{ +} +void SAL_CALL DialogModelProvider::removeVetoableChangeListener( const OUString& ,const uno::Reference< beans::XVetoableChangeListener >& ) +{ +} + +// com.sun.star.uno.XServiceInfo: +OUString SAL_CALL DialogModelProvider::getImplementationName() +{ + return "com.sun.star.comp.scripting.DialogModelProvider"; +} + +sal_Bool SAL_CALL DialogModelProvider::supportsService(OUString const & serviceName) +{ + return cppu::supportsService(this, serviceName); +} + +css::uno::Sequence< OUString > SAL_CALL DialogModelProvider::getSupportedServiceNames() +{ + return { "com.sun.star.awt.UnoControlDialogModelProvider" }; +} + +} // closing anonymous implementation namespace + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +scripting_DialogModelProvider_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new dlgprov::DialogModelProvider(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/scripting/source/dlgprov/DialogModelProvider.hxx b/scripting/source/dlgprov/DialogModelProvider.hxx new file mode 100644 index 000000000..d9a41ef64 --- /dev/null +++ b/scripting/source/dlgprov/DialogModelProvider.hxx @@ -0,0 +1,87 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> +#include <cppuhelper/factory.hxx> +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> + +/// anonymous implementation namespace +namespace dlgprov{ + +class DialogModelProvider: + public ::cppu::WeakImplHelper< + css::lang::XInitialization, + css::container::XNameContainer, + css::beans::XPropertySet, + css::lang::XServiceInfo> +{ +public: + explicit DialogModelProvider(css::uno::Reference< css::uno::XComponentContext > const & context); +private: + // css::lang::XInitialization: + virtual void SAL_CALL initialize(const css::uno::Sequence< css::uno::Any > & aArguments) override; + + // css::container::XElementAccess: + virtual css::uno::Type SAL_CALL getElementType() override; + virtual sal_Bool SAL_CALL hasElements() override; + + // css::container::XNameAccess: + virtual css::uno::Any SAL_CALL getByName(const OUString & aName) override; + virtual css::uno::Sequence< OUString > SAL_CALL getElementNames() override; + virtual sal_Bool SAL_CALL hasByName(const OUString & aName) override; + + // css::container::XNameReplace: + virtual void SAL_CALL replaceByName(const OUString & aName, const css::uno::Any & aElement) override; + + // css::container::XNameContainer: + virtual void SAL_CALL insertByName(const OUString & aName, const css::uno::Any & aElement) override; + virtual void SAL_CALL removeByName(const OUString & Name) override; + + // css::lang::XServiceInfo: + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService(const OUString & ServiceName) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo( ) override; + virtual void SAL_CALL setPropertyValue( const OUString& aPropertyName, const css::uno::Any& aValue ) override; + virtual css::uno::Any SAL_CALL getPropertyValue( const OUString& PropertyName ) override; + virtual void SAL_CALL addPropertyChangeListener( const OUString& aPropertyName, const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener ) override; + virtual void SAL_CALL removePropertyChangeListener( const OUString& aPropertyName, const css::uno::Reference< css::beans::XPropertyChangeListener >& aListener ) override; + virtual void SAL_CALL addVetoableChangeListener( const OUString& PropertyName, const css::uno::Reference< css::beans::XVetoableChangeListener >& aListener ) override; + virtual void SAL_CALL removeVetoableChangeListener( const OUString& PropertyName, const css::uno::Reference< css::beans::XVetoableChangeListener >& aListener ) override; + +private: + DialogModelProvider(const DialogModelProvider &) = delete; + DialogModelProvider& operator=(const DialogModelProvider &) = delete; + + // destructor is private and will be called indirectly by the release call virtual ~DialogModelProvider() {} + + css::uno::Reference< css::uno::XComponentContext > m_xContext; + css::uno::Reference< css::container::XNameContainer> m_xDialogModel; + css::uno::Reference< css::beans::XPropertySet> m_xDialogModelProp; +}; +} // closing anonymous implementation namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/scripting/source/dlgprov/dlgevtatt.cxx b/scripting/source/dlgprov/dlgevtatt.cxx new file mode 100644 index 000000000..704518e07 --- /dev/null +++ b/scripting/source/dlgprov/dlgevtatt.cxx @@ -0,0 +1,658 @@ +/* -*- 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 "dlgevtatt.hxx" + +#include "dlgprov.hxx" + +#include <sfx2/strings.hrc> +#include <sfx2/sfxresid.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <tools/diagnose_ex.h> + +#include <com/sun/star/awt/XControl.hpp> +#include <com/sun/star/awt/XControlContainer.hpp> +#include <com/sun/star/awt/XDialogEventHandler.hpp> +#include <com/sun/star/awt/XContainerWindowEventHandler.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/script/ScriptEventDescriptor.hpp> +#include <com/sun/star/script/XScriptEventsSupplier.hpp> +#include <com/sun/star/script/provider/XScriptProvider.hpp> +#include <com/sun/star/script/provider/theMasterScriptProviderFactory.hpp> +#include <com/sun/star/script/provider/XScriptProviderSupplier.hpp> +#include <com/sun/star/script/vba/XVBACompatibility.hpp> +#include <com/sun/star/lang/ServiceNotRegisteredException.hpp> +#include <com/sun/star/reflection/XIdlMethod.hpp> +#include <com/sun/star/beans/MethodConcept.hpp> +#include <com/sun/star/beans/XMaterialHolder.hpp> + +#include <ooo/vba/XVBAToOOEventDescGen.hpp> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::awt; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::script; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::reflection; + + +namespace dlgprov +{ + namespace { + + class DialogSFScriptListenerImpl : public DialogScriptListenerImpl + { + protected: + Reference< frame::XModel > m_xModel; + virtual void firing_impl( const script::ScriptEvent& aScriptEvent, uno::Any* pRet ) override; + public: + DialogSFScriptListenerImpl( const Reference< XComponentContext >& rxContext, const Reference< frame::XModel >& rxModel ) : DialogScriptListenerImpl( rxContext ), m_xModel( rxModel ) {} + }; + + class DialogLegacyScriptListenerImpl : public DialogSFScriptListenerImpl + { + protected: + virtual void firing_impl( const script::ScriptEvent& aScriptEvent, uno::Any* pRet ) override; + public: + DialogLegacyScriptListenerImpl( const Reference< XComponentContext >& rxContext, const Reference< frame::XModel >& rxModel ) : DialogSFScriptListenerImpl( rxContext, rxModel ){} + }; + + class DialogUnoScriptListenerImpl : public DialogSFScriptListenerImpl + { + Reference< awt::XControl > m_xControl; + Reference< XInterface > m_xHandler; + Reference< beans::XIntrospectionAccess > m_xIntrospectionAccess; + bool m_bDialogProviderMode; + + virtual void firing_impl( const script::ScriptEvent& aScriptEvent, uno::Any* pRet ) override; + + public: + DialogUnoScriptListenerImpl( const Reference< XComponentContext >& rxContext, + const Reference< frame::XModel >& rxModel, + const Reference< awt::XControl >& rxControl, + const Reference< XInterface >& rxHandler, + const Reference< beans::XIntrospectionAccess >& rxIntrospectionAccess, + bool bDialogProviderMode ); // false: ContainerWindowProvider mode + + }; + + class DialogVBAScriptListenerImpl : public DialogScriptListenerImpl + { + protected: + OUString msDialogCodeName; + OUString msDialogLibName; + Reference< script::XScriptListener > mxListener; + virtual void firing_impl( const script::ScriptEvent& aScriptEvent, uno::Any* pRet ) override; + public: + DialogVBAScriptListenerImpl( const Reference< XComponentContext >& rxContext, const Reference< awt::XControl >& rxControl, const Reference< frame::XModel >& xModel, const OUString& sDialogLibName ); + }; + + } + + DialogVBAScriptListenerImpl::DialogVBAScriptListenerImpl( const Reference< XComponentContext >& rxContext, const Reference< awt::XControl >& rxControl, const Reference< frame::XModel >& xModel, const OUString& sDialogLibName ) : DialogScriptListenerImpl( rxContext ), msDialogLibName( sDialogLibName ) + { + Reference< XMultiComponentFactory > xSMgr( m_xContext->getServiceManager() ); + Sequence< Any > args(1); + if ( xSMgr.is() ) + { + args.getArray()[0] <<= xModel; + mxListener.set( xSMgr->createInstanceWithArgumentsAndContext( "ooo.vba.EventListener", args, m_xContext ), UNO_QUERY ); + } + if ( !rxControl.is() ) + return; + + try + { + Reference< XPropertySet > xProps( rxControl->getModel(), UNO_QUERY_THROW ); + xProps->getPropertyValue("Name") >>= msDialogCodeName; + xProps.set( mxListener, UNO_QUERY_THROW ); + xProps->setPropertyValue("Model", args[ 0 ] ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("scripting"); + } + + } + + void DialogVBAScriptListenerImpl::firing_impl( const script::ScriptEvent& aScriptEvent, uno::Any* ) + { + if ( !(aScriptEvent.ScriptType == "VBAInterop" && mxListener.is()) ) + return; + + ScriptEvent aScriptEventCopy( aScriptEvent ); + aScriptEventCopy.ScriptCode = msDialogLibName + "." + msDialogCodeName; + try + { + mxListener->firing( aScriptEventCopy ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("scripting"); + } + } + + + // DialogEventsAttacherImpl + + + DialogEventsAttacherImpl::DialogEventsAttacherImpl( const Reference< XComponentContext >& rxContext, const Reference< frame::XModel >& rxModel, const Reference< awt::XControl >& rxControl, const Reference< XInterface >& rxHandler, const Reference< beans::XIntrospectionAccess >& rxIntrospect, bool bProviderMode, const Reference< script::XScriptListener >& rxRTLListener, const OUString& sDialogLibName ) + :mbUseFakeVBAEvents( false ), m_xContext( rxContext ) + { + // key listeners by protocol when ScriptType = 'Script' + // otherwise key is the ScriptType e.g. StarBasic + if ( rxRTLListener.is() ) // set up handler for RTL_BASIC + listenersForTypes[ OUString("StarBasic") ] = rxRTLListener; + else + listenersForTypes[ OUString("StarBasic") ] = new DialogLegacyScriptListenerImpl( rxContext, rxModel ); + // handler for Script & OUString("vnd.sun.star.UNO:") + listenersForTypes[ OUString("vnd.sun.star.UNO") ] = new DialogUnoScriptListenerImpl( rxContext, rxModel, rxControl, rxHandler, rxIntrospect, bProviderMode ); + listenersForTypes[ OUString("vnd.sun.star.script") ] = new DialogSFScriptListenerImpl( rxContext, rxModel ); + + // determine the VBA compatibility mode from the Basic library container + try + { + uno::Reference< beans::XPropertySet > xModelProps( rxModel, uno::UNO_QUERY_THROW ); + uno::Reference< script::vba::XVBACompatibility > xVBACompat( + xModelProps->getPropertyValue("BasicLibraries"), uno::UNO_QUERY_THROW ); + mbUseFakeVBAEvents = xVBACompat->getVBACompatibilityMode(); + } + catch( uno::Exception& ) + { + } + if ( mbUseFakeVBAEvents ) + listenersForTypes[ OUString("VBAInterop") ] = new DialogVBAScriptListenerImpl( rxContext, rxControl, rxModel, sDialogLibName ); + } + + + DialogEventsAttacherImpl::~DialogEventsAttacherImpl() + { + } + + + Reference< script::XScriptListener > const & + DialogEventsAttacherImpl::getScriptListenerForKey( const OUString& sKey ) + { + ListenerHash::iterator it = listenersForTypes.find( sKey ); + if ( it == listenersForTypes.end() ) + throw RuntimeException(); // more text info here please + return it->second; + } + Reference< XScriptEventsSupplier > DialogEventsAttacherImpl::getFakeVbaEventsSupplier( const Reference< XControl >& xControl, OUString const & sControlName ) + { + Reference< XScriptEventsSupplier > xEventsSupplier; + Reference< XMultiComponentFactory > xSMgr( m_xContext->getServiceManager() ); + if ( xSMgr.is() ) + { + Reference< ooo::vba::XVBAToOOEventDescGen > xVBAToOOEvtDesc( xSMgr->createInstanceWithContext("ooo.vba.VBAToOOEventDesc", m_xContext ), UNO_QUERY ); + if ( xVBAToOOEvtDesc.is() ) + xEventsSupplier = xVBAToOOEvtDesc->getEventSupplier( xControl, sControlName ); + + } + return xEventsSupplier; + } + + + void DialogEventsAttacherImpl::attachEventsToControl( const Reference< XControl>& xControl, const Reference< XScriptEventsSupplier >& xEventsSupplier, const Any& Helper ) + { + if ( !xEventsSupplier.is() ) + return; + + Reference< container::XNameContainer > xEventCont = xEventsSupplier->getEvents(); + + Reference< XControlModel > xControlModel = xControl->getModel(); + if ( !xEventCont.is() ) + return; + + const Sequence< OUString > aNames = xEventCont->getElementNames(); + + for ( const OUString& rName : aNames ) + { + ScriptEventDescriptor aDesc; + + Any aElement = xEventCont->getByName( rName ); + aElement >>= aDesc; + OUString sKey = aDesc.ScriptType; + if ( aDesc.ScriptType == "Script" || aDesc.ScriptType == "UNO" ) + { + sal_Int32 nIndex = aDesc.ScriptCode.indexOf( ':' ); + sKey = aDesc.ScriptCode.copy( 0, nIndex ); + } + Reference< XAllListener > xAllListener = + new DialogAllListenerImpl( getScriptListenerForKey( sKey ), aDesc.ScriptType, aDesc.ScriptCode ); + + // try first to attach event to the ControlModel + bool bSuccess = false; + try + { + Reference< XEventListener > xListener_ = m_xEventAttacher->attachSingleEventListener( + xControlModel, xAllListener, Helper, aDesc.ListenerType, + aDesc.AddListenerParam, aDesc.EventMethod ); + + if ( xListener_.is() ) + bSuccess = true; + } + catch ( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("scripting"); + } + + try + { + // if we had no success, try to attach to the control + if ( !bSuccess ) + { + m_xEventAttacher->attachSingleEventListener( + xControl, xAllListener, Helper, aDesc.ListenerType, + aDesc.AddListenerParam, aDesc.EventMethod ); + } + } + catch ( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("scripting"); + } + } + } + + + void DialogEventsAttacherImpl::nestedAttachEvents( const Sequence< Reference< XInterface > >& Objects, const Any& Helper, OUString& sDialogCodeName ) + { + for ( const Reference< XInterface >& rObject : Objects ) + { + // We know that we have to do with instances of XControl. + // Otherwise this is not the right implementation for + // XScriptEventsAttacher and we have to give up. + Reference< XControl > xControl( rObject, UNO_QUERY ); + Reference< XControlContainer > xControlContainer( xControl, UNO_QUERY ); + Reference< XDialog > xDialog( xControl, UNO_QUERY ); + if ( !xControl.is() ) + throw IllegalArgumentException(); + + // get XEventsSupplier from control model + Reference< XControlModel > xControlModel = xControl->getModel(); + Reference< XScriptEventsSupplier > xEventsSupplier( xControlModel, UNO_QUERY ); + attachEventsToControl( xControl, xEventsSupplier, Helper ); + if ( mbUseFakeVBAEvents ) + { + xEventsSupplier.set( getFakeVbaEventsSupplier( xControl, sDialogCodeName ) ); + Any newHelper(xControl ); + attachEventsToControl( xControl, xEventsSupplier, newHelper ); + } + if ( xControlContainer.is() && !xDialog.is() ) + { + Sequence< Reference< XControl > > aControls = xControlContainer->getControls(); + sal_Int32 nControlCount = aControls.getLength(); + + Sequence< Reference< XInterface > > aObjects( nControlCount ); + Reference< XInterface >* pObjects2 = aObjects.getArray(); + const Reference< XControl >* pControls = aControls.getConstArray(); + + for ( sal_Int32 i2 = 0; i2 < nControlCount; ++i2 ) + { + pObjects2[i2].set( pControls[i2], UNO_QUERY ); + } + nestedAttachEvents( aObjects, Helper, sDialogCodeName ); + } + } + } + + + // XScriptEventsAttacher + + + void SAL_CALL DialogEventsAttacherImpl::attachEvents( const Sequence< Reference< XInterface > >& Objects, + const css::uno::Reference<css::script::XScriptListener>&, + const Any& Helper ) + { + // get EventAttacher + { + ::osl::MutexGuard aGuard( getMutex() ); + + if ( !m_xEventAttacher.is() ) + { + Reference< XMultiComponentFactory > xSMgr( m_xContext->getServiceManager() ); + if ( !xSMgr.is() ) + throw RuntimeException(); + + m_xEventAttacher.set( xSMgr->createInstanceWithContext( + "com.sun.star.script.EventAttacher", m_xContext ), UNO_QUERY ); + + if ( !m_xEventAttacher.is() ) + throw ServiceNotRegisteredException(); + } + } + OUString sDialogCodeName; + sal_Int32 nObjCount = Objects.getLength(); + Reference< awt::XControl > xDlgControl( Objects[ nObjCount - 1 ], uno::UNO_QUERY ); // last object is the dialog + if ( xDlgControl.is() ) + { + Reference< XPropertySet > xProps( xDlgControl->getModel(), UNO_QUERY ); + try + { + xProps->getPropertyValue("Name") >>= sDialogCodeName; + } + catch( Exception& ){} + } + // go over all objects + nestedAttachEvents( Objects, Helper, sDialogCodeName ); + } + + + // DialogAllListenerImpl + + + DialogAllListenerImpl::DialogAllListenerImpl( const Reference< XScriptListener >& rxListener, + const OUString& rScriptType, const OUString& rScriptCode ) + :m_xScriptListener( rxListener ) + ,m_sScriptType( rScriptType ) + ,m_sScriptCode( rScriptCode ) + { + } + + + DialogAllListenerImpl::~DialogAllListenerImpl() + { + } + + + void DialogAllListenerImpl::firing_impl( const AllEventObject& Event, Any* pRet ) + { + ScriptEvent aScriptEvent; + aScriptEvent.Source = static_cast<OWeakObject *>(this); // get correct XInterface + aScriptEvent.ListenerType = Event.ListenerType; + aScriptEvent.MethodName = Event.MethodName; + aScriptEvent.Arguments = Event.Arguments; + aScriptEvent.Helper = Event.Helper; + aScriptEvent.ScriptType = m_sScriptType; + aScriptEvent.ScriptCode = m_sScriptCode; + + if ( m_xScriptListener.is() ) + { + if ( pRet ) + *pRet = m_xScriptListener->approveFiring( aScriptEvent ); + else + m_xScriptListener->firing( aScriptEvent ); + } + } + + + // XEventListener + + + void DialogAllListenerImpl::disposing(const EventObject& ) + { + } + + + // XAllListener + + + void DialogAllListenerImpl::firing( const AllEventObject& Event ) + { + //::osl::MutexGuard aGuard( getMutex() ); + + firing_impl( Event, nullptr ); + } + + + Any DialogAllListenerImpl::approveFiring( const AllEventObject& Event ) + { + //::osl::MutexGuard aGuard( getMutex() ); + + Any aReturn; + firing_impl( Event, &aReturn ); + return aReturn; + } + + + // DialogScriptListenerImpl + + + DialogUnoScriptListenerImpl::DialogUnoScriptListenerImpl( const Reference< XComponentContext >& rxContext, + const Reference< css::frame::XModel >& rxModel, + const Reference< css::awt::XControl >& rxControl, + const Reference< css::uno::XInterface >& rxHandler, + const Reference< css::beans::XIntrospectionAccess >& rxIntrospectionAccess, + bool bDialogProviderMode ) + : DialogSFScriptListenerImpl( rxContext, rxModel ) + ,m_xControl( rxControl ) + ,m_xHandler( rxHandler ) + ,m_xIntrospectionAccess( rxIntrospectionAccess ) + ,m_bDialogProviderMode( bDialogProviderMode ) + { + } + + + DialogScriptListenerImpl::~DialogScriptListenerImpl() + { + } + + + void DialogSFScriptListenerImpl::firing_impl( const ScriptEvent& aScriptEvent, Any* pRet ) + { + try + { + Reference< provider::XScriptProvider > xScriptProvider; + if ( m_xModel.is() ) + { + Reference< provider::XScriptProviderSupplier > xSupplier( m_xModel, UNO_QUERY ); + OSL_ENSURE( xSupplier.is(), "DialogScriptListenerImpl::firing_impl: failed to get script provider supplier" ); + if ( xSupplier.is() ) + xScriptProvider.set( xSupplier->getScriptProvider() ); + } + else + { + OSL_ASSERT( m_xContext.is() ); + if ( m_xContext.is() ) + { + Reference< provider::XScriptProviderFactory > xFactory = + provider::theMasterScriptProviderFactory::get( m_xContext ); + + Any aCtx; + aCtx <<= OUString("user"); + xScriptProvider = xFactory->createScriptProvider( aCtx ); + } + } + + OSL_ENSURE( xScriptProvider.is(), "DialogScriptListenerImpl::firing_impl: failed to get script provider" ); + + if ( xScriptProvider.is() ) + { + Reference< provider::XScript > xScript = xScriptProvider->getScript( aScriptEvent.ScriptCode ); + OSL_ENSURE( xScript.is(), "DialogScriptListenerImpl::firing_impl: failed to get script" ); + + if ( xScript.is() ) + { + Sequence< Any > aInParams; + Sequence< sal_Int16 > aOutParamsIndex; + Sequence< Any > aOutParams; + + // get arguments for script + aInParams = aScriptEvent.Arguments; + + Any aResult = xScript->invoke( aInParams, aOutParamsIndex, aOutParams ); + if ( pRet ) + *pRet = aResult; + } + } + } + catch ( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("scripting"); + } + } + + void DialogLegacyScriptListenerImpl::firing_impl( const ScriptEvent& aScriptEvent, Any* pRet ) + { + OUString sScriptURL; + OUString sScriptCode( aScriptEvent.ScriptCode ); + + if ( aScriptEvent.ScriptType != "StarBasic" ) + return; + + // StarBasic script: convert ScriptCode to scriptURL + sal_Int32 nIndex = sScriptCode.indexOf( ':' ); + if ( nIndex >= 0 && nIndex < sScriptCode.getLength() ) + { + sScriptURL = OUString::Concat("vnd.sun.star.script:") + + sScriptCode.subView( nIndex + 1 ) + + "?language=Basic&location=" + + sScriptCode.subView( 0, nIndex ); + } + ScriptEvent aSFScriptEvent( aScriptEvent ); + aSFScriptEvent.ScriptCode = sScriptURL; + DialogSFScriptListenerImpl::firing_impl( aSFScriptEvent, pRet ); + } + + void DialogUnoScriptListenerImpl::firing_impl( const ScriptEvent& aScriptEvent, Any* pRet ) + { + OUString aMethodName = aScriptEvent.ScriptCode.copy( strlen("vnd.sun.star.UNO:") ); + + const Any* pArguments = aScriptEvent.Arguments.getConstArray(); + Any aEventObject = pArguments[0]; + + bool bHandled = false; + if( m_xHandler.is() ) + { + if( m_bDialogProviderMode ) + { + Reference< XDialogEventHandler > xDialogEventHandler( m_xHandler, UNO_QUERY ); + if( xDialogEventHandler.is() ) + { + Reference< XDialog > xDialog( m_xControl, UNO_QUERY ); + bHandled = xDialogEventHandler->callHandlerMethod( xDialog, aEventObject, aMethodName ); + } + } + else + { + Reference< XContainerWindowEventHandler > xContainerWindowEventHandler( m_xHandler, UNO_QUERY ); + if( xContainerWindowEventHandler.is() ) + { + Reference< XWindow > xWindow( m_xControl, UNO_QUERY ); + bHandled = xContainerWindowEventHandler->callHandlerMethod( xWindow, aEventObject, aMethodName ); + } + } + } + + Any aRet; + if( !bHandled && m_xIntrospectionAccess.is() ) + { + try + { + // call method + const Reference< XIdlMethod >& rxMethod = m_xIntrospectionAccess-> + getMethod( aMethodName, MethodConcept::ALL - MethodConcept::DANGEROUS ); + + Reference< XMaterialHolder > xMaterialHolder = + Reference< XMaterialHolder >::query( m_xIntrospectionAccess ); + Any aHandlerObject = xMaterialHolder->getMaterial(); + + Sequence< Reference< XIdlClass > > aParamTypeSeq = rxMethod->getParameterTypes(); + sal_Int32 nParamCount = aParamTypeSeq.getLength(); + if( nParamCount == 0 ) + { + Sequence<Any> args; + rxMethod->invoke( aHandlerObject, args ); + bHandled = true; + } + else if( nParamCount == 2 ) + { + // Signature check automatically done by reflection + Sequence<Any> Args(2); + Any* pArgs = Args.getArray(); + if( m_bDialogProviderMode ) + { + Reference< XDialog > xDialog( m_xControl, UNO_QUERY ); + pArgs[0] <<= xDialog; + } + else + { + Reference< XWindow > xWindow( m_xControl, UNO_QUERY ); + pArgs[0] <<= xWindow; + } + pArgs[1] = aEventObject; + aRet = rxMethod->invoke( aHandlerObject, Args ); + bHandled = true; + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("scripting"); + } + } + + if( bHandled ) + { + if( pRet ) + *pRet = aRet; + } + else + { + OUString aRes(SfxResId(STR_ERRUNOEVENTBINDUNG)); + OUString aQuoteChar( "\"" ); + + sal_Int32 nIndex = aRes.indexOf( '%' ); + + OUString aOUFinal = + aRes.subView( 0, nIndex ) + + aQuoteChar + aMethodName + aQuoteChar + + aRes.subView( nIndex + 2 ); + + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(nullptr, + VclMessageType::Warning, VclButtonsType::Ok, aOUFinal)); + xBox->run(); + } + } + + + // XEventListener + + + void DialogScriptListenerImpl::disposing(const EventObject& ) + { + } + + + // XScriptListener + + + void DialogScriptListenerImpl::firing( const ScriptEvent& aScriptEvent ) + { + //::osl::MutexGuard aGuard( getMutex() ); + + firing_impl( aScriptEvent, nullptr ); + } + + + Any DialogScriptListenerImpl::approveFiring( const ScriptEvent& aScriptEvent ) + { + //::osl::MutexGuard aGuard( getMutex() ); + + Any aReturn; + firing_impl( aScriptEvent, &aReturn ); + return aReturn; + } + + +} // namespace dlgprov + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/scripting/source/dlgprov/dlgevtatt.hxx b/scripting/source/dlgprov/dlgevtatt.hxx new file mode 100644 index 000000000..239cb6530 --- /dev/null +++ b/scripting/source/dlgprov/dlgevtatt.hxx @@ -0,0 +1,130 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/script/XAllListener.hpp> +#include <com/sun/star/script/XEventAttacher.hpp> +#include <com/sun/star/script/XScriptEventsAttacher.hpp> +#include <com/sun/star/script/XScriptListener.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/awt/XControl.hpp> +#include <com/sun/star/beans/XIntrospectionAccess.hpp> +#include <cppuhelper/implbase.hxx> + +#include <com/sun/star/script/XScriptEventsSupplier.hpp> + +#include <unordered_map> + +namespace dlgprov +{ + typedef std::unordered_map< OUString, + css::uno::Reference< css::script::XScriptListener > > ListenerHash; + + typedef ::cppu::WeakImplHelper< + css::script::XScriptEventsAttacher > DialogEventsAttacherImpl_BASE; + + + class DialogEventsAttacherImpl : public DialogEventsAttacherImpl_BASE + { + private: + bool mbUseFakeVBAEvents; + ListenerHash listenersForTypes; + css::uno::Reference< css::uno::XComponentContext > m_xContext; + css::uno::Reference< css::script::XEventAttacher > m_xEventAttacher; + /// @throws css::uno::RuntimeException + css::uno::Reference< css::script::XScriptListener > const & getScriptListenerForKey( const OUString& sScriptName ); + css::uno::Reference< css::script::XScriptEventsSupplier > getFakeVbaEventsSupplier( const css::uno::Reference< css::awt::XControl>& xControl, OUString const & sCodeName ); + void nestedAttachEvents( const css::uno::Sequence< css::uno::Reference< css::uno::XInterface > >& Objects, const css::uno::Any& Helper, OUString& sDialogCodeName ); + void attachEventsToControl( const css::uno::Reference< css::awt::XControl>& xControl, const css::uno::Reference< css::script::XScriptEventsSupplier >& events, const css::uno::Any& Helper ); + public: + DialogEventsAttacherImpl( const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const css::uno::Reference< css::frame::XModel >& xModel, + const css::uno::Reference< css::awt::XControl >& xControl, + const css::uno::Reference< css::uno::XInterface >& xHandler, + const css::uno::Reference< css::beans::XIntrospectionAccess >& xIntrospect, + bool bProviderMode, + const css::uno::Reference< css::script::XScriptListener >& xRTLListener ,const OUString& sDialogLibName ); + virtual ~DialogEventsAttacherImpl() override; + + // XScriptEventsAttacher + virtual void SAL_CALL attachEvents( const css::uno::Sequence< + css::uno::Reference< css::uno::XInterface > >& Objects, + const css::uno::Reference<css::script::XScriptListener>&, + const css::uno::Any& Helper ) override; + }; + + + + + typedef ::cppu::WeakImplHelper< + css::script::XAllListener > DialogAllListenerImpl_BASE; + + + class DialogAllListenerImpl : public DialogAllListenerImpl_BASE + { + private: + css::uno::Reference< css::script::XScriptListener > m_xScriptListener; + OUString m_sScriptType; + OUString m_sScriptCode; + + void firing_impl( const css::script::AllEventObject& Event, css::uno::Any* pRet ); + + public: + DialogAllListenerImpl( const css::uno::Reference< css::script::XScriptListener >& rxListener, + const OUString& rScriptType, const OUString& rScriptCode ); + virtual ~DialogAllListenerImpl() override; + + // XEventListener + virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override; + + // XAllListener + virtual void SAL_CALL firing( const css::script::AllEventObject& Event ) override; + virtual css::uno::Any SAL_CALL approveFiring( const css::script::AllEventObject& Event ) override; + }; + + + + + typedef ::cppu::WeakImplHelper< + css::script::XScriptListener > DialogScriptListenerImpl_BASE; + + + class DialogScriptListenerImpl : public DialogScriptListenerImpl_BASE + { + protected: + css::uno::Reference< css::uno::XComponentContext > m_xContext; + virtual void firing_impl( const css::script::ScriptEvent& aScriptEvent, css::uno::Any* pRet ) = 0; + public: + explicit DialogScriptListenerImpl( const css::uno::Reference< css::uno::XComponentContext >& rxContext ) : m_xContext( rxContext ) {} + virtual ~DialogScriptListenerImpl() override; + + // XEventListener + virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override; + + // XScriptListener + virtual void SAL_CALL firing( const css::script::ScriptEvent& aScriptEvent ) override; + virtual css::uno::Any SAL_CALL approveFiring( const css::script::ScriptEvent& aScriptEvent ) override; + }; + + +} // namespace dlgprov + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/scripting/source/dlgprov/dlgprov.component b/scripting/source/dlgprov/dlgprov.component new file mode 100644 index 000000000..35203afb8 --- /dev/null +++ b/scripting/source/dlgprov/dlgprov.component @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.scripting.DialogProvider" + constructor="scripting_DialogProviderImpl_get_implementation"> + <service name="com.sun.star.awt.ContainerWindowProvider"/> + <service name="com.sun.star.awt.DialogProvider"/> + <service name="com.sun.star.awt.DialogProvider2"/> + </implementation> + <implementation name="com.sun.star.comp.scripting.DialogModelProvider" + constructor="scripting_DialogModelProvider_get_implementation"> + <service name="com.sun.star.awt.UnoControlDialogModelProvider"/> + </implementation> +</component> diff --git a/scripting/source/dlgprov/dlgprov.cxx b/scripting/source/dlgprov/dlgprov.cxx new file mode 100644 index 000000000..18815f499 --- /dev/null +++ b/scripting/source/dlgprov/dlgprov.cxx @@ -0,0 +1,702 @@ +/* -*- 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 "dlgprov.hxx" +#include "dlgevtatt.hxx" +#include <com/sun/star/awt/UnoControlDialog.hpp> +#include <com/sun/star/awt/Toolkit.hpp> +#include <com/sun/star/awt/XControlContainer.hpp> +#include <com/sun/star/awt/XWindowPeer.hpp> +#include <com/sun/star/beans/theIntrospection.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/document/XEmbeddedScripts.hpp> +#include <com/sun/star/lang/XMultiComponentFactory.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/io/XInputStreamProvider.hpp> +#include <com/sun/star/resource/XStringResourceSupplier.hpp> +#include <com/sun/star/resource/XStringResourceManager.hpp> +#include <com/sun/star/script/XLibraryContainer.hpp> +#include <com/sun/star/ucb/SimpleFileAccess.hpp> +#include <com/sun/star/uri/XUriReference.hpp> +#include <com/sun/star/uri/UriReferenceFactory.hpp> +#include <com/sun/star/uri/XVndSunStarScriptUrl.hpp> +#include <com/sun/star/uri/XVndSunStarExpandUrl.hpp> +#include <com/sun/star/util/theMacroExpander.hpp> + +#include <cppuhelper/exc_hlp.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <sfx2/app.hxx> +#include <xmlscript/xmldlg_imexp.hxx> +#include <tools/urlobj.hxx> +#include <comphelper/namedvaluecollection.hxx> +#include <util/MiscUtils.hxx> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> +#include <i18nlangtag/languagetag.hxx> + +using namespace ::com::sun::star; +using namespace awt; +using namespace lang; +using namespace uno; +using namespace script; +using namespace beans; +using namespace document; +using namespace ::sf_misc; + +namespace dlgprov +{ + + Reference< resource::XStringResourceManager > lcl_getStringResourceManager(const Reference< XComponentContext >& i_xContext, std::u16string_view i_sURL) + { + INetURLObject aInetObj( i_sURL ); + OUString aDlgName = aInetObj.GetBase(); + aInetObj.removeSegment(); + OUString aDlgLocation = aInetObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + css::lang::Locale aLocale = Application::GetSettings().GetUILanguageTag().getLocale(); + + Reference< task::XInteractionHandler > xDummyHandler; + + Sequence<Any> aArgs{ Any(aDlgLocation), + Any(true), // bReadOnly + Any(aLocale), + Any(aDlgName), + Any(OUString()), + Any(xDummyHandler) }; + + Reference< XMultiComponentFactory > xSMgr_( i_xContext->getServiceManager(), UNO_SET_THROW ); + // TODO: Ctor + Reference< resource::XStringResourceManager > xStringResourceManager( xSMgr_->createInstanceWithContext + ( "com.sun.star.resource.StringResourceWithLocation", + i_xContext ), UNO_QUERY ); + if( xStringResourceManager.is() ) + { + Reference< XInitialization > xInit( xStringResourceManager, UNO_QUERY ); + if( xInit.is() ) + xInit->initialize( aArgs ); + } + return xStringResourceManager; + } + Reference< container::XNameContainer > lcl_createControlModel(const Reference< XComponentContext >& i_xContext) + { + Reference< XMultiComponentFactory > xSMgr_( i_xContext->getServiceManager(), UNO_SET_THROW ); + Reference< container::XNameContainer > xControlModel( xSMgr_->createInstanceWithContext("com.sun.star.awt.UnoControlDialogModel", i_xContext ), UNO_QUERY_THROW ); + return xControlModel; + } + Reference< container::XNameContainer > lcl_createDialogModel( const Reference< XComponentContext >& i_xContext, + const Reference< io::XInputStream >& xInput, + const Reference< frame::XModel >& xModel, + const Reference< resource::XStringResourceManager >& xStringResourceManager, + const Any &aDialogSourceURL) + { + Reference< container::XNameContainer > xDialogModel( lcl_createControlModel(i_xContext) ); + + Reference< beans::XPropertySet > xDlgPropSet( xDialogModel, UNO_QUERY ); + xDlgPropSet->setPropertyValue( "DialogSourceURL", aDialogSourceURL ); + + // #TODO we really need to detect the source of the Dialog, is it + // the dialog. E.g. if the dialog was created from basic ( then we just + // can't tell where its from ) + // If we are happy to always substitute the form model for the awt + // one then maybe the presence of a document model is enough to trigger + // swapping out the models ( or perhaps we only want to do this + // for vba mode ) there are a number of feasible and valid possibilities + ::xmlscript::importDialogModel( xInput, xDialogModel, i_xContext, xModel ); + + // Set resource property + if( xStringResourceManager.is() ) + { + Reference< beans::XPropertySet > xDlgPSet( xDialogModel, UNO_QUERY ); + Any aStringResourceManagerAny; + aStringResourceManagerAny <<= xStringResourceManager; + xDlgPSet->setPropertyValue( "ResourceResolver", aStringResourceManagerAny ); + } + + return xDialogModel; + } + + // mutex + + + ::osl::Mutex& getMutex() + { + static ::osl::Mutex s_aMutex; + + return s_aMutex; + } + + + // DialogProviderImpl + + + DialogProviderImpl::DialogProviderImpl( const Reference< XComponentContext >& rxContext ) + :m_xContext( rxContext ) + { + } + + + DialogProviderImpl::~DialogProviderImpl() + { + } + + + static Reference< resource::XStringResourceManager > getStringResourceFromDialogLibrary + ( const Reference< container::XNameContainer >& xDialogLib ) + { + Reference< resource::XStringResourceManager > xStringResourceManager; + if( xDialogLib.is() ) + { + Reference< resource::XStringResourceSupplier > xStringResourceSupplier( xDialogLib, UNO_QUERY ); + if( xStringResourceSupplier.is() ) + { + Reference< resource::XStringResourceResolver > + xStringResourceResolver = xStringResourceSupplier->getStringResource(); + + xStringResourceManager = + Reference< resource::XStringResourceManager >( xStringResourceResolver, UNO_QUERY ); + } + } + return xStringResourceManager; + } + + Reference< container::XNameContainer > DialogProviderImpl::createDialogModel( + const Reference< io::XInputStream >& xInput, + const Reference< resource::XStringResourceManager >& xStringResourceManager, + const Any &aDialogSourceURL) + { + return lcl_createDialogModel(m_xContext,xInput,m_xModel,xStringResourceManager,aDialogSourceURL); + } + + Reference< XControlModel > DialogProviderImpl::createDialogModelForBasic() + { + if (!m_BasicInfo) + // shouldn't get here + throw RuntimeException("No information to create dialog" ); + Reference< resource::XStringResourceManager > xStringResourceManager = getStringResourceFromDialogLibrary( m_BasicInfo->mxDlgLib ); + + Any aDialogSourceURL((OUString())); + Reference< XControlModel > xCtrlModel( createDialogModel( m_BasicInfo->mxInput, xStringResourceManager, aDialogSourceURL ), UNO_QUERY_THROW ); + return xCtrlModel; + } + + Reference< XControlModel > DialogProviderImpl::createDialogModel( const OUString& sURL ) + { + + OUString aURL( sURL ); + + // parse URL + // TODO: use URL parsing class + // TODO: decoding of location + + Reference< uri::XUriReferenceFactory > xFac ( uri::UriReferenceFactory::create( m_xContext ) ); + + // i75778: Support non-script URLs + Reference< io::XInputStream > xInput; + Reference< container::XNameContainer > xDialogLib; + + // Accept file URL to single dialog + bool bSingleDialog = false; + + Reference< util::XMacroExpander > xMacroExpander = + util::theMacroExpander::get(m_xContext); + + Reference< uri::XUriReference > uriRef; + for (;;) + { + uriRef = xFac->parse( aURL ); + if ( !uriRef.is() ) + { + OUString errorMsg = "DialogProviderImpl::getDialogModel: failed to parse URI: " + aURL; + throw IllegalArgumentException( errorMsg, Reference< XInterface >(), 1 ); + } + Reference < uri::XVndSunStarExpandUrl > sxUri( uriRef, UNO_QUERY ); + if( !sxUri.is() ) + break; + + aURL = sxUri->expand( xMacroExpander ); + } + + Reference < uri::XVndSunStarScriptUrl > sfUri( uriRef, UNO_QUERY ); + if( !sfUri.is() ) + { + bSingleDialog = true; + + // Try any other URL with SimpleFileAccess + Reference< ucb::XSimpleFileAccess3 > xSFI = ucb::SimpleFileAccess::create(m_xContext); + + try + { + xInput = xSFI->openFileRead( aURL ); + } + catch( Exception& ) + {} + } + else + { + OUString sDescription = sfUri->getName(); + + sal_Int32 nIndex = 0; + + OUString sLibName = sDescription.getToken( 0, '.', nIndex ); + OUString sDlgName; + if ( nIndex != -1 ) + sDlgName = sDescription.getToken( 0, '.', nIndex ); + + OUString sLocation = sfUri->getParameter( "location" ); + + + // get dialog library container + // TODO: dialogs in packages + Reference< XLibraryContainer > xLibContainer; + + if ( sLocation == "application" ) + { + xLibContainer = SfxGetpApp()->GetDialogContainer(); + } + else if ( sLocation == "document" ) + { + Reference< XEmbeddedScripts > xDocumentScripts( m_xModel, UNO_QUERY ); + if ( xDocumentScripts.is() ) + { + xLibContainer = xDocumentScripts->getDialogLibraries(); + OSL_ENSURE( xLibContainer.is(), + "DialogProviderImpl::createDialogModel: invalid dialog container!" ); + } + } + else + { + const Sequence< OUString > aOpenDocsTdocURLs( MiscUtils::allOpenTDocUrls( m_xContext ) ); + for ( auto const & tdocURL : aOpenDocsTdocURLs ) + { + Reference< frame::XModel > xModel( MiscUtils::tDocUrlToModel( tdocURL ) ); + OSL_ENSURE( xModel.is(), "DialogProviderImpl::createDialogModel: invalid document model!" ); + if ( !xModel.is() ) + continue; + + OUString sDocURL = xModel->getURL(); + if ( sDocURL.isEmpty() ) + { + sDocURL = ::comphelper::NamedValueCollection::getOrDefault( xModel->getArgs(), u"Title", sDocURL ); + } + + if ( sLocation != sDocURL ) + continue; + + Reference< XEmbeddedScripts > xDocumentScripts( m_xModel, UNO_QUERY ); + if ( !xDocumentScripts.is() ) + continue; + + xLibContainer = xDocumentScripts->getDialogLibraries(); + OSL_ENSURE( xLibContainer.is(), + "DialogProviderImpl::createDialogModel: invalid dialog container!" ); + } + } + + // get input stream provider + Reference< io::XInputStreamProvider > xISP; + if ( !xLibContainer.is() ) + { + throw IllegalArgumentException( + "DialogProviderImpl::getDialog: library container not found!", + Reference< XInterface >(), 1 ); + } + + // load dialog library + if ( !xLibContainer->isLibraryLoaded( sLibName ) ) + xLibContainer->loadLibrary( sLibName ); + + // get dialog library + if ( xLibContainer->hasByName( sLibName ) ) + { + Any aElement = xLibContainer->getByName( sLibName ); + aElement >>= xDialogLib; + } + + if ( !xDialogLib.is() ) + { + throw IllegalArgumentException( + "DialogProviderImpl::getDialogModel: library not found!", + Reference< XInterface >(), 1 ); + } + + // get input stream provider + if ( xDialogLib->hasByName( sDlgName ) ) + { + Any aElement = xDialogLib->getByName( sDlgName ); + aElement >>= xISP; + } + + if ( !xISP.is() ) + { + throw IllegalArgumentException( + "DialogProviderImpl::getDialogModel: dialog not found!", + Reference< XInterface >(), 1 ); + } + + + + if ( xISP.is() ) + xInput = xISP->createInputStream(); + msDialogLibName = sLibName; + } + + // import dialog model + Reference< XControlModel > xCtrlModel; + if ( xInput.is() && m_xContext.is() ) + { + Reference< resource::XStringResourceManager > xStringResourceManager; + if( bSingleDialog ) + { + xStringResourceManager = lcl_getStringResourceManager(m_xContext,aURL); + } + else if( xDialogLib.is() ) + { + xStringResourceManager = getStringResourceFromDialogLibrary( xDialogLib ); + } + + Any aDialogSourceURLAny; + aDialogSourceURLAny <<= aURL; + + Reference< container::XNameContainer > xDialogModel( createDialogModel( xInput , xStringResourceManager, aDialogSourceURLAny ), UNO_SET_THROW); + + xCtrlModel.set( xDialogModel, UNO_QUERY ); + } + return xCtrlModel; + } + + + Reference< XUnoControlDialog > DialogProviderImpl::createDialogControl + ( const Reference< XControlModel >& rxDialogModel, const Reference< XWindowPeer >& xParent ) + { + OSL_ENSURE( rxDialogModel.is(), "DialogProviderImpl::getDialogControl: no dialog model" ); + + Reference< XUnoControlDialog > xDialogControl; + + if ( m_xContext.is() ) + { + xDialogControl = UnoControlDialog::create( m_xContext ); + + // set the model + if ( rxDialogModel.is() ) + xDialogControl->setModel( rxDialogModel ); + + // set visible + xDialogControl->setVisible( false ); + + // get the parent of the dialog control + Reference< XWindowPeer > xPeer; + if( xParent.is() ) + { + xPeer = xParent; + } + else if ( m_xModel.is() ) + { + Reference< frame::XController > xController = m_xModel->getCurrentController(); + if ( xController.is() ) + { + Reference< frame::XFrame > xFrame = xController->getFrame(); + if ( xFrame.is() ) + xPeer.set( xFrame->getContainerWindow(), UNO_QUERY ); + } + } + + // create a peer + Reference< XToolkit> xToolkit( Toolkit::create( m_xContext ), UNO_QUERY_THROW ); + xDialogControl->createPeer( xToolkit, xPeer ); + } + + return xDialogControl; + } + + + void DialogProviderImpl::attachControlEvents( + const Reference< XControl >& rxControl, + const Reference< XInterface >& rxHandler, + const Reference< XIntrospectionAccess >& rxIntrospectionAccess, + bool bDialogProviderMode ) + { + if ( !rxControl.is() ) + return; + + Reference< XControlContainer > xControlContainer( rxControl, UNO_QUERY ); + + if ( !xControlContainer.is() ) + return; + + Sequence< Reference< XControl > > aControls = xControlContainer->getControls(); + const Reference< XControl >* pControls = aControls.getConstArray(); + sal_Int32 nControlCount = aControls.getLength(); + + Sequence< Reference< XInterface > > aObjects( nControlCount + 1 ); + Reference< XInterface >* pObjects = aObjects.getArray(); + for ( sal_Int32 i = 0; i < nControlCount; ++i ) + { + pObjects[i].set( pControls[i], UNO_QUERY ); + } + + // also add the dialog control itself to the sequence + pObjects[nControlCount].set( rxControl, UNO_QUERY ); + + Reference<XScriptEventsAttacher> xScriptEventsAttacher + = new DialogEventsAttacherImpl( + m_xContext, m_xModel, rxControl, rxHandler, rxIntrospectionAccess, + bDialogProviderMode, + (m_BasicInfo ? m_BasicInfo->mxBasicRTLListener : nullptr), msDialogLibName); + + Any aHelper; + xScriptEventsAttacher->attachEvents( aObjects, Reference< XScriptListener >(), aHelper ); + } + + Reference< XIntrospectionAccess > DialogProviderImpl::inspectHandler( const Reference< XInterface >& rxHandler ) + { + Reference< XIntrospectionAccess > xIntrospectionAccess; + static Reference< XIntrospection > xIntrospection; + + if( !rxHandler.is() ) + return xIntrospectionAccess; + + if( !xIntrospection.is() ) + { + // Get introspection service + xIntrospection = theIntrospection::get( m_xContext ); + } + + // Do introspection + try + { + Any aHandlerAny; + aHandlerAny <<= rxHandler; + xIntrospectionAccess = xIntrospection->inspect( aHandlerAny ); + } + catch( RuntimeException& ) + { + xIntrospectionAccess.clear(); + } + return xIntrospectionAccess; + } + + + // XServiceInfo + + + OUString DialogProviderImpl::getImplementationName( ) + { + return "com.sun.star.comp.scripting.DialogProvider"; + } + + sal_Bool DialogProviderImpl::supportsService( const OUString& rServiceName ) + { + return cppu::supportsService(this, rServiceName); + } + + Sequence< OUString > DialogProviderImpl::getSupportedServiceNames( ) + { + return { "com.sun.star.awt.DialogProvider", + "com.sun.star.awt.DialogProvider2", + "com.sun.star.awt.ContainerWindowProvider" }; + } + + + // XInitialization + + + void DialogProviderImpl::initialize( const Sequence< Any >& aArguments ) + { + ::osl::MutexGuard aGuard( getMutex() ); + + if ( aArguments.getLength() == 1 ) + { + aArguments[0] >>= m_xModel; + + if ( !m_xModel.is() ) + { + throw RuntimeException( "DialogProviderImpl::initialize: invalid argument format!" ); + } + } + else if ( aArguments.getLength() == 4 ) + { + // call from RTL_Impl_CreateUnoDialog + aArguments[0] >>= m_xModel; + m_BasicInfo.reset( new BasicRTLParams ); + m_BasicInfo->mxInput.set( aArguments[ 1 ], UNO_QUERY_THROW ); + // allow null mxDlgLib, a document dialog instantiated from + // from application basic is unable to provide ( or find ) it's + // Library + aArguments[ 2 ] >>= m_BasicInfo->mxDlgLib; + // leave the possibility to optionally allow the old dialog creation + // to use the new XScriptListener ( which converts the old style macro + // to a SF url ) + m_BasicInfo->mxBasicRTLListener.set( aArguments[ 3 ], UNO_QUERY); + } + else if ( aArguments.getLength() > 4 ) + { + throw RuntimeException( "DialogProviderImpl::initialize: invalid number of arguments!" ); + } + } + + + // XDialogProvider + + + constexpr OUStringLiteral aDecorationPropName = u"Decoration"; + + Reference < XControl > DialogProviderImpl::createDialogImpl( + const OUString& URL, const Reference< XInterface >& xHandler, + const Reference< XWindowPeer >& xParent, bool bDialogProviderMode ) + { + // if the dialog is located in a document, the document must already be open! + + ::osl::MutexGuard aGuard( getMutex() ); + + + // m_xHandler = xHandler; + + //Reference< XDialog > xDialog; + Reference< XControl > xCtrl; + Reference< XControlModel > xCtrlMod; + try + { + // add support for basic RTL_FUNCTION + if (m_BasicInfo) + xCtrlMod = createDialogModelForBasic(); + else + { + OSL_ENSURE( !URL.isEmpty(), "DialogProviderImpl::getDialog: no URL!" ); + xCtrlMod = createDialogModel( URL ); + } + } + catch ( const RuntimeException& ) { throw; } + catch ( const Exception& ) + { + const Any aError( ::cppu::getCaughtException() ); + throw WrappedTargetRuntimeException( OUString(), *this, aError ); + } + if ( xCtrlMod.is() ) + { + // i83963 Force decoration + if( bDialogProviderMode ) + { + uno::Reference< beans::XPropertySet > xDlgModPropSet( xCtrlMod, uno::UNO_QUERY ); + if( xDlgModPropSet.is() ) + { + try + { + bool bDecoration = true; + Any aDecorationAny = xDlgModPropSet->getPropertyValue( aDecorationPropName ); + aDecorationAny >>= bDecoration; + if( !bDecoration ) + { + xDlgModPropSet->setPropertyValue( aDecorationPropName, Any( true ) ); + xDlgModPropSet->setPropertyValue( "Title", Any( OUString() ) ); + } + } + catch( UnknownPropertyException& ) + {} + } + } + + xCtrl.set( createDialogControl( xCtrlMod, xParent ) ); + if ( xCtrl.is() ) + { + Reference< XIntrospectionAccess > xIntrospectionAccess = inspectHandler( xHandler ); + attachControlEvents( xCtrl, xHandler, xIntrospectionAccess, bDialogProviderMode ); + } + } + + return xCtrl; + } + + Reference < XDialog > DialogProviderImpl::createDialog( const OUString& URL ) + { + Reference< XInterface > xDummyHandler; + Reference< XWindowPeer > xDummyPeer; + Reference < XControl > xControl = DialogProviderImpl::createDialogImpl( URL, xDummyHandler, xDummyPeer, true ); + Reference< XDialog > xDialog( xControl, UNO_QUERY ); + return xDialog; + } + + Reference < XDialog > DialogProviderImpl::createDialogWithHandler( + const OUString& URL, const Reference< XInterface >& xHandler ) + { + if( !xHandler.is() ) + { + throw IllegalArgumentException( + "DialogProviderImpl::createDialogWithHandler: Invalid xHandler!", + Reference< XInterface >(), 1 ); + } + Reference< XWindowPeer > xDummyPeer; + Reference < XControl > xControl = DialogProviderImpl::createDialogImpl( URL, xHandler, xDummyPeer, true ); + Reference< XDialog > xDialog( xControl, UNO_QUERY ); + return xDialog; + } + + Reference < XDialog > DialogProviderImpl::createDialogWithArguments( + const OUString& URL, const Sequence< NamedValue >& Arguments ) + { + ::comphelper::NamedValueCollection aArguments( Arguments ); + + Reference< XWindowPeer > xParentPeer; + if ( aArguments.has( "ParentWindow" ) ) + { + const Any& aParentWindow( aArguments.get( "ParentWindow" ) ); + if ( !( aParentWindow >>= xParentPeer ) ) + { + const Reference< XControl > xParentControl( aParentWindow, UNO_QUERY ); + if ( xParentControl.is() ) + xParentPeer = xParentControl->getPeer(); + } + } + + const Reference< XInterface > xHandler( aArguments.get( "EventHandler" ), UNO_QUERY ); + + Reference < XControl > xControl = DialogProviderImpl::createDialogImpl( URL, xHandler, xParentPeer, true ); + Reference< XDialog > xDialog( xControl, UNO_QUERY ); + return xDialog; + } + + Reference< XWindow > DialogProviderImpl::createContainerWindow( + const OUString& URL, const OUString&, + const Reference< XWindowPeer >& xParent, const Reference< XInterface >& xHandler ) + { + if( !xParent.is() ) + { + throw IllegalArgumentException( + "DialogProviderImpl::createContainerWindow: Invalid xParent!", + Reference< XInterface >(), 1 ); + } + Reference < XControl > xControl = DialogProviderImpl::createDialogImpl( URL, xHandler, xParent, false ); + Reference< XWindow> xWindow( xControl, UNO_QUERY ); + return xWindow; + } + + + // component operations + + + extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* + scripting_DialogProviderImpl_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&) + { + return cppu::acquire(new DialogProviderImpl(context)); + } + +} // namespace dlgprov + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/scripting/source/dlgprov/dlgprov.hxx b/scripting/source/dlgprov/dlgprov.hxx new file mode 100644 index 000000000..0a5d4483a --- /dev/null +++ b/scripting/source/dlgprov/dlgprov.hxx @@ -0,0 +1,148 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/awt/XControl.hpp> +#include <com/sun/star/awt/XDialog.hpp> +#include <com/sun/star/awt/XDialogProvider2.hpp> +#include <com/sun/star/awt/XContainerWindowProvider.hpp> +#include <com/sun/star/awt/XUnoControlDialog.hpp> +#include <com/sun/star/beans/XIntrospectionAccess.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/resource/XStringResourceManager.hpp> +#include <com/sun/star/script/XScriptListener.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +#include <cppuhelper/implbase.hxx> +#include <osl/mutex.hxx> +#include <memory> + + +namespace dlgprov +{ + + + // mutex + + + ::osl::Mutex& getMutex(); + + + + css::uno::Reference< css::container::XNameContainer > lcl_createControlModel(const css::uno::Reference< css::uno::XComponentContext >& i_xContext); + css::uno::Reference< css::resource::XStringResourceManager > lcl_getStringResourceManager(const css::uno::Reference< css::uno::XComponentContext >& i_xContext, std::u16string_view i_sURL); + /// @throws css::uno::Exception + css::uno::Reference< css::container::XNameContainer > lcl_createDialogModel( + const css::uno::Reference< css::uno::XComponentContext >& i_xContext, + const css::uno::Reference< css::io::XInputStream >& xInput, + const css::uno::Reference< css::frame::XModel >& xModel, + const css::uno::Reference< css::resource::XStringResourceManager >& xStringResourceManager, + const css::uno::Any &aDialogSourceURL); + + typedef ::cppu::WeakImplHelper< + css::lang::XServiceInfo, + css::lang::XInitialization, + css::awt::XDialogProvider2, + css::awt::XContainerWindowProvider > DialogProviderImpl_BASE; + + class DialogProviderImpl : public DialogProviderImpl_BASE + { + private: + struct BasicRTLParams + { + css::uno::Reference< css::io::XInputStream > mxInput; + css::uno::Reference< css::container::XNameContainer > mxDlgLib; + css::uno::Reference< css::script::XScriptListener > mxBasicRTLListener; + }; + std::unique_ptr< BasicRTLParams > m_BasicInfo; + css::uno::Reference< css::uno::XComponentContext > m_xContext; + css::uno::Reference< css::frame::XModel > m_xModel; + + OUString msDialogLibName; + css::uno::Reference< css::awt::XControlModel > createDialogModel( const OUString& sURL ); + + css::uno::Reference< css::awt::XUnoControlDialog > createDialogControl( + const css::uno::Reference< css::awt::XControlModel >& rxDialogModel, + const css::uno::Reference< css::awt::XWindowPeer >& xParent ); + + void attachControlEvents( const css::uno::Reference< css::awt::XControl >& rxControlContainer, + const css::uno::Reference< css::uno::XInterface >& rxHandler, + const css::uno::Reference< css::beans::XIntrospectionAccess >& rxIntrospectionAccess, + bool bDialogProviderMode ); + css::uno::Reference< css::beans::XIntrospectionAccess > inspectHandler( + const css::uno::Reference< css::uno::XInterface >& rxHandler ); + // helper methods + /// @throws css::uno::Exception + css::uno::Reference< css::container::XNameContainer > createDialogModel( + const css::uno::Reference< css::io::XInputStream >& xInput, + const css::uno::Reference< css::resource::XStringResourceManager >& xStringResourceManager, + const css::uno::Any &aDialogSourceURL); + /// @throws css::uno::Exception + css::uno::Reference< css::awt::XControlModel > createDialogModelForBasic(); + + // XDialogProvider / XDialogProvider2 impl method + /// @throws css::lang::IllegalArgumentException + /// @throws css::uno::RuntimeException + css::uno::Reference < css::awt::XControl > createDialogImpl( + const OUString& URL, + const css::uno::Reference< css::uno::XInterface >& xHandler, + const css::uno::Reference< css::awt::XWindowPeer >& xParent, + bool bDialogProviderMode ); + + public: + explicit DialogProviderImpl( + const css::uno::Reference< css::uno::XComponentContext >& rxContext ); + virtual ~DialogProviderImpl() override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + + // XInitialization + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override; + + // XDialogProvider + virtual css::uno::Reference < css::awt::XDialog > SAL_CALL createDialog( + const OUString& URL ) override; + + // XDialogProvider2 + virtual css::uno::Reference < css::awt::XDialog > SAL_CALL createDialogWithHandler( + const OUString& URL, + const css::uno::Reference< css::uno::XInterface >& xHandler ) override; + + virtual css::uno::Reference < css::awt::XDialog > SAL_CALL createDialogWithArguments( + const OUString& URL, + const css::uno::Sequence< css::beans::NamedValue >& Arguments ) override; + + virtual css::uno::Reference< css::awt::XWindow > SAL_CALL createContainerWindow( + const OUString& URL, const OUString& WindowType, + const css::uno::Reference< css::awt::XWindowPeer >& xParent, + const css::uno::Reference< css::uno::XInterface >& xHandler ) override; + }; + + +} // namespace dlgprov + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/scripting/source/inc/bcholder.hxx b/scripting/source/inc/bcholder.hxx new file mode 100644 index 000000000..9f8add31b --- /dev/null +++ b/scripting/source/inc/bcholder.hxx @@ -0,0 +1,44 @@ +/* -*- 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 . + */ + +#pragma once + +#include <osl/mutex.hxx> +#include <cppuhelper/interfacecontainer.h> + + +namespace scripting_helper +{ + + class OBroadcastHelperHolder + { + ::cppu::OBroadcastHelper m_aBHelper; + + public: + OBroadcastHelperHolder( ::osl::Mutex& rMutex ) : m_aBHelper( rMutex ) { } + + ::cppu::OBroadcastHelper& GetBroadcastHelper() { return m_aBHelper; } + const ::cppu::OBroadcastHelper& GetBroadcastHelper() const { return m_aBHelper; } + }; + + +} // namespace scripting_helper + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/scripting/source/inc/util/MiscUtils.hxx b/scripting/source/inc/util/MiscUtils.hxx new file mode 100644 index 000000000..2ba4492ab --- /dev/null +++ b/scripting/source/inc/util/MiscUtils.hxx @@ -0,0 +1,138 @@ +/* -*- 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 . + */ + +#pragma once + +#include <rtl/ustring.hxx> +#include <osl/diagnose.h> + +#include <ucbhelper/content.hxx> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/frame/XTransientDocumentsDocumentContentFactory.hpp> +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <com/sun/star/ucb/SimpleFileAccess.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/lang/XMultiComponentFactory.hpp> +#include <comphelper/processfactory.hxx> + +namespace sf_misc +{ + +class MiscUtils +{ +public: + +static css::uno::Sequence< OUString > allOpenTDocUrls( const css::uno::Reference< css::uno::XComponentContext >& xCtx) +{ + css::uno::Sequence< OUString > result; + try + { + if ( !xCtx.is() ) + { + return result; + } + css::uno::Reference < css::ucb::XSimpleFileAccess3 > xSFA( css::ucb::SimpleFileAccess::create(xCtx) ); + result = xSFA->getFolderContents( "vnd.sun.star.tdoc:/", true ); + } + catch ( css::uno::Exception& ) + { + } + return result; +} + +static OUString xModelToTdocUrl( const css::uno::Reference< css::frame::XModel >& xModel, + const css::uno::Reference< css::uno::XComponentContext >& xContext ) +{ + css::uno::Reference< css::lang::XMultiComponentFactory > xMCF( + xContext->getServiceManager() ); + css::uno::Reference< + css::frame::XTransientDocumentsDocumentContentFactory > xDocFac; + try + { + xDocFac.set(xMCF->createInstanceWithContext( + "com.sun.star.frame.TransientDocumentsDocumentContentFactory", + xContext ), + css::uno::UNO_QUERY ); + } + catch ( css::uno::Exception const & ) + { + // handled below + } + + if ( xDocFac.is() ) + { + try + { + css::uno::Reference< css::ucb::XContent > xContent( + xDocFac->createDocumentContent( xModel ) ); + return xContent->getIdentifier()->getContentIdentifier(); + } + catch ( css::lang::IllegalArgumentException const & ) + { + OSL_FAIL( "Invalid document model!" ); + } + } + + OSL_FAIL( "Unable to obtain URL for document model!" ); + return OUString(); +} + +static css::uno::Reference< css::frame::XModel > tDocUrlToModel( const OUString& url ) +{ + css::uno::Any result; + + try + { + ::ucbhelper::Content root( url, nullptr, comphelper::getProcessComponentContext() ); + result = getUCBProperty( root, "DocumentModel" ); + } + catch ( css::ucb::ContentCreationException& ) + { + // carry on, empty value will be returned + } + catch ( css::uno::RuntimeException& ) + { + // carry on, empty value will be returned + } + + css::uno::Reference< css::frame::XModel > xModel( + result, css::uno::UNO_QUERY ); + + return xModel; +} + + +static css::uno::Any getUCBProperty( ::ucbhelper::Content& content, OUString const & prop ) +{ + css::uno::Any result; + try + { + result = content.getPropertyValue( prop ); + } + catch ( css::uno::Exception& ) + { + } + return result; +} + +}; +} // namespace sf_misc + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/scripting/source/protocolhandler/protocolhandler.component b/scripting/source/protocolhandler/protocolhandler.component new file mode 100644 index 000000000..959c332c0 --- /dev/null +++ b/scripting/source/protocolhandler/protocolhandler.component @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.ScriptProtocolHandler" + constructor="scripting_ScriptProtocolHandler_get_implementation"> + <service name="com.sun.star.frame.ProtocolHandler"/> + </implementation> +</component> diff --git a/scripting/source/protocolhandler/scripthandler.cxx b/scripting/source/protocolhandler/scripthandler.cxx new file mode 100644 index 000000000..08ec8d302 --- /dev/null +++ b/scripting/source/protocolhandler/scripthandler.cxx @@ -0,0 +1,436 @@ +/* -*- 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 "scripthandler.hxx" + +#include <com/sun/star/frame/DispatchResultEvent.hpp> +#include <com/sun/star/frame/DispatchResultState.hpp> +#include <com/sun/star/frame/XController.hpp> +#include <com/sun/star/frame/XModel.hpp> + +#include <com/sun/star/document/XEmbeddedScripts.hpp> +#include <com/sun/star/document/XScriptInvocationContext.hpp> + +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/script/provider/ScriptFrameworkErrorException.hpp> +#include <com/sun/star/script/provider/XScriptProviderSupplier.hpp> +#include <com/sun/star/script/provider/theMasterScriptProviderFactory.hpp> +#include <com/sun/star/script/provider/ScriptFrameworkErrorType.hpp> + +#include <sfx2/objsh.hxx> +#include <sfx2/frame.hxx> +#include <sfx2/sfxdlg.hxx> +#include <tools/diagnose_ex.h> + +#include <cppuhelper/exc_hlp.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <framework/documentundoguard.hxx> +#include <officecfg/Office/Common.hxx> + +#include <com/sun/star/uri/XUriReference.hpp> +#include <com/sun/star/uri/XVndSunStarScriptUrlReference.hpp> +#include <com/sun/star/uri/UriReferenceFactory.hpp> + +#include <memory> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::util; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::script; +using namespace ::com::sun::star::script::provider; +using namespace ::com::sun::star::document; + +namespace scripting_protocolhandler +{ + +void SAL_CALL ScriptProtocolHandler::initialize( + const css::uno::Sequence < css::uno::Any >& aArguments ) +{ + if ( m_bInitialised ) + { + return ; + } + + // first argument contains a reference to the frame (may be empty or the desktop, + // but usually it's a "real" frame) + if ( aArguments.hasElements() && !( aArguments[ 0 ] >>= m_xFrame ) ) + { + throw RuntimeException( "ScriptProtocolHandler::initialize: could not extract reference to the frame" ); + } + + ENSURE_OR_THROW( m_xContext.is(), "ScriptProtocolHandler::initialize: No Service Manager available" ); + m_bInitialised = true; +} + +Reference< XDispatch > SAL_CALL ScriptProtocolHandler::queryDispatch( + const URL& aURL, const OUString&, sal_Int32 ) +{ + Reference< XDispatch > xDispatcher; + // get scheme of url + + Reference< uri::XUriReferenceFactory > xFac = uri::UriReferenceFactory::create( m_xContext ); + Reference< uri::XUriReference > uriRef = xFac->parse( aURL.Complete ); + if ( uriRef.is() ) + { + if ( uriRef->getScheme() == "vnd.sun.star.script" ) + { + xDispatcher = this; + } + } + + return xDispatcher; +} + +Sequence< Reference< XDispatch > > SAL_CALL +ScriptProtocolHandler::queryDispatches( +const Sequence < DispatchDescriptor >& seqDescriptor ) +{ + sal_Int32 nCount = seqDescriptor.getLength(); + Sequence< Reference< XDispatch > > lDispatcher( nCount ); + std::transform(seqDescriptor.begin(), seqDescriptor.end(), lDispatcher.getArray(), + [this](const DispatchDescriptor& rDescr) -> Reference<XDispatch> { + return queryDispatch(rDescr.FeatureURL, rDescr.FrameName, rDescr.SearchFlags); }); + return lDispatcher; +} + +void SAL_CALL ScriptProtocolHandler::dispatchWithNotification( + const URL& aURL, const Sequence < PropertyValue >& lArgs, + const Reference< XDispatchResultListener >& xListener ) +{ + if (officecfg::Office::Common::Security::Scripting::DisableMacrosExecution::get()) + return; + + bool bSuccess = false; + Any invokeResult; + bool bCaughtException = false; + Any aException; + + if ( m_bInitialised ) + { + try + { + css::uno::Reference<css::uri::XUriReferenceFactory> urifac( + css::uri::UriReferenceFactory::create(m_xContext)); + css::uno::Reference<css::uri::XVndSunStarScriptUrlReference> uri( + urifac->parse(aURL.Complete), css::uno::UNO_QUERY_THROW); + auto const loc = uri->getParameter("location"); + bool bIsDocumentScript = loc == "document"; + + if ( bIsDocumentScript ) + { + // obtain the component for our security check + Reference< XEmbeddedScripts > xDocumentScripts; + if ( getScriptInvocation() ) + xDocumentScripts.set( m_xScriptInvocation->getScriptContainer(), UNO_SET_THROW ); + + OSL_ENSURE( xDocumentScripts.is(), "ScriptProtocolHandler::dispatchWithNotification: can't do the security check!" ); + if ( !xDocumentScripts.is() || !xDocumentScripts->getAllowMacroExecution() ) + { + if ( xListener.is() ) + { + css::frame::DispatchResultEvent aEvent( + static_cast< ::cppu::OWeakObject* >( this ), + css::frame::DispatchResultState::FAILURE, + invokeResult ); + try + { + xListener->dispatchFinished( aEvent ) ; + } + catch(const RuntimeException &) + { + TOOLS_WARN_EXCEPTION("scripting", + "ScriptProtocolHandler::dispatchWithNotification: caught RuntimeException" + "while dispatchFinished with failure of the execution"); + } + } + return; + } + } + + // Creates a ScriptProvider ( if one is not created already ) + createScriptProvider(); + + Reference< provider::XScript > xFunc = + m_xScriptProvider->getScript( aURL.Complete ); + ENSURE_OR_THROW( xFunc.is(), + "ScriptProtocolHandler::dispatchWithNotification: validate xFunc - unable to obtain XScript interface" ); + + + Sequence< Any > inArgs; + Sequence< Any > outArgs; + Sequence< sal_Int16 > outIndex; + + if ( lArgs.hasElements() ) + { + int argCount = 0; + for ( const auto& rArg : lArgs ) + { + // Sometimes we get a propertyval with name = "Referer" or "SynchronMode". These + // are not actual arguments to be passed to script, but flags describing the + // call, so ignore. Who thought that passing such "meta-arguments" mixed in with + // real arguments was a good idea? + if ( (rArg.Name != "Referer" && + rArg.Name != "SynchronMode") || + rArg.Name.isEmpty() ) //TODO:??? + { + inArgs.realloc( ++argCount ); + inArgs.getArray()[ argCount - 1 ] = rArg.Value; + } + } + } + + // attempt to protect the document against the script tampering with its Undo Context + std::unique_ptr< ::framework::DocumentUndoGuard > pUndoGuard; + if ( bIsDocumentScript ) + pUndoGuard.reset( new ::framework::DocumentUndoGuard( m_xScriptInvocation ) ); + + bSuccess = false; + while ( !bSuccess ) + { + std::exception_ptr aFirstCaughtException; + try + { + invokeResult = xFunc->invoke( inArgs, outIndex, outArgs ); + bSuccess = true; + } + catch( const provider::ScriptFrameworkErrorException& se ) + { + if (!aFirstCaughtException) + aFirstCaughtException = std::current_exception(); + + if ( se.errorType != provider::ScriptFrameworkErrorType::NO_SUCH_SCRIPT ) + // the only condition which allows us to retry is if there is no method with the + // given name/signature + std::rethrow_exception(aFirstCaughtException); + + if ( !inArgs.hasElements() ) + // no chance to retry if we can't strip more in-args + std::rethrow_exception(aFirstCaughtException); + + // strip one argument, then retry + inArgs.realloc( inArgs.getLength() - 1 ); + } + } + } + // Office doesn't handle exceptions rethrown here very well, it cores, + // all we can is log them and then set fail for the dispatch event! + // (if there is a listener of course) + catch ( const Exception & e ) + { + aException = ::cppu::getCaughtException(); + + invokeResult <<= "ScriptProtocolHandler::dispatch: caught " + + aException.getValueTypeName() + ": " + e.Message; + + bCaughtException = true; + } + } + else + { + invokeResult <<= OUString( + "ScriptProtocolHandler::dispatchWithNotification failed, ScriptProtocolHandler not initialised" + ); + } + + if ( bCaughtException ) + { + SfxAbstractDialogFactory* pFact = SfxAbstractDialogFactory::Create(); + pFact->ShowAsyncScriptErrorDialog( nullptr, aException ); + } + + if ( !xListener.is() ) + return; + + // always call dispatchFinished(), because we didn't load a document but + // executed a macro instead! + css::frame::DispatchResultEvent aEvent; + + aEvent.Source = static_cast< ::cppu::OWeakObject* >( this ); + aEvent.Result = invokeResult; + if ( bSuccess ) + { + aEvent.State = css::frame::DispatchResultState::SUCCESS; + } + else + { + aEvent.State = css::frame::DispatchResultState::FAILURE; + } + + try + { + xListener->dispatchFinished( aEvent ) ; + } + catch(const RuntimeException &) + { + TOOLS_WARN_EXCEPTION("scripting", + "ScriptProtocolHandler::dispatchWithNotification: caught RuntimeException" + "while dispatchFinished" ); + } +} + +void SAL_CALL ScriptProtocolHandler::dispatch( +const URL& aURL, const Sequence< PropertyValue >& lArgs ) +{ + dispatchWithNotification( aURL, lArgs, Reference< XDispatchResultListener >() ); +} + +void SAL_CALL ScriptProtocolHandler::addStatusListener( +const Reference< XStatusListener >&, const URL& ) +{ + // implement if status is supported +} + +void SAL_CALL ScriptProtocolHandler::removeStatusListener( +const Reference< XStatusListener >&, const URL& ) +{} + +bool +ScriptProtocolHandler::getScriptInvocation() +{ + if ( !m_xScriptInvocation.is() && m_xFrame.is() ) + { + Reference< XController > xController = m_xFrame->getController(); + if ( xController .is() ) + { + // try to obtain an XScriptInvocationContext interface, preferred from the + // mode, then from the controller + if ( !m_xScriptInvocation.set( xController->getModel(), UNO_QUERY ) ) + m_xScriptInvocation.set( xController, UNO_QUERY ); + } + else + { + if ( m_xFrame.is() ) + { + SfxFrame* pFrame = nullptr; + for ( pFrame = SfxFrame::GetFirst(); pFrame; pFrame = SfxFrame::GetNext( *pFrame ) ) + { + if ( pFrame->GetFrameInterface() == m_xFrame ) + break; + } + if (SfxObjectShell* pDocShell = pFrame ? pFrame->GetCurrentDocument() : SfxObjectShell::Current()) + { + Reference< XModel > xModel( pDocShell->GetModel() ); + m_xScriptInvocation.set( xModel, UNO_QUERY ); + } + } + } + } + return m_xScriptInvocation.is(); +} + +void ScriptProtocolHandler::createScriptProvider() +{ + if ( m_xScriptProvider.is() ) + return; + + try + { + // first, ask the component supporting the XScriptInvocationContext interface + // (if there is one) for a script provider + if ( getScriptInvocation() ) + { + Reference< XScriptProviderSupplier > xSPS( m_xScriptInvocation, UNO_QUERY ); + if ( xSPS.is() ) + m_xScriptProvider = xSPS->getScriptProvider(); + } + + // second, ask the model in our frame + if ( !m_xScriptProvider.is() && m_xFrame.is() ) + { + Reference< XController > xController = m_xFrame->getController(); + if ( xController .is() ) + { + Reference< XScriptProviderSupplier > xSPS( xController->getModel(), UNO_QUERY ); + if ( xSPS.is() ) + m_xScriptProvider = xSPS->getScriptProvider(); + } + } + + + // as a fallback, ask the controller + if ( !m_xScriptProvider.is() && m_xFrame.is() ) + { + Reference< XScriptProviderSupplier > xSPS( m_xFrame->getController(), UNO_QUERY ); + if ( xSPS.is() ) + m_xScriptProvider = xSPS->getScriptProvider(); + } + + // if nothing of this is successful, use the master script provider + if ( !m_xScriptProvider.is() ) + { + Reference< provider::XScriptProviderFactory > xFac = + provider::theMasterScriptProviderFactory::get( m_xContext ); + + Any aContext; + if ( getScriptInvocation() ) + aContext <<= m_xScriptInvocation; + m_xScriptProvider.set( xFac->createScriptProvider( aContext ), UNO_SET_THROW ); + } + } + catch ( const Exception & e ) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw css::lang::WrappedTargetRuntimeException( + "ScriptProtocolHandler::createScriptProvider: " + e.Message, + nullptr, anyEx ); + } +} + +ScriptProtocolHandler::ScriptProtocolHandler( const Reference< css::uno::XComponentContext > & xContext ) + : m_bInitialised( false ), m_xContext( xContext ) +{ +} + +ScriptProtocolHandler::~ScriptProtocolHandler() +{ +} + +/* XServiceInfo */ +OUString SAL_CALL ScriptProtocolHandler::getImplementationName( ) +{ + return "com.sun.star.comp.ScriptProtocolHandler"; +} + +/* XServiceInfo */ +sal_Bool SAL_CALL ScriptProtocolHandler::supportsService(const OUString& sServiceName ) +{ + return cppu::supportsService(this, sServiceName); +} + +/* XServiceInfo */ +Sequence< OUString > SAL_CALL ScriptProtocolHandler::getSupportedServiceNames() +{ + return {"com.sun.star.frame.ProtocolHandler"}; +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +scripting_ScriptProtocolHandler_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new ScriptProtocolHandler(context)); +} + +} // namespace scripting_protocolhandler + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/scripting/source/protocolhandler/scripthandler.hxx b/scripting/source/protocolhandler/scripthandler.hxx new file mode 100644 index 000000000..74667b5a4 --- /dev/null +++ b/scripting/source/protocolhandler/scripthandler.hxx @@ -0,0 +1,114 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/frame/XNotifyingDispatch.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/script/provider/XScriptProvider.hpp> + + +namespace com::sun::star { + + namespace document { + class XScriptInvocationContext; + } + namespace uno { + class Any; + class XComponentContext; + } + namespace lang { + class XMultiServiceFactory; + class XSingleServiceFactory; + } + namespace frame { + class XFrame; + class XDispatch; + class XNotifyingDispatch; + class XDispatchResultListener; + struct DispatchDescriptor; + } + namespace beans { + struct PropertyValue; + } + namespace util { + struct URL; + } +} + +namespace scripting_protocolhandler +{ + +class ScriptProtocolHandler : +public ::cppu::WeakImplHelper< css::frame::XDispatchProvider, + css::frame::XNotifyingDispatch, css::lang::XServiceInfo, css::lang::XInitialization > +{ +private: + bool m_bInitialised; + css::uno::Reference < css::uno::XComponentContext > m_xContext; + css::uno::Reference < css::frame::XFrame > m_xFrame; + css::uno::Reference < css::script::provider::XScriptProvider > m_xScriptProvider; + css::uno::Reference< css::document::XScriptInvocationContext > m_xScriptInvocation; + + void createScriptProvider(); + bool getScriptInvocation(); + +public: + explicit ScriptProtocolHandler( const css::uno::Reference < css::uno::XComponentContext >& xContext ); + virtual ~ScriptProtocolHandler() override; + + /* XServiceInfo */ + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& sServiceName ) override; + virtual css::uno::Sequence < OUString > SAL_CALL getSupportedServiceNames() override; + + /* Implementation for XDispatchProvider */ + virtual css::uno::Reference < css::frame::XDispatch > SAL_CALL + queryDispatch( const css::util::URL& aURL, const OUString& sTargetFrameName, + sal_Int32 eSearchFlags ) override ; + virtual css::uno::Sequence< css::uno::Reference < css::frame::XDispatch > > SAL_CALL + queryDispatches( + const css::uno::Sequence < css::frame::DispatchDescriptor >& seqDescriptor ) override; + + /* Implementation for X(Notifying)Dispatch */ + virtual void SAL_CALL dispatchWithNotification( + const css::util::URL& aURL, + const css::uno::Sequence< css::beans::PropertyValue >& lArgs, + const css::uno::Reference< css::frame::XDispatchResultListener >& Listener ) override; + virtual void SAL_CALL dispatch( + const css::util::URL& aURL, + const css::uno::Sequence< css::beans::PropertyValue >& lArgs ) override; + virtual void SAL_CALL addStatusListener( + const css::uno::Reference< css::frame::XStatusListener >& xControl, + const css::util::URL& aURL ) override; + virtual void SAL_CALL removeStatusListener( + const css::uno::Reference< css::frame::XStatusListener >& xControl, + const css::util::URL& aURL ) override; + + /* Implementation for XInitialization */ + virtual void SAL_CALL initialize( + const css::uno::Sequence < css::uno::Any >& aArguments ) override; +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/scripting/source/provider/ActiveMSPList.cxx b/scripting/source/provider/ActiveMSPList.cxx new file mode 100644 index 000000000..c073c73b7 --- /dev/null +++ b/scripting/source/provider/ActiveMSPList.cxx @@ -0,0 +1,297 @@ +/* -*- 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 <cppuhelper/exc_hlp.hxx> +#include <util/MiscUtils.hxx> + +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> + +#include "ActiveMSPList.hxx" + +#include <tools/diagnose_ex.h> + +using namespace com::sun::star; +using namespace com::sun::star::uno; +using namespace com::sun::star::script; +using namespace ::sf_misc; + +namespace func_provider +{ + +ActiveMSPList::ActiveMSPList( const Reference< XComponentContext > & xContext ) : m_xContext( xContext ) +{ + userDirString = "user"; + shareDirString = "share"; + bundledDirString = "bundled"; +} + +ActiveMSPList::~ActiveMSPList() +{ +} + +Reference< provider::XScriptProvider > +ActiveMSPList::createNewMSP( const uno::Any& context ) +{ + Sequence< Any > args( &context, 1 ); + + Reference< provider::XScriptProvider > msp( + m_xContext->getServiceManager()->createInstanceWithArgumentsAndContext( + "com.sun.star.script.provider.MasterScriptProvider", args, m_xContext ), UNO_QUERY ); + return msp; +} + +class NonDocMSPCreator +{ +public: + explicit NonDocMSPCreator(ActiveMSPList *pList) + { + pList->createNonDocMSPs(); + } +}; + +namespace +{ + //thread-safe double-locked class to ensure createNonDocMSPs is called once + class theNonDocMSPCreator : public rtl::StaticWithArg<NonDocMSPCreator, ActiveMSPList*, theNonDocMSPCreator> {}; + + void ensureNonDocMSPs(ActiveMSPList *pList) + { + theNonDocMSPCreator::get(pList); + } +} + +Reference< provider::XScriptProvider > +ActiveMSPList::getMSPFromAnyContext( const Any& aContext ) +{ + Reference< provider::XScriptProvider > msp; + OUString sContext; + if ( aContext >>= sContext ) + { + msp = getMSPFromStringContext( sContext ); + return msp; + } + + Reference< frame::XModel > xModel( aContext, UNO_QUERY ); + + Reference< document::XScriptInvocationContext > xScriptContext( aContext, UNO_QUERY ); + if ( xScriptContext.is() ) + { + try + { + // the component supports executing scripts embedded in a - possibly foreign document. + // Check whether this other document it's the component itself. + if ( !xModel.is() || ( xModel != xScriptContext->getScriptContainer() ) ) + { + msp = getMSPFromInvocationContext( xScriptContext ); + return msp; + } + } + catch( const lang::IllegalArgumentException& ) + { + xModel.set( Reference< frame::XModel >() ); + } + } + + if ( xModel.is() ) + { + sContext = MiscUtils::xModelToTdocUrl( xModel, m_xContext ); + msp = getMSPFromStringContext( sContext ); + return msp; + } + + ensureNonDocMSPs(this); + return m_hMsps[ shareDirString ]; +} + +Reference< provider::XScriptProvider > + ActiveMSPList::getMSPFromInvocationContext( const Reference< document::XScriptInvocationContext >& xContext ) +{ + Reference< provider::XScriptProvider > msp; + + Reference< document::XEmbeddedScripts > xScripts; + if ( xContext.is() ) + xScripts.set( xContext->getScriptContainer() ); + if ( !xScripts.is() ) + { + throw lang::IllegalArgumentException( + "Failed to create MasterScriptProvider for ScriptInvocationContext: " + "Component supporting XEmbeddScripts interface not found.", + nullptr, 1 ); + } + + ::osl::MutexGuard guard( m_mutex ); + + Reference< XInterface > xNormalized( xContext, UNO_QUERY ); + ScriptComponent_map::const_iterator pos = m_mScriptComponents.find( xNormalized ); + if ( pos == m_mScriptComponents.end() ) + { + // TODO + msp = createNewMSP( uno::Any( xContext ) ); + addActiveMSP( xNormalized, msp ); + } + else + { + msp = pos->second; + } + + return msp; +} + +Reference< provider::XScriptProvider > + ActiveMSPList::getMSPFromStringContext( const OUString& context ) +{ + Reference< provider::XScriptProvider > msp; + try + { + if ( context.startsWith( "vnd.sun.star.tdoc" ) ) + { + Reference< frame::XModel > xModel( MiscUtils::tDocUrlToModel( context ) ); + + Reference< document::XEmbeddedScripts > xScripts( xModel, UNO_QUERY ); + Reference< document::XScriptInvocationContext > xScriptsContext( xModel, UNO_QUERY ); + if ( !xScripts.is() && !xScriptsContext.is() ) + { + throw lang::IllegalArgumentException( + "Failed to create MasterScriptProvider for '" + + context + + "': Either XEmbeddScripts or XScriptInvocationContext need to be supported by the document.", + nullptr, 1 ); + } + + ::osl::MutexGuard guard( m_mutex ); + Reference< XInterface > xNormalized( xModel, UNO_QUERY ); + ScriptComponent_map::const_iterator pos = m_mScriptComponents.find( xNormalized ); + if ( pos == m_mScriptComponents.end() ) + { + msp = createNewMSP( context ); + addActiveMSP( xNormalized, msp ); + } + else + { + msp = pos->second; + } + } + else + { + ::osl::MutexGuard guard( m_mutex ); + Msp_hash::iterator h_itEnd = m_hMsps.end(); + Msp_hash::const_iterator itr = m_hMsps.find( context ); + if ( itr == h_itEnd ) + { + msp = createNewMSP( context ); + m_hMsps[ context ] = msp; + } + else + { + msp = m_hMsps[ context ]; + } + } + } + catch( const lang::IllegalArgumentException& ) + { + // allowed to leave + } + catch( const RuntimeException& ) + { + // allowed to leave + } + catch( const Exception& ) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw lang::WrappedTargetRuntimeException( + "Failed to create MasterScriptProvider for context '" + + context + "'.", + *this, anyEx ); + } + return msp; +} + +void +ActiveMSPList::addActiveMSP( const Reference< uno::XInterface >& xComponent, + const Reference< provider::XScriptProvider >& msp ) +{ + ::osl::MutexGuard guard( m_mutex ); + Reference< XInterface > xNormalized( xComponent, UNO_QUERY ); + ScriptComponent_map::const_iterator pos = m_mScriptComponents.find( xNormalized ); + if ( pos != m_mScriptComponents.end() ) + return; + + m_mScriptComponents[ xNormalized ] = msp; + + // add self as listener for component disposal + // should probably throw from this method!!, reexamine + try + { + Reference< lang::XComponent > xBroadcaster( xComponent, UNO_QUERY_THROW ); + xBroadcaster->addEventListener( this ); + } + catch ( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("scripting"); + } +} + + +void SAL_CALL ActiveMSPList::disposing( const css::lang::EventObject& Source ) + +{ + try + { + Reference< XInterface > xNormalized( Source.Source, UNO_QUERY ); + if ( xNormalized.is() ) + { + ::osl::MutexGuard guard( m_mutex ); + ScriptComponent_map::iterator pos = m_mScriptComponents.find( xNormalized ); + if ( pos != m_mScriptComponents.end() ) + m_mScriptComponents.erase( pos ); + } + } + catch ( const Exception& ) + { + // if we get an exception here, there is not much we can do about + // it can't throw as it will screw up the model that is calling dispose + DBG_UNHANDLED_EXCEPTION("scripting"); + } +} + +void +ActiveMSPList::createNonDocMSPs() +{ + // do creation of user and share MSPs here + OUString serviceName("com.sun.star.script.provider.MasterScriptProvider"); + + Sequence< Any > args{ Any(userDirString) }; + Reference< provider::XScriptProvider > userMsp( m_xContext->getServiceManager()->createInstanceWithArgumentsAndContext( serviceName, args, m_xContext ), UNO_QUERY ); + // should check if provider reference is valid + m_hMsps[ userDirString ] = userMsp; + + args = { Any(shareDirString) }; + Reference< provider::XScriptProvider > shareMsp( m_xContext->getServiceManager()->createInstanceWithArgumentsAndContext( serviceName, args, m_xContext ), UNO_QUERY ); + // should check if provider reference is valid + m_hMsps[ shareDirString ] = shareMsp; + + args = { Any(bundledDirString) }; + Reference< provider::XScriptProvider > bundledMsp( m_xContext->getServiceManager()->createInstanceWithArgumentsAndContext( serviceName, args, m_xContext ), UNO_QUERY ); + // should check if provider reference is valid + m_hMsps[ bundledDirString ] = bundledMsp; +} + +} // namespace func_provider + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/scripting/source/provider/ActiveMSPList.hxx b/scripting/source/provider/ActiveMSPList.hxx new file mode 100644 index 000000000..fb52629c1 --- /dev/null +++ b/scripting/source/provider/ActiveMSPList.hxx @@ -0,0 +1,94 @@ +/* -*- 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 . + */ +#pragma once + +#include <osl/mutex.hxx> +#include <rtl/ustring.hxx> +#include <cppuhelper/implbase.hxx> + +#include <com/sun/star/lang/XEventListener.hpp> + +#include <com/sun/star/script/provider/XScriptProvider.hpp> +#include <com/sun/star/document/XScriptInvocationContext.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +#include <map> +#include <unordered_map> + +namespace func_provider +{ + +//Typedefs +typedef std::map < css::uno::Reference< css::uno::XInterface > + , css::uno::Reference< css::script::provider::XScriptProvider > + > ScriptComponent_map; + +typedef std::unordered_map< OUString, + css::uno::Reference< css::script::provider::XScriptProvider > > Msp_hash; + +class NonDocMSPCreator; + +class ActiveMSPList : public ::cppu::WeakImplHelper< css::lang::XEventListener > +{ + +public: + + explicit ActiveMSPList( const css::uno::Reference< + css::uno::XComponentContext > & xContext ); + virtual ~ActiveMSPList() override; + + css::uno::Reference< css::script::provider::XScriptProvider > + getMSPFromStringContext( const OUString& context ); + + css::uno::Reference< css::script::provider::XScriptProvider > + getMSPFromAnyContext( const css::uno::Any& context ); + + css::uno::Reference< css::script::provider::XScriptProvider > + getMSPFromInvocationContext( const css::uno::Reference< css::document::XScriptInvocationContext >& context ); + + //XEventListener + + + virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override; + +private: + void addActiveMSP( const css::uno::Reference< css::uno::XInterface >& xComponent, + const css::uno::Reference< css::script::provider::XScriptProvider >& msp ); + css::uno::Reference< css::script::provider::XScriptProvider > + createNewMSP( const css::uno::Any& context ); + css::uno::Reference< css::script::provider::XScriptProvider > + createNewMSP( const OUString& context ) + { + return createNewMSP( css::uno::Any( context ) ); + } + + friend class NonDocMSPCreator; + void createNonDocMSPs(); + + Msp_hash m_hMsps; + ScriptComponent_map m_mScriptComponents; + osl::Mutex m_mutex; + OUString userDirString; + OUString shareDirString; + OUString bundledDirString; + css::uno::Reference< css::uno::XComponentContext > m_xContext; +}; +} // func_provider + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/scripting/source/provider/BrowseNodeFactoryImpl.cxx b/scripting/source/provider/BrowseNodeFactoryImpl.cxx new file mode 100644 index 000000000..9d070ab00 --- /dev/null +++ b/scripting/source/provider/BrowseNodeFactoryImpl.cxx @@ -0,0 +1,650 @@ +/* -*- 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 <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <unotools/mediadescriptor.hxx> + +#include <com/sun/star/document/XEmbeddedScripts.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/reflection/ProxyFactory.hpp> + +#include <com/sun/star/script/provider/theMasterScriptProviderFactory.hpp> +#include <com/sun/star/script/browse/BrowseNodeFactoryViewTypes.hpp> +#include <com/sun/star/script/browse/BrowseNodeTypes.hpp> + +#include <tools/diagnose_ex.h> + +#include "BrowseNodeFactoryImpl.hxx" +#include <util/MiscUtils.hxx> + +#include <vector> +#include <algorithm> +#include <memory> +#include <optional> +#include <string_view> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::script; +using namespace ::sf_misc; + +namespace browsenodefactory +{ +namespace { +class BrowseNodeAggregator : + public ::cppu::WeakImplHelper< browse::XBrowseNode > +{ +private: + OUString m_Name; + std::vector< Reference< browse::XBrowseNode > > m_Nodes; + +public: + + explicit BrowseNodeAggregator( const Reference< browse::XBrowseNode >& node ) + : m_Name(node->getName()) + { + m_Nodes.resize( 1 ); + m_Nodes[ 0 ] = node; + } + + void addBrowseNode( const Reference< browse::XBrowseNode>& node ) + { + m_Nodes.push_back( node ); + } + + virtual OUString + SAL_CALL getName() override + { + return m_Name; + } + + virtual Sequence< Reference< browse::XBrowseNode > > SAL_CALL + getChildNodes() override + { + std::vector< Sequence< Reference < browse::XBrowseNode > > > seqs; + seqs.reserve( m_Nodes.size() ); + + sal_Int32 numChildren = 0; + + for (Reference<XBrowseNode> & xNode : m_Nodes) + { + Sequence< Reference < browse::XBrowseNode > > children; + try + { + children = xNode->getChildNodes(); + seqs.push_back( children ); + numChildren += children.getLength(); + } + catch ( Exception& ) + { + // some form of exception getting child nodes so they + // won't be displayed + } + } + + Sequence< Reference < browse::XBrowseNode > > result( numChildren ); + sal_Int32 index = 0; + for ( const Sequence< Reference < browse::XBrowseNode > >& children : seqs ) + { + std::copy(children.begin(), children.end(), std::next(result.getArray(), index)); + index += children.getLength(); + + if (index >= numChildren) + break; + } + return result; + } + + virtual sal_Bool SAL_CALL + hasChildNodes() override + { + for (Reference<XBrowseNode> & xNode : m_Nodes) + { + try + { + if ( xNode->hasChildNodes() ) + { + return true; + } + } + catch ( Exception& ) + { + // some form of exception getting child nodes so move + // on to the next one + } + } + + return false; + } + + virtual sal_Int16 SAL_CALL getType() override + { + return browse::BrowseNodeTypes::CONTAINER; + } +}; + +struct alphaSort +{ + bool operator()( std::u16string_view a, std::u16string_view b ) + { + return a.compare( b ) < 0; + } +}; +class LocationBrowseNode : + public ::cppu::WeakImplHelper< browse::XBrowseNode > +{ +private: + std::optional<std::unordered_map< OUString, Reference< browse::XBrowseNode > >> m_hBNA; + std::vector< OUString > m_vStr; + OUString m_sNodeName; + Reference< browse::XBrowseNode > m_origNode; + +public: + + explicit LocationBrowseNode( const Reference< browse::XBrowseNode >& node ) + : m_sNodeName(node->getName()) + { + m_origNode.set( node ); + } + + + // XBrowseNode + + virtual OUString SAL_CALL getName() override + { + return m_sNodeName; + } + + virtual Sequence< Reference< browse::XBrowseNode > > SAL_CALL + getChildNodes() override + { + if ( !m_hBNA ) + { + loadChildNodes(); + } + + Sequence< Reference< browse::XBrowseNode > > children( m_hBNA->size() ); + auto childrenRange = asNonConstRange(children); + sal_Int32 index = 0; + + for ( const auto& str : m_vStr ) + { + childrenRange[ index ].set( m_hBNA->find( str )->second ); + ++index; + } + + return children; + } + + virtual sal_Bool SAL_CALL hasChildNodes() override + { + return true; + } + + virtual sal_Int16 SAL_CALL getType() override + { + return browse::BrowseNodeTypes::CONTAINER; + } + +private: + + void loadChildNodes() + { + m_hBNA.emplace(); + + const Sequence< Reference< browse::XBrowseNode > > langNodes = + m_origNode->getChildNodes(); + + for ( const auto& rLangNode : langNodes ) + { + Reference< browse::XBrowseNode > xbn; + if ( rLangNode->getName() == "uno_packages" ) + { + xbn.set( new LocationBrowseNode( rLangNode ) ); + } + else + { + xbn.set( rLangNode ); + } + + const Sequence< Reference< browse::XBrowseNode > > grandchildren = + xbn->getChildNodes(); + + for ( const Reference< browse::XBrowseNode >& grandchild : grandchildren ) + { + auto h_it = + m_hBNA->find( grandchild->getName() ); + + if ( h_it != m_hBNA->end() ) + { + BrowseNodeAggregator* bna = static_cast< BrowseNodeAggregator* >( h_it->second.get() ); + bna->addBrowseNode( grandchild ); + } + else + { + Reference< browse::XBrowseNode > bna( + new BrowseNodeAggregator( grandchild ) ); + (*m_hBNA)[ grandchild->getName() ].set( bna ); + m_vStr.push_back( grandchild->getName() ); + } + } + } + // sort children alphabetically + ::std::sort( m_vStr.begin(), m_vStr.end(), alphaSort() ); + } +}; + +std::vector< Reference< browse::XBrowseNode > > getAllBrowseNodes( const Reference< XComponentContext >& xCtx ) +{ + const Sequence< OUString > openDocs = + MiscUtils::allOpenTDocUrls( xCtx ); + + Reference< provider::XScriptProviderFactory > xFac; + sal_Int32 initialSize = openDocs.getLength() + 2; + sal_Int32 mspIndex = 0; + + std::vector< Reference < browse::XBrowseNode > > locnBNs( initialSize ); + try + { + xFac = provider::theMasterScriptProviderFactory::get( xCtx ); + + locnBNs[ mspIndex++ ].set( xFac->createScriptProvider( Any( OUString("user") ) ), UNO_QUERY_THROW ); + locnBNs[ mspIndex++ ].set( xFac->createScriptProvider( Any( OUString("share") ) ), UNO_QUERY_THROW ); + } + // TODO proper exception handling, should throw + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION("scripting", "Caught" ); + locnBNs.resize( mspIndex ); + return locnBNs; + } + + for ( const auto& rDoc : openDocs ) + { + try + { + Reference< frame::XModel > model( MiscUtils::tDocUrlToModel( rDoc ), UNO_SET_THROW ); + + // #i44599 Check if it's a real document or something special like Hidden/Preview + css::uno::Reference< css::frame::XController > xCurrentController = model->getCurrentController(); + if( xCurrentController.is() ) + { + utl::MediaDescriptor aMD( model->getArgs() ); + bool bDefault = false; + bool bHidden = aMD.getUnpackedValueOrDefault( utl::MediaDescriptor::PROP_HIDDEN, bDefault ); + bool bPreview = aMD.getUnpackedValueOrDefault( utl::MediaDescriptor::PROP_PREVIEW, bDefault ); + if( !bHidden && !bPreview ) + { + Reference< document::XEmbeddedScripts > xScripts( model, UNO_QUERY ); + if ( xScripts.is() ) + locnBNs[ mspIndex++ ].set( xFac->createScriptProvider( Any( model ) ), UNO_QUERY_THROW ); + } + } + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("scripting"); + } + + } + + std::vector< Reference < browse::XBrowseNode > > locnBNs_Return( mspIndex ); + for ( sal_Int32 j = 0; j < mspIndex; j++ ) + locnBNs_Return[j] = locnBNs[j]; + + return locnBNs_Return; +} + +} // namespace + +typedef ::std::vector< Reference< browse::XBrowseNode > > vXBrowseNodes; + +namespace { + +struct alphaSortForBNodes +{ + bool operator()( const Reference< browse::XBrowseNode >& a, const Reference< browse::XBrowseNode >& b ) + { + return a->getName().compareTo( b->getName() ) < 0; + } +}; + +} + +typedef ::cppu::WeakImplHelper< browse::XBrowseNode > t_BrowseNodeBase; + +namespace { + +class DefaultBrowseNode : + public t_BrowseNodeBase +{ + +private: + Reference< browse::XBrowseNode > m_xWrappedBrowseNode; + Reference< lang::XTypeProvider > m_xWrappedTypeProv; + Reference< XAggregation > m_xAggProxy; + Reference< XComponentContext > m_xCtx; + +public: + DefaultBrowseNode( const Reference< XComponentContext >& xCtx, const Reference< browse::XBrowseNode>& xNode ) : m_xWrappedBrowseNode( xNode ), m_xWrappedTypeProv( xNode, UNO_QUERY ), m_xCtx( xCtx ) + { + OSL_ENSURE( m_xWrappedBrowseNode.is(), "DefaultBrowseNode::DefaultBrowseNode(): No BrowseNode to wrap" ); + OSL_ENSURE( m_xWrappedTypeProv.is(), "DefaultBrowseNode::DefaultBrowseNode(): No BrowseNode to wrap" ); + OSL_ENSURE( m_xCtx.is(), "DefaultBrowseNode::DefaultBrowseNode(): No ComponentContext" ); + // Use proxy factory service to create aggregatable proxy. + try + { + Reference< reflection::XProxyFactory > xProxyFac = + reflection::ProxyFactory::create( m_xCtx ); + m_xAggProxy = xProxyFac->createProxy( m_xWrappedBrowseNode ); + } + catch( uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "scripting", "DefaultBrowseNode::DefaultBrowseNode" ); + } + OSL_ENSURE( m_xAggProxy.is(), + "DefaultBrowseNode::DefaultBrowseNode: Wrapped BrowseNode cannot be aggregated!" ); + + if ( !m_xAggProxy.is() ) + return; + + osl_atomic_increment( &m_refCount ); + + /* i35609 - Fix crash on Solaris. The setDelegator call needs + to be in its own block to ensure that all temporary Reference + instances that are acquired during the call are released + before m_refCount is decremented again */ + { + m_xAggProxy->setDelegator( + static_cast< cppu::OWeakObject * >( this ) ); + } + + osl_atomic_decrement( &m_refCount ); + } + + virtual ~DefaultBrowseNode() override + { + if ( m_xAggProxy.is() ) + { + m_xAggProxy->setDelegator( uno::Reference< uno::XInterface >() ); + } + } + + virtual Sequence< Reference< browse::XBrowseNode > > SAL_CALL + getChildNodes() override + { + if ( hasChildNodes() ) + { + vXBrowseNodes aVNodes; + const Sequence < Reference< browse::XBrowseNode > > nodes = + m_xWrappedBrowseNode->getChildNodes(); + for ( const Reference< browse::XBrowseNode >& xBrowseNode : nodes ) + { + OSL_ENSURE( xBrowseNode.is(), "DefaultBrowseNode::getChildNodes(): Invalid BrowseNode" ); + if( xBrowseNode.is() ) + aVNodes.push_back( new DefaultBrowseNode( m_xCtx, xBrowseNode ) ); + } + + ::std::sort( aVNodes.begin(), aVNodes.end(), alphaSortForBNodes() ); + Sequence < Reference< browse::XBrowseNode > > children( aVNodes.size() ); + auto childrenRange = asNonConstRange(children); + sal_Int32 i = 0; + for ( const auto& rxNode : aVNodes ) + { + childrenRange[ i ].set( rxNode ); + i++; + } + return children; + } + else + { + // no nodes + + Sequence < Reference< browse::XBrowseNode > > none; + return none; + } + } + + virtual sal_Int16 SAL_CALL getType() override + { + return m_xWrappedBrowseNode->getType(); + } + + virtual OUString + SAL_CALL getName() override + { + return m_xWrappedBrowseNode->getName(); + } + + virtual sal_Bool SAL_CALL + hasChildNodes() override + { + return m_xWrappedBrowseNode->hasChildNodes(); + } + + // XInterface + virtual Any SAL_CALL queryInterface( const Type& aType ) override + { + Any aRet = t_BrowseNodeBase::queryInterface( aType ); + if ( aRet.hasValue() ) + { + return aRet; + } + if ( m_xAggProxy.is() ) + { + return m_xAggProxy->queryAggregation( aType ); + } + else + { + return Any(); + } + } + + // XTypeProvider (implemented by base, but needs to be overridden for + // delegating to aggregate) + virtual Sequence< Type > SAL_CALL getTypes() override + { + return m_xWrappedTypeProv->getTypes(); + } + virtual Sequence< sal_Int8 > SAL_CALL getImplementationId() override + { + return css::uno::Sequence<sal_Int8>(); + } +}; + +class DefaultRootBrowseNode : + public ::cppu::WeakImplHelper< browse::XBrowseNode > +{ + +private: + vXBrowseNodes m_vNodes; + OUString m_Name; + +public: + explicit DefaultRootBrowseNode( const Reference< XComponentContext >& xCtx ) + { + std::vector< Reference< browse::XBrowseNode > > nodes = + getAllBrowseNodes( xCtx ); + + for (Reference< browse::XBrowseNode > & xNode : nodes) + { + m_vNodes.push_back( new DefaultBrowseNode( xCtx, xNode ) ); + } + m_Name = "Root"; + } + + virtual Sequence< Reference< browse::XBrowseNode > > SAL_CALL + getChildNodes() override + { + // no need to sort user, share, doc1...docN + //::std::sort( m_vNodes.begin(), m_vNodes.end(), alphaSortForBNodes() ); + Sequence < Reference< browse::XBrowseNode > > children( m_vNodes.size() ); + auto childrenRange = asNonConstRange(children); + sal_Int32 i = 0; + for ( const auto& rxNode : m_vNodes ) + { + childrenRange[ i ].set( rxNode ); + i++; + } + return children; + } + + virtual sal_Int16 SAL_CALL getType() override + { + return browse::BrowseNodeTypes::ROOT; + } + + virtual OUString + SAL_CALL getName() override + { + return m_Name; + } + + virtual sal_Bool SAL_CALL + hasChildNodes() override + { + bool result = true; + if ( m_vNodes.empty() ) + { + result = false; + } + return result; + } +}; + + +class SelectorBrowseNode : + public ::cppu::WeakImplHelper< browse::XBrowseNode > +{ +private: + Reference< XComponentContext > m_xComponentContext; + +public: + explicit SelectorBrowseNode( const Reference< XComponentContext >& xContext ) + : m_xComponentContext( xContext ) + { + } + + virtual OUString SAL_CALL getName() override + { + return "Root"; + } + + virtual Sequence< Reference< browse::XBrowseNode > > SAL_CALL + getChildNodes() override + { + + std::vector< Reference < browse::XBrowseNode > > locnBNs = getAllBrowseNodes( m_xComponentContext ); + + Sequence< Reference< browse::XBrowseNode > > children( + locnBNs.size() ); + auto childrenRange = asNonConstRange(children); + + for ( size_t j = 0; j < locnBNs.size(); j++ ) + { + childrenRange[j] = new LocationBrowseNode( locnBNs[j] ); + } + + return children; + } + + virtual sal_Bool SAL_CALL hasChildNodes() override + { + return true; // will always be user and share + } + + virtual sal_Int16 SAL_CALL getType() override + { + return browse::BrowseNodeTypes::CONTAINER; + } +}; + +} + +BrowseNodeFactoryImpl::BrowseNodeFactoryImpl( + Reference< XComponentContext > const & xComponentContext ) + : m_xComponentContext( xComponentContext ) +{ +} + +BrowseNodeFactoryImpl::~BrowseNodeFactoryImpl() +{ +} + + +// Implementation of XBrowseNodeFactory + + +/* + * The selector hierarchy is the standard hierarchy for organizers with the + * language nodes removed. + */ +Reference< browse::XBrowseNode > SAL_CALL +BrowseNodeFactoryImpl::createView( sal_Int16 viewType ) +{ + switch( viewType ) + { + case browse::BrowseNodeFactoryViewTypes::MACROSELECTOR: + return new SelectorBrowseNode( m_xComponentContext ); + case browse::BrowseNodeFactoryViewTypes::MACROORGANIZER: + return getOrganizerHierarchy(); + default: + throw RuntimeException( "Unknown view type" ); + } +} + +Reference< browse::XBrowseNode > +BrowseNodeFactoryImpl::getOrganizerHierarchy() const +{ + Reference< browse::XBrowseNode > xRet = new DefaultRootBrowseNode( m_xComponentContext ); + return xRet; +} + +// Implementation of XServiceInfo + + +OUString SAL_CALL +BrowseNodeFactoryImpl::getImplementationName() +{ + return "com.sun.star.script.browse.BrowseNodeFactory"; +} + +Sequence< OUString > SAL_CALL +BrowseNodeFactoryImpl::getSupportedServiceNames() +{ + return { "com.sun.star.script.browse.BrowseNodeFactory" }; +} + +sal_Bool BrowseNodeFactoryImpl::supportsService(OUString const & serviceName ) +{ + return cppu::supportsService(this, serviceName); +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +scripting_BrowseNodeFactoryImpl_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new BrowseNodeFactoryImpl(context)); +} + +} // namespace browsenodefactory + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/scripting/source/provider/BrowseNodeFactoryImpl.hxx b/scripting/source/provider/BrowseNodeFactoryImpl.hxx new file mode 100644 index 000000000..908568021 --- /dev/null +++ b/scripting/source/provider/BrowseNodeFactoryImpl.hxx @@ -0,0 +1,70 @@ +/* -*- 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 . + */ + +#pragma once + +#include <rtl/ustring.hxx> +#include <cppuhelper/implbase.hxx> + +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> + +#include <com/sun/star/script/browse/XBrowseNode.hpp> +#include <com/sun/star/script/browse/XBrowseNodeFactory.hpp> + +namespace browsenodefactory +{ + +class BrowseNodeFactoryImpl : + public ::cppu::WeakImplHelper < + css::script::browse::XBrowseNodeFactory, + css::lang::XServiceInfo > +{ +private: + css::uno::Reference< css::uno::XComponentContext > m_xComponentContext; + +protected: + virtual ~BrowseNodeFactoryImpl() override; + +public: + explicit BrowseNodeFactoryImpl( + css::uno::Reference< css::uno::XComponentContext > const & xComponentContext ); + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + + virtual sal_Bool SAL_CALL + supportsService( OUString const & serviceName ) override; + + virtual css::uno::Sequence< OUString > SAL_CALL + getSupportedServiceNames() override; + + // XBrowseNodeFactory + virtual css::uno::Reference< css::script::browse::XBrowseNode > SAL_CALL + createView( sal_Int16 viewType ) override; +private: + /// @throws css::uno::RuntimeException + css::uno::Reference< css::script::browse::XBrowseNode > + getOrganizerHierarchy() const; +}; + + +} // namespace browsenodefactory + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/scripting/source/provider/MasterScriptProvider.cxx b/scripting/source/provider/MasterScriptProvider.cxx new file mode 100644 index 000000000..93d6a60f3 --- /dev/null +++ b/scripting/source/provider/MasterScriptProvider.cxx @@ -0,0 +1,676 @@ +/* -*- 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 <comphelper/SetFlagContextHelper.hxx> +#include <comphelper/documentinfo.hxx> + +#include <cppuhelper/exc_hlp.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <tools/diagnose_ex.h> +#include <tools/urlobj.hxx> + +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/script/provider/ScriptFrameworkErrorException.hpp> +#include <com/sun/star/uri/XUriReference.hpp> +#include <com/sun/star/uri/UriReferenceFactory.hpp> +#include <com/sun/star/uri/XVndSunStarScriptUrl.hpp> + +#include <com/sun/star/deployment/XPackage.hpp> +#include <com/sun/star/script/browse/BrowseNodeTypes.hpp> +#include <com/sun/star/script/provider/theMasterScriptProviderFactory.hpp> +#include <com/sun/star/script/provider/ScriptFrameworkErrorType.hpp> + +#include <util/MiscUtils.hxx> +#include <sal/log.hxx> + +#include "MasterScriptProvider.hxx" + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::script; +using namespace ::com::sun::star::document; +using namespace ::sf_misc; + +namespace func_provider +{ + +static bool endsWith( const OUString& target, const OUString& item ) +{ + sal_Int32 index = target.indexOf( item ); + return index != -1 && + index == ( target.getLength() - item.getLength() ); +} + +/* should be available in some central location. */ + +// XScriptProvider implementation + + +MasterScriptProvider::MasterScriptProvider( const Reference< XComponentContext > & xContext ): + m_xContext( xContext ), m_bIsValid( false ), m_bInitialised( false ), + m_bIsPkgMSP( false ) +{ + ENSURE_OR_THROW( m_xContext.is(), "MasterScriptProvider::MasterScriptProvider: No context available\n" ); + m_xMgr = m_xContext->getServiceManager(); + ENSURE_OR_THROW( m_xMgr.is(), "MasterScriptProvider::MasterScriptProvider: No service manager available\n" ); + m_bIsValid = true; +} + + +MasterScriptProvider::~MasterScriptProvider() +{ +} + + +void SAL_CALL MasterScriptProvider::initialize( const Sequence < Any >& args ) +{ + if ( m_bInitialised ) + return; + + m_bIsValid = false; + + sal_Int32 len = args.getLength(); + if ( len > 1 ) + { + throw RuntimeException( + "MasterScriptProvider::initialize: invalid number of arguments" ); + } + + Sequence< Any > invokeArgs( len ); + + if ( len != 0 ) + { + auto pinvokeArgs = invokeArgs.getArray(); + // check if first parameter is a string + // if it is, this implies that this is a MSP created + // with a user or share ctx ( used for browse functionality ) + + if ( args[ 0 ] >>= m_sCtxString ) + { + pinvokeArgs[ 0 ] = args[ 0 ]; + if ( m_sCtxString.startsWith( "vnd.sun.star.tdoc" ) ) + { + m_xModel = MiscUtils::tDocUrlToModel( m_sCtxString ); + } + } + else if ( args[ 0 ] >>= m_xInvocationContext ) + { + m_xModel.set( m_xInvocationContext->getScriptContainer(), UNO_QUERY_THROW ); + } + else + { + args[ 0 ] >>= m_xModel; + } + + if ( m_xModel.is() ) + { + // from the arguments, we were able to deduce a model. That alone doesn't + // suffice, we also need an XEmbeddedScripts which actually indicates support + // for embedding scripts + Reference< XEmbeddedScripts > xScripts( m_xModel, UNO_QUERY ); + if ( !xScripts.is() ) + { + throw lang::IllegalArgumentException( + "The given document does not support embedding scripts into it, and cannot be associated with such a document.", + *this, + 1 + ); + } + + try + { + m_sCtxString = MiscUtils::xModelToTdocUrl( m_xModel, m_xContext ); + } + catch ( const Exception& ) + { + Any aError( ::cppu::getCaughtException() ); + + Exception aException; + aError >>= aException; + OUString buf = + "MasterScriptProvider::initialize: caught " + + aError.getValueTypeName() + + ":" + + aException.Message; + throw lang::WrappedTargetException( buf, *this, aError ); + } + + if ( m_xInvocationContext.is() && m_xInvocationContext != m_xModel ) + pinvokeArgs[ 0 ] <<= m_xInvocationContext; + else + pinvokeArgs[ 0 ] <<= m_sCtxString; + } + + OUString pkgSpec = "uno_packages"; + sal_Int32 indexOfPkgSpec = m_sCtxString.lastIndexOf( pkgSpec ); + + // if context string ends with "uno_packages" + if ( indexOfPkgSpec > -1 && m_sCtxString.match( pkgSpec, indexOfPkgSpec ) ) + { + m_bIsPkgMSP = true; + } + else + { + m_bIsPkgMSP = false; + } + } + else // no args + { + // use either scripting context or maybe zero args? + invokeArgs = Sequence< Any >( 0 ); // no arguments + } + m_sAargs = invokeArgs; + // don't create pkg mgr MSP for documents, not supported + if ( !m_bIsPkgMSP && !m_xModel.is() ) + { + createPkgProvider(); + } + + m_bInitialised = true; + m_bIsValid = true; +} + + +void MasterScriptProvider::createPkgProvider() +{ + try + { + Any location; + location <<= m_sCtxString + ":uno_packages"; + + Reference< provider::XScriptProviderFactory > xFac = + provider::theMasterScriptProviderFactory::get( m_xContext ); + + m_xMSPPkg.set( + xFac->createScriptProvider( location ), UNO_SET_THROW ); + + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION("scripting.provider", "Exception creating MasterScriptProvider for uno_packages in context " + << m_sCtxString ); + } +} + + +Reference< provider::XScript > +MasterScriptProvider::getScript( const OUString& scriptURI ) +{ + if ( !m_bIsValid ) + { + throw provider::ScriptFrameworkErrorException( + "MasterScriptProvider not initialised", Reference< XInterface >(), + scriptURI, "", + provider::ScriptFrameworkErrorType::UNKNOWN ); + } + + // need to get the language from the string + + Reference< uri::XUriReferenceFactory > xFac ( uri::UriReferenceFactory::create( m_xContext ) ); + + Reference< uri::XUriReference > uriRef = xFac->parse( scriptURI ); + + Reference < uri::XVndSunStarScriptUrl > sfUri( uriRef, UNO_QUERY ); + + if ( !uriRef.is() || !sfUri.is() ) + { + throw provider::ScriptFrameworkErrorException( + "Incorrect format for Script URI: " + scriptURI, + Reference< XInterface >(), + scriptURI, "", + provider::ScriptFrameworkErrorType::UNKNOWN ); + } + + OUString langKey("language"); + OUString locKey("location"); + + if ( !sfUri->hasParameter( langKey ) || + !sfUri->hasParameter( locKey ) || + ( sfUri->getName().isEmpty() ) ) + { + throw provider::ScriptFrameworkErrorException( + "Incorrect format for Script URI: " + scriptURI, + Reference< XInterface >(), + scriptURI, "", + provider::ScriptFrameworkErrorType::UNKNOWN ); + } + + OUString language = sfUri->getParameter( langKey ); + OUString location = sfUri->getParameter( locKey ); + + // if script us located in uno pkg + sal_Int32 index = -1; + OUString pkgTag(":uno_packages"); + // for languages other than basic, scripts located in uno packages + // are merged into the user/share location context. + // For other languages the location attribute in script url has the form + // location = [user|share]:uno_packages or location :uno_packages/xxxx.uno.pkg + // we need to extract the value of location part from the + // location attribute of the script, if the script is located in an + // uno package then that is the location part up to and including + // ":uno_packages", if the script is not in a uno package then the + // normal value is used e.g. user or share. + // The value extracted will be used to determine if the script is + // located in the same location context as this MSP. + // For Basic, the language script provider can handle the execution of a + // script in any location context + if ( ( index = location.indexOf( pkgTag ) ) > -1 ) + { + location = location.copy( 0, index + pkgTag.getLength() ); + } + + Reference< provider::XScript > xScript; + + // If the script location is in the same location context as this + // MSP then delete to the language provider controlled by this MSP + // ** Special case is BASIC, all calls to getScript will be handled + // by the language script provider in the current location context + // even if it's different + if ( ( location == "document" + && m_xModel.is() + ) + || ( endsWith( m_sCtxString, location ) ) + || ( language == "Basic" ) + ) + { + Reference< provider::XScriptProvider > xScriptProvider; + OUString serviceName = "com.sun.star.script.provider.ScriptProviderFor" + language; + if ( !providerCache() ) + { + throw provider::ScriptFrameworkErrorException( + "No LanguageProviders detected", + Reference< XInterface >(), + sfUri->getName(), language, + provider::ScriptFrameworkErrorType::NOTSUPPORTED ); + } + + try + { + xScriptProvider.set( + providerCache()->getProvider( serviceName ), + UNO_SET_THROW ); + } + catch( const Exception& e ) + { + throw provider::ScriptFrameworkErrorException( + e.Message, Reference< XInterface >(), + sfUri->getName(), language, + provider::ScriptFrameworkErrorType::NOTSUPPORTED ); + } + + xScript=xScriptProvider->getScript( scriptURI ); + } + else + { + Reference< provider::XScriptProviderFactory > xFac_ = + provider::theMasterScriptProviderFactory::get( m_xContext ); + + Reference< provider::XScriptProvider > xSP( + xFac_->createScriptProvider( Any( location ) ), UNO_SET_THROW ); + xScript = xSP->getScript( scriptURI ); + } + + return xScript; +} + + +ProviderCache* +MasterScriptProvider::providerCache() +{ + if ( !m_pPCache ) + { + std::scoped_lock aGuard( m_mutex ); + if ( !m_pPCache ) + { + Sequence<OUString> denylist { "com.sun.star.script.provider.ScriptProviderForBasic" }; + + if ( !m_bIsPkgMSP ) + { + m_pPCache.reset( new ProviderCache( m_xContext, m_sAargs ) ); + } + else + { + m_pPCache.reset( new ProviderCache( m_xContext, m_sAargs, denylist ) ); + } + } + } + return m_pPCache.get(); +} + + +OUString SAL_CALL +MasterScriptProvider::getName() +{ + if ( !m_bIsPkgMSP ) + { + OUString sCtx = getContextString(); + if ( sCtx.startsWith( "vnd.sun.star.tdoc" ) ) + { + Reference< frame::XModel > xModel = m_xModel; + if ( !xModel.is() ) + { + xModel = MiscUtils::tDocUrlToModel( sCtx ); + } + + m_sNodeName = ::comphelper::DocumentInfo::getDocumentTitle( xModel ); + } + else + { + m_sNodeName = parseLocationName( getContextString() ); + } + } + else + { + m_sNodeName = "uno_packages"; + } + return m_sNodeName; +} + + +Sequence< Reference< browse::XBrowseNode > > SAL_CALL +MasterScriptProvider::getChildNodes() +{ + Sequence< Reference< provider::XScriptProvider > > providers = providerCache()->getAllProviders(); + + sal_Int32 size = providers.getLength(); + bool hasPkgs = m_xMSPPkg.is(); + if ( hasPkgs ) + { + size++; + } + Sequence< Reference< browse::XBrowseNode > > children( size ); + auto childrenRange = asNonConstRange(children); + sal_Int32 provIndex = 0; + for ( ; provIndex < providers.getLength(); provIndex++ ) + { + childrenRange[ provIndex ].set( providers[ provIndex ], UNO_QUERY ); + } + + if ( hasPkgs ) + { + childrenRange[ provIndex ].set( m_xMSPPkg, UNO_QUERY ); + + } + + return children; +} + + +sal_Bool SAL_CALL +MasterScriptProvider::hasChildNodes() +{ + return true; +} + + +sal_Int16 SAL_CALL +MasterScriptProvider::getType() +{ + return browse::BrowseNodeTypes::CONTAINER; +} + + +OUString +MasterScriptProvider::parseLocationName( const OUString& location ) +{ + // strip out the last leaf of location name + // e.g. file://dir1/dir2/Blah.sxw - > Blah.sxw + OUString temp = location; + INetURLObject aURLObj( temp ); + if ( !aURLObj.HasError() ) + temp = aURLObj.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset ); + return temp; +} + +namespace +{ +template <typename Proc> bool FindProviderAndApply(ProviderCache& rCache, Proc p) +{ + auto pass = [&rCache, &p]() -> bool + { + bool bResult = false; + const Sequence<Reference<provider::XScriptProvider>> aAllProviders = rCache.getAllProviders(); + for (const auto& rProv : aAllProviders) + { + Reference<container::XNameContainer> xCont(rProv, UNO_QUERY); + if (!xCont.is()) + { + continue; + } + try + { + bResult = p(xCont); + if (bResult) + break; + } + catch (const Exception&) + { + TOOLS_INFO_EXCEPTION("scripting.provider", "ignoring"); + } + } + return bResult; + }; + bool bSuccess = false; + // 1. Try to perform the operation without trying to enable JVM (if disabled) + // This allows us to avoid useless user interaction in case when other provider + // (not JVM) actually handles the operation. + { + css::uno::ContextLayer layer(comphelper::NoEnableJavaInteractionContext()); + bSuccess = pass(); + } + // 2. Now retry asking to enable JVM in case we didn't succeed first time + if (!bSuccess) + { + bSuccess = pass(); + } + return bSuccess; +} +} // namespace + +// Register Package +void SAL_CALL +MasterScriptProvider::insertByName( const OUString& aName, const Any& aElement ) +{ + if ( !m_bIsPkgMSP ) + { + if ( !m_xMSPPkg.is() ) + { + throw RuntimeException( "PackageMasterScriptProvider is unitialised" ); + } + + Reference< container::XNameContainer > xCont( m_xMSPPkg, UNO_QUERY_THROW ); + xCont->insertByName( aName, aElement ); + } + else + { + Reference< deployment::XPackage > xPkg( aElement, UNO_QUERY ); + if ( !xPkg.is() ) + { + throw lang::IllegalArgumentException( "Couldn't convert to XPackage", + Reference < XInterface > (), 2 ); + } + if ( aName.isEmpty() ) + { + throw lang::IllegalArgumentException( "Name not set!!", + Reference < XInterface > (), 1 ); + } + // TODO for library package parse the language, for the moment will try + // to get each provider to process the new Package, the first one the succeeds + // will terminate processing + const bool bSuccess = FindProviderAndApply( + *providerCache(), [&aName, &aElement](Reference<container::XNameContainer>& xCont) { + xCont->insertByName(aName, aElement); + return true; + }); + if (!bSuccess) + { + // No script providers could process the package + throw lang::IllegalArgumentException( "Failed to register package for " + aName, + Reference < XInterface > (), 2 ); + } + } +} + + +// Revoke Package +void SAL_CALL +MasterScriptProvider::removeByName( const OUString& Name ) +{ + if ( !m_bIsPkgMSP ) + { + if ( !m_xMSPPkg.is() ) + { + throw RuntimeException( "PackageMasterScriptProvider is unitialised" ); + } + + Reference< container::XNameContainer > xCont( m_xMSPPkg, UNO_QUERY_THROW ); + xCont->removeByName( Name ); + } + else + { + if ( Name.isEmpty() ) + { + throw lang::IllegalArgumentException( "Name not set!!", + Reference < XInterface > (), 1 ); + } + // TODO for Script library package url parse the language, + // for the moment will just try to get each provider to process remove/revoke + // request, the first one the succeeds will terminate processing + const bool bSuccess = FindProviderAndApply( + *providerCache(), [&Name](Reference<container::XNameContainer>& xCont) { + xCont->removeByName(Name); + return true; + }); + if (!bSuccess) + { + // No script providers could process the package + throw lang::IllegalArgumentException( "Failed to revoke package for " + Name, + Reference < XInterface > (), 1 ); + } + + } +} + + +void SAL_CALL +MasterScriptProvider::replaceByName( const OUString& /*aName*/, const Any& /*aElement*/ ) +{ + // TODO needs implementing + throw RuntimeException( "replaceByName not implemented!!!!" ); +} + +Any SAL_CALL +MasterScriptProvider::getByName( const OUString& /*aName*/ ) +{ + // TODO needs to be implemented + throw RuntimeException( "getByName not implemented!!!!" ); +} + +sal_Bool SAL_CALL +MasterScriptProvider::hasByName( const OUString& aName ) +{ + bool result = false; + if ( !m_bIsPkgMSP ) + { + if ( m_xMSPPkg.is() ) + { + Reference< container::XNameContainer > xCont( m_xMSPPkg, UNO_QUERY_THROW ); + result = xCont->hasByName( aName ); + } + // If this is a document provider then we shouldn't + // have a PackageProvider + else if (!m_xModel.is()) + { + throw RuntimeException( "PackageMasterScriptProvider is unitialised" ); + } + + } + else + { + if ( aName.isEmpty() ) + { + throw lang::IllegalArgumentException( "Name not set!!", + Reference < XInterface > (), 1 ); + } + // TODO for Script library package url parse the language, + // for the moment will just try to get each provider to see if the + // package exists in any provider, first one that succeed will + // terminate the loop + result = FindProviderAndApply( + *providerCache(), [&aName](Reference<container::XNameContainer>& xCont) { + return xCont->hasByName(aName); + }); + } + return result; +} + + +Sequence< OUString > SAL_CALL +MasterScriptProvider::getElementNames( ) +{ + // TODO needs implementing + throw RuntimeException( "getElementNames not implemented!!!!" ); +} + +Type SAL_CALL +MasterScriptProvider::getElementType( ) +{ + // TODO needs implementing + Type t; + return t; +} + +sal_Bool SAL_CALL MasterScriptProvider::hasElements( ) +{ + // TODO needs implementing + throw RuntimeException( "hasElements not implemented!!!!" ); +} + + +OUString SAL_CALL MasterScriptProvider::getImplementationName( ) +{ + return "com.sun.star.script.provider.MasterScriptProvider"; +} + +sal_Bool SAL_CALL MasterScriptProvider::supportsService( const OUString& serviceName ) +{ + return cppu::supportsService(this, serviceName); +} + + +Sequence< OUString > SAL_CALL MasterScriptProvider::getSupportedServiceNames( ) +{ + return { + "com.sun.star.script.provider.MasterScriptProvider", + "com.sun.star.script.browse.BrowseNode", + "com.sun.star.script.provider.ScriptProvider" }; +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +scripting_MasterScriptProvider_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new MasterScriptProvider(context)); +} + +} // namespace func_provider + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/scripting/source/provider/MasterScriptProvider.hxx b/scripting/source/provider/MasterScriptProvider.hxx new file mode 100644 index 000000000..0e6c40f5f --- /dev/null +++ b/scripting/source/provider/MasterScriptProvider.hxx @@ -0,0 +1,131 @@ +/* -*- 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 . + */ + +#pragma once + +#include <rtl/ustring.hxx> + +#include <cppuhelper/implbase.hxx> + +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/document/XScriptInvocationContext.hpp> + +#include <com/sun/star/lang/XInitialization.hpp> + +#include <com/sun/star/script/provider/XScriptProvider.hpp> +#include <com/sun/star/script/browse/XBrowseNode.hpp> + +#include "ProviderCache.hxx" +#include <memory> +#include <mutex> + +namespace func_provider +{ + + typedef ::cppu::WeakImplHelper< + css::script::provider::XScriptProvider, + css::script::browse::XBrowseNode, css::lang::XServiceInfo, + css::lang::XInitialization, + css::container::XNameContainer > t_helper; + +class MasterScriptProvider : + public t_helper +{ +public: + /// @throws css::uno::RuntimeException + explicit MasterScriptProvider( + const css::uno::Reference< css::uno::XComponentContext > + & xContext ); + virtual ~MasterScriptProvider() override; + + // XServiceInfo implementation + virtual OUString SAL_CALL getImplementationName( ) override; + + // XBrowseNode implementation + virtual OUString SAL_CALL getName() override; + virtual css::uno::Sequence< css::uno::Reference< css::script::browse::XBrowseNode > > SAL_CALL getChildNodes() override; + virtual sal_Bool SAL_CALL hasChildNodes() override; + virtual sal_Int16 SAL_CALL getType() override; + // XNameContainer + virtual void SAL_CALL insertByName( const OUString& aName, const css::uno::Any& aElement ) override; + virtual void SAL_CALL removeByName( const OUString& Name ) override; + + // XNameReplace + virtual void SAL_CALL replaceByName( const OUString& aName, const css::uno::Any& aElement ) override; + // XNameAccess + virtual css::uno::Any SAL_CALL getByName( const OUString& aName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getElementNames( ) override; + virtual sal_Bool SAL_CALL hasByName( const OUString& aName ) override; + + // XElementAccess + virtual css::uno::Type SAL_CALL getElementType( ) override; + virtual sal_Bool SAL_CALL hasElements( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + + // XScriptProvider implementation + virtual css::uno::Reference < css::script::provider::XScript > SAL_CALL + getScript( const OUString& scriptURI ) override; + + /** + * XInitialise implementation + * + * @param args expected to contain a single OUString + * containing the URI + */ + virtual void SAL_CALL initialize( const css::uno::Sequence < css::uno::Any > & args ) override; + + // returns context string for this provider, eg + const OUString& getContextString() const { return m_sCtxString; } + +private: + static OUString parseLocationName( const OUString& location ); + void createPkgProvider(); + + ProviderCache* providerCache(); + /* to obtain other services if needed */ + css::uno::Reference< css::uno::XComponentContext > m_xContext; + css::uno::Reference< css::lang::XMultiComponentFactory > m_xMgr; + css::uno::Reference< css::frame::XModel > m_xModel; + css::uno::Reference< css::document::XScriptInvocationContext > m_xInvocationContext; + css::uno::Sequence< css::uno::Any > m_sAargs; + OUString m_sNodeName; + + // This component supports XInitialization, it can be created + // using createInstanceXXX() or createInstanceWithArgumentsXXX using + // the service Manager. + // Need to detect proper initialisation and validity + // for the object, so m_bIsValid indicates that the object is valid is set in ctor + // in case of createInstanceWithArgumentsXXX() called m_bIsValid is set to reset + // and then set to true when initialisation is complete + bool m_bIsValid; + // m_bInitialised ensure initialisation only takes place once. + bool m_bInitialised; + bool m_bIsPkgMSP; + css::uno::Reference< css::script::provider::XScriptProvider > m_xMSPPkg; + std::unique_ptr<ProviderCache> m_pPCache; + std::mutex m_mutex; + OUString m_sCtxString; +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/scripting/source/provider/MasterScriptProviderFactory.cxx b/scripting/source/provider/MasterScriptProviderFactory.cxx new file mode 100644 index 000000000..acdb8f8da --- /dev/null +++ b/scripting/source/provider/MasterScriptProviderFactory.cxx @@ -0,0 +1,85 @@ +/* -*- 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 <cppuhelper/supportsservice.hxx> + +#include "MasterScriptProviderFactory.hxx" + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::script; + +namespace func_provider +{ + +MasterScriptProviderFactory::MasterScriptProviderFactory( + Reference< XComponentContext > const & xComponentContext ) + : m_xComponentContext( xComponentContext ) +{ +} + +MasterScriptProviderFactory::~MasterScriptProviderFactory() +{ +} + +Reference< provider::XScriptProvider > SAL_CALL +MasterScriptProviderFactory::createScriptProvider( const Any& context ) +{ + Reference< provider::XScriptProvider > xMsp( getActiveMSPList() ->getMSPFromAnyContext( context ), UNO_SET_THROW ); + return xMsp; +} + +const rtl::Reference< ActiveMSPList > & +MasterScriptProviderFactory::getActiveMSPList() const +{ + if ( !m_MSPList.is() ) + { + ::osl::MutexGuard guard( ::osl::Mutex::getGlobalMutex() ); + if ( !m_MSPList.is() ) + m_MSPList = new ActiveMSPList( m_xComponentContext ); + } + return m_MSPList; +} + +OUString SAL_CALL MasterScriptProviderFactory::getImplementationName() +{ + return "com.sun.star.script.provider.MasterScriptProviderFactory"; +} + +Sequence< OUString > SAL_CALL MasterScriptProviderFactory::getSupportedServiceNames() +{ + return { "com.sun.star.script.provider.MasterScriptProviderFactory" }; +} + +sal_Bool MasterScriptProviderFactory::supportsService( + OUString const & serviceName ) +{ + return cppu::supportsService(this, serviceName); +} + +} // namespace func_provider + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +scripting_MasterScriptProviderFactory_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new func_provider::MasterScriptProviderFactory(context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/scripting/source/provider/MasterScriptProviderFactory.hxx b/scripting/source/provider/MasterScriptProviderFactory.hxx new file mode 100644 index 000000000..9fbc8705d --- /dev/null +++ b/scripting/source/provider/MasterScriptProviderFactory.hxx @@ -0,0 +1,74 @@ +/* -*- 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 . + */ + +#pragma once + +#include <rtl/ustring.hxx> +#include <rtl/ref.hxx> +#include <cppuhelper/implbase.hxx> + +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> + +#include <com/sun/star/script/provider/XScriptProviderFactory.hpp> +#include <com/sun/star/script/provider/XScriptProvider.hpp> + +#include "ActiveMSPList.hxx" + +namespace func_provider +{ + +class MasterScriptProviderFactory : + public ::cppu::WeakImplHelper < + css::script::provider::XScriptProviderFactory, + css::lang::XServiceInfo > +{ +private: + + mutable rtl::Reference< ActiveMSPList > m_MSPList; + + const css::uno::Reference< css::uno::XComponentContext > m_xComponentContext; + + const rtl::Reference< ActiveMSPList > & getActiveMSPList() const; + +protected: + virtual ~MasterScriptProviderFactory() override; + +public: + explicit MasterScriptProviderFactory( + css::uno::Reference< css::uno::XComponentContext > const & xComponentContext ); + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + + virtual sal_Bool SAL_CALL + supportsService( OUString const & serviceName ) override; + + virtual css::uno::Sequence< OUString > SAL_CALL + getSupportedServiceNames() override; + + // XScriptProviderFactory + virtual css::uno::Reference< css::script::provider::XScriptProvider > + SAL_CALL createScriptProvider( const css::uno::Any& context ) override; +}; + + +} // namespace func_provider + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/scripting/source/provider/ProviderCache.cxx b/scripting/source/provider/ProviderCache.cxx new file mode 100644 index 000000000..8533fc384 --- /dev/null +++ b/scripting/source/provider/ProviderCache.cxx @@ -0,0 +1,203 @@ +/* -*- 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 <comphelper/sequence.hxx> +#include <tools/diagnose_ex.h> +#include <sal/log.hxx> + +#include <com/sun/star/container/XContentEnumerationAccess.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include "ProviderCache.hxx" + +using namespace com::sun::star; +using namespace com::sun::star::uno; +using namespace com::sun::star::script; + +namespace func_provider +{ + +ProviderCache::ProviderCache( const Reference< XComponentContext >& xContext, const Sequence< Any >& scriptContext ) : m_Sctx( scriptContext ), m_xContext( xContext ) +{ + // initialise m_hProviderDetailsCache with details of ScriptProviders + // will use createContentEnumeration + + m_xMgr = m_xContext->getServiceManager(); + ENSURE_OR_THROW( m_xMgr.is(), "ProviderCache::ProviderCache() failed to obtain ServiceManager" ); + populateCache(); +} + + +ProviderCache::ProviderCache( const Reference< XComponentContext >& xContext, const Sequence< Any >& scriptContext, const Sequence< OUString >& denyList ) : m_sDenyList( denyList ), m_Sctx( scriptContext ), m_xContext( xContext ) + +{ + // initialise m_hProviderDetailsCache with details of ScriptProviders + // will use createContentEnumeration + + m_xMgr = m_xContext->getServiceManager(); + ENSURE_OR_THROW( m_xMgr.is(), "ProviderCache::ProviderCache() failed to obtain ServiceManager" ); + populateCache(); +} + +ProviderCache::~ProviderCache() +{ +} + +Reference< provider::XScriptProvider > +ProviderCache::getProvider( const OUString& providerName ) +{ + std::scoped_lock aGuard( m_mutex ); + Reference< provider::XScriptProvider > provider; + ProviderDetails_hash::iterator h_it = m_hProviderDetailsCache.find( providerName ); + if ( h_it != m_hProviderDetailsCache.end() ) + { + if ( h_it->second.provider.is() ) + { + provider = h_it->second.provider; + } + else + { + // need to create provider and insert into hash + provider = createProvider( h_it->second ); + } + } + return provider; +} + +Sequence < Reference< provider::XScriptProvider > > +ProviderCache::getAllProviders() +{ + // need to create providers that haven't been created already + // so check what providers exist and what ones don't + + std::scoped_lock aGuard( m_mutex ); + Sequence < Reference< provider::XScriptProvider > > providers ( m_hProviderDetailsCache.size() ); + // should assert if size !> 0 + if ( !m_hProviderDetailsCache.empty() ) + { + auto pproviders = providers.getArray(); + sal_Int32 providerIndex = 0; + for (auto& rDetail : m_hProviderDetailsCache) + { + Reference<provider::XScriptProvider> xScriptProvider = rDetail.second.provider; + if ( xScriptProvider.is() ) + { + pproviders[ providerIndex++ ] = xScriptProvider; + } + else + { + // create provider + try + { + xScriptProvider = createProvider(rDetail.second); + pproviders[ providerIndex++ ] = xScriptProvider; + } + catch ( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("scripting"); + } + } + } + + if (providerIndex < providers.getLength()) + { + providers.realloc( providerIndex ); + } + + } + else + { + SAL_WARN("scripting", "no available providers, something very wrong!!!"); + } + return providers; +} + +void +ProviderCache::populateCache() +{ + // wrong name in services.rdb + OUString serviceName; + std::scoped_lock aGuard( m_mutex ); + try + { + Reference< container::XContentEnumerationAccess > xEnumAccess( m_xMgr, UNO_QUERY_THROW ); + Reference< container::XEnumeration > xEnum = xEnumAccess->createContentEnumeration ( "com.sun.star.script.provider.LanguageScriptProvider" ); + + while ( xEnum->hasMoreElements() ) + { + + Reference< lang::XSingleComponentFactory > factory( xEnum->nextElement(), UNO_QUERY_THROW ); + Reference< lang::XServiceInfo > xServiceInfo( factory, UNO_QUERY_THROW ); + + const Sequence< OUString > serviceNames = xServiceInfo->getSupportedServiceNames(); + + if ( serviceNames.hasElements() ) + { + auto pName = std::find_if(serviceNames.begin(), serviceNames.end(), + [this](const OUString& rName) { + return rName.startsWith("com.sun.star.script.provider.ScriptProviderFor") + && !isInDenyList(rName); + }); + if (pName != serviceNames.end()) + { + serviceName = *pName; + ProviderDetails details; + details.factory = factory; + m_hProviderDetailsCache[ serviceName ] = details; + } + } + } + } + catch ( const Exception &e ) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw css::lang::WrappedTargetRuntimeException( + "ProviderCache::populateCache: couldn't obtain XSingleComponentFactory for " + serviceName + + " " + e.Message, + nullptr, anyEx ); + } +} + +Reference< provider::XScriptProvider > +ProviderCache::createProvider( ProviderDetails& details ) +{ + try + { + details.provider.set( + details.factory->createInstanceWithArgumentsAndContext( m_Sctx, m_xContext ), UNO_QUERY_THROW ); + } + catch ( const Exception& e ) + { + css::uno::Any anyEx = cppu::getCaughtException(); + throw css::lang::WrappedTargetRuntimeException( + "ProviderCache::createProvider() Error creating provider from factory. " + e.Message, + nullptr, anyEx ); + } + + return details.provider; +} + +bool +ProviderCache::isInDenyList( const OUString& serviceName ) const +{ + return comphelper::findValue(m_sDenyList, serviceName) != -1; +} +} //end namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/scripting/source/provider/ProviderCache.hxx b/scripting/source/provider/ProviderCache.hxx new file mode 100644 index 000000000..b565d1d08 --- /dev/null +++ b/scripting/source/provider/ProviderCache.hxx @@ -0,0 +1,80 @@ +/* -*- 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 . + */ + +#pragma once + +#include <rtl/ustring.hxx> +#include <com/sun/star/lang/XSingleComponentFactory.hpp> +#include <com/sun/star/lang/XMultiComponentFactory.hpp> + +#include <com/sun/star/script/provider/XScriptProvider.hpp> + +#include <mutex> +#include <unordered_map> + +namespace func_provider +{ + +//Typedefs + + +struct ProviderDetails +{ + //css::uno::Reference< css::lang::XSingleServiceFactory > factory; + css::uno::Reference< css::lang::XSingleComponentFactory > factory; + css::uno::Reference< css::script::provider::XScriptProvider > provider; +}; +typedef std::unordered_map < OUString, ProviderDetails > ProviderDetails_hash; + + +class ProviderCache +{ + +public: + /// @throws css::uno::RuntimeException + ProviderCache( const css::uno::Reference< css::uno::XComponentContext >& xContext, const css::uno::Sequence< css::uno::Any >& scriptContext ); + /// @throws css::uno::RuntimeException + ProviderCache( const css::uno::Reference< css::uno::XComponentContext >& xContext, const css::uno::Sequence< css::uno::Any >& scriptContext, + const css::uno::Sequence< OUString >& denyList ); + ~ProviderCache(); + css::uno::Reference< css::script::provider::XScriptProvider > + getProvider( const OUString& providerName ); + /// @throws css::uno::RuntimeException + css::uno::Sequence < css::uno::Reference< css::script::provider::XScriptProvider > > + getAllProviders(); +private: + /// @throws css::uno::RuntimeException + void populateCache(); + + /// @throws css::uno::RuntimeException + css::uno::Reference< css::script::provider::XScriptProvider > + createProvider( ProviderDetails& details ); + bool isInDenyList( const OUString& serviceName ) const; + css::uno::Sequence< OUString > m_sDenyList; + ProviderDetails_hash m_hProviderDetailsCache; + std::mutex m_mutex; + css::uno::Sequence< css::uno::Any > m_Sctx; + css::uno::Reference< css::uno::XComponentContext > m_xContext; + css::uno::Reference< css::lang::XMultiComponentFactory > m_xMgr; + + +}; +} // func_provider + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/scripting/source/provider/URIHelper.cxx b/scripting/source/provider/URIHelper.cxx new file mode 100644 index 000000000..5333bee34 --- /dev/null +++ b/scripting/source/provider/URIHelper.cxx @@ -0,0 +1,256 @@ +/* -*- 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 <config_folders.h> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/ucb/SimpleFileAccess.hpp> +#include <com/sun/star/uri/XVndSunStarScriptUrl.hpp> +#include <com/sun/star/uri/UriReferenceFactory.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <osl/diagnose.h> +#include "URIHelper.hxx" + +namespace func_provider +{ + +namespace uno = ::com::sun::star::uno; +namespace ucb = ::com::sun::star::ucb; +namespace lang = ::com::sun::star::lang; +namespace uri = ::com::sun::star::uri; + +constexpr OUStringLiteral SHARE = u"share"; + +constexpr OUStringLiteral SHARE_UNO_PACKAGES_URI = + u"vnd.sun.star.expand:$UNO_SHARED_PACKAGES_CACHE"; + +constexpr OUStringLiteral USER = u"user"; +constexpr OUStringLiteral USER_URI = + u"vnd.sun.star.expand:${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}"; + + + +ScriptingFrameworkURIHelper::ScriptingFrameworkURIHelper( + const uno::Reference< uno::XComponentContext >& xContext) +{ + try + { + m_xSimpleFileAccess = ucb::SimpleFileAccess::create(xContext); + } + catch (uno::Exception&) + { + OSL_FAIL("Scripting Framework error initialising XSimpleFileAccess"); + } + + try + { + m_xUriReferenceFactory = uri::UriReferenceFactory::create( xContext ); + } + catch (uno::Exception&) + { + OSL_FAIL("Scripting Framework error initialising XUriReferenceFactory"); + } +} + +ScriptingFrameworkURIHelper::~ScriptingFrameworkURIHelper() +{ + // currently does nothing +} + +void SAL_CALL +ScriptingFrameworkURIHelper::initialize( + const uno::Sequence < uno::Any >& args ) +{ + if ( args.getLength() != 2 || + args[0].getValueType() != ::cppu::UnoType<OUString>::get() || + args[1].getValueType() != ::cppu::UnoType<OUString>::get() ) + { + throw uno::RuntimeException( "ScriptingFrameworkURIHelper got invalid argument list" ); + } + + if ( !(args[0] >>= m_sLanguage) || !(args[1] >>= m_sLocation) ) + { + throw uno::RuntimeException( "ScriptingFrameworkURIHelper error parsing args" ); + } + + SCRIPTS_PART = "/Scripts/" + m_sLanguage.toAsciiLowerCase(); + + if ( !initBaseURI() ) + { + throw uno::RuntimeException( "ScriptingFrameworkURIHelper cannot find script directory" ); + } +} + +bool +ScriptingFrameworkURIHelper::initBaseURI() +{ + OUString uri, test; + bool bAppendScriptsPart = false; + + if ( m_sLocation == USER ) + { + test = USER; + uri = USER_URI; + bAppendScriptsPart = true; + } + else if ( m_sLocation == "user:uno_packages" ) + { + test = "uno_packages"; + uri = USER_URI + "/user/uno_packages/cache"; + } + else if (m_sLocation == SHARE) + { + test = SHARE; + uri = "vnd.sun.star.expand:$BRAND_BASE_DIR"; + bAppendScriptsPart = true; + } + else if (m_sLocation == "share:uno_packages") + { + test = "uno_packages"; + uri = SHARE_UNO_PACKAGES_URI; + } + else if (m_sLocation.startsWith("vnd.sun.star.tdoc")) + { + m_sBaseURI = m_sLocation + SCRIPTS_PART; + m_sLocation = "document"; + return true; + } + else + { + return false; + } + + if ( !m_xSimpleFileAccess->exists( uri ) || + !m_xSimpleFileAccess->isFolder( uri ) ) + { + return false; + } + + const uno::Sequence< OUString > children = + m_xSimpleFileAccess->getFolderContents( uri, true ); + + auto pChild = std::find_if(children.begin(), children.end(), [&test](const OUString& child) { + sal_Int32 idx = child.lastIndexOf(test); + return idx != -1 && (idx + test.getLength()) == child.getLength(); + }); + if (pChild != children.end()) + { + if ( bAppendScriptsPart ) + { + m_sBaseURI = *pChild + SCRIPTS_PART; + } + else + { + m_sBaseURI = *pChild; + } + return true; + } + return false; +} + +OUString +ScriptingFrameworkURIHelper::getLanguagePart(std::u16string_view rStorageURI) +{ + OUString result; + + size_t idx = rStorageURI.find(m_sBaseURI); + sal_Int32 len = m_sBaseURI.getLength() + 1; + + if ( idx != std::u16string_view::npos ) + { + result = rStorageURI.substr(idx + len); + result = result.replace('/', '|'); + } + return result; +} + +OUString +ScriptingFrameworkURIHelper::getLanguagePath(const OUString& rLanguagePart) +{ + OUString result = rLanguagePart.replace('|', '/'); + return result; +} + +OUString SAL_CALL +ScriptingFrameworkURIHelper::getScriptURI(const OUString& rStorageURI) +{ + return + "vnd.sun.star.script:" + + getLanguagePart(rStorageURI) + + "?language=" + + m_sLanguage + + "&location=" + + m_sLocation; +} + +OUString SAL_CALL +ScriptingFrameworkURIHelper::getStorageURI(const OUString& rScriptURI) +{ + OUString sLanguagePart; + try + { + uno::Reference < uri::XVndSunStarScriptUrl > xURI( + m_xUriReferenceFactory->parse( rScriptURI ), uno::UNO_QUERY_THROW ); + sLanguagePart = xURI->getName(); + } + catch ( uno::Exception& ) + { + throw lang::IllegalArgumentException( + "Script URI not valid", + uno::Reference< uno::XInterface >(), 1 ); + } + + return m_sBaseURI + "/" + getLanguagePath(sLanguagePart); +} + +OUString SAL_CALL +ScriptingFrameworkURIHelper::getRootStorageURI() +{ + return m_sBaseURI; +} + +OUString SAL_CALL +ScriptingFrameworkURIHelper::getImplementationName() +{ + return + "com.sun.star.script.provider.ScriptURIHelper"; +} + +sal_Bool SAL_CALL +ScriptingFrameworkURIHelper::supportsService( const OUString& serviceName ) +{ + return cppu::supportsService( this, serviceName ); +} + +uno::Sequence< OUString > SAL_CALL +ScriptingFrameworkURIHelper::getSupportedServiceNames() +{ + return { "com.sun.star.script.provider.ScriptURIHelper" }; +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +scripting_ScriptingFrameworkURIHelper_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new ScriptingFrameworkURIHelper(context)); +} + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/scripting/source/provider/URIHelper.hxx b/scripting/source/provider/URIHelper.hxx new file mode 100644 index 000000000..9d200a1a3 --- /dev/null +++ b/scripting/source/provider/URIHelper.hxx @@ -0,0 +1,87 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/script/provider/XScriptURIHelper.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/ucb/XSimpleFileAccess3.hpp> +#include <com/sun/star/uri/XUriReferenceFactory.hpp> + +#include <rtl/ustring.hxx> +#include <cppuhelper/implbase.hxx> + +namespace func_provider +{ + +class ScriptingFrameworkURIHelper : + public ::cppu::WeakImplHelper< + css::script::provider::XScriptURIHelper, + css::lang::XServiceInfo, + css::lang::XInitialization > +{ +private: + + css::uno::Reference< css::ucb::XSimpleFileAccess3 > m_xSimpleFileAccess; + css::uno::Reference<css::uri::XUriReferenceFactory> m_xUriReferenceFactory; + + OUString m_sLanguage; + OUString m_sLocation; + OUString m_sBaseURI; + + OUString SCRIPTS_PART; + + bool initBaseURI(); + OUString getLanguagePart(std::u16string_view rStorageURI); + static OUString getLanguagePath(const OUString& rLanguagePart); + +public: + /// @throws css::uno::RuntimeException + explicit ScriptingFrameworkURIHelper( + const css::uno::Reference< css::uno::XComponentContext >& xContext ); + + virtual ~ScriptingFrameworkURIHelper() override; + + virtual void SAL_CALL + initialize( const css::uno::Sequence < css::uno::Any > & args ) override; + + virtual OUString SAL_CALL + getRootStorageURI() override; + + virtual OUString SAL_CALL + getScriptURI( const OUString& rStorageURI ) override; + + virtual OUString SAL_CALL + getStorageURI( const OUString& rScriptURI ) override; + + virtual OUString SAL_CALL + getImplementationName() override; + + virtual sal_Bool SAL_CALL + supportsService( const OUString& ServiceName ) override; + + virtual css::uno::Sequence< OUString > SAL_CALL + getSupportedServiceNames() override; +}; + +} // namespace func_provider + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/scripting/source/pyprov/mailmerge.README b/scripting/source/pyprov/mailmerge.README new file mode 100644 index 000000000..6b4fb5ba4 --- /dev/null +++ b/scripting/source/pyprov/mailmerge.README @@ -0,0 +1,18 @@ +Easiest way I find to test this is to... + +1) + +a) install fakemail and run it +b) tools->options->writer->mail merge email +c) localhost 8025 + +2) + +a) type some text into writer that will exercise utf-8, e.g. "Caolán's test" +b) tools->mail merge wizard->next->email message->select address book +c) create, add one user with your own email address, ok +d) next, next, text, send merged document as email +e) and test all of plain text, html and the various attachment options + +fake mail will dump the mail it gets into its pwd, if all that works, you can +then try with your own normal mail server. diff --git a/scripting/source/pyprov/mailmerge.component b/scripting/source/pyprov/mailmerge.component new file mode 100644 index 000000000..8a9325341 --- /dev/null +++ b/scripting/source/pyprov/mailmerge.component @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.Python" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="org.openoffice.pyuno.MailMessage"> + <service name="com.sun.star.mail.MailMessage"/> + </implementation> + <implementation name="org.openoffice.pyuno.MailServiceProvider"> + <service name="com.sun.star.mail.MailServiceProvider"/> + </implementation> +</component> diff --git a/scripting/source/pyprov/mailmerge.py b/scripting/source/pyprov/mailmerge.py new file mode 100644 index 000000000..0ef37b477 --- /dev/null +++ b/scripting/source/pyprov/mailmerge.py @@ -0,0 +1,503 @@ +# Caolan McNamara caolanm@redhat.com +# a simple email mailmerge component + +# manual installation for hackers, not necessary for users +# cp mailmerge.py /usr/lib/libreoffice/program +# cd /usr/lib/libreoffice/program +# ./unopkg add --shared mailmerge.py +# edit ~/.openoffice.org2/user/registry/data/org/openoffice/Office/Writer.xcu +# and change EMailSupported to as follows... +# <prop oor:name="EMailSupported" oor:type="xs:boolean"> +# <value>true</value> +# </prop> + +import unohelper +import uno +import re +import os +import encodings.idna + +#to implement com::sun::star::mail::XMailServiceProvider +#and +#to implement com.sun.star.mail.XMailMessage + +from com.sun.star.mail import XMailServiceProvider +from com.sun.star.mail import XMailService +from com.sun.star.mail import XSmtpService +from com.sun.star.mail import XConnectionListener +from com.sun.star.mail import XAuthenticator +from com.sun.star.mail import XMailMessage +from com.sun.star.mail.MailServiceType import SMTP +from com.sun.star.mail.MailServiceType import POP3 +from com.sun.star.mail.MailServiceType import IMAP +from com.sun.star.uno import XCurrentContext +from com.sun.star.lang import IllegalArgumentException +from com.sun.star.lang import EventObject +from com.sun.star.lang import XServiceInfo +from com.sun.star.mail import SendMailMessageFailedException + +from email.mime.base import MIMEBase +from email.message import Message +from email.charset import Charset +from email.charset import QP +from email.encoders import encode_base64 +from email.header import Header +from email.mime.multipart import MIMEMultipart +from email.utils import formatdate +from email.utils import parseaddr +from socket import _GLOBAL_DEFAULT_TIMEOUT + +import sys, smtplib, imaplib, poplib +dbg = False + +# pythonloader looks for a static g_ImplementationHelper variable +g_ImplementationHelper = unohelper.ImplementationHelper() +g_providerImplName = "org.openoffice.pyuno.MailServiceProvider" +g_messageImplName = "org.openoffice.pyuno.MailMessage" + +class PyMailSMTPService(unohelper.Base, XSmtpService): + def __init__( self, ctx ): + self.ctx = ctx + self.listeners = [] + self.supportedtypes = ('Insecure', 'Ssl') + self.server = None + self.connectioncontext = None + self.notify = EventObject(self) + if dbg: + print("PyMailSMTPService init", file=sys.stderr) + print("python version is: " + sys.version, file=sys.stderr) + def addConnectionListener(self, xListener): + if dbg: + print("PyMailSMTPService addConnectionListener", file=sys.stderr) + self.listeners.append(xListener) + def removeConnectionListener(self, xListener): + if dbg: + print("PyMailSMTPService removeConnectionListener", file=sys.stderr) + self.listeners.remove(xListener) + def getSupportedConnectionTypes(self): + if dbg: + print("PyMailSMTPService getSupportedConnectionTypes", file=sys.stderr) + return self.supportedtypes + def connect(self, xConnectionContext, xAuthenticator): + self.connectioncontext = xConnectionContext + if dbg: + print("PyMailSMTPService connect", file=sys.stderr) + server = xConnectionContext.getValueByName("ServerName").strip() + if dbg: + print("ServerName: " + server, file=sys.stderr) + port = int(xConnectionContext.getValueByName("Port")) + if dbg: + print("Port: " + str(port), file=sys.stderr) + tout = xConnectionContext.getValueByName("Timeout") + if dbg: + print(isinstance(tout,int), file=sys.stderr) + if not isinstance(tout,int): + tout = _GLOBAL_DEFAULT_TIMEOUT + if dbg: + print("Timeout: " + str(tout), file=sys.stderr) + if port == 465: + self.server = smtplib.SMTP_SSL(server, port,timeout=tout) + else: + self.server = smtplib.SMTP(server, port,timeout=tout) + + if dbg: + self.server.set_debuglevel(1) + + connectiontype = xConnectionContext.getValueByName("ConnectionType") + if dbg: + print("ConnectionType: " + connectiontype, file=sys.stderr) + if connectiontype.upper() == 'SSL' and port != 465: + self.server.ehlo() + self.server.starttls() + self.server.ehlo() + + user = xAuthenticator.getUserName() + password = xAuthenticator.getPassword() + if user != '': + if dbg: + print("Logging in, username of: " + user, file=sys.stderr) + self.server.login(user, password) + + for listener in self.listeners: + listener.connected(self.notify) + def disconnect(self): + if dbg: + print("PyMailSMTPService disconnect", file=sys.stderr) + if self.server: + self.server.quit() + self.server = None + for listener in self.listeners: + listener.disconnected(self.notify) + def isConnected(self): + if dbg: + print("PyMailSMTPService isConnected", file=sys.stderr) + return self.server != None + def getCurrentConnectionContext(self): + if dbg: + print("PyMailSMTPService getCurrentConnectionContext", file=sys.stderr) + return self.connectioncontext + def sendMailMessage(self, xMailMessage): + COMMASPACE = ', ' + + if dbg: + print("PyMailSMTPService sendMailMessage", file=sys.stderr) + recipients = xMailMessage.getRecipients() + sendermail = xMailMessage.SenderAddress + sendername = xMailMessage.SenderName + subject = xMailMessage.Subject + ccrecipients = xMailMessage.getCcRecipients() + bccrecipients = xMailMessage.getBccRecipients() + if dbg: + print("PyMailSMTPService subject: " + subject, file=sys.stderr) + print("PyMailSMTPService from: " + sendername, file=sys.stderr) + print("PyMailSMTPService from: " + sendermail, file=sys.stderr) + print("PyMailSMTPService send to: %s" % (recipients,), file=sys.stderr) + + attachments = xMailMessage.getAttachments() + + textmsg = Message() + + content = xMailMessage.Body + flavors = content.getTransferDataFlavors() + if dbg: + print("PyMailSMTPService flavors len: %d" % (len(flavors),), file=sys.stderr) + + #Use first flavor that's sane for an email body + for flavor in flavors: + if flavor.MimeType.find('text/html') != -1 or flavor.MimeType.find('text/plain') != -1: + if dbg: + print("PyMailSMTPService mimetype is: " + flavor.MimeType, file=sys.stderr) + textbody = content.getTransferData(flavor) + + if len(textbody): + mimeEncoding = re.sub("charset=.*", "charset=UTF-8", flavor.MimeType) + if mimeEncoding.find('charset=UTF-8') == -1: + mimeEncoding = mimeEncoding + "; charset=UTF-8" + textmsg['Content-Type'] = mimeEncoding + textmsg['MIME-Version'] = '1.0' + + try: + #it's a string, get it as utf-8 bytes + textbody = textbody.encode('utf-8') + except: + #it's a bytesequence, get raw bytes + textbody = textbody.value + textbody = textbody.decode('utf-8') + c = Charset('utf-8') + c.body_encoding = QP + textmsg.set_payload(textbody, c) + + break + + if (len(attachments)): + msg = MIMEMultipart() + msg.epilogue = '' + msg.attach(textmsg) + else: + msg = textmsg + + hdr = Header(sendername, 'utf-8') + hdr.append('<'+sendermail+'>','us-ascii') + msg['Subject'] = subject + msg['From'] = hdr + msg['To'] = COMMASPACE.join(recipients) + if len(ccrecipients): + msg['Cc'] = COMMASPACE.join(ccrecipients) + if xMailMessage.ReplyToAddress != '': + msg['Reply-To'] = xMailMessage.ReplyToAddress + + mailerstring = "LibreOffice via Caolan's mailmerge component" + try: + ctx = uno.getComponentContext() + aConfigProvider = ctx.ServiceManager.createInstance("com.sun.star.configuration.ConfigurationProvider") + prop = uno.createUnoStruct('com.sun.star.beans.PropertyValue') + prop.Name = "nodepath" + prop.Value = "/org.openoffice.Setup/Product" + aSettings = aConfigProvider.createInstanceWithArguments("com.sun.star.configuration.ConfigurationAccess", + (prop,)) + mailerstring = aSettings.getByName("ooName") + " " + \ + aSettings.getByName("ooSetupVersion") + " via Caolan's mailmerge component" + except: + pass + + msg['X-Mailer'] = mailerstring + msg['Date'] = formatdate(localtime=True) + + for attachment in attachments: + content = attachment.Data + flavors = content.getTransferDataFlavors() + flavor = flavors[0] + ctype = flavor.MimeType + maintype, subtype = ctype.split('/', 1) + msgattachment = MIMEBase(maintype, subtype) + data = content.getTransferData(flavor) + msgattachment.set_payload(data.value) + encode_base64(msgattachment) + fname = attachment.ReadableName + try: + msgattachment.add_header('Content-Disposition', 'attachment', \ + filename=fname) + except: + msgattachment.add_header('Content-Disposition', 'attachment', \ + filename=('utf-8','',fname)) + if dbg: + print(("PyMailSMTPService attachmentheader: ", str(msgattachment)), file=sys.stderr) + + msg.attach(msgattachment) + + uniquer = {} + for key in recipients: + uniquer[key] = True + if len(ccrecipients): + for key in ccrecipients: + uniquer[key] = True + if len(bccrecipients): + for key in bccrecipients: + uniquer[key] = True + truerecipients = uniquer.keys() + + if dbg: + print(("PyMailSMTPService recipients are: ", truerecipients), file=sys.stderr) + + self.server.sendmail(sendermail, truerecipients, msg.as_string()) + +class PyMailIMAPService(unohelper.Base, XMailService): + def __init__( self, ctx ): + self.ctx = ctx + self.listeners = [] + self.supportedtypes = ('Insecure', 'Ssl') + self.server = None + self.connectioncontext = None + self.notify = EventObject(self) + if dbg: + print("PyMailIMAPService init", file=sys.stderr) + def addConnectionListener(self, xListener): + if dbg: + print("PyMailIMAPService addConnectionListener", file=sys.stderr) + self.listeners.append(xListener) + def removeConnectionListener(self, xListener): + if dbg: + print("PyMailIMAPService removeConnectionListener", file=sys.stderr) + self.listeners.remove(xListener) + def getSupportedConnectionTypes(self): + if dbg: + print("PyMailIMAPService getSupportedConnectionTypes", file=sys.stderr) + return self.supportedtypes + def connect(self, xConnectionContext, xAuthenticator): + if dbg: + print("PyMailIMAPService connect", file=sys.stderr) + + self.connectioncontext = xConnectionContext + server = xConnectionContext.getValueByName("ServerName") + if dbg: + print(server, file=sys.stderr) + port = int(xConnectionContext.getValueByName("Port")) + if dbg: + print(port, file=sys.stderr) + connectiontype = xConnectionContext.getValueByName("ConnectionType") + if dbg: + print(connectiontype, file=sys.stderr) + print("BEFORE", file=sys.stderr) + if connectiontype.upper() == 'SSL': + self.server = imaplib.IMAP4_SSL(server, port) + else: + self.server = imaplib.IMAP4(server, port) + print("AFTER", file=sys.stderr) + + user = xAuthenticator.getUserName() + password = xAuthenticator.getPassword() + if user != '': + if dbg: + print("Logging in, username of: " + user, file=sys.stderr) + self.server.login(user, password) + + for listener in self.listeners: + listener.connected(self.notify) + def disconnect(self): + if dbg: + print("PyMailIMAPService disconnect", file=sys.stderr) + if self.server: + self.server.logout() + self.server = None + for listener in self.listeners: + listener.disconnected(self.notify) + def isConnected(self): + if dbg: + print("PyMailIMAPService isConnected", file=sys.stderr) + return self.server != None + def getCurrentConnectionContext(self): + if dbg: + print("PyMailIMAPService getCurrentConnectionContext", file=sys.stderr) + return self.connectioncontext + +class PyMailPOP3Service(unohelper.Base, XMailService): + def __init__( self, ctx ): + self.ctx = ctx + self.listeners = [] + self.supportedtypes = ('Insecure', 'Ssl') + self.server = None + self.connectioncontext = None + self.notify = EventObject(self) + if dbg: + print("PyMailPOP3Service init", file=sys.stderr) + def addConnectionListener(self, xListener): + if dbg: + print("PyMailPOP3Service addConnectionListener", file=sys.stderr) + self.listeners.append(xListener) + def removeConnectionListener(self, xListener): + if dbg: + print("PyMailPOP3Service removeConnectionListener", file=sys.stderr) + self.listeners.remove(xListener) + def getSupportedConnectionTypes(self): + if dbg: + print("PyMailPOP3Service getSupportedConnectionTypes", file=sys.stderr) + return self.supportedtypes + def connect(self, xConnectionContext, xAuthenticator): + if dbg: + print("PyMailPOP3Service connect", file=sys.stderr) + + self.connectioncontext = xConnectionContext + server = xConnectionContext.getValueByName("ServerName") + if dbg: + print(server, file=sys.stderr) + port = int(xConnectionContext.getValueByName("Port")) + if dbg: + print(port, file=sys.stderr) + connectiontype = xConnectionContext.getValueByName("ConnectionType") + if dbg: + print(connectiontype, file=sys.stderr) + print("BEFORE", file=sys.stderr) + if connectiontype.upper() == 'SSL': + self.server = poplib.POP3_SSL(server, port) + else: + tout = xConnectionContext.getValueByName("Timeout") + if dbg: + print(isinstance(tout,int), file=sys.stderr) + if not isinstance(tout,int): + tout = _GLOBAL_DEFAULT_TIMEOUT + if dbg: + print("Timeout: " + str(tout), file=sys.stderr) + self.server = poplib.POP3(server, port, timeout=tout) + print("AFTER", file=sys.stderr) + + user = xAuthenticator.getUserName() + password = xAuthenticator.getPassword() + if dbg: + print("Logging in, username of: " + user, file=sys.stderr) + self.server.user(user) + self.server.pass_(password) + + for listener in self.listeners: + listener.connected(self.notify) + def disconnect(self): + if dbg: + print("PyMailPOP3Service disconnect", file=sys.stderr) + if self.server: + self.server.quit() + self.server = None + for listener in self.listeners: + listener.disconnected(self.notify) + def isConnected(self): + if dbg: + print("PyMailPOP3Service isConnected", file=sys.stderr) + return self.server != None + def getCurrentConnectionContext(self): + if dbg: + print("PyMailPOP3Service getCurrentConnectionContext", file=sys.stderr) + return self.connectioncontext + +class PyMailServiceProvider(unohelper.Base, XMailServiceProvider, XServiceInfo): + def __init__( self, ctx ): + if dbg: + print("PyMailServiceProvider init", file=sys.stderr) + self.ctx = ctx + def create(self, aType): + if dbg: + print("PyMailServiceProvider create with", aType, file=sys.stderr) + if aType == SMTP: + return PyMailSMTPService(self.ctx); + elif aType == POP3: + return PyMailPOP3Service(self.ctx); + elif aType == IMAP: + return PyMailIMAPService(self.ctx); + else: + print("PyMailServiceProvider, unknown TYPE " + aType, file=sys.stderr) + + def getImplementationName(self): + return g_providerImplName + + def supportsService(self, ServiceName): + return g_ImplementationHelper.supportsService(g_providerImplName, ServiceName) + + def getSupportedServiceNames(self): + return g_ImplementationHelper.getSupportedServiceNames(g_providerImplName) + +class PyMailMessage(unohelper.Base, XMailMessage): + def __init__( self, ctx, sTo='', sFrom='', Subject='', Body=None, aMailAttachment=None ): + if dbg: + print("PyMailMessage init", file=sys.stderr) + self.ctx = ctx + + self.recipients = [sTo] + self.ccrecipients = [] + self.bccrecipients = [] + self.aMailAttachments = [] + if aMailAttachment != None: + self.aMailAttachments.append(aMailAttachment) + + self.SenderName, self.SenderAddress = parseaddr(sFrom) + self.ReplyToAddress = sFrom + self.Subject = Subject + self.Body = Body + if dbg: + print("post PyMailMessage init", file=sys.stderr) + def addRecipient( self, recipient ): + if dbg: + print("PyMailMessage.addRecipient: " + recipient, file=sys.stderr) + self.recipients.append(recipient) + def addCcRecipient( self, ccrecipient ): + if dbg: + print("PyMailMessage.addCcRecipient: " + ccrecipient, file=sys.stderr) + self.ccrecipients.append(ccrecipient) + def addBccRecipient( self, bccrecipient ): + if dbg: + print("PyMailMessage.addBccRecipient: " + bccrecipient, file=sys.stderr) + self.bccrecipients.append(bccrecipient) + def getRecipients( self ): + if dbg: + print("PyMailMessage.getRecipients: " + str(self.recipients), file=sys.stderr) + return tuple(self.recipients) + def getCcRecipients( self ): + if dbg: + print("PyMailMessage.getCcRecipients: " + str(self.ccrecipients), file=sys.stderr) + return tuple(self.ccrecipients) + def getBccRecipients( self ): + if dbg: + print("PyMailMessage.getBccRecipients: " + str(self.bccrecipients), file=sys.stderr) + return tuple(self.bccrecipients) + def addAttachment( self, aMailAttachment ): + if dbg: + print("PyMailMessage.addAttachment", file=sys.stderr) + self.aMailAttachments.append(aMailAttachment) + def getAttachments( self ): + if dbg: + print("PyMailMessage.getAttachments", file=sys.stderr) + return tuple(self.aMailAttachments) + + def getImplementationName(self): + return g_messageImplName + + def supportsService(self, ServiceName): + return g_ImplementationHelper.supportsService(g_messageImplName, ServiceName) + + def getSupportedServiceNames(self): + return g_ImplementationHelper.getSupportedServiceNames(g_messageImplName) + +g_ImplementationHelper.addImplementation( \ + PyMailServiceProvider, g_providerImplName, + ("com.sun.star.mail.MailServiceProvider",),) +g_ImplementationHelper.addImplementation( \ + PyMailMessage, g_messageImplName, + ("com.sun.star.mail.MailMessage",),) + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/scripting/source/pyprov/msgbox.py b/scripting/source/pyprov/msgbox.py new file mode 100644 index 000000000..f9c93df17 --- /dev/null +++ b/scripting/source/pyprov/msgbox.py @@ -0,0 +1,241 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-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/. +# + +# prepare Python environment - Add the path of this class +from os import path +from sys import modules +from sys import path as syspath + +# pyUNO program itself +import uno, unohelper + +# UNO GUI toolkit +from com.sun.star.awt.WindowClass import TOP, SIMPLE +from com.sun.star.awt.PushButtonType import STANDARD as standard +from com.sun.star.awt.PushButtonType import OK as ok +from com.sun.star.awt.PushButtonType import CANCEL as cancel +from com.sun.star.awt.PushButtonType import HELP as help +from com.sun.star.awt.TextAlign import CENTER as center +from com.sun.star.awt.TextAlign import LEFT as left +from com.sun.star.awt.TextAlign import RIGHT as right + +# used UNO listeners +from com.sun.star.awt import XActionListener + +class MsgBox(unohelper.Base): + """Inspect UNO object, link to sdk and recursive calls""" + + def __init__(self, aContext): + """acontext : a Valid UNO context + """ + + self.VERSION = '0.1' + self.ctx = aContext + self.smgr = aContext.ServiceManager + # UI Dialog object + self.dialog=None + # List of opened Listeners + self.lst_listeners={} + #UI parameters + self.ButtonSize = 50 + self.boxSize = 200 + self.lineHeight = 10 + self.fromBroxSize = False + self.numberOfLines = -1 + + self.Buttons = [] + self.Response = '' + + return + + ##################################################### + # GUI definition # + ##################################################### + def _createBox(self): + """Create the Box""" + + # computes parameters of the message dialog + if self.numberOfLines == -1: + #calculate + numberOfLines = len(self.message.split(chr(10))) + else: + numberOfLines = self.numberOfLines + + numberOfButtons = len(self.Buttons) + self.ButtonSpace = self.ButtonSize/2 + if self.fromBroxSize: + # button size is calculated from boxsize + size = (2 * self.boxSize) / (3 * numberOfButtons + 1) + self.ButtonSize = size + self.ButtonSpace = self.ButtonSize/2 + else: + # boxsize is calculated from buttonsize + self.boxSize = numberOfButtons * (self.ButtonSize + + self.ButtonSpace) + self.ButtonSpace + + # create the dialog model and set the properties + dialog_model = self.smgr.createInstanceWithContext( + 'com.sun.star.awt.UnoControlDialogModel', + self.ctx) + dialog_model.PositionX = 50 + dialog_model.Step = 1 + dialog_model.TabIndex = 7 + dialog_model.Width = self.boxSize#numberOfButtons * (self.ButtonSize + + # self.ButtonSpace) + 25 + dialog_model.Height = 10 + self.lineHeight * numberOfLines + 10 + 12 + 10 + dialog_model.PositionY = 63 + dialog_model.Sizeable = True + dialog_model.Closeable = False + + dialog = self.smgr.createInstanceWithContext( + 'com.sun.star.awt.UnoControlDialog', self.ctx) + + # label Label0 + label = dialog_model.createInstance( + 'com.sun.star.awt.UnoControlFixedTextModel') + label.PositionX = 10 + label.TabIndex = 9 + label.Width = dialog_model.Width - label.PositionX + label.Height = self.lineHeight* numberOfLines + label.PositionY = 10 + label.Align = left + label.MultiLine = True + label.Label = self.message + dialog_model.insertByName('Label0', label) + + nb = 0 + for buttonName in self.Buttons: + nb +=1 + button = dialog_model.createInstance( + 'com.sun.star.awt.UnoControlButtonModel') + button.PositionX = nb * self.ButtonSpace + (nb-1)* self.ButtonSize + button.TabIndex = 8 + button.Height = 12 + button.Width = self.ButtonSize + button.PositionY = 10 + label.Height + 10 + button.PushButtonType = standard + if nb == 1: + button.DefaultButton = True + else: + button.DefaultButton = False + button.Label = buttonName + dialog_model.insertByName('Btn' + str(nb), button ) + + if not dialog.getModel(): + dialog.setModel(dialog_model) + + # UNO toolkit definition + toolkit = self.smgr.createInstanceWithContext('com.sun.star.awt.Toolkit', self.ctx) + a_rect = uno.createUnoStruct( 'com.sun.star.awt.Rectangle' ) + a_rect.X = 50 + dialog.setTitle ( self.title ) + a_rect.Width = 270 + a_rect.Height = 261 + a_rect.Y = 63 + win_descriptor = uno.createUnoStruct('com.sun.star.awt.WindowDescriptor') + win_descriptor.Type = TOP + win_descriptor.ParentIndex = -1 + win_descriptor.Bounds = a_rect + peer = toolkit.createWindow( win_descriptor ) + dialog.createPeer( toolkit, peer ) + + return dialog + + def _addListeners(self): + """Add listeners to dialog""" + nb = 0 + for buttonName in self.Buttons: + nb +=1 + a_control = self.dialog.getControl('Btn'+str(nb)) + the_listener = ButtonListener(self) + a_control.addActionListener(the_listener) + self.lst_listeners['Btn'+str(nb)] = the_listener + return + + def _removeListeners(self): + """ remove listeners on exiting""" + nb = 0 + for buttonName in self.Buttons: + nb +=1 + a_control = self.dialog.getControl('Btn'+str(nb)) + a_control.removeActionListener(self.lst_listeners['Btn'+str(nb)]) + return + + def show(self, message, decoration, title): + self.message = message + self.decoration = decoration + self.title = title + # Create GUI + self.dialog = self._createBox() + self._addListeners() + #execute the dialog --> blocking call + self.dialog.execute() + #end --> release listeners and dispose dialog + self._removeListeners() + self.dialog.dispose() + return self.Response + + def addButton(self, caption): + self.Buttons.append(caption) + return + + def renderFromBoxSize(self, size = 150): + self.boxSize = size + self.fromBroxSize = True + return + + def renderFromButtonSize(self, size = 50): + self.ButtonSize = size + self.fromBroxSize = False + return + +class ButtonListener(unohelper.Base, XActionListener): + """Stops the MessageBox, sets the button label as returned value""" + def __init__(self, caller): + self.caller = caller + + def disposing(self, eventObject): + pass + + def actionPerformed(self, actionEvent): + button = actionEvent.Source + self.caller.Response = button.Model.Label + self.caller.dialog.endExecute() + return + +### TEST +if __name__ == '__main__': + # get the uno component context from the PyUNO runtime + localContext = uno.getComponentContext() + + # create the UnoUrlResolver + resolver = localContext.ServiceManager.createInstanceWithContext( + "com.sun.star.bridge.UnoUrlResolver", localContext ) + + # connect to the running office + # LibO has to be launched in listen mode as + # ./soffice "--accept=socket,host=localhost,port=2002;urp;" + ctx = resolver.resolve( "uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext" ) + myBox = MsgBox(ctx) + myBox.addButton("Yes") + myBox.addButton("No") + myBox.addButton("May be") + myBox.renderFromBoxSize(150) + myBox.numberOflines = 2 + + print(myBox.show("A very long message A very long message A very long message A very long message A very long message A very long message A very long message A very long message A very long message A very long message " + chr(10)+chr(10)+"Do you agree ?",0,"Dialog title")) + + myBox = MsgBox(ctx) + myBox.addButton("oK") + myBox.renderFromButtonSize() + myBox.numberOflines = 2 + + print(myBox.show("A small message",0,"Dialog title")) + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/scripting/source/pyprov/pythonscript.py b/scripting/source/pyprov/pythonscript.py new file mode 100644 index 000000000..4955c8c54 --- /dev/null +++ b/scripting/source/pyprov/pythonscript.py @@ -0,0 +1,1145 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-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 . +# +# XScript implementation for python +import uno +import unohelper +import sys +import os +import types +import time +import ast +import platform +from com.sun.star.uri.RelativeUriExcessParentSegments import RETAIN + +class LogLevel: + NONE = 0 # production level + ERROR = 1 # for script developers + DEBUG = 2 # for script framework developers + +PYSCRIPT_LOG_ENV = "PYSCRIPT_LOG_LEVEL" +PYSCRIPT_LOG_STDOUT_ENV = "PYSCRIPT_LOG_STDOUT" + +# Configuration ---------------------------------------------------- +LogLevel.use = LogLevel.NONE +if os.environ.get(PYSCRIPT_LOG_ENV) == "ERROR": + LogLevel.use = LogLevel.ERROR +elif os.environ.get(PYSCRIPT_LOG_ENV) == "DEBUG": + LogLevel.use = LogLevel.DEBUG + +# True, writes to stdout (difficult on windows) +# False, writes to user/Scripts/python/log.txt +LOG_STDOUT = os.environ.get(PYSCRIPT_LOG_STDOUT_ENV, "1") != "0" + +ENABLE_EDIT_DIALOG=False # offers a minimal editor for editing. +#------------------------------------------------------------------- + +def encfile(uni): + return uni.encode( sys.getfilesystemencoding()) + +def lastException2String(): + (excType,excInstance,excTraceback) = sys.exc_info() + ret = str(excType) + ": "+str(excInstance) + "\n" + \ + uno._uno_extract_printable_stacktrace( excTraceback ) + return ret + +def logLevel2String( level ): + ret = " NONE" + if level == LogLevel.ERROR: + ret = "ERROR" + elif level >= LogLevel.DEBUG: + ret = "DEBUG" + return ret + +def getLogTarget(): + ret = sys.stdout + if not LOG_STDOUT: + try: + pathSubst = uno.getComponentContext().ServiceManager.createInstance( + "com.sun.star.util.PathSubstitution" ) + userInstallation = pathSubst.getSubstituteVariableValue( "user" ) + if len( userInstallation ) > 0: + systemPath = uno.fileUrlToSystemPath( userInstallation + "/Scripts/python/log.txt" ) + ret = open( systemPath , "a" ) + except: + print("Exception during creation of pythonscript logfile: "+ lastException2String() + "\n, delegating log to stdout\n") + return ret + +class Logger(LogLevel): + def __init__(self , target ): + self.target = target + + def isDebugLevel( self ): + return self.use >= self.DEBUG + + def debug( self, msg ): + if self.isDebugLevel(): + self.log( self.DEBUG, msg ) + + def isErrorLevel( self ): + return self.use >= self.ERROR + + def error( self, msg ): + if self.isErrorLevel(): + self.log( self.ERROR, msg ) + + def log( self, level, msg ): + if self.use >= level: + try: + self.target.write( + time.asctime() + + " [" + + logLevel2String( level ) + + "] " + + msg + + "\n" ) + self.target.flush() + except: + print("Error during writing to stdout: " +lastException2String() + "\n") + +log = Logger( getLogTarget() ) + +log.debug( "pythonscript loading" ) + +#from com.sun.star.lang import typeOfXServiceInfo, typeOfXTypeProvider +from com.sun.star.uno import RuntimeException +from com.sun.star.lang import IllegalArgumentException +from com.sun.star.container import NoSuchElementException +from com.sun.star.lang import XServiceInfo +from com.sun.star.io import IOException +from com.sun.star.ucb import CommandAbortedException, XCommandEnvironment, XProgressHandler, Command +from com.sun.star.task import XInteractionHandler +from com.sun.star.beans import XPropertySet, Property +from com.sun.star.container import XNameContainer +from com.sun.star.xml.sax import XDocumentHandler, InputSource +from com.sun.star.uno import Exception as UnoException +from com.sun.star.script import XInvocation +from com.sun.star.awt import XActionListener + +from com.sun.star.script.provider import XScriptProvider, XScript, XScriptContext, ScriptFrameworkErrorException +from com.sun.star.script.browse import XBrowseNode +from com.sun.star.script.browse.BrowseNodeTypes import SCRIPT, CONTAINER, ROOT +from com.sun.star.util import XModifyListener + +LANGUAGENAME = "Python" +GLOBAL_SCRIPTCONTEXT_NAME = "XSCRIPTCONTEXT" +CALLABLE_CONTAINER_NAME = "g_exportedScripts" + +# pythonloader looks for a static g_ImplementationHelper variable +g_ImplementationHelper = unohelper.ImplementationHelper() +g_implName = "org.libreoffice.pyuno.LanguageScriptProviderFor"+LANGUAGENAME + + + +BLOCK_SIZE = 65536 +def readTextFromStream( inputStream ): + # read the file + code = uno.ByteSequence( b"" ) + while True: + read,out = inputStream.readBytes( None , BLOCK_SIZE ) + code = code + out + if read < BLOCK_SIZE: + break + return code.value + +def toIniName( str ): + if platform.system() == "Windows": + return str + ".ini" + else: + return str + "rc" + + +""" definition: storageURI is the system dependent, absolute file url, where the script is stored on disk + scriptURI is the system independent uri +""" +class MyUriHelper: + + def __init__( self, ctx, location ): + self.ctx = ctx + self.s_UriMap = \ + { "share" : "vnd.sun.star.expand:$BRAND_BASE_DIR/$BRAND_SHARE_SUBDIR/Scripts/python" , \ + "share:uno_packages" : "vnd.sun.star.expand:$UNO_SHARED_PACKAGES_CACHE/uno_packages", \ + "user" : "vnd.sun.star.expand:${$BRAND_INI_DIR/" + toIniName( "bootstrap") + "::UserInstallation}/user/Scripts/python" , \ + "user:uno_packages" : "vnd.sun.star.expand:$UNO_USER_PACKAGES_CACHE/uno_packages" } + self.m_uriRefFac = ctx.ServiceManager.createInstanceWithContext("com.sun.star.uri.UriReferenceFactory",ctx) + if location.startswith( "vnd.sun.star.tdoc" ): + self.m_baseUri = location + "/Scripts/python" + self.m_scriptUriLocation = "document" + else: + self.m_baseUri = expandUri( self.s_UriMap[location] ) + self.m_scriptUriLocation = location + log.debug( "initialized urihelper with baseUri="+self.m_baseUri + ",m_scriptUriLocation="+self.m_scriptUriLocation ) + + def getRootStorageURI( self ): + return self.m_baseUri + + def getStorageURI( self, scriptURI ): + return self.scriptURI2StorageUri(scriptURI) + + def getScriptURI( self, storageURI ): + return self.storageURI2ScriptUri(storageURI) + + def storageURI2ScriptUri( self, storageURI ): + if not storageURI.startswith( self.m_baseUri ): + message = "pythonscript: storage uri '" + storageURI + "' not in base uri '" + self.m_baseUri + "'" + log.debug( message ) + raise RuntimeException( message, self.ctx ) + + ret = "vnd.sun.star.script:" + \ + storageURI[len(self.m_baseUri)+1:].replace("/","|") + \ + "?language=" + LANGUAGENAME + "&location=" + self.m_scriptUriLocation + log.debug( "converting storageURI="+storageURI + " to scriptURI=" + ret ) + return ret + + def scriptURI2StorageUri( self, scriptURI ): + try: + # base path to the python script location + sBaseUri = self.m_baseUri + "/" + xBaseUri = self.m_uriRefFac.parse(sBaseUri) + + # path to the .py file + "$functionname, arguments, etc + xStorageUri = self.m_uriRefFac.parse(scriptURI) + # getName will apply url-decoding to the name, so encode back + sStorageUri = xStorageUri.getName().replace("%", "%25") + sStorageUri = sStorageUri.replace( "|", "/" ) + + # path to the .py file, relative to the base + funcNameStart = sStorageUri.find("$") + if funcNameStart != -1: + sFileUri = sStorageUri[0:funcNameStart] + sFuncName = sStorageUri[funcNameStart+1:] + else: + sFileUri = sStorageUri + + xFileUri = self.m_uriRefFac.parse(sFileUri) + if not xFileUri: + message = "pythonscript: invalid relative uri '" + sFileUri+ "'" + log.debug( message ) + raise RuntimeException( message, self.ctx ) + + if not xFileUri.hasRelativePath(): + message = "pythonscript: an absolute uri is invalid '" + sFileUri+ "'" + log.debug( message ) + raise RuntimeException( message, self.ctx ) + + # absolute path to the .py file + xAbsScriptUri = self.m_uriRefFac.makeAbsolute(xBaseUri, xFileUri, True, RETAIN) + sAbsScriptUri = xAbsScriptUri.getUriReference() + + # ensure py file is under the base path + if not sAbsScriptUri.startswith(sBaseUri): + message = "pythonscript: storage uri '" + sAbsScriptUri + "' not in base uri '" + self.m_baseUri + "'" + log.debug( message ) + raise RuntimeException( message, self.ctx ) + + ret = sAbsScriptUri + if funcNameStart != -1: + ret = ret + "$" + sFuncName + log.debug( "converting scriptURI="+scriptURI + " to storageURI=" + ret ) + return ret + except UnoException as e: + log.error( "error during converting scriptURI="+scriptURI + ": " + e.Message) + raise RuntimeException( "pythonscript:scriptURI2StorageUri: " + e.Message, self.ctx ) + except Exception as e: + log.error( "error during converting scriptURI="+scriptURI + ": " + str(e)) + raise RuntimeException( "pythonscript:scriptURI2StorageUri: " + str(e), self.ctx ) + + +class ModuleEntry: + def __init__( self, lastRead, module ): + self.lastRead = lastRead + self.module = module + +def hasChanged( oldDate, newDate ): + return newDate.Year > oldDate.Year or \ + newDate.Month > oldDate.Month or \ + newDate.Day > oldDate.Day or \ + newDate.Hours > oldDate.Hours or \ + newDate.Minutes > oldDate.Minutes or \ + newDate.Seconds > oldDate.Seconds or \ + newDate.NanoSeconds > oldDate.NanoSeconds + +def ensureSourceState( code ): + if code.endswith(b"\n"): + code = code + b"\n" + code = code.replace(b"\r", b"") + return code + + +def checkForPythonPathBesideScript( url ): + if url.startswith( "file:" ): + path = unohelper.fileUrlToSystemPath( url+"/pythonpath.zip" ); + log.log( LogLevel.DEBUG, "checking for existence of " + path ) + if 1 == os.access( encfile(path), os.F_OK) and not path in sys.path: + log.log( LogLevel.DEBUG, "adding " + path + " to sys.path" ) + sys.path.append( path ) + + path = unohelper.fileUrlToSystemPath( url+"/pythonpath" ); + log.log( LogLevel.DEBUG, "checking for existence of " + path ) + if 1 == os.access( encfile(path), os.F_OK) and not path in sys.path: + log.log( LogLevel.DEBUG, "adding " + path + " to sys.path" ) + sys.path.append( path ) + + +class ScriptContext(unohelper.Base): + def __init__( self, ctx, doc, inv ): + self.ctx = ctx + self.doc = doc + self.inv = inv + + # XScriptContext + def getDocument(self): + if self.doc: + return self.doc + return self.getDesktop().getCurrentComponent() + + def getDesktop(self): + return self.ctx.ServiceManager.createInstanceWithContext( + "com.sun.star.frame.Desktop", self.ctx ) + + def getComponentContext(self): + return self.ctx + + def getInvocationContext(self): + return self.inv + +#---------------------------------- +# Global Module Administration +# does not fit together with script +# engine lifetime management +#---------------------------------- +#g_scriptContext = ScriptContext( uno.getComponentContext(), None ) +#g_modules = {} +#def getModuleByUrl( url, sfa ): +# entry = g_modules.get(url) +# load = True +# lastRead = sfa.getDateTimeModified( url ) +# if entry: +# if hasChanged( entry.lastRead, lastRead ): +# log.debug("file " + url + " has changed, reloading") +# else: +# load = False +# +# if load: +# log.debug( "opening >" + url + "<" ) +# +# code = readTextFromStream( sfa.openFileRead( url ) ) + + # execute the module +# entry = ModuleEntry( lastRead, types.ModuleType("ooo_script_framework") ) +# entry.module.__dict__[GLOBAL_SCRIPTCONTEXT_NAME] = g_scriptContext +# entry.module.__file__ = url +# exec code in entry.module.__dict__ +# g_modules[ url ] = entry +# log.debug( "mapped " + url + " to " + str( entry.module ) ) +# return entry.module + +class ProviderContext: + def __init__( self, storageType, sfa, uriHelper, scriptContext ): + self.storageType = storageType + self.sfa = sfa + self.uriHelper = uriHelper + self.scriptContext = scriptContext + self.modules = {} + self.rootUrl = None + self.mapPackageName2Path = None + + def getTransientPartFromUrl( self, url ): + rest = url.replace( self.rootUrl , "",1 ).replace( "/","",1) + return rest[0:rest.find("/")] + + def getPackageNameFromUrl( self, url ): + rest = url.replace( self.rootUrl , "",1 ).replace( "/","",1) + start = rest.find("/") +1 + return rest[start:rest.find("/",start)] + + + def removePackageByUrl( self, url ): + items = self.mapPackageName2Path.items() + for i in items: + if url in i[1].paths: + self.mapPackageName2Path.pop(i[0]) + break + + def addPackageByUrl( self, url ): + packageName = self.getPackageNameFromUrl( url ) + transientPart = self.getTransientPartFromUrl( url ) + log.debug( "addPackageByUrl : " + packageName + ", " + transientPart + "("+url+")" + ", rootUrl="+self.rootUrl ) + if packageName in self.mapPackageName2Path: + package = self.mapPackageName2Path[ packageName ] + package.paths = package.paths + (url, ) + else: + package = Package( (url,), transientPart) + self.mapPackageName2Path[ packageName ] = package + + def isUrlInPackage( self, url ): + values = self.mapPackageName2Path.values() + for i in values: +# print ("checking " + url + " in " + str(i.paths)) + if url in i.paths: + return True +# print ("false") + return False + + def setPackageAttributes( self, mapPackageName2Path, rootUrl ): + self.mapPackageName2Path = mapPackageName2Path + self.rootUrl = rootUrl + + def getPersistentUrlFromStorageUrl( self, url ): + # package name is the second directory + ret = url + if self.rootUrl: + pos = len( self.rootUrl) +1 + ret = url[0:pos]+url[url.find("/",pos)+1:len(url)] + log.debug( "getPersistentUrlFromStorageUrl " + url + " -> "+ ret) + return ret + + def getStorageUrlFromPersistentUrl( self, url): + ret = url + if self.rootUrl: + pos = len(self.rootUrl)+1 + packageName = url[pos:url.find("/",pos+1)] + package = self.mapPackageName2Path[ packageName ] + ret = url[0:pos]+ package.transientPathElement + "/" + url[pos:len(url)] + log.debug( "getStorageUrlFromPersistentUrl " + url + " -> "+ ret) + return ret + + def getFuncsByUrl( self, url ): + src = readTextFromStream( self.sfa.openFileRead( url ) ) + checkForPythonPathBesideScript( url[0:url.rfind('/')] ) + src = ensureSourceState( src ) + + try: + code = ast.parse( src ) + except: + log.isDebugLevel() and log.debug( "pythonscript: getFuncsByUrl: exception while parsing: " + lastException2String()) + raise + + allFuncs = [] + + if code is None: + return allFuncs + + g_exportedScripts = [] + for node in ast.iter_child_nodes(code): + if isinstance(node, ast.FunctionDef): + allFuncs.append(node.name) + elif isinstance(node, ast.Assign): + for target in node.targets: + try: + identifier = target.id + except AttributeError: + identifier = "" + pass + if identifier == "g_exportedScripts": + for value in node.value.elts: + g_exportedScripts.append(value.id) + return g_exportedScripts + +# Python 2 only +# for node in code.node.nodes: +# if node.__class__.__name__ == 'Function': +# allFuncs.append(node.name) +# elif node.__class__.__name__ == 'Assign': +# for assignee in node.nodes: +# if assignee.name == 'g_exportedScripts': +# for item in node.expr.nodes: +# if item.__class__.__name__ == 'Name': +# g_exportedScripts.append(item.name) +# return g_exportedScripts + + return allFuncs + + def getModuleByUrl( self, url ): + entry = self.modules.get(url) + load = True + lastRead = self.sfa.getDateTimeModified( url ) + if entry: + if hasChanged( entry.lastRead, lastRead ): + log.debug( "file " + url + " has changed, reloading" ) + else: + load = False + + if load: + log.debug( "opening >" + url + "<" ) + + src = readTextFromStream( self.sfa.openFileRead( url ) ) + checkForPythonPathBesideScript( url[0:url.rfind('/')] ) + src = ensureSourceState( src ) + + # execute the module + entry = ModuleEntry( lastRead, types.ModuleType("ooo_script_framework") ) + entry.module.__dict__[GLOBAL_SCRIPTCONTEXT_NAME] = self.scriptContext + + code = None + if url.startswith( "file:" ): + code = compile( src, encfile(uno.fileUrlToSystemPath( url ) ), "exec" ) + else: + code = compile( src, url, "exec" ) + exec(code, entry.module.__dict__) + entry.module.__file__ = url + self.modules[ url ] = entry + log.debug( "mapped " + url + " to " + str( entry.module ) ) + return entry.module + +#-------------------------------------------------- +def isScript( candidate ): + ret = False + if isinstance( candidate, type(isScript) ): + ret = True + return ret + +#------------------------------------------------------- +class ScriptBrowseNode( unohelper.Base, XBrowseNode , XPropertySet, XInvocation, XActionListener ): + def __init__( self, provCtx, uri, fileName, funcName ): + self.fileName = fileName + self.funcName = funcName + self.provCtx = provCtx + self.uri = uri + + def getName( self ): + return self.funcName + + def getChildNodes(self): + return () + + def hasChildNodes(self): + return False + + def getType( self): + return SCRIPT + + def getPropertyValue( self, name ): + ret = None + try: + if name == "URI": + ret = self.provCtx.uriHelper.getScriptURI( + self.provCtx.getPersistentUrlFromStorageUrl( self.uri + "$" + self.funcName ) ) + elif name == "Editable" and ENABLE_EDIT_DIALOG: + ret = not self.provCtx.sfa.isReadOnly( self.uri ) + + log.debug( "ScriptBrowseNode.getPropertyValue called for " + name + ", returning " + str(ret) ) + except: + log.error( "ScriptBrowseNode.getPropertyValue error " + lastException2String()) + raise + + return ret + def setPropertyValue( self, name, value ): + log.debug( "ScriptBrowseNode.setPropertyValue called " + name + "=" +str(value ) ) + def getPropertySetInfo( self ): + log.debug( "ScriptBrowseNode.getPropertySetInfo called " ) + return None + + def getIntrospection( self ): + return None + + def invoke( self, name, params, outparamindex, outparams ): + if name == "Editable": + servicename = "com.sun.star.awt.DialogProvider" + ctx = self.provCtx.scriptContext.getComponentContext() + dlgprov = ctx.ServiceManager.createInstanceWithContext( + servicename, ctx ) + + self.editor = dlgprov.createDialog( + "vnd.sun.star.script:" + + "ScriptBindingLibrary.MacroEditor?location=application") + + code = readTextFromStream(self.provCtx.sfa.openFileRead(self.uri)) + code = ensureSourceState( code ) + self.editor.getControl("EditorTextField").setText(code) + + self.editor.getControl("RunButton").setActionCommand("Run") + self.editor.getControl("RunButton").addActionListener(self) + self.editor.getControl("SaveButton").setActionCommand("Save") + self.editor.getControl("SaveButton").addActionListener(self) + + self.editor.execute() + + return None + + def actionPerformed( self, event ): + try: + if event.ActionCommand == "Run": + code = self.editor.getControl("EditorTextField").getText() + code = ensureSourceState( code ) + mod = types.ModuleType("ooo_script_framework") + mod.__dict__[GLOBAL_SCRIPTCONTEXT_NAME] = self.provCtx.scriptContext + exec(code, mod.__dict__) + values = mod.__dict__.get( CALLABLE_CONTAINER_NAME , None ) + if not values: + values = mod.__dict__.values() + + for i in values: + if isScript( i ): + i() + break + + elif event.ActionCommand == "Save": + toWrite = uno.ByteSequence( + self.editor.getControl("EditorTextField").getText().encode( + sys.getdefaultencoding()) ) + copyUrl = self.uri + ".orig" + self.provCtx.sfa.move( self.uri, copyUrl ) + out = self.provCtx.sfa.openFileWrite( self.uri ) + out.writeBytes( toWrite ) + out.close() + self.provCtx.sfa.kill( copyUrl ) +# log.debug("Save is not implemented yet") +# text = self.editor.getControl("EditorTextField").getText() +# log.debug("Would save: " + text) + except: + # TODO: add an error box here! + log.error( lastException2String() ) + + + def setValue( self, name, value ): + return None + + def getValue( self, name ): + return None + + def hasMethod( self, name ): + return False + + def hasProperty( self, name ): + return False + + +#------------------------------------------------------- +class FileBrowseNode( unohelper.Base, XBrowseNode ): + def __init__( self, provCtx, uri , name ): + self.provCtx = provCtx + self.uri = uri + self.name = name + self.funcnames = None + + def getName( self ): + return self.name + + def getChildNodes(self): + ret = () + try: + self.funcnames = self.provCtx.getFuncsByUrl( self.uri ) + + scriptNodeList = [] + for i in self.funcnames: + scriptNodeList.append( + ScriptBrowseNode( + self.provCtx, self.uri, self.name, i )) + ret = tuple( scriptNodeList ) + log.debug( "returning " +str(len(ret)) + " ScriptChildNodes on " + self.uri ) + except: + text = lastException2String() + log.error( "Error while evaluating " + self.uri + ":" + text ) + raise + return ret + + def hasChildNodes(self): + try: + return len(self.getChildNodes()) > 0 + except: + return False + + def getType( self): + return CONTAINER + + + +class DirBrowseNode( unohelper.Base, XBrowseNode ): + def __init__( self, provCtx, name, rootUrl ): + self.provCtx = provCtx + self.name = name + self.rootUrl = rootUrl + + def getName( self ): + return self.name + + def getChildNodes( self ): + try: + log.debug( "DirBrowseNode.getChildNodes called for " + self.rootUrl ) + contents = self.provCtx.sfa.getFolderContents( self.rootUrl, True ) + browseNodeList = [] + for i in contents: + if i.endswith( ".py" ): + log.debug( "adding filenode " + i ) + browseNodeList.append( + FileBrowseNode( self.provCtx, i, i[i.rfind("/")+1:len(i)-3] ) ) + elif self.provCtx.sfa.isFolder( i ) and not i.endswith("/pythonpath"): + log.debug( "adding DirBrowseNode " + i ) + browseNodeList.append( DirBrowseNode( self.provCtx, i[i.rfind("/")+1:len(i)],i)) + return tuple( browseNodeList ) + except Exception as e: + text = lastException2String() + log.error( "DirBrowseNode error: " + str(e) + " while evaluating " + self.rootUrl) + log.error( text) + return () + + def hasChildNodes( self ): + return True + + def getType( self ): + return CONTAINER + + def getScript( self, uri ): + log.debug( "DirBrowseNode getScript " + uri + " invoked" ) + raise IllegalArgumentException( "DirBrowseNode couldn't instantiate script " + uri , self , 0 ) + + +class ManifestHandler( XDocumentHandler, unohelper.Base ): + def __init__( self, rootUrl ): + self.rootUrl = rootUrl + + def startDocument( self ): + self.urlList = [] + + def endDocument( self ): + pass + + def startElement( self , name, attlist): + if name == "manifest:file-entry": + if attlist.getValueByName( "manifest:media-type" ) == "application/vnd.sun.star.framework-script": + self.urlList.append( + self.rootUrl + "/" + attlist.getValueByName( "manifest:full-path" ) ) + + def endElement( self, name ): + pass + + def characters ( self, chars ): + pass + + def ignoreableWhitespace( self, chars ): + pass + + def setDocumentLocator( self, locator ): + pass + +def isPyFileInPath( sfa, path ): + ret = False + contents = sfa.getFolderContents( path, True ) + for i in contents: + if sfa.isFolder(i): + ret = isPyFileInPath(sfa,i) + else: + if i.endswith(".py"): + ret = True + if ret: + break + return ret + +# extracts META-INF directory from +def getPathsFromPackage( rootUrl, sfa ): + ret = () + try: + fileUrl = rootUrl + "/META-INF/manifest.xml" + inputStream = sfa.openFileRead( fileUrl ) + parser = uno.getComponentContext().ServiceManager.createInstance( "com.sun.star.xml.sax.Parser" ) + handler = ManifestHandler( rootUrl ) + parser.setDocumentHandler( handler ) + parser.parseStream( InputSource( inputStream , "", fileUrl, fileUrl ) ) + for i in tuple(handler.urlList): + if not isPyFileInPath( sfa, i ): + handler.urlList.remove(i) + ret = tuple( handler.urlList ) + except UnoException: + text = lastException2String() + log.debug( "getPathsFromPackage " + fileUrl + " Exception: " +text ) + pass + return ret + + +class Package: + def __init__( self, paths, transientPathElement ): + self.paths = paths + self.transientPathElement = transientPathElement + +class DummyInteractionHandler( unohelper.Base, XInteractionHandler ): + def __init__( self ): + pass + def handle( self, event): + log.debug( "pythonscript: DummyInteractionHandler.handle " + str( event ) ) + +class DummyProgressHandler( unohelper.Base, XProgressHandler ): + def __init__( self ): + pass + + def push( self,status ): + log.debug( "pythonscript: DummyProgressHandler.push " + str( status ) ) + def update( self,status ): + log.debug( "pythonscript: DummyProgressHandler.update " + str( status ) ) + def pop( self, event ): + log.debug( "pythonscript: DummyProgressHandler.push " + str( event ) ) + +class CommandEnvironment(unohelper.Base, XCommandEnvironment): + def __init__( self ): + self.progressHandler = DummyProgressHandler() + self.interactionHandler = DummyInteractionHandler() + def getInteractionHandler( self ): + return self.interactionHandler + def getProgressHandler( self ): + return self.progressHandler + +#maybe useful for debugging purposes +#class ModifyListener( unohelper.Base, XModifyListener ): +# def __init__( self ): +# pass +# def modified( self, event ): +# log.debug( "pythonscript: ModifyListener.modified " + str( event ) ) +# def disposing( self, event ): +# log.debug( "pythonscript: ModifyListener.disposing " + str( event ) ) + +def getModelFromDocUrl(ctx, url): + """Get document model from document url.""" + doc = None + args = ("Local", "Office") + ucb = ctx.getServiceManager().createInstanceWithArgumentsAndContext( + "com.sun.star.ucb.UniversalContentBroker", args, ctx) + identifier = ucb.createContentIdentifier(url) + content = ucb.queryContent(identifier) + p = Property() + p.Name = "DocumentModel" + p.Handle = -1 + + c = Command() + c.Handle = -1 + c.Name = "getPropertyValues" + c.Argument = uno.Any("[]com.sun.star.beans.Property", (p,)) + + env = CommandEnvironment() + try: + ret = content.execute(c, 0, env) + doc = ret.getObject(1, None) + except Exception as e: + log.isErrorLevel() and log.error("getModelFromDocUrl: %s" % url) + return doc + +def mapStorageType2PackageContext( storageType ): + ret = storageType + if( storageType == "share:uno_packages" ): + ret = "shared" + if( storageType == "user:uno_packages" ): + ret = "user" + return ret + +def getPackageName2PathMap( sfa, storageType ): + ret = {} + packageManagerFactory = uno.getComponentContext().getValueByName( + "/singletons/com.sun.star.deployment.thePackageManagerFactory" ) + packageManager = packageManagerFactory.getPackageManager( + mapStorageType2PackageContext(storageType)) +# packageManager.addModifyListener( ModifyListener() ) + log.debug( "pythonscript: getPackageName2PathMap start getDeployedPackages" ) + packages = packageManager.getDeployedPackages( + packageManager.createAbortChannel(), CommandEnvironment( ) ) + log.debug( "pythonscript: getPackageName2PathMap end getDeployedPackages (" + str(len(packages))+")" ) + + for i in packages: + log.debug( "inspecting package " + i.Name + "("+i.Identifier.Value+")" ) + transientPathElement = penultimateElement( i.URL ) + j = expandUri( i.URL ) + paths = getPathsFromPackage( j, sfa ) + if len( paths ) > 0: + # map package name to url, we need this later + log.isErrorLevel() and log.error( "adding Package " + transientPathElement + " " + str( paths ) ) + ret[ lastElement( j ) ] = Package( paths, transientPathElement ) + return ret + +def penultimateElement( aStr ): + lastSlash = aStr.rindex("/") + penultimateSlash = aStr.rindex("/",0,lastSlash-1) + return aStr[ penultimateSlash+1:lastSlash ] + +def lastElement( aStr): + return aStr[ aStr.rfind( "/" )+1:len(aStr)] + +class PackageBrowseNode( unohelper.Base, XBrowseNode ): + def __init__( self, provCtx, name, rootUrl ): + self.provCtx = provCtx + self.name = name + self.rootUrl = rootUrl + + def getName( self ): + return self.name + + def getChildNodes( self ): + items = self.provCtx.mapPackageName2Path.items() + browseNodeList = [] + for i in items: + if len( i[1].paths ) == 1: + browseNodeList.append( + DirBrowseNode( self.provCtx, i[0], i[1].paths[0] )) + else: + for j in i[1].paths: + browseNodeList.append( + DirBrowseNode( self.provCtx, i[0]+"."+lastElement(j), j ) ) + return tuple( browseNodeList ) + + def hasChildNodes( self ): + return len( self.provCtx.mapPackageName2Path ) > 0 + + def getType( self ): + return CONTAINER + + def getScript( self, uri ): + log.debug( "PackageBrowseNode getScript " + uri + " invoked" ) + raise IllegalArgumentException( "PackageBrowseNode couldn't instantiate script " + uri , self , 0 ) + + + + +class PythonScript( unohelper.Base, XScript ): + def __init__( self, func, mod, args ): + self.func = func + self.mod = mod + self.args = args + + def invoke(self, args, out, outindex ): + log.debug( "PythonScript.invoke " + str( args ) ) + try: + if (self.args): + args += self.args + ret = self.func( *args ) + except UnoException as e: + # UNO Exception continue to fly ... + text = lastException2String() + complete = "Error during invoking function " + \ + str(self.func.__name__) + " in module " + \ + self.mod.__file__ + " (" + text + ")" + log.debug( complete ) + # some people may beat me up for modifying the exception text, + # but otherwise office just shows + # the type name and message text with no more information, + # this is really bad for most users. + e.Message = e.Message + " (" + complete + ")" + raise + except Exception as e: + # General python exception are converted to uno RuntimeException + text = lastException2String() + complete = "Error during invoking function " + \ + str(self.func.__name__) + " in module " + \ + self.mod.__file__ + " (" + text + ")" + log.debug( complete ) + raise RuntimeException( complete , self ) + log.debug( "PythonScript.invoke ret = " + str( ret ) ) + return ret, (), () + +def expandUri( uri ): + if uri.startswith( "vnd.sun.star.expand:" ): + uri = uri.replace( "vnd.sun.star.expand:", "",1) + uri = uno.getComponentContext().getByName( + "/singletons/com.sun.star.util.theMacroExpander" ).expandMacros( uri ) + if uri.startswith( "file:" ): + uri = uno.absolutize("",uri) # necessary to get rid of .. in uri + return uri + +#-------------------------------------------------------------- +class PythonScriptProvider( unohelper.Base, XBrowseNode, XScriptProvider, XNameContainer): + def __init__( self, ctx, *args ): + if log.isDebugLevel(): + mystr = "" + for i in args: + if len(mystr) > 0: + mystr = mystr +"," + mystr = mystr + str(i) + log.debug( "Entering PythonScriptProvider.ctor" + mystr ) + + doc = None + inv = None + storageType = "" + + if isinstance(args[0], str): + storageType = args[0] + if storageType.startswith( "vnd.sun.star.tdoc" ): + doc = getModelFromDocUrl(ctx, storageType) + else: + inv = args[0] + try: + doc = inv.ScriptContainer + content = ctx.getServiceManager().createInstanceWithContext( + "com.sun.star.frame.TransientDocumentsDocumentContentFactory", + ctx).createDocumentContent(doc) + storageType = content.getIdentifier().getContentIdentifier() + except Exception as e: + text = lastException2String() + log.error( text ) + + isPackage = storageType.endswith( ":uno_packages" ) + + try: +# urlHelper = ctx.ServiceManager.createInstanceWithArgumentsAndContext( +# "com.sun.star.script.provider.ScriptURIHelper", (LANGUAGENAME, storageType), ctx) + urlHelper = MyUriHelper( ctx, storageType ) + log.debug( "got urlHelper " + str( urlHelper ) ) + + rootUrl = expandUri( urlHelper.getRootStorageURI() ) + log.debug( storageType + " transformed to " + rootUrl ) + + ucbService = "com.sun.star.ucb.SimpleFileAccess" + sfa = ctx.ServiceManager.createInstanceWithContext( ucbService, ctx ) + if not sfa: + log.debug("PythonScriptProvider couldn't instantiate " +ucbService) + raise RuntimeException( + "PythonScriptProvider couldn't instantiate " +ucbService, self) + self.provCtx = ProviderContext( + storageType, sfa, urlHelper, ScriptContext( uno.getComponentContext(), doc, inv ) ) + if isPackage: + mapPackageName2Path = getPackageName2PathMap( sfa, storageType ) + self.provCtx.setPackageAttributes( mapPackageName2Path , rootUrl ) + self.dirBrowseNode = PackageBrowseNode( self.provCtx, LANGUAGENAME, rootUrl ) + else: + self.dirBrowseNode = DirBrowseNode( self.provCtx, LANGUAGENAME, rootUrl ) + + except Exception as e: + text = lastException2String() + log.debug( "PythonScriptProvider could not be instantiated because of : " + text ) + raise e + + def getName( self ): + return self.dirBrowseNode.getName() + + def getChildNodes( self ): + return self.dirBrowseNode.getChildNodes() + + def hasChildNodes( self ): + return self.dirBrowseNode.hasChildNodes() + + def getType( self ): + return self.dirBrowseNode.getType() + + # retrieve function args in parenthesis + def getFunctionArguments(self, func_signature): + nOpenParenthesis = func_signature.find( "(" ) + if -1 == nOpenParenthesis: + function_name = func_signature + arguments = None + else: + function_name = func_signature[0:nOpenParenthesis] + arg_part = func_signature[nOpenParenthesis+1:len(func_signature)] + nCloseParenthesis = arg_part.find( ")" ) + if -1 == nCloseParenthesis: + raise IllegalArgumentException( "PythonLoader: mismatch parenthesis " + func_signature, self, 0 ) + arguments = arg_part[0:nCloseParenthesis].strip() + if arguments == "": + arguments = None + else: + arguments = tuple([x.strip().strip('"') for x in arguments.split(",")]) + return function_name, arguments + + def getScript( self, scriptUri ): + try: + log.debug( "getScript " + scriptUri + " invoked") + + storageUri = self.provCtx.getStorageUrlFromPersistentUrl( + self.provCtx.uriHelper.getStorageURI(scriptUri) ); + log.debug( "getScript: storageUri = " + storageUri) + fileUri = storageUri[0:storageUri.find( "$" )] + funcName = storageUri[storageUri.find( "$" )+1:len(storageUri)] + + # retrieve arguments in parenthesis + funcName, funcArgs = self.getFunctionArguments(funcName) + log.debug( " getScript : parsed funcname " + str(funcName) ) + log.debug( " getScript : func args " + str(funcArgs) ) + + mod = self.provCtx.getModuleByUrl( fileUri ) + log.debug( " got mod " + str(mod) ) + + func = mod.__dict__[ funcName ] + + log.debug( "got func " + str( func ) ) + return PythonScript( func, mod, funcArgs ) + except: + text = lastException2String() + log.error( text ) + raise ScriptFrameworkErrorException( text, self, scriptUri, LANGUAGENAME, 0 ) + + + # XServiceInfo + def getSupportedServices( self ): + return g_ImplementationHelper.getSupportedServices(g_implName) + + def supportsService( self, ServiceName ): + return g_ImplementationHelper.supportsService( g_implName, ServiceName ) + + def getImplementationName(self): + return g_implName + + def getByName( self, name ): + log.debug( "getByName called" + str( name )) + return None + + + def getElementNames( self ): + log.debug( "getElementNames called") + return () + + def hasByName( self, name ): + try: + log.debug( "hasByName called " + str( name )) + uri = expandUri(name) + ret = self.provCtx.isUrlInPackage( uri ) + log.debug( "hasByName " + uri + " " +str( ret ) ) + return ret + except: + text = lastException2String() + log.debug( "Error in hasByName:" + text ) + return False + + def removeByName( self, name ): + log.debug( "removeByName called" + str( name )) + uri = expandUri( name ) + if self.provCtx.isUrlInPackage( uri ): + self.provCtx.removePackageByUrl( uri ) + else: + log.debug( "removeByName unknown uri " + str( name ) + ", ignoring" ) + raise NoSuchElementException( uri + "is not in package" , self ) + log.debug( "removeByName called" + str( uri ) + " successful" ) + + def insertByName( self, name, value ): + log.debug( "insertByName called " + str( name ) + " " + str( value )) + uri = expandUri( name ) + if isPyFileInPath( self.provCtx.sfa, uri ): + self.provCtx.addPackageByUrl( uri ) + else: + # package is no python package ... + log.debug( "insertByName: no python files in " + str( uri ) + ", ignoring" ) + raise IllegalArgumentException( uri + " does not contain .py files", self, 1 ) + log.debug( "insertByName called " + str( uri ) + " successful" ) + + def replaceByName( self, name, value ): + log.debug( "replaceByName called " + str( name ) + " " + str( value )) + uri = expandUri( name ) + self.removeByName( name ) + self.insertByName( name, value ) + log.debug( "replaceByName called" + str( uri ) + " successful" ) + + def getElementType( self ): + log.debug( "getElementType called" ) + return uno.getTypeByName( "void" ) + + def hasElements( self ): + log.debug( "hasElements got called") + return False + +g_ImplementationHelper.addImplementation( \ + PythonScriptProvider,g_implName, \ + ("com.sun.star.script.provider.LanguageScriptProvider", + "com.sun.star.script.provider.ScriptProviderFor"+ LANGUAGENAME,),) + + +log.debug( "pythonscript finished initializing" ) + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/scripting/source/pyprov/scriptproviderforpython.rdb b/scripting/source/pyprov/scriptproviderforpython.rdb new file mode 100644 index 000000000..09a37e4da --- /dev/null +++ b/scripting/source/pyprov/scriptproviderforpython.rdb @@ -0,0 +1,28 @@ +<?xml version="1.0"?> +<!-- + * 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 . +--> +<components xmlns="http://openoffice.org/2010/uno-components"> + <component loader="com.sun.star.loader.Python" + uri="vnd.openoffice.pymodule:pythonscript"> + <implementation + name="org.libreoffice.pyuno.LanguageScriptProviderForPython"> + <service name="com.sun.star.script.provider.ScriptProviderForPython"/> + <service name="com.sun.star.script.provider.LanguageScriptProvider"/> + </implementation> + </component> +</components> diff --git a/scripting/source/stringresource/stringresource.component b/scripting/source/stringresource/stringresource.component new file mode 100644 index 000000000..a2b2a24f3 --- /dev/null +++ b/scripting/source/stringresource/stringresource.component @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.scripting.StringResource" + constructor="scripting_StringResourcePersistenceImpl_implementation"> + <service name="com.sun.star.resource.StringResource"/> + </implementation> + <implementation name="com.sun.star.comp.scripting.StringResourceWithLocation" + constructor="scripting_StringResourceWithLocationImpl_get_implementation"> + <service name="com.sun.star.resource.StringResourceWithLocation"/> + </implementation> + <implementation name="com.sun.star.comp.scripting.StringResourceWithStorage" + constructor="scripting_StringResourceWithStorageImpl_get_implementation"> + <service name="com.sun.star.resource.StringResourceWithStorage"/> + </implementation> +</component> diff --git a/scripting/source/stringresource/stringresource.cxx b/scripting/source/stringresource/stringresource.cxx new file mode 100644 index 000000000..06b52b037 --- /dev/null +++ b/scripting/source/stringresource/stringresource.cxx @@ -0,0 +1,2598 @@ +/* -*- 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 <memory> +#include "stringresource.hxx" +#include <com/sun/star/io/TempFile.hpp> +#include <com/sun/star/io/TextInputStream.hpp> +#include <com/sun/star/io/TextOutputStream.hpp> +#include <com/sun/star/io/XStream.hpp> +#include <com/sun/star/io/XSeekable.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/lang/NoSupportException.hpp> +#include <com/sun/star/resource/MissingResourceException.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/container/ElementExistException.hpp> +#include <com/sun/star/ucb/SimpleFileAccess.hpp> + +#include <osl/diagnose.h> +#include <rtl/tencinfo.h> +#include <rtl/ustrbuf.hxx> +#include <tools/urlobj.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <sal/log.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::ucb; +using namespace ::com::sun::star::util; +using namespace ::com::sun::star::embed; +using namespace ::com::sun::star::container; + + +namespace stringresource +{ + +// StringResourceImpl + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +scripting_StringResourcePersistenceImpl_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new StringResourcePersistenceImpl(context)); +} + + +StringResourceImpl::StringResourceImpl( const Reference< XComponentContext >& rxContext ) + : m_xContext( rxContext ) + , m_pCurrentLocaleItem( nullptr ) + , m_pDefaultLocaleItem( nullptr ) + , m_bDefaultModified( false ) + , m_bModified( false ) + , m_bReadOnly( false ) + , m_nNextUniqueNumericId( UNIQUE_NUMBER_NEEDS_INITIALISATION ) +{ +} + + +StringResourceImpl::~StringResourceImpl() +{ +} + + +// XServiceInfo + +OUString StringResourceImpl::getImplementationName( ) +{ + return "com.sun.star.comp.scripting.StringResource"; +} + +sal_Bool StringResourceImpl::supportsService( const OUString& rServiceName ) +{ + return cppu::supportsService(this, rServiceName); +} + +Sequence< OUString > StringResourceImpl::getSupportedServiceNames( ) +{ + return { "com.sun.star.resource.StringResource" }; +} + + +// XModifyBroadcaster + +void StringResourceImpl::addModifyListener( const Reference< XModifyListener >& aListener ) +{ + if( !aListener.is() ) + throw RuntimeException(); + + std::unique_lock aGuard( m_aMutex ); + m_aListenerContainer.addInterface( aGuard, aListener ); +} + +void StringResourceImpl::removeModifyListener( const Reference< XModifyListener >& aListener ) +{ + if( !aListener.is() ) + throw RuntimeException(); + + std::unique_lock aGuard( m_aMutex ); + m_aListenerContainer.removeInterface( aGuard, aListener ); +} + + +// XStringResourceResolver + +OUString StringResourceImpl::implResolveString + ( const OUString& ResourceID, LocaleItem* pLocaleItem ) +{ + OUString aRetStr; + bool bSuccess = false; + if( pLocaleItem != nullptr && loadLocale( pLocaleItem ) ) + { + IdToStringMap::iterator it = pLocaleItem->m_aIdToStringMap.find( ResourceID ); + if( it != pLocaleItem->m_aIdToStringMap.end() ) + { + aRetStr = (*it).second; + bSuccess = true; + } + } + if( !bSuccess ) + { + throw css::resource::MissingResourceException( "StringResourceImpl: No entry for ResourceID: " + ResourceID ); + } + return aRetStr; +} + +OUString StringResourceImpl::resolveString( const OUString& ResourceID ) +{ + std::unique_lock aGuard( m_aMutex ); + return implResolveString( ResourceID, m_pCurrentLocaleItem ); +} + +OUString StringResourceImpl::resolveStringForLocale( const OUString& ResourceID, const Locale& locale ) +{ + std::unique_lock aGuard( m_aMutex ); + LocaleItem* pLocaleItem = getItemForLocale( locale, false ); + return implResolveString( ResourceID, pLocaleItem ); +} + +bool StringResourceImpl::implHasEntryForId( const OUString& ResourceID, LocaleItem* pLocaleItem ) +{ + bool bSuccess = false; + if( pLocaleItem != nullptr && loadLocale( pLocaleItem ) ) + { + IdToStringMap::iterator it = pLocaleItem->m_aIdToStringMap.find( ResourceID ); + if( it != pLocaleItem->m_aIdToStringMap.end() ) + bSuccess = true; + } + return bSuccess; +} + +sal_Bool StringResourceImpl::hasEntryForId( const OUString& ResourceID ) +{ + std::unique_lock aGuard( m_aMutex ); + return implHasEntryForId( ResourceID, m_pCurrentLocaleItem ); +} + +sal_Bool StringResourceImpl::hasEntryForIdAndLocale( const OUString& ResourceID, + const Locale& locale ) +{ + std::unique_lock aGuard( m_aMutex ); + LocaleItem* pLocaleItem = getItemForLocale( locale, false ); + return implHasEntryForId( ResourceID, pLocaleItem ); +} + +Sequence< OUString > StringResourceImpl::implGetResourceIDs( LocaleItem* pLocaleItem ) +{ + Sequence< OUString > aIDSeq( 0 ); + if( pLocaleItem && loadLocale( pLocaleItem ) ) + { + const IdToStringMap& rHashMap = pLocaleItem->m_aIdToStringMap; + sal_Int32 nResourceIDCount = rHashMap.size(); + aIDSeq.realloc( nResourceIDCount ); + OUString* pStrings = aIDSeq.getArray(); + + int iTarget = 0; + for( const auto& rEntry : rHashMap ) + { + OUString aStr = rEntry.first; + pStrings[iTarget] = aStr; + iTarget++; + } + } + return aIDSeq; +} + +Sequence< OUString > StringResourceImpl::getResourceIDsForLocale + ( const Locale& locale ) +{ + std::unique_lock aGuard( m_aMutex ); + LocaleItem* pLocaleItem = getItemForLocale( locale, false ); + return implGetResourceIDs( pLocaleItem ); +} + +Sequence< OUString > StringResourceImpl::getResourceIDs( ) +{ + std::unique_lock aGuard( m_aMutex ); + return implGetResourceIDs( m_pCurrentLocaleItem ); +} + +Locale StringResourceImpl::getCurrentLocale() +{ + std::unique_lock aGuard( m_aMutex ); + + Locale aRetLocale; + if( m_pCurrentLocaleItem != nullptr ) + aRetLocale = m_pCurrentLocaleItem->m_locale; + return aRetLocale; +} + +Locale StringResourceImpl::getDefaultLocale( ) +{ + std::unique_lock aGuard( m_aMutex ); + + Locale aRetLocale; + if( m_pDefaultLocaleItem != nullptr ) + aRetLocale = m_pDefaultLocaleItem->m_locale; + return aRetLocale; +} + +Sequence< Locale > StringResourceImpl::getLocales( ) +{ + std::unique_lock aGuard( m_aMutex ); + + sal_Int32 nSize = m_aLocaleItemVector.size(); + Sequence< Locale > aLocalSeq( nSize ); + Locale* pLocales = aLocalSeq.getArray(); + int iTarget = 0; + for( const auto& pLocaleItem : m_aLocaleItemVector ) + { + pLocales[iTarget] = pLocaleItem->m_locale; + iTarget++; + } + return aLocalSeq; +} + + +// XStringResourceManager + +void StringResourceImpl::implCheckReadOnly( const char* pExceptionMsg ) +{ + if( m_bReadOnly ) + { + OUString errorMsg = OUString::createFromAscii( pExceptionMsg ); + throw NoSupportException( errorMsg ); + } +} + +sal_Bool StringResourceImpl::isReadOnly() +{ + return m_bReadOnly; +} + +void StringResourceImpl::implSetCurrentLocale( std::unique_lock<std::mutex>& rGuard, const Locale& locale, + bool FindClosestMatch, bool bUseDefaultIfNoMatch ) +{ + LocaleItem* pLocaleItem = nullptr; + if( FindClosestMatch ) + pLocaleItem = getClosestMatchItemForLocale( locale ); + else + pLocaleItem = getItemForLocale( locale, true ); + + if( pLocaleItem == nullptr && bUseDefaultIfNoMatch ) + pLocaleItem = m_pDefaultLocaleItem; + + if( pLocaleItem != nullptr ) + { + (void)loadLocale( pLocaleItem ); + m_pCurrentLocaleItem = pLocaleItem; + + // Only notify without modifying + implNotifyListeners(rGuard); + } +} + +void StringResourceImpl::setCurrentLocale( const Locale& locale, sal_Bool FindClosestMatch ) +{ + std::unique_lock aGuard( m_aMutex ); + implSetCurrentLocale( aGuard, locale, FindClosestMatch, false/*bUseDefaultIfNoMatch*/ ); +} + +void StringResourceImpl::setDefaultLocale( const Locale& locale ) +{ + std::unique_lock aGuard( m_aMutex ); + implCheckReadOnly( "StringResourceImpl::setDefaultLocale(): Read only" ); + + LocaleItem* pLocaleItem = getItemForLocale( locale, true ); + if( pLocaleItem && pLocaleItem != m_pDefaultLocaleItem ) + { + if( m_pDefaultLocaleItem ) + { + m_aChangedDefaultLocaleVector.push_back( + std::make_unique<LocaleItem>( m_pDefaultLocaleItem->m_locale ) ); + } + + m_pDefaultLocaleItem = pLocaleItem; + m_bDefaultModified = true; + implModified(aGuard); + } +} + +void StringResourceImpl::implSetString( std::unique_lock<std::mutex>& rGuard, const OUString& ResourceID, + const OUString& Str, LocaleItem* pLocaleItem ) +{ + if( !(pLocaleItem != nullptr && loadLocale( pLocaleItem )) ) + return; + + IdToStringMap& rHashMap = pLocaleItem->m_aIdToStringMap; + + IdToStringMap::iterator it = rHashMap.find( ResourceID ); + bool bNew = ( it == rHashMap.end() ); + if( bNew ) + { + IdToIndexMap& rIndexMap = pLocaleItem->m_aIdToIndexMap; + rIndexMap[ ResourceID ] = pLocaleItem->m_nNextIndex++; + implScanIdForNumber( ResourceID ); + } + rHashMap[ ResourceID ] = Str; + pLocaleItem->m_bModified = true; + implModified(rGuard); +} + +void StringResourceImpl::setString( const OUString& ResourceID, const OUString& Str ) +{ + std::unique_lock aGuard( m_aMutex ); + implCheckReadOnly( "StringResourceImpl::setString(): Read only" ); + implSetString( aGuard, ResourceID, Str, m_pCurrentLocaleItem ); +} + +void StringResourceImpl::setStringForLocale + ( const OUString& ResourceID, const OUString& Str, const Locale& locale ) +{ + std::unique_lock aGuard( m_aMutex ); + implCheckReadOnly( "StringResourceImpl::setStringForLocale(): Read only" ); + LocaleItem* pLocaleItem = getItemForLocale( locale, false ); + implSetString( aGuard, ResourceID, Str, pLocaleItem ); +} + +void StringResourceImpl::implRemoveId( std::unique_lock<std::mutex>& rGuard, const OUString& ResourceID, LocaleItem* pLocaleItem ) +{ + if( pLocaleItem != nullptr && loadLocale( pLocaleItem ) ) + { + IdToStringMap& rHashMap = pLocaleItem->m_aIdToStringMap; + IdToStringMap::iterator it = rHashMap.find( ResourceID ); + if( it == rHashMap.end() ) + { + throw css::resource::MissingResourceException( "StringResourceImpl: No entries for ResourceID: " + ResourceID ); + } + rHashMap.erase( it ); + pLocaleItem->m_bModified = true; + implModified(rGuard); + } +} + +void StringResourceImpl::removeId( const OUString& ResourceID ) +{ + std::unique_lock aGuard( m_aMutex ); + implCheckReadOnly( "StringResourceImpl::removeId(): Read only" ); + implRemoveId( aGuard, ResourceID, m_pCurrentLocaleItem ); +} + +void StringResourceImpl::removeIdForLocale( const OUString& ResourceID, const Locale& locale ) +{ + std::unique_lock aGuard( m_aMutex ); + implCheckReadOnly( "StringResourceImpl::removeIdForLocale(): Read only" ); + LocaleItem* pLocaleItem = getItemForLocale( locale, false ); + implRemoveId( aGuard, ResourceID, pLocaleItem ); +} + +void StringResourceImpl::newLocale( const Locale& locale ) +{ + std::unique_lock aGuard( m_aMutex ); + implCheckReadOnly( "StringResourceImpl::newLocale(): Read only" ); + + if( getItemForLocale( locale, false ) != nullptr ) + { + throw ElementExistException( "StringResourceImpl: locale already exists" ); + } + + // TODO?: Check if locale is valid? How? + //if (!bValid) + //{ + // OUString errorMsg("StringResourceImpl: Invalid locale"); + // throw IllegalArgumentException( errorMsg, Reference< XInterface >(), 0 ); + //} + + LocaleItem* pLocaleItem = new LocaleItem( locale ); + m_aLocaleItemVector.emplace_back( pLocaleItem ); + pLocaleItem->m_bModified = true; + + // Copy strings from default locale + LocaleItem* pCopyFromItem = m_pDefaultLocaleItem; + if( pCopyFromItem == nullptr ) + pCopyFromItem = m_pCurrentLocaleItem; + if( pCopyFromItem != nullptr && loadLocale( pCopyFromItem ) ) + { + const IdToStringMap& rSourceMap = pCopyFromItem->m_aIdToStringMap; + IdToStringMap& rTargetMap = pLocaleItem->m_aIdToStringMap; + for( const auto& rEntry : rSourceMap ) + { + OUString aId = rEntry.first; + OUString aStr = rEntry.second; + rTargetMap[ aId ] = aStr; + } + + const IdToIndexMap& rSourceIndexMap = pCopyFromItem->m_aIdToIndexMap; + IdToIndexMap& rTargetIndexMap = pLocaleItem->m_aIdToIndexMap; + for( const auto& rIndex : rSourceIndexMap ) + { + OUString aId = rIndex.first; + sal_Int32 nIndex = rIndex.second; + rTargetIndexMap[ aId ] = nIndex; + } + pLocaleItem->m_nNextIndex = pCopyFromItem->m_nNextIndex; + } + + if( m_pCurrentLocaleItem == nullptr ) + m_pCurrentLocaleItem = pLocaleItem; + + if( m_pDefaultLocaleItem == nullptr ) + { + m_pDefaultLocaleItem = pLocaleItem; + m_bDefaultModified = true; + } + + implModified(aGuard); +} + +void StringResourceImpl::removeLocale( const Locale& locale ) +{ + std::unique_lock aGuard( m_aMutex ); + implCheckReadOnly( "StringResourceImpl::removeLocale(): Read only" ); + + LocaleItem* pRemoveItem = getItemForLocale( locale, true ); + if( !pRemoveItem ) + return; + + // Last locale? + sal_Int32 nLocaleCount = m_aLocaleItemVector.size(); + if( nLocaleCount > 1 ) + { + if( m_pCurrentLocaleItem == pRemoveItem || + m_pDefaultLocaleItem == pRemoveItem ) + { + LocaleItem* pFallbackItem = nullptr; + for( const auto& pLocaleItem : m_aLocaleItemVector ) + { + if( pLocaleItem.get() != pRemoveItem ) + { + pFallbackItem = pLocaleItem.get(); + break; + } + } + if( m_pCurrentLocaleItem == pRemoveItem ) + { + setCurrentLocale( pFallbackItem->m_locale, false/*FindClosestMatch*/ ); + } + if( m_pDefaultLocaleItem == pRemoveItem ) + { + setDefaultLocale( pFallbackItem->m_locale ); + } + } + } + auto it = std::find_if(m_aLocaleItemVector.begin(), m_aLocaleItemVector.end(), + [&pRemoveItem](const std::unique_ptr<LocaleItem>& rxItem) { return rxItem.get() == pRemoveItem; }); + if (it == m_aLocaleItemVector.end()) + return; + + // Remember locale item to delete file while storing + m_aDeletedLocaleItemVector.push_back( std::move(*it) ); + + // Last locale? + if( nLocaleCount == 1 ) + { + m_nNextUniqueNumericId = 0; + if( m_pDefaultLocaleItem ) + { + m_aChangedDefaultLocaleVector.push_back( + std::make_unique<LocaleItem>( m_pDefaultLocaleItem->m_locale ) ); + } + m_pCurrentLocaleItem = nullptr; + m_pDefaultLocaleItem = nullptr; + } + + m_aLocaleItemVector.erase( it ); + + implModified(aGuard); +} + +void StringResourceImpl::implScanIdForNumber( const OUString& ResourceID ) +{ + const sal_Unicode* pSrc = ResourceID.getStr(); + sal_Int32 nLen = ResourceID.getLength(); + + sal_Int32 nNumber = 0; + for( sal_Int32 i = 0 ; i < nLen ; i++ ) + { + sal_Unicode c = pSrc[i]; + if( c >= '0' && c <= '9' ) + { + sal_uInt16 nDigitVal = c - '0'; + nNumber = 10*nNumber + nDigitVal; + } + else + break; + } + + if( m_nNextUniqueNumericId < nNumber + 1 ) + m_nNextUniqueNumericId = nNumber + 1; +} + +sal_Int32 StringResourceImpl::getUniqueNumericId( ) +{ + if( m_nNextUniqueNumericId == UNIQUE_NUMBER_NEEDS_INITIALISATION ) + { + implLoadAllLocales(); + m_nNextUniqueNumericId = 0; + } + + if( m_nNextUniqueNumericId < UNIQUE_NUMBER_NEEDS_INITIALISATION ) + { + throw NoSupportException( "getUniqueNumericId: Extended sal_Int32 range" ); + } + return m_nNextUniqueNumericId; +} + + +// Private helper methods + +LocaleItem* StringResourceImpl::getItemForLocale + ( const Locale& locale, bool bException ) +{ + LocaleItem* pRetItem = nullptr; + + // Search for locale + for( auto& pLocaleItem : m_aLocaleItemVector ) + { + if( pLocaleItem ) + { + Locale& cmp_locale = pLocaleItem->m_locale; + if( cmp_locale.Language == locale.Language && + cmp_locale.Country == locale.Country && + cmp_locale.Variant == locale.Variant ) + { + pRetItem = pLocaleItem.get(); + break; + } + } + } + + if( pRetItem == nullptr && bException ) + { + throw IllegalArgumentException( "StringResourceImpl: Invalid locale", Reference< XInterface >(), 0 ); + } + return pRetItem; +} + +// Returns the LocaleItem for a given locale, if it exists, otherwise NULL. +// This method performs a closest match search, at least the language must match. +LocaleItem* StringResourceImpl::getClosestMatchItemForLocale( const Locale& locale ) +{ + LocaleItem* pRetItem = nullptr; + + ::std::vector< Locale > aLocales( m_aLocaleItemVector.size()); + size_t i = 0; + for( const auto& pLocaleItem : m_aLocaleItemVector ) + { + aLocales[i] = (pLocaleItem ? pLocaleItem->m_locale : Locale()); + ++i; + } + ::std::vector< Locale >::const_iterator iFound( LanguageTag::getMatchingFallback( aLocales, locale)); + if (iFound != aLocales.end()) + pRetItem = (m_aLocaleItemVector.begin() + (iFound - aLocales.begin()))->get(); + + return pRetItem; +} + +void StringResourceImpl::implModified(std::unique_lock<std::mutex>& rGuard) +{ + m_bModified = true; + implNotifyListeners(rGuard); +} + +void StringResourceImpl::implNotifyListeners(std::unique_lock<std::mutex>& rGuard) +{ + EventObject aEvent; + aEvent.Source = static_cast< XInterface* >( static_cast<OWeakObject*>(this) ); + m_aListenerContainer.forEach(rGuard, + [&aEvent](const css::uno::Reference<XModifyListener>& xListener) + { + xListener->modified(aEvent); + } + ); +} + + +// Loading + +bool StringResourceImpl::loadLocale( LocaleItem* ) +{ + // Base implementation has nothing to load + return true; +} + +void StringResourceImpl::implLoadAllLocales() +{ + // Base implementation has nothing to load +} + + +// StringResourcePersistenceImpl + + +StringResourcePersistenceImpl::StringResourcePersistenceImpl( const Reference< XComponentContext >& rxContext ) + : StringResourcePersistenceImpl_BASE( rxContext ) +{ +} + + +StringResourcePersistenceImpl::~StringResourcePersistenceImpl() +{ +} + + +// XServiceInfo + + +OUString StringResourcePersistenceImpl::getImplementationName( ) +{ + return "com.sun.star.comp.scripting.StringResource"; +} + + +sal_Bool StringResourcePersistenceImpl::supportsService( const OUString& rServiceName ) +{ + return cppu::supportsService( this, rServiceName ); +} + + +Sequence< OUString > StringResourcePersistenceImpl::getSupportedServiceNames( ) +{ + return StringResourceImpl::getSupportedServiceNames(); +} + + +// XInitialization base functionality for derived classes + + +constexpr OUStringLiteral aNameBaseDefaultStr = u"strings"; + +void StringResourcePersistenceImpl::implInitializeCommonParameters + ( std::unique_lock<std::mutex>& rGuard, const Sequence< Any >& aArguments ) +{ + bool bReadOnlyOk = (aArguments[1] >>= m_bReadOnly); + if( !bReadOnlyOk ) + { + throw IllegalArgumentException( "XInitialization::initialize: Expected ReadOnly flag", Reference< XInterface >(), 1 ); + } + + css::lang::Locale aCurrentLocale; + bool bLocaleOk = (aArguments[2] >>= aCurrentLocale); + if( !bLocaleOk ) + { + throw IllegalArgumentException( "XInitialization::initialize: Expected Locale", Reference< XInterface >(), 2 ); + } + + bool bNameBaseOk = (aArguments[3] >>= m_aNameBase); + if( !bNameBaseOk ) + { + throw IllegalArgumentException( "XInitialization::initialize: Expected NameBase string", Reference< XInterface >(), 3 ); + } + if( m_aNameBase.isEmpty() ) + m_aNameBase = aNameBaseDefaultStr; + + bool bCommentOk = (aArguments[4] >>= m_aComment); + if( !bCommentOk ) + { + throw IllegalArgumentException( "XInitialization::initialize: Expected Comment string", Reference< XInterface >(), 4 ); + } + + implScanLocales(); + + implSetCurrentLocale( rGuard, aCurrentLocale, true/*FindClosestMatch*/, true/*bUseDefaultIfNoMatch*/ ); +} + + +// Forwarding calls to base class + +// XModifyBroadcaster +void StringResourcePersistenceImpl::addModifyListener( const Reference< XModifyListener >& aListener ) +{ + StringResourceImpl::addModifyListener( aListener ); +} +void StringResourcePersistenceImpl::removeModifyListener( const Reference< XModifyListener >& aListener ) +{ + StringResourceImpl::removeModifyListener( aListener ); +} + +// XStringResourceResolver +OUString StringResourcePersistenceImpl::resolveString( const OUString& ResourceID ) +{ + return StringResourceImpl::resolveString( ResourceID ) ; +} +OUString StringResourcePersistenceImpl::resolveStringForLocale( const OUString& ResourceID, const Locale& locale ) +{ + return StringResourceImpl::resolveStringForLocale( ResourceID, locale ); +} +sal_Bool StringResourcePersistenceImpl::hasEntryForId( const OUString& ResourceID ) +{ + return StringResourceImpl::hasEntryForId( ResourceID ) ; +} +sal_Bool StringResourcePersistenceImpl::hasEntryForIdAndLocale( const OUString& ResourceID, + const Locale& locale ) +{ + return StringResourceImpl::hasEntryForIdAndLocale( ResourceID, locale ); +} +Locale StringResourcePersistenceImpl::getCurrentLocale() +{ + return StringResourceImpl::getCurrentLocale(); +} +Locale StringResourcePersistenceImpl::getDefaultLocale( ) +{ + return StringResourceImpl::getDefaultLocale(); +} +Sequence< Locale > StringResourcePersistenceImpl::getLocales( ) +{ + return StringResourceImpl::getLocales(); +} + +// XStringResourceManager +sal_Bool StringResourcePersistenceImpl::isReadOnly() +{ + return StringResourceImpl::isReadOnly(); +} +void StringResourcePersistenceImpl::setCurrentLocale( const Locale& locale, sal_Bool FindClosestMatch ) +{ + StringResourceImpl::setCurrentLocale( locale, FindClosestMatch ); +} +void StringResourcePersistenceImpl::setDefaultLocale( const Locale& locale ) +{ + StringResourceImpl::setDefaultLocale( locale ); +} +Sequence< OUString > StringResourcePersistenceImpl::getResourceIDs( ) +{ + return StringResourceImpl::getResourceIDs(); +} +void StringResourcePersistenceImpl::setString( const OUString& ResourceID, const OUString& Str ) +{ + StringResourceImpl::setString( ResourceID, Str ); +} +void StringResourcePersistenceImpl::setStringForLocale + ( const OUString& ResourceID, const OUString& Str, const Locale& locale ) +{ + StringResourceImpl::setStringForLocale( ResourceID, Str, locale ); +} +Sequence< OUString > StringResourcePersistenceImpl::getResourceIDsForLocale + ( const Locale& locale ) +{ + return StringResourceImpl::getResourceIDsForLocale( locale ); +} +void StringResourcePersistenceImpl::removeId( const OUString& ResourceID ) +{ + StringResourceImpl::removeId( ResourceID ); +} +void StringResourcePersistenceImpl::removeIdForLocale( const OUString& ResourceID, const Locale& locale ) +{ + StringResourceImpl::removeIdForLocale( ResourceID, locale ); +} +void StringResourcePersistenceImpl::newLocale( const Locale& locale ) +{ + StringResourceImpl::newLocale( locale ); +} +void StringResourcePersistenceImpl::removeLocale( const Locale& locale ) +{ + StringResourceImpl::removeLocale( locale ); +} +sal_Int32 StringResourcePersistenceImpl::getUniqueNumericId( ) +{ + return StringResourceImpl::getUniqueNumericId(); +} + + +// XStringResourcePersistence + +void StringResourcePersistenceImpl::store() +{ +} + +sal_Bool StringResourcePersistenceImpl::isModified( ) +{ + std::unique_lock aGuard( m_aMutex ); + + return m_bModified; +} + +void StringResourcePersistenceImpl::setComment( const OUString& Comment ) +{ + m_aComment = Comment; +} + +void StringResourcePersistenceImpl::storeToStorage( const Reference< XStorage >& Storage, + const OUString& NameBase, const OUString& Comment ) +{ + std::unique_lock aGuard( m_aMutex ); + + implStoreAtStorage( NameBase, Comment, Storage, false/*bUsedForStore*/, true/*bStoreAll*/ ); +} + +void StringResourcePersistenceImpl::implStoreAtStorage +( + const OUString& aNameBase, + const OUString& aComment, + const Reference< css::embed::XStorage >& Storage, + bool bUsedForStore, + bool bStoreAll +) +{ + // Delete files for deleted locales + if( bUsedForStore ) + { + for( auto& pLocaleItem : m_aDeletedLocaleItemVector ) + { + if( pLocaleItem ) + { + OUString aStreamName = implGetFileNameForLocaleItem( pLocaleItem.get(), m_aNameBase ) + ".properties"; + + try + { + Storage->removeElement( aStreamName ); + } + catch( Exception& ) + {} + + pLocaleItem.reset(); + } + } + m_aDeletedLocaleItemVector.clear(); + } + + for( auto& pLocaleItem : m_aLocaleItemVector ) + { + if( pLocaleItem != nullptr && (bStoreAll || pLocaleItem->m_bModified) && + loadLocale( pLocaleItem.get() ) ) + { + OUString aStreamName = implGetFileNameForLocaleItem( pLocaleItem.get(), aNameBase ) + ".properties"; + + Reference< io::XStream > xElementStream = + Storage->openStreamElement( aStreamName, ElementModes::READWRITE ); + + uno::Reference< beans::XPropertySet > xProps( xElementStream, uno::UNO_QUERY ); + OSL_ENSURE( xProps.is(), "The StorageStream must implement XPropertySet interface!" ); + if ( xProps.is() ) + { + OUString aPropName("MediaType"); + xProps->setPropertyValue( aPropName, uno::Any( OUString("text/plain") ) ); + + aPropName = "UseCommonStoragePasswordEncryption"; + xProps->setPropertyValue( aPropName, uno::Any( true ) ); + } + + Reference< io::XOutputStream > xOutputStream = xElementStream->getOutputStream(); + if( xOutputStream.is() ) + implWritePropertiesFile( pLocaleItem.get(), xOutputStream, aComment ); + xOutputStream->closeOutput(); + + if( bUsedForStore ) + pLocaleItem->m_bModified = false; + } + } + + // Delete files for changed defaults + if( bUsedForStore ) + { + for( auto& pLocaleItem : m_aChangedDefaultLocaleVector ) + { + OUString aStreamName = implGetFileNameForLocaleItem( pLocaleItem.get(), m_aNameBase ) + ".default"; + + try + { + Storage->removeElement( aStreamName ); + } + catch( Exception& ) + {} + + pLocaleItem.reset(); + } + m_aChangedDefaultLocaleVector.clear(); + } + + // Default locale + if( !(m_pDefaultLocaleItem != nullptr && (bStoreAll || m_bDefaultModified)) ) + return; + + OUString aStreamName = implGetFileNameForLocaleItem( m_pDefaultLocaleItem, aNameBase ) + ".default"; + + Reference< io::XStream > xElementStream = + Storage->openStreamElement( aStreamName, ElementModes::READWRITE ); + + // Only create stream without content + Reference< io::XOutputStream > xOutputStream = xElementStream->getOutputStream(); + xOutputStream->closeOutput(); + + if( bUsedForStore ) + m_bDefaultModified = false; +} + +void StringResourcePersistenceImpl::storeToURL( const OUString& URL, + const OUString& NameBase, const OUString& Comment, + const Reference< css::task::XInteractionHandler >& Handler ) +{ + std::unique_lock aGuard( m_aMutex ); + + Reference< ucb::XSimpleFileAccess3 > xFileAccess = ucb::SimpleFileAccess::create(m_xContext); + if( xFileAccess.is() && Handler.is() ) + xFileAccess->setInteractionHandler( Handler ); + + implStoreAtLocation( URL, NameBase, Comment, xFileAccess, false/*bUsedForStore*/, true/*bStoreAll*/ ); +} + +void StringResourcePersistenceImpl::implKillRemovedLocaleFiles +( + std::u16string_view Location, + const OUString& aNameBase, + const css::uno::Reference< css::ucb::XSimpleFileAccess3 >& xFileAccess +) +{ + // Delete files for deleted locales + for( auto& pLocaleItem : m_aDeletedLocaleItemVector ) + { + if( pLocaleItem ) + { + OUString aCompleteFileName = + implGetPathForLocaleItem( pLocaleItem.get(), aNameBase, Location ); + if( xFileAccess->exists( aCompleteFileName ) ) + xFileAccess->kill( aCompleteFileName ); + + pLocaleItem.reset(); + } + } + m_aDeletedLocaleItemVector.clear(); +} + +void StringResourcePersistenceImpl::implKillChangedDefaultFiles +( + std::u16string_view Location, + const OUString& aNameBase, + const css::uno::Reference< css::ucb::XSimpleFileAccess3 >& xFileAccess +) +{ + // Delete files for changed defaults + for( auto& pLocaleItem : m_aChangedDefaultLocaleVector ) + { + OUString aCompleteFileName = + implGetPathForLocaleItem( pLocaleItem.get(), aNameBase, Location, true ); + if( xFileAccess->exists( aCompleteFileName ) ) + xFileAccess->kill( aCompleteFileName ); + pLocaleItem.reset(); + } + m_aChangedDefaultLocaleVector.clear(); +} + +void StringResourcePersistenceImpl::implStoreAtLocation +( + std::u16string_view Location, + const OUString& aNameBase, + const OUString& aComment, + const Reference< ucb::XSimpleFileAccess3 >& xFileAccess, + bool bUsedForStore, + bool bStoreAll, + bool bKillAll +) +{ + // Delete files for deleted locales + if( bUsedForStore || bKillAll ) + implKillRemovedLocaleFiles( Location, aNameBase, xFileAccess ); + + for( auto& pLocaleItem : m_aLocaleItemVector ) + { + if( pLocaleItem != nullptr && (bStoreAll || bKillAll || pLocaleItem->m_bModified) && + loadLocale( pLocaleItem.get() ) ) + { + OUString aCompleteFileName = + implGetPathForLocaleItem( pLocaleItem.get(), aNameBase, Location ); + if( xFileAccess->exists( aCompleteFileName ) ) + xFileAccess->kill( aCompleteFileName ); + + if( !bKillAll ) + { + // Create Output stream + Reference< io::XOutputStream > xOutputStream = xFileAccess->openFileWrite( aCompleteFileName ); + if( xOutputStream.is() ) + { + implWritePropertiesFile( pLocaleItem.get(), xOutputStream, aComment ); + xOutputStream->closeOutput(); + } + if( bUsedForStore ) + pLocaleItem->m_bModified = false; + } + } + } + + // Delete files for changed defaults + if( bUsedForStore || bKillAll ) + implKillChangedDefaultFiles( Location, aNameBase, xFileAccess ); + + // Default locale + if( !(m_pDefaultLocaleItem != nullptr && (bStoreAll || bKillAll || m_bDefaultModified)) ) + return; + + OUString aCompleteFileName = + implGetPathForLocaleItem( m_pDefaultLocaleItem, aNameBase, Location, true ); + if( xFileAccess->exists( aCompleteFileName ) ) + xFileAccess->kill( aCompleteFileName ); + + if( !bKillAll ) + { + // Create Output stream + Reference< io::XOutputStream > xOutputStream = xFileAccess->openFileWrite( aCompleteFileName ); + if( xOutputStream.is() ) + xOutputStream->closeOutput(); + + if( bUsedForStore ) + m_bDefaultModified = false; + } +} + + +// BinaryOutput, helper class for exportBinary + +class BinaryOutput +{ + Reference< XComponentContext > m_xContext; + Reference< XInterface > m_xTempFile; + Reference< io::XOutputStream > m_xOutputStream; + +public: + explicit BinaryOutput( Reference< XComponentContext > const & xContext ); + + const Reference< io::XOutputStream >& getOutputStream() const + { return m_xOutputStream; } + + Sequence< ::sal_Int8 > closeAndGetData(); + + // Template to be used with sal_Int16 and sal_Unicode + template< class T > + void write16BitInt( T n ); + void writeInt16( sal_Int16 n ) + { write16BitInt( n ); } + void writeUnicodeChar( sal_Unicode n ) + { write16BitInt( n ); } + void writeInt32( sal_Int32 n ); + void writeString( const OUString& aStr ); +}; + +BinaryOutput::BinaryOutput( Reference< XComponentContext > const & xContext ) + : m_xContext( xContext ) +{ + m_xTempFile = io::TempFile::create( m_xContext ); + m_xOutputStream.set( m_xTempFile, UNO_QUERY_THROW ); +} + +template< class T > +void BinaryOutput::write16BitInt( T n ) +{ + if( !m_xOutputStream.is() ) + return; + + Sequence< sal_Int8 > aSeq( 2 ); + sal_Int8* p = aSeq.getArray(); + + sal_Int8 nLow = sal_Int8( n & 0xff ); + sal_Int8 nHigh = sal_Int8( n >> 8 ); + + p[0] = nLow; + p[1] = nHigh; + m_xOutputStream->writeBytes( aSeq ); +} + +void BinaryOutput::writeInt32( sal_Int32 n ) +{ + if( !m_xOutputStream.is() ) + return; + + Sequence< sal_Int8 > aSeq( 4 ); + sal_Int8* p = aSeq.getArray(); + + for( sal_Int16 i = 0 ; i < 4 ; i++ ) + { + p[i] = sal_Int8( n & 0xff ); + n >>= 8; + } + m_xOutputStream->writeBytes( aSeq ); +} + +void BinaryOutput::writeString( const OUString& aStr ) +{ + sal_Int32 nLen = aStr.getLength(); + const sal_Unicode* pStr = aStr.getStr(); + + for( sal_Int32 i = 0 ; i < nLen ; i++ ) + writeUnicodeChar( pStr[i] ); + + writeUnicodeChar( 0 ); +} + +Sequence< ::sal_Int8 > BinaryOutput::closeAndGetData() +{ + Sequence< ::sal_Int8 > aRetSeq; + if( !m_xOutputStream.is() ) + return aRetSeq; + + m_xOutputStream->closeOutput(); + + Reference< io::XSeekable> xSeekable( m_xTempFile, UNO_QUERY ); + if( !xSeekable.is() ) + return aRetSeq; + + sal_Int32 nSize = static_cast<sal_Int32>(xSeekable->getPosition()); + + Reference< io::XInputStream> xInputStream( m_xTempFile, UNO_QUERY ); + if( !xInputStream.is() ) + return aRetSeq; + + xSeekable->seek( 0 ); + sal_Int32 nRead = xInputStream->readBytes( aRetSeq, nSize ); + OSL_ENSURE( nRead == nSize, "BinaryOutput::closeAndGetData: nRead != nSize" ); + + return aRetSeq; +} + + +// Binary format: + +// Header +// Byte Content +// 0 + 1 sal_Int16: Version, currently 0, low byte first +// 2 + 3 sal_Int16: Locale count = n, low byte first +// 4 + 5 sal_Int16: Default Locale position in Locale list, == n if none +// 6 - 7 sal_Int32: Start index locale block 0, lowest byte first +// (n-1) * sal_Int32: Start index locale block 1 to n, lowest byte first +// 6 + 4*n sal_Int32: "Start index" non existing locale block n+1, +// marks the first invalid index, kind of EOF + +// Locale block +// All strings are stored as 2-Byte-0 terminated sequence +// of 16 bit Unicode characters, each with low byte first +// Empty strings only contain the 2-Byte-0 + +// Members of com.sun.star.lang.Locale +// with l1 = Locale.Language.getLength() +// with l2 = Locale.Country.getLength() +// with l3 = Locale.Variant.getLength() +// pos0 = 0 Locale.Language +// pos1 = 2 * (l1 + 1) Locale.Country +// pos2 = pos1 + 2 * (l2 + 1) Locale.Variant +// pos3 = pos2 + 2 * (l3 + 1) +// pos3 Properties file written by implWritePropertiesFile + +Sequence< sal_Int8 > StringResourcePersistenceImpl::exportBinary( ) +{ + BinaryOutput aOut( m_xContext ); + + sal_Int32 nLocaleCount = m_aLocaleItemVector.size(); + std::vector<Sequence< sal_Int8 >> aLocaleDataSeq(nLocaleCount); + + sal_Int32 iLocale = 0; + sal_Int32 iDefault = 0; + for( auto& pLocaleItem : m_aLocaleItemVector ) + { + if( pLocaleItem != nullptr && loadLocale( pLocaleItem.get() ) ) + { + if( m_pDefaultLocaleItem == pLocaleItem.get() ) + iDefault = iLocale; + + BinaryOutput aLocaleOut( m_xContext ); + implWriteLocaleBinary( pLocaleItem.get(), aLocaleOut ); + + aLocaleDataSeq[iLocale] = aLocaleOut.closeAndGetData(); + } + ++iLocale; + } + + // Write header + sal_Int16 nLocaleCount16 = static_cast<sal_Int16>(nLocaleCount); + sal_Int16 iDefault16 = static_cast<sal_Int16>(iDefault); + aOut.writeInt16( 0 ); // nVersion + aOut.writeInt16( nLocaleCount16 ); + aOut.writeInt16( iDefault16 ); + + // Write data positions + sal_Int32 nDataPos = 6 + 4 * (nLocaleCount + 1); + for( iLocale = 0; iLocale < nLocaleCount; iLocale++ ) + { + aOut.writeInt32( nDataPos ); + + Sequence< sal_Int8 >& rSeq = aLocaleDataSeq[iLocale]; + sal_Int32 nSeqLen = rSeq.getLength(); + nDataPos += nSeqLen; + } + // Write final position + aOut.writeInt32( nDataPos ); + + // Write data + Reference< io::XOutputStream > xOutputStream = aOut.getOutputStream(); + if( xOutputStream.is() ) + { + for( iLocale = 0; iLocale < nLocaleCount; iLocale++ ) + { + Sequence< sal_Int8 >& rSeq = aLocaleDataSeq[iLocale]; + xOutputStream->writeBytes( rSeq ); + } + } + + Sequence< sal_Int8 > aRetSeq = aOut.closeAndGetData(); + return aRetSeq; +} + +void StringResourcePersistenceImpl::implWriteLocaleBinary + ( LocaleItem* pLocaleItem, BinaryOutput& rOut ) +{ + Reference< io::XOutputStream > xOutputStream = rOut.getOutputStream(); + if( !xOutputStream.is() ) + return; + + Locale& rLocale = pLocaleItem->m_locale; + rOut.writeString( rLocale.Language ); + rOut.writeString( rLocale.Country ); + rOut.writeString( rLocale.Variant ); + implWritePropertiesFile( pLocaleItem, xOutputStream, m_aComment ); +} + + +// BinaryOutput, helper class for exportBinary + +namespace { + +class BinaryInput +{ + Sequence< sal_Int8 > m_aData; + Reference< XComponentContext > m_xContext; + + const sal_Int8* m_pData; + sal_Int32 m_nCurPos; + sal_Int32 m_nSize; + +public: + BinaryInput( const Sequence< ::sal_Int8 >& aData, Reference< XComponentContext > const & xContext ); + + Reference< io::XInputStream > getInputStreamForSection( sal_Int32 nSize ); + + void seek( sal_Int32 nPos ); + sal_Int32 getPosition() const + { return m_nCurPos; } + + sal_Int16 readInt16(); + sal_Int32 readInt32(); + sal_Unicode readUnicodeChar(); + OUString readString(); +}; + +} + +BinaryInput::BinaryInput( const Sequence< ::sal_Int8 >& aData, Reference< XComponentContext > const & xContext ) + : m_aData( aData ) + , m_xContext( xContext ) +{ + m_pData = m_aData.getConstArray(); + m_nCurPos = 0; + m_nSize = m_aData.getLength(); +} + +Reference< io::XInputStream > BinaryInput::getInputStreamForSection( sal_Int32 nSize ) +{ + Reference< io::XInputStream > xIn; + if( m_nCurPos + nSize <= m_nSize ) + { + Reference< io::XOutputStream > xTempOut( io::TempFile::create(m_xContext), UNO_QUERY_THROW ); + Sequence< sal_Int8 > aSection( m_pData + m_nCurPos, nSize ); + xTempOut->writeBytes( aSection ); + + Reference< io::XSeekable> xSeekable( xTempOut, UNO_QUERY ); + if( xSeekable.is() ) + xSeekable->seek( 0 ); + + xIn.set( xTempOut, UNO_QUERY ); + } + else + OSL_FAIL( "BinaryInput::getInputStreamForSection(): Read past end" ); + + return xIn; +} + +void BinaryInput::seek( sal_Int32 nPos ) +{ + if( nPos <= m_nSize ) + m_nCurPos = nPos; + else + OSL_FAIL( "BinaryInput::seek(): Position past end" ); +} + + +sal_Int16 BinaryInput::readInt16() +{ + sal_Int16 nRet = 0; + if( m_nCurPos + 2 <= m_nSize ) + { + nRet = nRet + sal_Int16( sal_uInt8( m_pData[m_nCurPos++] ) ); + nRet += 256 * sal_Int16( sal_uInt8( m_pData[m_nCurPos++] ) ); + } + else + OSL_FAIL( "BinaryInput::readInt16(): Read past end" ); + + return nRet; +} + +sal_Int32 BinaryInput::readInt32() +{ + sal_Int32 nRet = 0; + if( m_nCurPos + 4 <= m_nSize ) + { + sal_Int32 nFactor = 1; + for( sal_Int16 i = 0; i < 4; i++ ) + { + nRet += sal_uInt8( m_pData[m_nCurPos++] ) * nFactor; + nFactor *= 256; + } + } + else + OSL_FAIL( "BinaryInput::readInt32(): Read past end" ); + + return nRet; +} + +sal_Unicode BinaryInput::readUnicodeChar() +{ + sal_uInt16 nRet = 0; + if( m_nCurPos + 2 <= m_nSize ) + { + nRet = nRet + sal_uInt8( m_pData[m_nCurPos++] ); + nRet += 256 * sal_uInt8( m_pData[m_nCurPos++] ); + } + else + OSL_FAIL( "BinaryInput::readUnicodeChar(): Read past end" ); + + sal_Unicode cRet = nRet; + return cRet; +} + +OUString BinaryInput::readString() +{ + OUStringBuffer aBuf; + sal_Unicode c; + do + { + c = readUnicodeChar(); + if( c != 0 ) + aBuf.append( c ); + } + while( c != 0 ); + + OUString aRetStr = aBuf.makeStringAndClear(); + return aRetStr; +} + +void StringResourcePersistenceImpl::importBinary( const Sequence< ::sal_Int8 >& Data ) +{ + // Init: Remove all locales + sal_Int32 nOldLocaleCount = 0; + do + { + Sequence< Locale > aLocaleSeq = getLocales(); + nOldLocaleCount = aLocaleSeq.getLength(); + if( nOldLocaleCount > 0 ) + { + Locale aLocale = aLocaleSeq[0]; + removeLocale( aLocale ); + } + } + while( nOldLocaleCount > 0 ); + + // Import data + BinaryInput aIn( Data, m_xContext ); + + aIn.readInt16(); // version + sal_Int32 nLocaleCount = aIn.readInt16(); + sal_Int32 iDefault = aIn.readInt16(); + + std::unique_ptr<sal_Int32[]> pPositions( new sal_Int32[nLocaleCount + 1] ); + for( sal_Int32 i = 0; i < nLocaleCount + 1; i++ ) + pPositions[i] = aIn.readInt32(); + + // Import locales + LocaleItem* pUseAsDefaultItem = nullptr; + for( sal_Int32 i = 0; i < nLocaleCount; i++ ) + { + sal_Int32 nPos = pPositions[i]; + aIn.seek( nPos ); + + Locale aLocale; + aLocale.Language = aIn.readString(); + aLocale.Country = aIn.readString(); + aLocale.Variant = aIn.readString(); + + sal_Int32 nAfterStringPos = aIn.getPosition(); + sal_Int32 nSize = pPositions[i+1] - nAfterStringPos; + Reference< io::XInputStream > xInput = aIn.getInputStreamForSection( nSize ); + if( xInput.is() ) + { + LocaleItem* pLocaleItem = new LocaleItem( std::move(aLocale) ); + if( iDefault == i ) + pUseAsDefaultItem = pLocaleItem; + m_aLocaleItemVector.emplace_back( pLocaleItem ); + implReadPropertiesFile( pLocaleItem, xInput ); + } + } + + if( pUseAsDefaultItem != nullptr ) + setDefaultLocale( pUseAsDefaultItem->m_locale ); +} + + +// Private helper methods + +static bool checkNamingSceme( const OUString& aName, const OUString& aNameBase, + Locale& aLocale ) +{ + bool bSuccess = false; + + sal_Int32 nNameLen = aName.getLength(); + sal_Int32 nNameBaseLen = aNameBase.getLength(); + + // Name has to start with NameBase followed + // by a '_' and at least one more character + if( aName.startsWith( aNameBase ) && nNameBaseLen < nNameLen-1 && + aName[nNameBaseLen] == '_' ) + { + bSuccess = true; + + /* FIXME-BCP47: this uses '_' underscore character as separator and + * also appends Variant, which can't be blindly changed as it would + * violate the naming scheme in use. */ + + sal_Int32 iStart = nNameBaseLen + 1; + sal_Int32 iNext_ = aName.indexOf( '_', iStart ); + if( iNext_ != -1 && iNext_ < nNameLen-1 ) + { + aLocale.Language = aName.copy( iStart, iNext_ - iStart ); + + iStart = iNext_ + 1; + iNext_ = aName.indexOf( '_', iStart ); + if( iNext_ != -1 && iNext_ < nNameLen-1 ) + { + aLocale.Country = aName.copy( iStart, iNext_ - iStart ); + aLocale.Variant = aName.copy( iNext_ + 1 ); + } + else + aLocale.Country = aName.copy( iStart ); + } + else + aLocale.Language = aName.copy( iStart ); + } + return bSuccess; +} + +void StringResourcePersistenceImpl::implLoadAllLocales() +{ + for( auto& pLocaleItem : m_aLocaleItemVector ) + if( pLocaleItem ) + loadLocale( pLocaleItem.get() ); +} + +// Scan locale properties files helper +void StringResourcePersistenceImpl::implScanLocaleNames( const Sequence< OUString >& aContentSeq ) +{ + Locale aDefaultLocale; + bool bDefaultFound = false; + + for( const OUString& aCompleteName : aContentSeq ) + { + OUString aPureName; + OUString aExtension; + sal_Int32 iDot = aCompleteName.lastIndexOf( '.' ); + sal_Int32 iSlash = aCompleteName.lastIndexOf( '/' ); + if( iDot != -1 && iDot > iSlash) + { + sal_Int32 iCopyFrom = (iSlash != -1) ? iSlash + 1 : 0; + aPureName = aCompleteName.copy( iCopyFrom, iDot-iCopyFrom ); + aExtension = aCompleteName.copy( iDot + 1 ); + } + + if ( aExtension == "properties" ) + { + //OUString aName = aInetObj.getBase(); + Locale aLocale; + + if( checkNamingSceme( aPureName, m_aNameBase, aLocale ) ) + { + LocaleItem* pLocaleItem = new LocaleItem( std::move(aLocale), false ); + m_aLocaleItemVector.emplace_back( pLocaleItem ); + + if( m_pCurrentLocaleItem == nullptr ) + m_pCurrentLocaleItem = pLocaleItem; + + if( m_pDefaultLocaleItem == nullptr ) + { + m_pDefaultLocaleItem = pLocaleItem; + m_bDefaultModified = true; + } + } + } + else if( !bDefaultFound && aExtension == "default" ) + { + if( checkNamingSceme( aPureName, m_aNameBase, aDefaultLocale ) ) + bDefaultFound = true; + } + } + if( bDefaultFound ) + { + LocaleItem* pLocaleItem = getItemForLocale( aDefaultLocale, false ); + if( pLocaleItem ) + { + m_pDefaultLocaleItem = pLocaleItem; + m_bDefaultModified = false; + } + } +} + +// Scan locale properties files +void StringResourcePersistenceImpl::implScanLocales() +{ + // Dummy implementation, method not called for this + // base class, but pure virtual not possible- +} + +bool StringResourcePersistenceImpl::loadLocale( LocaleItem* pLocaleItem ) +{ + bool bSuccess = false; + + OSL_ENSURE( pLocaleItem, "StringResourcePersistenceImpl::loadLocale(): pLocaleItem == NULL" ); + if( pLocaleItem ) + { + if( pLocaleItem->m_bLoaded ) + { + bSuccess = true; + } + else + { + bSuccess = implLoadLocale( pLocaleItem ); + pLocaleItem->m_bLoaded = true; // = bSuccess??? -> leads to more tries + } + } + return bSuccess; +} + +bool StringResourcePersistenceImpl::implLoadLocale( LocaleItem* ) +{ + // Dummy implementation, method not called for this + // base class, but pure virtual not possible- + return false; +} + +static OUString implGetNameScemeForLocaleItem( const LocaleItem* pLocaleItem ) +{ + /* FIXME-BCP47: this uses '_' underscore character as separator and + * also appends Variant, which can't be blindly changed as it would + * violate the naming scheme in use. */ + + static const char aUnder[] = "_"; + + OSL_ENSURE( pLocaleItem, + "StringResourcePersistenceImpl::implGetNameScemeForLocaleItem(): pLocaleItem == NULL" ); + Locale aLocale = pLocaleItem->m_locale; + + OUString aRetStr = aUnder + aLocale.Language; + + OUString aCountry = aLocale.Country; + if( !aCountry.isEmpty() ) + { + aRetStr += aUnder + aCountry; + } + + OUString aVariant = aLocale.Variant; + if( !aVariant.isEmpty() ) + { + aRetStr += aUnder + aVariant; + } + return aRetStr; +} + +OUString StringResourcePersistenceImpl::implGetFileNameForLocaleItem + ( LocaleItem const * pLocaleItem, const OUString& aNameBase ) +{ + OUString aFileName = aNameBase; + if( aFileName.isEmpty() ) + aFileName = aNameBaseDefaultStr; + + aFileName += implGetNameScemeForLocaleItem( pLocaleItem ); + return aFileName; +} + +OUString StringResourcePersistenceImpl::implGetPathForLocaleItem + ( LocaleItem const * pLocaleItem, const OUString& aNameBase, + std::u16string_view aLocation, bool bDefaultFile ) +{ + OUString aFileName = implGetFileNameForLocaleItem( pLocaleItem, aNameBase ); + INetURLObject aInetObj( aLocation ); + aInetObj.insertName( aFileName, true, INetURLObject::LAST_SEGMENT, INetURLObject::EncodeMechanism::All ); + if( bDefaultFile ) + aInetObj.setExtension( u"default" ); + else + aInetObj.setExtension( u"properties" ); + OUString aCompleteFileName = aInetObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + return aCompleteFileName; +} + +// White space according to Java property files specification in +// http://java.sun.com/j2se/1.4.2/docs/api/java/util/Properties.html#load(java.io.InputStream) +static bool isWhiteSpace( sal_Unicode c ) +{ + bool bWhite = ( c == 0x0020 || // space + c == 0x0009 || // tab + c == 0x000a || // line feed, not always handled by TextInputStream + c == 0x000d || // carriage return, not always handled by TextInputStream + c == 0x000C ); // form feed + return bWhite; +} + +static void skipWhites( const sal_Unicode* pBuf, sal_Int32 nLen, sal_Int32& ri ) +{ + while( ri < nLen ) + { + if( !isWhiteSpace( pBuf[ri] ) ) + break; + ri++; + } +} + +static bool isHexDigit( sal_Unicode c, sal_uInt16& nDigitVal ) +{ + bool bRet = true; + if( c >= '0' && c <= '9' ) + nDigitVal = c - '0'; + else if( c >= 'a' && c <= 'f' ) + nDigitVal = c - 'a' + 10; + else if( c >= 'A' && c <= 'F' ) + nDigitVal = c - 'A' + 10; + else + bRet = false; + return bRet; +} + +static sal_Unicode getEscapeChar( const sal_Unicode* pBuf, sal_Int32 nLen, sal_Int32& ri ) +{ + sal_Int32 i = ri; + + sal_Unicode cRet = 0; + sal_Unicode c = pBuf[i]; + switch( c ) + { + case 't': + cRet = 0x0009; + break; + case 'n': + cRet = 0x000a; + break; + case 'f': + cRet = 0x000c; + break; + case 'r': + cRet = 0x000d; + break; + case '\\': + cRet = '\\'; + break; + case 'u': + { + // Skip multiple u + i++; + while( i < nLen && pBuf[i] == 'u' ) + i++; + + // Process hex digits + sal_Int32 nDigitCount = 0; + sal_uInt16 nDigitVal; + while( i < nLen && isHexDigit( pBuf[i], nDigitVal ) ) + { + cRet = 16 * cRet + nDigitVal; + + nDigitCount++; + if( nDigitCount == 4 ) + { + // Write back position + ri = i; + break; + } + i++; + } + break; + } + default: + cRet = c; + } + + return cRet; +} + +static void CheckContinueInNextLine( const Reference< io::XTextInputStream2 >& xTextInputStream, + OUString& aLine, bool& bEscapePending, const sal_Unicode*& pBuf, + sal_Int32& nLen, sal_Int32& i ) +{ + if( !(i == nLen && bEscapePending) ) + return; + + bEscapePending = false; + + if( !xTextInputStream->isEOF() ) + { + aLine = xTextInputStream->readLine(); + nLen = aLine.getLength(); + pBuf = aLine.getStr(); + i = 0; + + skipWhites( pBuf, nLen, i ); + } +} + +bool StringResourcePersistenceImpl::implReadPropertiesFile + ( LocaleItem* pLocaleItem, const Reference< io::XInputStream >& xInputStream ) +{ + if( !xInputStream.is() || pLocaleItem == nullptr ) + return false; + + Reference< io::XTextInputStream2 > xTextInputStream = io::TextInputStream::create( m_xContext ); + + xTextInputStream->setInputStream( xInputStream ); + + OUString aEncodingStr = OUString::createFromAscii + ( rtl_getMimeCharsetFromTextEncoding( RTL_TEXTENCODING_ISO_8859_1 ) ); + xTextInputStream->setEncoding( aEncodingStr ); + + OUString aLine; + while( !xTextInputStream->isEOF() ) + { + aLine = xTextInputStream->readLine(); + + sal_Int32 nLen = aLine.getLength(); + if( 0 == nLen ) + continue; + const sal_Unicode* pBuf = aLine.getStr(); + OUStringBuffer aBuf; + sal_Unicode c = 0; + sal_Int32 i = 0; + + skipWhites( pBuf, nLen, i ); + if( i == nLen ) + continue; // line contains only white spaces + + // Comment? + c = pBuf[i]; + if( c == '#' || c == '!' ) + continue; + + // Scan key + OUString aResourceID; + bool bEscapePending = false; + bool bStrComplete = false; + while( i < nLen && !bStrComplete ) + { + c = pBuf[i]; + if( bEscapePending ) + { + aBuf.append( getEscapeChar( pBuf, nLen, i ) ); + bEscapePending = false; + } + else + { + if( c == '\\' ) + { + bEscapePending = true; + } + else + { + if( c == ':' || c == '=' || isWhiteSpace( c ) ) + bStrComplete = true; + else + aBuf.append( c ); + } + } + i++; + + CheckContinueInNextLine( xTextInputStream, aLine, bEscapePending, pBuf, nLen, i ); + if( i == nLen ) + bStrComplete = true; + + if( bStrComplete ) + aResourceID = aBuf.makeStringAndClear(); + } + + // Ignore lines with empty keys + if( aResourceID.isEmpty() ) + continue; + + // Scan value + skipWhites( pBuf, nLen, i ); + + OUString aValueStr; + bEscapePending = false; + bStrComplete = false; + while( i < nLen && !bStrComplete ) + { + c = pBuf[i]; + if( c == 0x000a || c == 0x000d ) // line feed/carriage return, not always handled by TextInputStream + { + i++; + } + else + { + if( bEscapePending ) + { + aBuf.append( getEscapeChar( pBuf, nLen, i ) ); + bEscapePending = false; + } + else if( c == '\\' ) + bEscapePending = true; + else + aBuf.append( c ); + i++; + + CheckContinueInNextLine( xTextInputStream, aLine, bEscapePending, pBuf, nLen, i ); + } + if( i == nLen ) + bStrComplete = true; + + if( bStrComplete ) + aValueStr = aBuf.makeStringAndClear(); + } + + // Push into table + pLocaleItem->m_aIdToStringMap[ aResourceID ] = aValueStr; + implScanIdForNumber( aResourceID ); + IdToIndexMap& rIndexMap = pLocaleItem->m_aIdToIndexMap; + rIndexMap[ aResourceID ] = pLocaleItem->m_nNextIndex++; + } + + return true; +} + + +static sal_Unicode getHexCharForDigit( sal_uInt16 nDigitVal ) +{ + sal_Unicode cRet = ( nDigitVal < 10 ) ? ('0' + nDigitVal) : ('a' + (nDigitVal-10)); + return cRet; +} + +static void implWriteCharToBuffer( OUStringBuffer& aBuf, sal_Unicode cu, bool bKey ) +{ + if( cu == '\\' ) + { + aBuf.append( '\\' ); + aBuf.append( '\\' ); + } + else if( cu == 0x000a ) + { + aBuf.append( '\\' ); + aBuf.append( 'n' ); + } + else if( cu == 0x000d ) + { + aBuf.append( '\\' ); + aBuf.append( 'r' ); + } + else if( bKey && cu == '=' ) + { + aBuf.append( '\\' ); + aBuf.append( '=' ); + } + else if( bKey && cu == ':' ) + { + aBuf.append( '\\' ); + aBuf.append( ':' ); + } + // ISO/IEC 8859-1 range according to: + // http://en.wikipedia.org/wiki/ISO/IEC_8859-1 + else if( cu >= 0x20 && cu <= 0x7e ) + //TODO: Check why (cu >= 0xa0 && cu <= 0xFF) + //is encoded in sample properties files + //else if( (cu >= 0x20 && cu <= 0x7e) || + // (cu >= 0xa0 && cu <= 0xFF) ) + { + aBuf.append( cu ); + } + else + { + // Unicode encoding + aBuf.append( '\\' ); + aBuf.append( 'u' ); + + sal_uInt16 nVal = cu; + for( sal_uInt16 i = 0 ; i < 4 ; i++ ) + { + sal_uInt16 nDigit = nVal / 0x1000; + nVal -= nDigit * 0x1000; + nVal *= 0x10; + aBuf.append( getHexCharForDigit( nDigit ) ); + } + } +} + +static void implWriteStringWithEncoding( const OUString& aStr, + Reference< io::XTextOutputStream2 > const & xTextOutputStream, bool bKey ) +{ + static const sal_Unicode cLineFeed = 0xa; + + OUStringBuffer aBuf; + sal_Int32 nLen = aStr.getLength(); + const sal_Unicode* pSrc = aStr.getStr(); + for( sal_Int32 i = 0 ; i < nLen ; i++ ) + { + sal_Unicode cu = pSrc[i]; + implWriteCharToBuffer( aBuf, cu, bKey ); + // TODO?: split long lines + } + if( !bKey ) + aBuf.append( cLineFeed ); + + OUString aWriteStr = aBuf.makeStringAndClear(); + xTextOutputStream->writeString( aWriteStr ); +} + +bool StringResourcePersistenceImpl::implWritePropertiesFile( LocaleItem const * pLocaleItem, + const Reference< io::XOutputStream >& xOutputStream, const OUString& aComment ) +{ + if( !xOutputStream.is() || pLocaleItem == nullptr ) + return false; + + bool bSuccess = false; + Reference< io::XTextOutputStream2 > xTextOutputStream = io::TextOutputStream::create(m_xContext); + + xTextOutputStream->setOutputStream( xOutputStream ); + + OUString aEncodingStr = OUString::createFromAscii + ( rtl_getMimeCharsetFromTextEncoding( RTL_TEXTENCODING_ISO_8859_1 ) ); + xTextOutputStream->setEncoding( aEncodingStr ); + + xTextOutputStream->writeString( aComment ); + xTextOutputStream->writeString( "\n" ); + + const IdToStringMap& rHashMap = pLocaleItem->m_aIdToStringMap; + if( !rHashMap.empty() ) + { + // Sort ids according to read order + const IdToIndexMap& rIndexMap = pLocaleItem->m_aIdToIndexMap; + + // Find max/min index + auto itMinMax = std::minmax_element(rIndexMap.begin(), rIndexMap.end(), + [](const IdToIndexMap::value_type& a, const IdToIndexMap::value_type& b) { return a.second < b.second; }); + sal_Int32 nMinIndex = itMinMax.first->second; + sal_Int32 nMaxIndex = itMinMax.second->second; + sal_Int32 nTabSize = nMaxIndex - nMinIndex + 1; + + // Create sorted array of pointers to the id strings + std::unique_ptr<const OUString*[]> pIdPtrs( new const OUString*[nTabSize] ); + for(sal_Int32 i = 0 ; i < nTabSize ; i++ ) + pIdPtrs[i] = nullptr; + for( const auto& rIndex : rIndexMap ) + { + sal_Int32 nIndex = rIndex.second; + pIdPtrs[nIndex - nMinIndex] = &(rIndex.first); + } + + // Write lines in correct order + for(sal_Int32 i = 0 ; i < nTabSize ; i++ ) + { + const OUString* pStr = pIdPtrs[i]; + if( pStr != nullptr ) + { + OUString aResourceID = *pStr; + IdToStringMap::const_iterator it = rHashMap.find( aResourceID ); + if( it != rHashMap.end() ) + { + implWriteStringWithEncoding( aResourceID, xTextOutputStream, true ); + xTextOutputStream->writeString( "=" ); + OUString aValStr = (*it).second; + implWriteStringWithEncoding( aValStr, xTextOutputStream, false ); + } + } + } + } + + bSuccess = true; + + return bSuccess; +} + + +// StringResourceWithStorageImpl + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +scripting_StringResourceWithStorageImpl_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new StringResourceWithStorageImpl(context)); +} + + +StringResourceWithStorageImpl::StringResourceWithStorageImpl( const Reference< XComponentContext >& rxContext ) + : StringResourceWithStorageImpl_BASE( rxContext ) + , m_bStorageChanged( false ) +{ +} + + +StringResourceWithStorageImpl::~StringResourceWithStorageImpl() +{ +} + + +// XServiceInfo + + +OUString StringResourceWithStorageImpl::getImplementationName( ) +{ + return "com.sun.star.comp.scripting.StringResourceWithStorage"; +} + +sal_Bool StringResourceWithStorageImpl::supportsService( const OUString& rServiceName ) +{ + return cppu::supportsService(this, rServiceName); +} + +Sequence< OUString > StringResourceWithStorageImpl::getSupportedServiceNames( ) +{ + return { "com.sun.star.resource.StringResourceWithStorage" }; +} + + +// XInitialization + + +void StringResourceWithStorageImpl::initialize( const Sequence< Any >& aArguments ) +{ + std::unique_lock aGuard( m_aMutex ); + + if ( aArguments.getLength() != 5 ) + { + throw RuntimeException( + "StringResourceWithStorageImpl::initialize: invalid number of arguments!" ); + } + + bool bOk = (aArguments[0] >>= m_xStorage); + if( bOk && !m_xStorage.is() ) + bOk = false; + + if( !bOk ) + { + throw IllegalArgumentException( "StringResourceWithStorageImpl::initialize: invalid storage", Reference< XInterface >(), 0 ); + } + + implInitializeCommonParameters( aGuard, aArguments ); +} + + +// Forwarding calls to base class + +// XModifyBroadcaster +void StringResourceWithStorageImpl::addModifyListener( const Reference< XModifyListener >& aListener ) +{ + StringResourceImpl::addModifyListener( aListener ); +} +void StringResourceWithStorageImpl::removeModifyListener( const Reference< XModifyListener >& aListener ) +{ + StringResourceImpl::removeModifyListener( aListener ); +} + +// XStringResourceResolver +OUString StringResourceWithStorageImpl::resolveString( const OUString& ResourceID ) +{ + return StringResourceImpl::resolveString( ResourceID ) ; +} +OUString StringResourceWithStorageImpl::resolveStringForLocale( const OUString& ResourceID, const Locale& locale ) +{ + return StringResourceImpl::resolveStringForLocale( ResourceID, locale ); +} +sal_Bool StringResourceWithStorageImpl::hasEntryForId( const OUString& ResourceID ) +{ + return StringResourceImpl::hasEntryForId( ResourceID ) ; +} +sal_Bool StringResourceWithStorageImpl::hasEntryForIdAndLocale( const OUString& ResourceID, + const Locale& locale ) +{ + return StringResourceImpl::hasEntryForIdAndLocale( ResourceID, locale ); +} +Sequence< OUString > StringResourceWithStorageImpl::getResourceIDs( ) +{ + return StringResourceImpl::getResourceIDs(); +} +Sequence< OUString > StringResourceWithStorageImpl::getResourceIDsForLocale + ( const Locale& locale ) +{ + return StringResourceImpl::getResourceIDsForLocale( locale ); +} +Locale StringResourceWithStorageImpl::getCurrentLocale() +{ + return StringResourceImpl::getCurrentLocale(); +} +Locale StringResourceWithStorageImpl::getDefaultLocale( ) +{ + return StringResourceImpl::getDefaultLocale(); +} +Sequence< Locale > StringResourceWithStorageImpl::getLocales( ) +{ + return StringResourceImpl::getLocales(); +} + +// XStringResourceManager +sal_Bool StringResourceWithStorageImpl::isReadOnly() +{ + return StringResourceImpl::isReadOnly(); +} +void StringResourceWithStorageImpl::setCurrentLocale( const Locale& locale, sal_Bool FindClosestMatch ) +{ + StringResourceImpl::setCurrentLocale( locale, FindClosestMatch ); +} +void StringResourceWithStorageImpl::setDefaultLocale( const Locale& locale ) +{ + StringResourceImpl::setDefaultLocale( locale ); +} +void StringResourceWithStorageImpl::setString( const OUString& ResourceID, const OUString& Str ) +{ + StringResourceImpl::setString( ResourceID, Str ); +} +void StringResourceWithStorageImpl::setStringForLocale + ( const OUString& ResourceID, const OUString& Str, const Locale& locale ) +{ + StringResourceImpl::setStringForLocale( ResourceID, Str, locale ); +} +void StringResourceWithStorageImpl::removeId( const OUString& ResourceID ) +{ + StringResourceImpl::removeId( ResourceID ); +} +void StringResourceWithStorageImpl::removeIdForLocale( const OUString& ResourceID, const Locale& locale ) +{ + StringResourceImpl::removeIdForLocale( ResourceID, locale ); +} +void StringResourceWithStorageImpl::newLocale( const Locale& locale ) +{ + StringResourceImpl::newLocale( locale ); +} +void StringResourceWithStorageImpl::removeLocale( const Locale& locale ) +{ + StringResourceImpl::removeLocale( locale ); +} +sal_Int32 StringResourceWithStorageImpl::getUniqueNumericId( ) +{ + return StringResourceImpl::getUniqueNumericId(); +} + +// XStringResourcePersistence +void StringResourceWithStorageImpl::store() +{ + std::unique_lock aGuard( m_aMutex ); + implCheckReadOnly( "StringResourceWithStorageImpl::store(): Read only" ); + + bool bStoreAll = m_bStorageChanged; + m_bStorageChanged = false; + if( !m_bModified && !bStoreAll ) + return; + + implStoreAtStorage( m_aNameBase, m_aComment, m_xStorage, true/*bUsedForStore*/, bStoreAll ); + m_bModified = false; +} + +sal_Bool StringResourceWithStorageImpl::isModified( ) +{ + return StringResourcePersistenceImpl::isModified(); +} +void StringResourceWithStorageImpl::setComment( const OUString& Comment ) +{ + StringResourcePersistenceImpl::setComment( Comment ); +} +void StringResourceWithStorageImpl::storeToStorage( const Reference< XStorage >& Storage, + const OUString& NameBase, const OUString& Comment ) +{ + StringResourcePersistenceImpl::storeToStorage( Storage, NameBase, Comment ); +} +void StringResourceWithStorageImpl::storeToURL( const OUString& URL, + const OUString& NameBase, const OUString& Comment, + const Reference< css::task::XInteractionHandler >& Handler ) +{ + StringResourcePersistenceImpl::storeToURL( URL, NameBase, Comment, Handler ); +} +Sequence< ::sal_Int8 > StringResourceWithStorageImpl::exportBinary( ) +{ + return StringResourcePersistenceImpl::exportBinary(); +} +void StringResourceWithStorageImpl::importBinary( const Sequence< ::sal_Int8 >& Data ) +{ + StringResourcePersistenceImpl::importBinary( Data ); +} + + +// XStringResourceWithStorage + +void StringResourceWithStorageImpl::storeAsStorage( const Reference< XStorage >& Storage ) +{ + setStorage( Storage ); + store(); +} + +void StringResourceWithStorageImpl::setStorage( const Reference< XStorage >& Storage ) +{ + std::unique_lock aGuard( m_aMutex ); + + if( !Storage.is() ) + { + throw IllegalArgumentException( "StringResourceWithStorageImpl::setStorage: invalid storage", Reference< XInterface >(), 0 ); + } + + implLoadAllLocales(); + + m_xStorage = Storage; + m_bStorageChanged = true; +} + + +// Private helper methods + + +// Scan locale properties files +void StringResourceWithStorageImpl::implScanLocales() +{ + if( m_xStorage.is() ) + { + Sequence< OUString > aContentSeq = m_xStorage->getElementNames(); + implScanLocaleNames( aContentSeq ); + } + + implLoadAllLocales(); +} + +// Loading +bool StringResourceWithStorageImpl::implLoadLocale( LocaleItem* pLocaleItem ) +{ + bool bSuccess = false; + try + { + OUString aStreamName = implGetFileNameForLocaleItem( pLocaleItem, m_aNameBase ) + ".properties"; + + Reference< io::XStream > xElementStream = + m_xStorage->openStreamElement( aStreamName, ElementModes::READ ); + + if( xElementStream.is() ) + { + Reference< io::XInputStream > xInputStream = xElementStream->getInputStream(); + if( xInputStream.is() ) + { + bSuccess = StringResourcePersistenceImpl::implReadPropertiesFile( pLocaleItem, xInputStream ); + xInputStream->closeInput(); + } + } + } + catch( uno::Exception& ) + {} + + return bSuccess; +} + + +// StringResourceWithLocationImpl + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +scripting_StringResourceWithLocationImpl_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new StringResourceWithLocationImpl(context)); +} + + + +StringResourceWithLocationImpl::StringResourceWithLocationImpl( const Reference< XComponentContext >& rxContext ) + : StringResourceWithLocationImpl_BASE( rxContext ) + , m_bLocationChanged( false ) +{ +} + + +StringResourceWithLocationImpl::~StringResourceWithLocationImpl() +{ +} + + +// XServiceInfo + + +OUString StringResourceWithLocationImpl::getImplementationName( ) +{ + return "com.sun.star.comp.scripting.StringResourceWithLocation"; +} + +sal_Bool StringResourceWithLocationImpl::supportsService( const OUString& rServiceName ) +{ + return cppu::supportsService(this, rServiceName); +} + +Sequence< OUString > StringResourceWithLocationImpl::getSupportedServiceNames( ) +{ + return { "com.sun.star.resource.StringResourceWithLocation" }; +} + + +// XInitialization + + +void StringResourceWithLocationImpl::initialize( const Sequence< Any >& aArguments ) +{ + std::unique_lock aGuard( m_aMutex ); + + if ( aArguments.getLength() != 6 ) + { + throw RuntimeException( + "XInitialization::initialize: invalid number of arguments!" ); + } + + bool bOk = (aArguments[0] >>= m_aLocation); + sal_Int32 nLen = m_aLocation.getLength(); + if( bOk && nLen == 0 ) + { + bOk = false; + } + else + { + if( m_aLocation[nLen - 1] != '/' ) + m_aLocation += "/"; + } + + if( !bOk ) + { + throw IllegalArgumentException( "XInitialization::initialize: invalid URL", Reference< XInterface >(), 0 ); + } + + + bOk = (aArguments[5] >>= m_xInteractionHandler); + if( !bOk ) + { + throw IllegalArgumentException( "StringResourceWithStorageImpl::initialize: invalid type", Reference< XInterface >(), 5 ); + } + + implInitializeCommonParameters( aGuard, aArguments ); +} + + +// Forwarding calls to base class + +// XModifyBroadcaster +void StringResourceWithLocationImpl::addModifyListener( const Reference< XModifyListener >& aListener ) +{ + StringResourceImpl::addModifyListener( aListener ); +} +void StringResourceWithLocationImpl::removeModifyListener( const Reference< XModifyListener >& aListener ) +{ + StringResourceImpl::removeModifyListener( aListener ); +} + +// XStringResourceResolver +OUString StringResourceWithLocationImpl::resolveString( const OUString& ResourceID ) +{ + return StringResourceImpl::resolveString( ResourceID ) ; +} +OUString StringResourceWithLocationImpl::resolveStringForLocale( const OUString& ResourceID, const Locale& locale ) +{ + return StringResourceImpl::resolveStringForLocale( ResourceID, locale ); +} +sal_Bool StringResourceWithLocationImpl::hasEntryForId( const OUString& ResourceID ) +{ + return StringResourceImpl::hasEntryForId( ResourceID ) ; +} +sal_Bool StringResourceWithLocationImpl::hasEntryForIdAndLocale( const OUString& ResourceID, + const Locale& locale ) +{ + return StringResourceImpl::hasEntryForIdAndLocale( ResourceID, locale ); +} +Sequence< OUString > StringResourceWithLocationImpl::getResourceIDs( ) +{ + return StringResourceImpl::getResourceIDs(); +} +Sequence< OUString > StringResourceWithLocationImpl::getResourceIDsForLocale + ( const Locale& locale ) +{ + return StringResourceImpl::getResourceIDsForLocale( locale ); +} +Locale StringResourceWithLocationImpl::getCurrentLocale() +{ + return StringResourceImpl::getCurrentLocale(); +} +Locale StringResourceWithLocationImpl::getDefaultLocale( ) +{ + return StringResourceImpl::getDefaultLocale(); +} +Sequence< Locale > StringResourceWithLocationImpl::getLocales( ) +{ + return StringResourceImpl::getLocales(); +} + +// XStringResourceManager +sal_Bool StringResourceWithLocationImpl::isReadOnly() +{ + return StringResourceImpl::isReadOnly(); +} +void StringResourceWithLocationImpl::setCurrentLocale( const Locale& locale, sal_Bool FindClosestMatch ) +{ + StringResourceImpl::setCurrentLocale( locale, FindClosestMatch ); +} +void StringResourceWithLocationImpl::setDefaultLocale( const Locale& locale ) +{ + StringResourceImpl::setDefaultLocale( locale ); +} +void StringResourceWithLocationImpl::setString( const OUString& ResourceID, const OUString& Str ) +{ + StringResourceImpl::setString( ResourceID, Str ); +} +void StringResourceWithLocationImpl::setStringForLocale + ( const OUString& ResourceID, const OUString& Str, const Locale& locale ) +{ + StringResourceImpl::setStringForLocale( ResourceID, Str, locale ); +} +void StringResourceWithLocationImpl::removeId( const OUString& ResourceID ) +{ + StringResourceImpl::removeId( ResourceID ); +} +void StringResourceWithLocationImpl::removeIdForLocale( const OUString& ResourceID, const Locale& locale ) +{ + StringResourceImpl::removeIdForLocale( ResourceID, locale ); +} +void StringResourceWithLocationImpl::newLocale( const Locale& locale ) +{ + StringResourceImpl::newLocale( locale ); +} +void StringResourceWithLocationImpl::removeLocale( const Locale& locale ) +{ + StringResourceImpl::removeLocale( locale ); +} +sal_Int32 StringResourceWithLocationImpl::getUniqueNumericId( ) +{ + return StringResourceImpl::getUniqueNumericId(); +} + +// XStringResourcePersistence +void StringResourceWithLocationImpl::store() +{ + std::unique_lock aGuard( m_aMutex ); + implCheckReadOnly( "StringResourceWithLocationImpl::store(): Read only" ); + + bool bStoreAll = m_bLocationChanged; + m_bLocationChanged = false; + if( !m_bModified && !bStoreAll ) + return; + + Reference< ucb::XSimpleFileAccess3 > xFileAccess = getFileAccessImpl(); + implStoreAtLocation( m_aLocation, m_aNameBase, m_aComment, + xFileAccess, true/*bUsedForStore*/, bStoreAll ); + m_bModified = false; +} + +sal_Bool StringResourceWithLocationImpl::isModified( ) +{ + return StringResourcePersistenceImpl::isModified(); +} +void StringResourceWithLocationImpl::setComment( const OUString& Comment ) +{ + StringResourcePersistenceImpl::setComment( Comment ); +} +void StringResourceWithLocationImpl::storeToStorage( const Reference< XStorage >& Storage, + const OUString& NameBase, const OUString& Comment ) +{ + StringResourcePersistenceImpl::storeToStorage( Storage, NameBase, Comment ); +} +void StringResourceWithLocationImpl::storeToURL( const OUString& URL, + const OUString& NameBase, const OUString& Comment, + const Reference< css::task::XInteractionHandler >& Handler ) +{ + StringResourcePersistenceImpl::storeToURL( URL, NameBase, Comment, Handler ); +} +Sequence< ::sal_Int8 > StringResourceWithLocationImpl::exportBinary( ) +{ + return StringResourcePersistenceImpl::exportBinary(); +} +void StringResourceWithLocationImpl::importBinary( const Sequence< ::sal_Int8 >& Data ) +{ + StringResourcePersistenceImpl::importBinary( Data ); +} + + +// XStringResourceWithLocation + +// XStringResourceWithLocation +void StringResourceWithLocationImpl::storeAsURL( const OUString& URL ) +{ + setURL( URL ); + store(); +} + +void StringResourceWithLocationImpl::setURL( const OUString& URL ) +{ + std::unique_lock aGuard( m_aMutex ); + implCheckReadOnly( "StringResourceWithLocationImpl::setURL(): Read only" ); + + sal_Int32 nLen = URL.getLength(); + if( nLen == 0 ) + { + throw IllegalArgumentException( "StringResourceWithLocationImpl::setURL: invalid URL", Reference< XInterface >(), 0 ); + } + + implLoadAllLocales(); + + // Delete files at old location + implStoreAtLocation( m_aLocation, m_aNameBase, m_aComment, + getFileAccessImpl(), false/*bUsedForStore*/, false/*bStoreAll*/, true/*bKillAll*/ ); + + m_aLocation = URL; + m_bLocationChanged = true; +} + + +// Private helper methods + + +// Scan locale properties files +void StringResourceWithLocationImpl::implScanLocales() +{ + const Reference< ucb::XSimpleFileAccess3 > xFileAccess = getFileAccessImpl(); + if( xFileAccess.is() && xFileAccess->isFolder( m_aLocation ) ) + { + Sequence< OUString > aContentSeq = xFileAccess->getFolderContents( m_aLocation, false ); + implScanLocaleNames( aContentSeq ); + } +} + +// Loading +bool StringResourceWithLocationImpl::implLoadLocale( LocaleItem* pLocaleItem ) +{ + bool bSuccess = false; + + const Reference< ucb::XSimpleFileAccess3 > xFileAccess = getFileAccessImpl(); + if( xFileAccess.is() ) + { + OUString aCompleteFileName = + implGetPathForLocaleItem( pLocaleItem, m_aNameBase, m_aLocation ); + + Reference< io::XInputStream > xInputStream; + try + { + xInputStream = xFileAccess->openFileRead( aCompleteFileName ); + } + catch( Exception& ) + {} + if( xInputStream.is() ) + { + bSuccess = StringResourcePersistenceImpl::implReadPropertiesFile( pLocaleItem, xInputStream ); + xInputStream->closeInput(); + } + } + + return bSuccess; +} + +const Reference< ucb::XSimpleFileAccess3 > & StringResourceWithLocationImpl::getFileAccessImpl() +{ + if( !m_xSFI.is() ) + { + m_xSFI = ucb::SimpleFileAccess::create(m_xContext); + + if( m_xSFI.is() && m_xInteractionHandler.is() ) + m_xSFI->setInteractionHandler( m_xInteractionHandler ); + } + return m_xSFI; +} + +} // namespace stringresource + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/scripting/source/stringresource/stringresource.hxx b/scripting/source/stringresource/stringresource.hxx new file mode 100644 index 000000000..7722da62f --- /dev/null +++ b/scripting/source/stringresource/stringresource.hxx @@ -0,0 +1,485 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/resource/XStringResourceWithStorage.hpp> +#include <com/sun/star/resource/XStringResourceWithLocation.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/ucb/XSimpleFileAccess3.hpp> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/io/XOutputStream.hpp> +#include <cppuhelper/implbase.hxx> +#include <comphelper/interfacecontainer4.hxx> +#include <mutex> +#include <unordered_map> +#include <vector> + + +namespace stringresource +{ + + +// class stringresourceImpl + + +// Hashtable to map string ids to string +typedef std::unordered_map +< + OUString, + OUString +> +IdToStringMap; + +typedef std::unordered_map +< + OUString, + sal_Int32 +> +IdToIndexMap; + + +struct LocaleItem +{ + css::lang::Locale m_locale; + IdToStringMap m_aIdToStringMap; + IdToIndexMap m_aIdToIndexMap; + sal_Int32 m_nNextIndex; + bool m_bLoaded; + bool m_bModified; + + LocaleItem( css::lang::Locale locale, bool bLoaded=true ) + : m_locale( locale ) + , m_nNextIndex( 0 ) + , m_bLoaded( bLoaded ) + , m_bModified( false ) + {} +}; + +typedef ::cppu::WeakImplHelper< + css::lang::XServiceInfo, + css::resource::XStringResourceManager > StringResourceImpl_BASE; + +class StringResourceImpl : public StringResourceImpl_BASE +{ +protected: + std::mutex m_aMutex; + css::uno::Reference< css::uno::XComponentContext > m_xContext; + + LocaleItem* m_pCurrentLocaleItem; + LocaleItem* m_pDefaultLocaleItem; + bool m_bDefaultModified; + + ::comphelper::OInterfaceContainerHelper4<css::util::XModifyListener> m_aListenerContainer; + + std::vector< std::unique_ptr<LocaleItem> > m_aLocaleItemVector; + std::vector< std::unique_ptr<LocaleItem> > m_aDeletedLocaleItemVector; + std::vector< std::unique_ptr<LocaleItem> > m_aChangedDefaultLocaleVector; + + bool m_bModified; + bool m_bReadOnly; + + sal_Int32 m_nNextUniqueNumericId; + + // Scans ResourceID to start with number and adapt m_nNextUniqueNumericId + void implScanIdForNumber( const OUString& ResourceID ); + const static sal_Int32 UNIQUE_NUMBER_NEEDS_INITIALISATION = -1; + + // Checks read only status and throws exception if it's true + /// @throws css::lang::NoSupportException + void implCheckReadOnly( const char* pExceptionMsg ); + + // Returns the LocalItem for a given locale, if it exists, otherwise NULL + // This method compares the locales exactly, no closest match search is performed + /// @throws css::lang::IllegalArgumentException + LocaleItem* getItemForLocale( const css::lang::Locale& locale, bool bException ); + + // Returns the LocalItem for a given locale, if it exists, otherwise NULL + // This method performs a closest match search, at least the language must match + LocaleItem* getClosestMatchItemForLocale( const css::lang::Locale& locale ); + /// @throws css::lang::IllegalArgumentException + /// @throws css::uno::RuntimeException + void implSetCurrentLocale( std::unique_lock<std::mutex>& rGuard, const css::lang::Locale& locale, + bool FindClosestMatch, bool bUseDefaultIfNoMatch ); + + void implModified(std::unique_lock<std::mutex>&); + void implNotifyListeners(std::unique_lock<std::mutex>&); + + //=== Impl methods for ...ForLocale methods === + /// @throws css::resource::MissingResourceException + OUString implResolveString( const OUString& ResourceID, LocaleItem* pLocaleItem ); + bool implHasEntryForId( const OUString& ResourceID, LocaleItem* pLocaleItem ); + css::uno::Sequence< OUString > implGetResourceIDs( LocaleItem* pLocaleItem ); + void implSetString( std::unique_lock<std::mutex>& rGuard, const OUString& ResourceID, + const OUString& Str, LocaleItem* pLocaleItem ); + /// @throws css::resource::MissingResourceException + void implRemoveId( std::unique_lock<std::mutex>& rGuard, const OUString& ResourceID, LocaleItem* pLocaleItem ); + + // Method to load a locale if necessary, returns true if loading was + // successful. Default implementation in base class just returns true. + virtual bool loadLocale( LocaleItem* pLocaleItem ); + + virtual void implLoadAllLocales(); + +public: + explicit StringResourceImpl( + const css::uno::Reference< css::uno::XComponentContext >& rxContext ); + virtual ~StringResourceImpl() override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + + // XModifyBroadcaster + virtual void SAL_CALL addModifyListener( const css::uno::Reference< css::util::XModifyListener >& aListener ) override; + virtual void SAL_CALL removeModifyListener( const css::uno::Reference< css::util::XModifyListener >& aListener ) override; + + // XStringResourceResolver + virtual OUString SAL_CALL resolveString( const OUString& ResourceID ) override; + virtual OUString SAL_CALL resolveStringForLocale( const OUString& ResourceID, + const css::lang::Locale& locale ) override; + virtual sal_Bool SAL_CALL hasEntryForId( const OUString& ResourceID ) override; + virtual sal_Bool SAL_CALL hasEntryForIdAndLocale( const OUString& ResourceID, + const css::lang::Locale& locale ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getResourceIDs( ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getResourceIDsForLocale + ( const css::lang::Locale& locale ) override; + virtual css::lang::Locale SAL_CALL getCurrentLocale( ) override; + virtual css::lang::Locale SAL_CALL getDefaultLocale( ) override; + virtual css::uno::Sequence< css::lang::Locale > SAL_CALL getLocales( ) override; + + // XStringResourceManager + virtual sal_Bool SAL_CALL isReadOnly() override; + virtual void SAL_CALL setCurrentLocale( const css::lang::Locale& locale, sal_Bool FindClosestMatch ) override; + virtual void SAL_CALL setDefaultLocale( const css::lang::Locale& locale ) override; + virtual void SAL_CALL setString( const OUString& ResourceID, const OUString& Str ) override; + virtual void SAL_CALL setStringForLocale( const OUString& ResourceID, const OUString& Str, + const css::lang::Locale& locale ) override; + virtual void SAL_CALL removeId( const OUString& ResourceID ) override; + virtual void SAL_CALL removeIdForLocale( const OUString& ResourceID, + const css::lang::Locale& locale ) override; + virtual void SAL_CALL newLocale( const css::lang::Locale& locale ) override; + virtual void SAL_CALL removeLocale( const css::lang::Locale& locale ) override; + virtual ::sal_Int32 SAL_CALL getUniqueNumericId( ) override; + }; + +typedef ::cppu::ImplInheritanceHelper< + StringResourceImpl, + css::resource::XStringResourcePersistence > StringResourcePersistenceImpl_BASE; + +class BinaryOutput; + +class StringResourcePersistenceImpl : public StringResourcePersistenceImpl_BASE +{ +protected: + OUString m_aNameBase; + OUString m_aComment; + + /// @throws css::uno::Exception + /// @throws css::uno::RuntimeException + void implInitializeCommonParameters( std::unique_lock<std::mutex>& rGuard, const css::uno::Sequence< css::uno::Any >& aArguments ); + + // Scan locale properties files + virtual void implScanLocales(); + + // Method to load a locale if necessary, returns true if loading was successful + virtual bool loadLocale( LocaleItem* pLocaleItem ) override; + + // does the actual loading + virtual bool implLoadLocale( LocaleItem* pLocaleItem ); + + virtual void implLoadAllLocales() override; + + void implScanLocaleNames( const css::uno::Sequence< OUString >& aContentSeq ); + static OUString implGetFileNameForLocaleItem( LocaleItem const * pLocaleItem, const OUString& aNameBase ); + static OUString implGetPathForLocaleItem( LocaleItem const * pLocaleItem, const OUString& aNameBase, + std::u16string_view aLocation, bool bDefaultFile=false ); + + bool implReadPropertiesFile( LocaleItem* pLocaleItem, + const css::uno::Reference< css::io::XInputStream >& xInput ); + + bool implWritePropertiesFile( LocaleItem const * pLocaleItem, + const css::uno::Reference< css::io::XOutputStream >& xOutputStream, + const OUString& aComment ); + + void implWriteLocaleBinary( LocaleItem* pLocaleItem, BinaryOutput& rOut ); + + /// @throws css::uno::Exception + /// @throws css::uno::RuntimeException + void implStoreAtStorage + ( + const OUString& aNameBase, + const OUString& aComment, + const css::uno::Reference< css::embed::XStorage >& Storage, + bool bUsedForStore, + bool bStoreAll + ); + + /// @throws css::uno::Exception + /// @throws css::uno::RuntimeException + void implKillRemovedLocaleFiles + ( + std::u16string_view Location, + const OUString& aNameBase, + const css::uno::Reference< css::ucb::XSimpleFileAccess3 >& xFileAccess + ); + + /// @throws css::uno::Exception + /// @throws css::uno::RuntimeException + void implKillChangedDefaultFiles + ( + std::u16string_view Location, + const OUString& aNameBase, + const css::uno::Reference< css::ucb::XSimpleFileAccess3 >& xFileAccess + ); + + /// @throws css::uno::Exception + /// @throws css::uno::RuntimeException + void implStoreAtLocation + ( + std::u16string_view Location, + const OUString& aNameBase, + const OUString& aComment, + const css::uno::Reference< css::ucb::XSimpleFileAccess3 >& xFileAccess, + bool bUsedForStore, + bool bStoreAll, + bool bKillAll = false + ); + +public: + explicit StringResourcePersistenceImpl( + const css::uno::Reference< css::uno::XComponentContext >& rxContext ); + virtual ~StringResourcePersistenceImpl() override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + + // XModifyBroadcaster + virtual void SAL_CALL addModifyListener( const css::uno::Reference< css::util::XModifyListener >& aListener ) override; + virtual void SAL_CALL removeModifyListener( const css::uno::Reference< css::util::XModifyListener >& aListener ) override; + + // XStringResourceResolver + virtual OUString SAL_CALL resolveString( const OUString& ResourceID ) override; + virtual OUString SAL_CALL resolveStringForLocale( const OUString& ResourceID, + const css::lang::Locale& locale ) override; + virtual sal_Bool SAL_CALL hasEntryForId( const OUString& ResourceID ) override; + virtual sal_Bool SAL_CALL hasEntryForIdAndLocale( const OUString& ResourceID, + const css::lang::Locale& locale ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getResourceIDs( ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getResourceIDsForLocale + ( const css::lang::Locale& locale ) override; + virtual css::lang::Locale SAL_CALL getCurrentLocale( ) override; + virtual css::lang::Locale SAL_CALL getDefaultLocale( ) override; + virtual css::uno::Sequence< css::lang::Locale > SAL_CALL getLocales( ) override; + + // XStringResourceManager + virtual sal_Bool SAL_CALL isReadOnly() override; + virtual void SAL_CALL setCurrentLocale( const css::lang::Locale& locale, sal_Bool FindClosestMatch ) override; + virtual void SAL_CALL setDefaultLocale( const css::lang::Locale& locale ) override; + virtual void SAL_CALL setString( const OUString& ResourceID, const OUString& Str ) override; + virtual void SAL_CALL setStringForLocale( const OUString& ResourceID, const OUString& Str, + const css::lang::Locale& locale ) override; + virtual void SAL_CALL removeId( const OUString& ResourceID ) override; + virtual void SAL_CALL removeIdForLocale( const OUString& ResourceID, + const css::lang::Locale& locale ) override; + virtual void SAL_CALL newLocale( const css::lang::Locale& locale ) override; + virtual void SAL_CALL removeLocale( const css::lang::Locale& locale ) override; + virtual ::sal_Int32 SAL_CALL getUniqueNumericId( ) override; + + // XStringResourcePersistence + virtual void SAL_CALL store( ) override; + virtual sal_Bool SAL_CALL isModified( ) override; + virtual void SAL_CALL setComment( const OUString& Comment ) override; + virtual void SAL_CALL storeToStorage + ( const css::uno::Reference< css::embed::XStorage >& Storage, + const OUString& NameBase, const OUString& Comment ) override; + virtual void SAL_CALL storeToURL( const OUString& URL, const OUString& NameBase, + const OUString& Comment, const css::uno::Reference + < css::task::XInteractionHandler >& Handler ) override; + virtual css::uno::Sequence< ::sal_Int8 > SAL_CALL exportBinary( ) override; + virtual void SAL_CALL importBinary( const css::uno::Sequence< ::sal_Int8 >& Data ) override; +}; + + +typedef ::cppu::ImplInheritanceHelper< + StringResourcePersistenceImpl, + css::lang::XInitialization, + css::resource::XStringResourceWithStorage > StringResourceWithStorageImpl_BASE; + +class StringResourceWithStorageImpl : public StringResourceWithStorageImpl_BASE +{ + css::uno::Reference< css::embed::XStorage > m_xStorage; + bool m_bStorageChanged; + + virtual void implScanLocales() override; + virtual bool implLoadLocale( LocaleItem* pLocaleItem ) override; + +public: + explicit StringResourceWithStorageImpl( const css::uno::Reference< css::uno::XComponentContext >& rxContext ); + virtual ~StringResourceWithStorageImpl() override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + + // XInitialization + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override; + + // XModifyBroadcaster + virtual void SAL_CALL addModifyListener( const css::uno::Reference< css::util::XModifyListener >& aListener ) override; + virtual void SAL_CALL removeModifyListener( const css::uno::Reference< css::util::XModifyListener >& aListener ) override; + + // XStringResourceResolver + virtual OUString SAL_CALL resolveString( const OUString& ResourceID ) override; + virtual OUString SAL_CALL resolveStringForLocale( const OUString& ResourceID, + const css::lang::Locale& locale ) override; + virtual sal_Bool SAL_CALL hasEntryForId( const OUString& ResourceID ) override; + virtual sal_Bool SAL_CALL hasEntryForIdAndLocale( const OUString& ResourceID, + const css::lang::Locale& locale ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getResourceIDs( ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getResourceIDsForLocale + ( const css::lang::Locale& locale ) override; + virtual css::lang::Locale SAL_CALL getCurrentLocale( ) override; + virtual css::lang::Locale SAL_CALL getDefaultLocale( ) override; + virtual css::uno::Sequence< css::lang::Locale > SAL_CALL getLocales( ) override; + + // XStringResourceManager + virtual sal_Bool SAL_CALL isReadOnly() override; + virtual void SAL_CALL setCurrentLocale( const css::lang::Locale& locale, sal_Bool FindClosestMatch ) override; + virtual void SAL_CALL setDefaultLocale( const css::lang::Locale& locale ) override; + virtual void SAL_CALL setString( const OUString& ResourceID, const OUString& Str ) override; + virtual void SAL_CALL setStringForLocale( const OUString& ResourceID, const OUString& Str, + const css::lang::Locale& locale ) override; + virtual void SAL_CALL removeId( const OUString& ResourceID ) override; + virtual void SAL_CALL removeIdForLocale( const OUString& ResourceID, + const css::lang::Locale& locale ) override; + virtual void SAL_CALL newLocale( const css::lang::Locale& locale ) override; + virtual void SAL_CALL removeLocale( const css::lang::Locale& locale ) override; + virtual ::sal_Int32 SAL_CALL getUniqueNumericId( ) override; + + // XStringResourcePersistence + virtual void SAL_CALL store( ) override; + virtual sal_Bool SAL_CALL isModified( ) override; + virtual void SAL_CALL setComment( const OUString& Comment ) override; + virtual void SAL_CALL storeToStorage + ( const css::uno::Reference< css::embed::XStorage >& Storage, + const OUString& NameBase, const OUString& Comment ) override; + virtual void SAL_CALL storeToURL( const OUString& URL, const OUString& NameBase, + const OUString& Comment, const css::uno::Reference + < css::task::XInteractionHandler >& Handler ) override; + virtual css::uno::Sequence< ::sal_Int8 > SAL_CALL exportBinary( ) override; + virtual void SAL_CALL importBinary( const css::uno::Sequence< ::sal_Int8 >& Data ) override; + + // XStringResourceWithStorage + virtual void SAL_CALL storeAsStorage + ( const css::uno::Reference< css::embed::XStorage >& Storage ) override; + virtual void SAL_CALL setStorage + ( const css::uno::Reference< css::embed::XStorage >& Storage ) override; +}; + + +typedef ::cppu::ImplInheritanceHelper< + StringResourcePersistenceImpl, + css::lang::XInitialization, + css::resource::XStringResourceWithLocation > StringResourceWithLocationImpl_BASE; + +class StringResourceWithLocationImpl : public StringResourceWithLocationImpl_BASE +{ + OUString m_aLocation; + bool m_bLocationChanged; + css::uno::Reference< css::ucb::XSimpleFileAccess3 > m_xSFI; + css::uno::Reference< css::task::XInteractionHandler > m_xInteractionHandler; + + const css::uno::Reference< css::ucb::XSimpleFileAccess3 > & getFileAccessImpl(); + + virtual void implScanLocales() override; + virtual bool implLoadLocale( LocaleItem* pLocaleItem ) override; + +public: + explicit StringResourceWithLocationImpl( const css::uno::Reference< css::uno::XComponentContext >& rxContext ); + virtual ~StringResourceWithLocationImpl() override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + + // XInitialization + virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override; + + // XModifyBroadcaster + virtual void SAL_CALL addModifyListener( const css::uno::Reference< css::util::XModifyListener >& aListener ) override; + virtual void SAL_CALL removeModifyListener( const css::uno::Reference< css::util::XModifyListener >& aListener ) override; + + // XStringResourceResolver + virtual OUString SAL_CALL resolveString( const OUString& ResourceID ) override; + virtual OUString SAL_CALL resolveStringForLocale( const OUString& ResourceID, + const css::lang::Locale& locale ) override; + virtual sal_Bool SAL_CALL hasEntryForId( const OUString& ResourceID ) override; + virtual sal_Bool SAL_CALL hasEntryForIdAndLocale( const OUString& ResourceID, + const css::lang::Locale& locale ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getResourceIDs( ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getResourceIDsForLocale + ( const css::lang::Locale& locale ) override; + virtual css::lang::Locale SAL_CALL getCurrentLocale( ) override; + virtual css::lang::Locale SAL_CALL getDefaultLocale( ) override; + virtual css::uno::Sequence< css::lang::Locale > SAL_CALL getLocales( ) override; + + // XStringResourceManager + virtual sal_Bool SAL_CALL isReadOnly() override; + virtual void SAL_CALL setCurrentLocale( const css::lang::Locale& locale, sal_Bool FindClosestMatch ) override; + virtual void SAL_CALL setDefaultLocale( const css::lang::Locale& locale ) override; + virtual void SAL_CALL setString( const OUString& ResourceID, const OUString& Str ) override; + virtual void SAL_CALL setStringForLocale( const OUString& ResourceID, const OUString& Str, + const css::lang::Locale& locale ) override; + virtual void SAL_CALL removeId( const OUString& ResourceID ) override; + virtual void SAL_CALL removeIdForLocale( const OUString& ResourceID, + const css::lang::Locale& locale ) override; + virtual void SAL_CALL newLocale( const css::lang::Locale& locale ) override; + virtual void SAL_CALL removeLocale( const css::lang::Locale& locale ) override; + virtual ::sal_Int32 SAL_CALL getUniqueNumericId( ) override; + + // XStringResourcePersistence + virtual void SAL_CALL store( ) override; + virtual sal_Bool SAL_CALL isModified( ) override; + virtual void SAL_CALL setComment( const OUString& Comment ) override; + virtual void SAL_CALL storeToStorage + ( const css::uno::Reference< css::embed::XStorage >& Storage, + const OUString& NameBase, const OUString& Comment ) override; + virtual void SAL_CALL storeToURL( const OUString& URL, const OUString& NameBase, + const OUString& Comment, const css::uno::Reference + < css::task::XInteractionHandler >& Handler ) override; + virtual css::uno::Sequence< ::sal_Int8 > SAL_CALL exportBinary( ) override; + virtual void SAL_CALL importBinary( const css::uno::Sequence< ::sal_Int8 >& Data ) override; + + // XStringResourceWithLocation + virtual void SAL_CALL storeAsURL( const OUString& URL ) override; + virtual void SAL_CALL setURL( const OUString& URL ) override; +}; + + +} // namespace stringtable + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/scripting/source/vbaevents/eventhelper.cxx b/scripting/source/vbaevents/eventhelper.cxx new file mode 100644 index 000000000..9da09b734 --- /dev/null +++ b/scripting/source/vbaevents/eventhelper.cxx @@ -0,0 +1,982 @@ +/* -*- 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 <sal/macros.h> +#include <sal/log.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/uno3.hxx> +#include <comphelper/proparrhlp.hxx> +#include <comphelper/propertycontainer.hxx> +#include <tools/diagnose_ex.h> + +#include <ooo/vba/XVBAToOOEventDescGen.hpp> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/theIntrospection.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> + +#include <com/sun/star/lang/XMultiComponentFactory.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XInitialization.hpp> + +#include <com/sun/star/util/XCloseListener.hpp> +#include <com/sun/star/util/XCloseBroadcaster.hpp> + +#include <com/sun/star/frame/XModel.hpp> + +#include <com/sun/star/script/ScriptEventDescriptor.hpp> +#include <com/sun/star/script/provider/XScriptProviderSupplier.hpp> + +#include <com/sun/star/container/XNamed.hpp> + +#include <com/sun/star/drawing/XControlShape.hpp> + +#include <com/sun/star/awt/XControl.hpp> +#include <com/sun/star/awt/XDialog.hpp> +#include <com/sun/star/awt/KeyEvent.hpp> +#include <com/sun/star/awt/MouseEvent.hpp> +#include <com/sun/star/awt/XFixedText.hpp> +#include <com/sun/star/awt/XTextComponent.hpp> +#include <com/sun/star/awt/XComboBox.hpp> +#include <com/sun/star/awt/XRadioButton.hpp> +#include <com/sun/star/awt/XListBox.hpp> + +#include <sfx2/objsh.hxx> +#include <basic/basmgr.hxx> +#include <filter/msfilter/msvbahelper.hxx> +#include <vbahelper/vbareturntypes.hxx> + +#include <com/sun/star/script/XScriptListener.hpp> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <comphelper/evtmethodhelper.hxx> + +#include <vector> +#include <unordered_map> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::script; +using namespace ::com::sun::star::uno; +using namespace ::ooo::vba; + +// Some constants +constexpr std::u16string_view DELIM = u"::"; +constexpr sal_Int32 DELIMLEN = DELIM.size(); + +static bool isKeyEventOk( awt::KeyEvent& evt, const Sequence< Any >& params ) +{ + return params.hasElements() && ( params[ 0 ] >>= evt ); +} + +static bool isMouseEventOk( awt::MouseEvent& evt, const Sequence< Any >& params ) +{ + return params.hasElements() && ( params[ 0 ] >>= evt ); +} + +static Sequence< Any > ooMouseEvtToVBADblClick( const Sequence< Any >& params ) +{ + awt::MouseEvent evt; + + if ( !( isMouseEventOk(evt, params)) || + (evt.ClickCount != 2) ) + return Sequence< Any >(); + // give back orig params, this will signal that the event is good + return params; +} + +static Sequence< Any > ooMouseEvtToVBAMouseEvt( const Sequence< Any >& params ) +{ + awt::MouseEvent evt; + + if ( !isMouseEventOk(evt, params) ) + return Sequence< Any >(); + + Sequence< Any > translatedParams{ Any(evt.Buttons), // Buttons + Any(evt.Modifiers), // Shift + Any(evt.X), // X + Any(evt.Y) }; // Y + return translatedParams; +} + +static Sequence< Any > ooKeyPressedToVBAKeyPressed( const Sequence< Any >& params ) +{ + awt::KeyEvent evt; + + if ( !isKeyEventOk( evt, params ) ) + return Sequence< Any >(); + + Reference< msforms::XReturnInteger> xKeyCode = new ReturnInteger( sal_Int32( evt.KeyCode ) ); + Sequence< Any > translatedParams{ Any(xKeyCode) }; + return translatedParams; +} + +static Sequence< Any > ooKeyPressedToVBAKeyUpDown( const Sequence< Any >& params ) +{ + awt::KeyEvent evt; + + if ( !isKeyEventOk( evt, params ) ) + return Sequence< Any >(); + + Reference< msforms::XReturnInteger> xKeyCode = new ReturnInteger( evt.KeyCode ); + sal_Int8 shift = sal::static_int_cast<sal_Int8>( evt.Modifiers ); + + // #TODO check whether values from OOO conform to values generated from vba + Sequence< Any > translatedParams{ Any(xKeyCode), Any(shift) }; + return translatedParams; +} + +typedef Sequence< Any > (*Translator)(const Sequence< Any >&); + +namespace { + +//expand the "TranslateInfo" struct to support more kinds of events +struct TranslateInfo +{ + OUString sVBAName; //vba event name + Translator toVBA; //the method to convert OO event parameters to VBA event parameters + bool (*ApproveRule)(const ScriptEvent& evt, void const * pPara); //this method is used to determine which types of controls should execute the event + void const *pPara; //Parameters for the above approve method +}; + +} + +typedef std::unordered_map< + OUString, + std::vector< TranslateInfo > > EventInfoHash; + +namespace { + +struct TranslatePropMap +{ + OUString sEventInfo; //OO event name + TranslateInfo aTransInfo; +}; + +} + +static bool ApproveAll(const ScriptEvent& evt, void const * pPara); //allow all types of controls to execute the event +static bool ApproveType(const ScriptEvent& evt, void const * pPara); //certain types of controls should execute the event, those types are given by pPara +static bool DenyType(const ScriptEvent& evt, void const * pPara); //certain types of controls should not execute the event, those types are given by pPara +static bool DenyMouseDrag(const ScriptEvent& evt, void const * pPara); //used for VBA MouseMove event when "Shift" key is pressed + +namespace { + +struct TypeList +{ + uno::Type const * pTypeList; + int nListLength; +}; + +} + +Type const typeXFixedText = cppu::UnoType<awt::XFixedText>::get(); +Type const typeXTextComponent = cppu::UnoType<awt::XTextComponent>::get(); +Type const typeXComboBox = cppu::UnoType<awt::XComboBox>::get(); +Type const typeXRadioButton = cppu::UnoType<awt::XRadioButton>::get(); +Type const typeXListBox = cppu::UnoType<awt::XListBox>::get(); + + +TypeList const fixedTextList = {&typeXFixedText, 1}; +TypeList const textCompList = {&typeXTextComponent, 1}; +TypeList const radioButtonList = {&typeXRadioButton, 1}; +TypeList const comboBoxList = {&typeXComboBox, 1}; +TypeList const listBoxList = {&typeXListBox, 1}; + +//this array stores the OO event to VBA event translation info +static TranslatePropMap aTranslatePropMap_Impl[] = +{ + { OUString("actionPerformed"), { OUString("_Change"), nullptr, DenyType, static_cast<void const *>(&radioButtonList) } }, + // actionPerformed ooo event + { OUString("actionPerformed"), { OUString("_Click"), nullptr, ApproveAll, nullptr } }, + { OUString("itemStateChanged"), { OUString("_Change"), nullptr, ApproveType, static_cast<void const *>(&radioButtonList) } }, + // itemStateChanged ooo event + { OUString("itemStateChanged"), { OUString("_Click"), nullptr, ApproveType, static_cast<void const *>(&comboBoxList) } }, + + { OUString("itemStateChanged"), { OUString("_Click"), nullptr, ApproveType, static_cast<void const *>(&listBoxList) } }, + // changed ooo event + { OUString("changed"), { OUString("_Change"), nullptr, ApproveAll, nullptr } }, + + // focusGained ooo event + { OUString("focusGained"), { OUString("_GotFocus"), nullptr, ApproveAll, nullptr } }, + + // focusLost ooo event + { OUString("focusLost"), { OUString("_LostFocus"), nullptr, ApproveAll, nullptr } }, + { OUString("focusLost"), { OUString("_Exit"), nullptr, ApproveType, static_cast<void const *>(&textCompList) } }, // support VBA TextBox_Exit event + + // adjustmentValueChanged ooo event + { OUString("adjustmentValueChanged"), { OUString("_Scroll"), nullptr, ApproveAll, nullptr } }, + { OUString("adjustmentValueChanged"), { OUString("_Change"), nullptr, ApproveAll, nullptr } }, + + // textChanged ooo event + { OUString("textChanged"), { OUString("_Change"), nullptr, ApproveAll, nullptr } }, + + // keyReleased ooo event + { OUString("keyReleased"), { OUString("_KeyUp"), ooKeyPressedToVBAKeyUpDown, ApproveAll, nullptr } }, + + // mouseReleased ooo event + { OUString("mouseReleased"), { OUString("_Click"), ooMouseEvtToVBAMouseEvt, ApproveType, static_cast<void const *>(&fixedTextList) } }, + { OUString("mouseReleased"), { OUString("_MouseUp"), ooMouseEvtToVBAMouseEvt, ApproveAll, nullptr } }, + + // mousePressed ooo event + { OUString("mousePressed"), { OUString("_MouseDown"), ooMouseEvtToVBAMouseEvt, ApproveAll, nullptr } }, + { OUString("mousePressed"), { OUString("_DblClick"), ooMouseEvtToVBADblClick, ApproveAll, nullptr } }, + + // mouseMoved ooo event + { OUString("mouseMoved"), { OUString("_MouseMove"), ooMouseEvtToVBAMouseEvt, ApproveAll, nullptr } }, + { OUString("mouseDragged"), { OUString("_MouseMove"), ooMouseEvtToVBAMouseEvt, DenyMouseDrag, nullptr } }, + + // keyPressed ooo event + { OUString("keyPressed"), { OUString("_KeyDown"), ooKeyPressedToVBAKeyUpDown, ApproveAll, nullptr } }, + { OUString("keyPressed"), { OUString("_KeyPress"), ooKeyPressedToVBAKeyPressed, ApproveAll, nullptr } } +}; + +static EventInfoHash& getEventTransInfo() +{ + static EventInfoHash eventTransInfo = []() + { + EventInfoHash tmp; + OUString sEventInfo; + TranslatePropMap* pTransProp = aTranslatePropMap_Impl; + int nCount = SAL_N_ELEMENTS(aTranslatePropMap_Impl); + + int i = 0; + while (i < nCount) + { + sEventInfo = pTransProp->sEventInfo; + std::vector< TranslateInfo > infoList; + do + { + infoList.push_back( pTransProp->aTransInfo ); + pTransProp++; + i++; + }while(i < nCount && sEventInfo == pTransProp->sEventInfo); + tmp[sEventInfo] = std::move(infoList); + } + return tmp; + }(); + return eventTransInfo; +} + + +// Helper class + +namespace { + +class ScriptEventHelper +{ +public: + explicit ScriptEventHelper( const Reference< XInterface >& xControl ); + explicit ScriptEventHelper( const OUString& sCntrlServiceName ); + ~ScriptEventHelper(); + Sequence< ScriptEventDescriptor > createEvents( const OUString& sCodeName ); + Sequence< OUString > getEventListeners() const; +private: + Reference< XComponentContext > m_xCtx; + Reference< XInterface > m_xControl; + bool m_bDispose; +}; + +} + +static bool +eventMethodToDescriptor( std::u16string_view rEventMethod, ScriptEventDescriptor& evtDesc, const OUString& sCodeName ) +{ + // format of ControlListener is TypeName::methodname e.g. + // "com.sun.star.awt.XActionListener::actionPerformed" or + // "XActionListener::actionPerformed + + OUString sMethodName; + OUString sTypeName; + size_t nDelimPos = rEventMethod.find( DELIM ); + if ( nDelimPos == std::u16string_view::npos ) + { + return false; + } + sMethodName = rEventMethod.substr( nDelimPos + DELIMLEN ); + sTypeName = rEventMethod.substr( 0, nDelimPos ); + + EventInfoHash& infos = getEventTransInfo(); + + // Only create an ScriptEventDescriptor for an event we can translate + // or emulate + if ( !sMethodName.isEmpty() + && !sTypeName.isEmpty() + && ( infos.find( sMethodName ) != infos.end() ) ) + { + // just fill in CodeName, when the event fires the other + // info is gathered from the event source to determine what + // event handler we try to call + evtDesc.ScriptCode = sCodeName; + evtDesc.ListenerType = sTypeName; + evtDesc.EventMethod = sMethodName; + + // set this it VBAInterop, ensures that it doesn't + // get persisted or shown in property editors + evtDesc.ScriptType = "VBAInterop"; + return true; + } + return false; + +} + +ScriptEventHelper::ScriptEventHelper( const Reference< XInterface >& xControl ) : + m_xCtx( comphelper::getProcessComponentContext() ), + m_xControl( xControl ), + m_bDispose( false ) +{} + +ScriptEventHelper::ScriptEventHelper( const OUString& sCntrlServiceName ) : + m_xCtx( comphelper::getProcessComponentContext() ), + m_bDispose( true ) +{ + m_xControl.set( m_xCtx->getServiceManager()->createInstanceWithContext( sCntrlServiceName, m_xCtx ), uno::UNO_QUERY ); +} + +ScriptEventHelper::~ScriptEventHelper() +{ + // dispose control ( and remove any associated event registrations ) + if ( m_bDispose ) + { + try + { + uno::Reference< lang::XComponent > xComp( m_xControl, uno::UNO_QUERY_THROW ); + xComp->dispose(); + } + // destructor can't throw + catch( uno::Exception& ) + { + } + } +} + +Sequence< OUString > +ScriptEventHelper::getEventListeners() const +{ + std::vector< OUString > eventMethods; + + Reference< beans::XIntrospection > xIntrospection = beans::theIntrospection::get( m_xCtx ); + + Reference< beans::XIntrospectionAccess > xIntrospectionAccess = + xIntrospection->inspect( Any( m_xControl ) ); + const Sequence< Type > aControlListeners = + xIntrospectionAccess->getSupportedListeners(); + for ( const Type& listType : aControlListeners ) + { + OUString sFullTypeName = listType.getTypeName(); + const Sequence< OUString > sMeths = + comphelper::getEventMethodsForType( listType ); + std::transform(sMeths.begin(), sMeths.end(), std::back_inserter(eventMethods), + [&sFullTypeName](const OUString& rMeth) -> OUString { return sFullTypeName + DELIM + rMeth; }); + } + + return comphelper::containerToSequence(eventMethods); +} + +Sequence< ScriptEventDescriptor > +ScriptEventHelper::createEvents( const OUString& sCodeName ) +{ + const Sequence< OUString > aControlListeners = getEventListeners(); + sal_Int32 nLength = aControlListeners.getLength(); + + Sequence< ScriptEventDescriptor > aDest( nLength ); + sal_Int32 nEvts = 0; + for ( OUString const & i : aControlListeners) + { + // from getListeners eventName is of form + // "com.sun.star.awt.XActionListener::actionPerformed" + // we need to strip "com.sun.star.awt." from that for form + // controls + ScriptEventDescriptor evtDesc; + if ( eventMethodToDescriptor( i, evtDesc, sCodeName ) ) + { + sal_Int32 dIndex = nEvts; + ++nEvts; + if ( nEvts > aDest.getLength() ) + aDest.realloc( nEvts );// should never happen + aDest.getArray()[ dIndex ] = evtDesc; + } + } + aDest.realloc( nEvts ); + + return aDest; +} + + +typedef ::cppu::WeakImplHelper< container::XNameContainer > NameContainer_BASE; + +namespace { + +class ReadOnlyEventsNameContainer : public NameContainer_BASE +{ +public: + ReadOnlyEventsNameContainer( const Sequence< OUString >& eventMethods, const OUString& sCodeName ); + // XNameContainer + + virtual void SAL_CALL insertByName( const OUString&, const Any& ) override + { + throw RuntimeException("ReadOnly container" ); + + } + virtual void SAL_CALL removeByName( const OUString& ) override + { + throw RuntimeException("ReadOnly container" ); + } + + // XNameReplace + virtual void SAL_CALL replaceByName( const OUString&, const Any& ) override + { + throw RuntimeException("ReadOnly container" ); + + } + + // XNameAccess + virtual Any SAL_CALL getByName( const OUString& aName ) override; + virtual Sequence< OUString > SAL_CALL getElementNames( ) override; + virtual sal_Bool SAL_CALL hasByName( const OUString& aName ) override; + + // XElementAccess + virtual Type SAL_CALL getElementType( ) override + { return cppu::UnoType<OUString>::get(); } + virtual sal_Bool SAL_CALL hasElements( ) override + { return !m_hEvents.empty(); } +private: + +typedef std::unordered_map< OUString, Any > EventSupplierHash; + + EventSupplierHash m_hEvents; +}; + +} + +ReadOnlyEventsNameContainer::ReadOnlyEventsNameContainer( const Sequence< OUString >& eventMethods, const OUString& sCodeName ) +{ + for ( const OUString& rSrc : eventMethods ) + { + Any aDesc; + ScriptEventDescriptor evtDesc; + if ( eventMethodToDescriptor( rSrc, evtDesc, sCodeName ) ) + { + aDesc <<= evtDesc; + m_hEvents[ rSrc ] = aDesc; + } + } +} + +Any SAL_CALL +ReadOnlyEventsNameContainer::getByName( const OUString& aName ){ + EventSupplierHash::const_iterator it = m_hEvents.find( aName ); + if ( it == m_hEvents.end() ) + throw container::NoSuchElementException(); + return it->second; +} + +Sequence< OUString > SAL_CALL +ReadOnlyEventsNameContainer::getElementNames( ) +{ + return comphelper::mapKeysToSequence(m_hEvents); +} + +sal_Bool SAL_CALL +ReadOnlyEventsNameContainer::hasByName( const OUString& aName ) +{ + EventSupplierHash::const_iterator it = m_hEvents.find( aName ); + if ( it == m_hEvents.end() ) + return false; + return true; +} + +namespace { + +class ReadOnlyEventsSupplier : public ::cppu::WeakImplHelper< XScriptEventsSupplier > +{ +public: + ReadOnlyEventsSupplier( const Sequence< OUString >& eventMethods, const OUString& sCodeName ) + { m_xNameContainer = new ReadOnlyEventsNameContainer( eventMethods, sCodeName ); } + + // XScriptEventSupplier + virtual Reference< container::XNameContainer > SAL_CALL getEvents( ) override { return m_xNameContainer; } +private: + Reference< container::XNameContainer > m_xNameContainer; +}; + +} + +typedef ::cppu::WeakImplHelper< XScriptListener, util::XCloseListener, lang::XInitialization, css::lang::XServiceInfo > EventListener_BASE; + +#define EVENTLSTNR_PROPERTY_ID_MODEL 1 +constexpr OUStringLiteral EVENTLSTNR_PROPERTY_MODEL = u"Model"; + +namespace { + +class EventListener : public EventListener_BASE + ,public ::comphelper::OMutexAndBroadcastHelper + ,public ::comphelper::OPropertyContainer + ,public ::comphelper::OPropertyArrayUsageHelper< EventListener > +{ + +public: + EventListener(); + // XEventListener + virtual void SAL_CALL disposing(const lang::EventObject& Source) override; + using cppu::OPropertySetHelper::disposing; + + // XScriptListener + virtual void SAL_CALL firing(const ScriptEvent& evt) override; + virtual Any SAL_CALL approveFiring(const ScriptEvent& evt) override; + // XCloseListener + virtual void SAL_CALL queryClosing( const lang::EventObject& Source, sal_Bool GetsOwnership ) override; + virtual void SAL_CALL notifyClosing( const lang::EventObject& Source ) override; + // XPropertySet + virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo( ) override; + // XInitialization + virtual void SAL_CALL initialize( const Sequence< Any >& aArguments ) override; + // XInterface + DECLARE_XINTERFACE() + + // XTypeProvider + DECLARE_XTYPEPROVIDER() + virtual void SAL_CALL setFastPropertyValue( sal_Int32 nHandle, const css::uno::Any& rValue ) override + { + if ( nHandle == EVENTLSTNR_PROPERTY_ID_MODEL ) + { + uno::Reference< frame::XModel > xModel( rValue, uno::UNO_QUERY ); + if( xModel != m_xModel) + { + // Remove the listener from the old XCloseBroadcaster. + uno::Reference< util::XCloseBroadcaster > xCloseBroadcaster( m_xModel, uno::UNO_QUERY ); + if (xCloseBroadcaster.is()) + { + xCloseBroadcaster->removeCloseListener( this ); + } + // Add the listener into the new XCloseBroadcaster. + xCloseBroadcaster.set( xModel, uno::UNO_QUERY ); + if (xCloseBroadcaster.is()) + { + xCloseBroadcaster->addCloseListener( this ); + } + } + } + OPropertyContainer::setFastPropertyValue( nHandle, rValue ); + if ( nHandle == EVENTLSTNR_PROPERTY_ID_MODEL ) + setShellFromModel(); + } + + OUString SAL_CALL getImplementationName() override + { + return "ooo.vba.EventListener"; + } + + sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override + { + return cppu::supportsService(this, ServiceName); + } + + css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override + { + return { getImplementationName() }; + } + +protected: + // OPropertySetHelper + virtual ::cppu::IPropertyArrayHelper& SAL_CALL getInfoHelper( ) override; + + // OPropertyArrayUsageHelper + virtual ::cppu::IPropertyArrayHelper* createArrayHelper( ) const override; + +private: + void setShellFromModel(); + /// @throws RuntimeException + void firing_Impl( const ScriptEvent& evt, Any *pSyncRet ); + + Reference< frame::XModel > m_xModel; + bool m_bDocClosed; + SfxObjectShell* mpShell; +}; + +} + +EventListener::EventListener() : +OPropertyContainer(GetBroadcastHelper()), m_bDocClosed(false), mpShell( nullptr ) +{ + registerProperty( EVENTLSTNR_PROPERTY_MODEL, EVENTLSTNR_PROPERTY_ID_MODEL, + beans::PropertyAttribute::TRANSIENT, &m_xModel, cppu::UnoType<decltype(m_xModel)>::get() ); +} + +void +EventListener::setShellFromModel() +{ + // reset mpShell + mpShell = nullptr; + SfxObjectShell* pShell = SfxObjectShell::GetFirst(); + while ( m_xModel.is() && pShell ) + { + if ( pShell->GetModel() == m_xModel ) + { + mpShell = pShell; + break; + } + pShell = SfxObjectShell::GetNext( *pShell ); + } +} + +//XEventListener +void +EventListener::disposing(const lang::EventObject&) +{ +} + +//XScriptListener + +void SAL_CALL +EventListener::firing(const ScriptEvent& evt) +{ + firing_Impl( evt, nullptr ); +} + +Any SAL_CALL +EventListener::approveFiring(const ScriptEvent& evt) +{ + Any ret; + firing_Impl( evt, &ret ); + return ret; +} + +// XCloseListener +void SAL_CALL +EventListener::queryClosing( const lang::EventObject& /*Source*/, sal_Bool /*GetsOwnership*/ ) +{ + //Nothing to do +} + +void SAL_CALL +EventListener::notifyClosing( const lang::EventObject& /*Source*/ ) +{ + m_bDocClosed = true; + uno::Reference< util::XCloseBroadcaster > xCloseBroadcaster( m_xModel, uno::UNO_QUERY ); + if (xCloseBroadcaster.is()) + { + xCloseBroadcaster->removeCloseListener( this ); + } +} + +// XInitialization +void SAL_CALL +EventListener::initialize( const Sequence< Any >& aArguments ) +{ + if ( aArguments.getLength() == 1 ) + aArguments[0] >>= m_xModel; + SAL_INFO( + "scripting", + "args " << aArguments.getLength() << " m_xModel " << m_xModel.is()); +} + +// XInterface + +IMPLEMENT_FORWARD_XINTERFACE2( EventListener, EventListener_BASE, OPropertyContainer ) + +// XTypeProvider + +IMPLEMENT_FORWARD_XTYPEPROVIDER2( EventListener, EventListener_BASE, OPropertyContainer ) + +// OPropertySetHelper + +::cppu::IPropertyArrayHelper& +EventListener::getInfoHelper( ) +{ + return *getArrayHelper(); +} + +// OPropertyArrayUsageHelper + +::cppu::IPropertyArrayHelper* +EventListener::createArrayHelper( ) const +{ + Sequence< beans::Property > aProps; + describeProperties( aProps ); + return new ::cppu::OPropertyArrayHelper( aProps ); +} + +// XPropertySet +Reference< beans::XPropertySetInfo > +EventListener::getPropertySetInfo( ) +{ + Reference< beans::XPropertySetInfo > xInfo( createPropertySetInfo( getInfoHelper() ) ); + return xInfo; +} + + +//decide if the control should execute the event +bool ApproveAll(SAL_UNUSED_PARAMETER const ScriptEvent&, SAL_UNUSED_PARAMETER void const * ) +{ + return true; +} + +//for the given control type in evt.Arguments[0], look for if it appears in the type list in pPara +static bool FindControl(const ScriptEvent& evt, void const * pPara) +{ + lang::EventObject aEvent; + evt.Arguments[ 0 ] >>= aEvent; + uno::Reference< uno::XInterface > xInterface( aEvent.Source, uno::UNO_QUERY ); + + TypeList const * pTypeListInfo = static_cast<TypeList const *>(pPara); + Type const * pType = pTypeListInfo->pTypeList; + int nLen = pTypeListInfo->nListLength; + + for (int i = 0; i < nLen; i++) + { + if ( xInterface->queryInterface( *pType ).hasValue() ) + { + return true; + } + pType++; + } + + return false; +} + +//if the given control type in evt.Arguments[0] appears in the type list in pPara, then approve the execution +bool ApproveType(const ScriptEvent& evt, void const * pPara) +{ + return FindControl(evt, pPara); +} + +//if the given control type in evt.Arguments[0] appears in the type list in pPara, then deny the execution +bool DenyType(const ScriptEvent& evt, void const * pPara) +{ + return !FindControl(evt, pPara); +} + +//when mouse is moving, either the mouse button is pressed or some key is pressed can trigger the OO mouseDragged event, +//the former should be denied, and the latter allowed, only by doing so can the VBA MouseMove event when the "Shift" key is +//pressed can be correctly triggered +bool DenyMouseDrag(const ScriptEvent& evt, SAL_UNUSED_PARAMETER void const * ) +{ + awt::MouseEvent aEvent; + evt.Arguments[ 0 ] >>= aEvent; + return aEvent.Buttons == 0; +} + + +// EventListener + +void +EventListener::firing_Impl(const ScriptEvent& evt, Any* pRet ) +{ + // let default handlers deal with non vba stuff + if ( evt.ScriptType != "VBAInterop" ) + return; + lang::EventObject aEvent; + evt.Arguments[ 0 ] >>= aEvent; + OUString sName = "UserForm"; + + uno::Reference< awt::XDialog > xDlg( aEvent.Source, uno::UNO_QUERY ); + if ( !xDlg.is() ) + { + // evt.Source is + // a) Dialog + // b) xShapeControl ( from api (sheet control) ) + // c) eventmanager ( I guess ) + // d) vba control ( from api also ) + uno::Reference< drawing::XControlShape > xCntrlShape( evt.Source, uno::UNO_QUERY ); + uno::Reference< awt::XControl > xControl( aEvent.Source, uno::UNO_QUERY ); + if ( xCntrlShape.is() ) + { + // for sheet controls ( that fire from the api ) we don't + // have the real control ( that's only available from the view ) + // api code creates just a control instance that is transferred + // via aEvent.Arguments[ 0 ] that control though has no + // info like name etc. + uno::Reference< container::XNamed > xName( xCntrlShape->getControl(), uno::UNO_QUERY_THROW ); + sName = xName->getName(); + } + else + { + // Userform control ( fired from the api or from event manager ) + uno::Reference< beans::XPropertySet > xProps; + xProps.set( xControl->getModel(), uno::UNO_QUERY_THROW ); + xProps->getPropertyValue("Name") >>= sName; + } + } + //dumpEvent( evt ); + EventInfoHash& infos = getEventTransInfo(); + EventInfoHash::const_iterator eventInfo_it = infos.find( evt.MethodName ); + EventInfoHash::const_iterator it_end = infos.end(); + if ( eventInfo_it == it_end ) + { + SAL_WARN("scripting", "Bogus event for " << evt.ScriptType ); + return; + } + + uno::Reference< script::provider::XScriptProviderSupplier > xSPS( m_xModel, uno::UNO_QUERY ); + uno::Reference< script::provider::XScriptProvider > xScriptProvider; + if ( xSPS.is() ) + { + xScriptProvider = xSPS->getScriptProvider(); + } + if ( !(xScriptProvider.is() && mpShell) ) + return; + + BasicManager* pBasicManager = mpShell->GetBasicManager(); + OUString sProject; + OUString sScriptCode( evt.ScriptCode ); + // dialogs pass their own library, presence of Dot determines that + if ( sScriptCode.indexOf( '.' ) == -1 ) + { + //'Project' is a better default but I want to force failures + //OUString sMacroLoc("Project"); + sProject = "Standard"; + + if (!pBasicManager->GetName().isEmpty()) + { + sProject = pBasicManager->GetName(); + } + } + else + { + sal_Int32 nIndex = sScriptCode.indexOf( '.' ); + sProject = sScriptCode.copy( 0, nIndex ); + sScriptCode = sScriptCode.copy( nIndex + 1 ); + } + OUString sMacroLoc = sProject + "." + sScriptCode + "."; + + for (const auto& rTxInfo : eventInfo_it->second) + { + // If the document is closed, we should not execute macro. + if (m_bDocClosed) + { + break; + } + + // see if we have a match for the handlerextension + // where ScriptCode is methodname_handlerextension + OUString sToResolve = sMacroLoc + sName + rTxInfo.sVBAName; + + ooo::vba::MacroResolvedInfo aMacroResolvedInfo = ooo::vba::resolveVBAMacro( mpShell, sToResolve ); + if ( aMacroResolvedInfo.mbFound ) + { + + if (! rTxInfo.ApproveRule(evt, rTxInfo.pPara) ) + { + continue; + } + + // !! translate arguments & emulate events where necessary + Sequence< Any > aArguments; + if ( rTxInfo.toVBA ) + { + aArguments = rTxInfo.toVBA( evt.Arguments ); + } + else + { + aArguments = evt.Arguments; + } + if ( aArguments.hasElements() ) + { + // call basic event handlers for event + + // create script url + OUString url = aMacroResolvedInfo.msResolvedMacro; + try + { + uno::Any aDummyCaller( OUString("Error") ); + if ( pRet ) + { + ooo::vba::executeMacro( mpShell, url, aArguments, *pRet, aDummyCaller ); + } + else + { + uno::Any aRet; + ooo::vba::executeMacro( mpShell, url, aArguments, aRet, aDummyCaller ); + } + } + catch ( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("scripting", "event script raised" ); + } + } + } + } +} + +namespace { + +class VBAToOOEventDescGen : public ::cppu::WeakImplHelper< XVBAToOOEventDescGen, css::lang::XServiceInfo > +{ +public: + VBAToOOEventDescGen(); + + // XVBAToOOEventDescGen + virtual Sequence< ScriptEventDescriptor > SAL_CALL getEventDescriptions( const OUString& sCtrlServiceName, const OUString& sCodeName ) override; + virtual Reference< XScriptEventsSupplier > SAL_CALL getEventSupplier( const Reference< XInterface >& xControl, const OUString& sCodeName ) override; + + OUString SAL_CALL getImplementationName() override + { + return "ooo.vba.VBAToOOEventDesc"; + } + + sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override + { + return cppu::supportsService(this, ServiceName); + } + + css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override + { + return { getImplementationName() }; + } + +}; + +} + +VBAToOOEventDescGen::VBAToOOEventDescGen() {} + +Sequence< ScriptEventDescriptor > SAL_CALL +VBAToOOEventDescGen::getEventDescriptions( const OUString& sCntrlServiceName, const OUString& sCodeName ) +{ + ScriptEventHelper evntHelper( sCntrlServiceName ); + return evntHelper.createEvents( sCodeName ); +} + +Reference< XScriptEventsSupplier > SAL_CALL +VBAToOOEventDescGen::getEventSupplier( const Reference< XInterface >& xControl, const OUString& sCodeName ) +{ + ScriptEventHelper evntHelper( xControl ); + Reference< XScriptEventsSupplier > xSupplier = + new ReadOnlyEventsSupplier( + evntHelper.getEventListeners(), sCodeName ) ; + return xSupplier; +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +ooo_vba_EventListener_get_implementation(css::uno::XComponentContext*, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new EventListener); +} + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +ooo_vba_VBAToOOEventDesc_get_implementation(css::uno::XComponentContext*, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new VBAToOOEventDescGen); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/scripting/source/vbaevents/vbaevents.component b/scripting/source/vbaevents/vbaevents.component new file mode 100644 index 000000000..75fcf0230 --- /dev/null +++ b/scripting/source/vbaevents/vbaevents.component @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="ooo.vba.EventListener" + constructor="ooo_vba_EventListener_get_implementation"> + <service name="ooo.vba.EventListener"/> + </implementation> + <implementation name="ooo.vba.VBAToOOEventDesc" + constructor="ooo_vba_VBAToOOEventDesc_get_implementation"> + <service name="ooo.vba.VBAToOOEventDesc"/> + </implementation> +</component> |