<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd">
<script:module xmlns:script="http://openoffice.org/2000/script" script:name="SF_Services" script:language="StarBasic" script:moduleType="normal">REM =======================================================================================================================
REM ===			The ScriptForge library and its associated libraries are part of the LibreOffice project.				===
REM ===					Full documentation is available on https://help.libreoffice.org/								===
REM =======================================================================================================================

Option Compatible
Option Explicit

&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;
&apos;&apos;&apos;	SF_Services
&apos;&apos;&apos;	===========
&apos;&apos;&apos;		Singleton class implementing the &quot;ScriptForge.Services&quot; service
&apos;&apos;&apos;		Implemented as a usual Basic module
&apos;&apos;&apos;
&apos;&apos;&apos;		The modules and classes implemented in ScriptForge will be invoked
&apos;&apos;&apos;		from user scripts as &quot;Services&quot;.
&apos;&apos;&apos;
&apos;&apos;&apos;		A generic constructor of those services has been designed for that purpose:
&apos;&apos;&apos;			CreateScriptService(&quot;servicename&quot;[, arg0, arg1, ...])
&apos;&apos;&apos;		All top-level services are created through this mechanism.
&apos;&apos;&apos;
&apos;&apos;&apos;		To become a candidate for incorporation into ScriptForge, a library of services
&apos;&apos;&apos;		must register its services by implementing a
&apos;&apos;&apos;			RegisterScriptServices(...)
&apos;&apos;&apos;		method. This method must be present in any external library wanting to extend
&apos;&apos;&apos;		the set of available services.
&apos;&apos;&apos;
&apos;&apos;&apos;		Above mechanisms make ScriptForge an easily extensible ecosystem combining a core library,
&apos;&apos;&apos;		additional standard libraries and libraries published as extensions or distributed
&apos;&apos;&apos;		as company extensions.
&apos;&apos;&apos;
&apos;&apos;&apos;		The methods in this module constitute the kernel of the ScriptForge framework
&apos;&apos;&apos;			RegisterScriptServices()
&apos;&apos;&apos;				Register for a library the list of services it implements.
&apos;&apos;&apos;				Each library in the framework must implement its own RegisterScriptServices() method.
&apos;&apos;&apos;				This method consists in a series of invocations of next 2 methods
&apos;&apos;&apos;					RegisterService()
&apos;&apos;&apos;						Register a single service
&apos;&apos;&apos;					RegisterEventManager()
&apos;&apos;&apos;						Register a single event manager
&apos;&apos;&apos;			CreateScriptService()
&apos;&apos;&apos;				Called by user scripts to get an object giving access to a service or to the event manager
&apos;&apos;&apos;
&apos;&apos;&apos;		Detailed user documentation:
&apos;&apos;&apos;			https://help.libreoffice.org/latest/en-US/text/sbasic/shared/03/sf_services.html?DbPAR=BASIC
&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;

REM ================================================================== EXCEPTIONS

Const UNKNOWNSERVICEERROR		=	&quot;UNKNOWNSERVICEERROR&quot;		&apos;	Service not found within the registered services of the given library
Const SERVICESNOTLOADEDERROR	=	&quot;SERVICESNOTLOADEDERROR&quot;	&apos;	Failure during the registering of the services of the given library
Const UNKNOWNFILEERROR			=	&quot;UNKNOWNFILEERROR&quot;			&apos;	Source file does not exist

REM ============================================================== PUBLIC MEMBERS

&apos;	Defines an entry in the services dictionary
Type _Service
	ServiceName				As String
	ServiceType				As Integer
		&apos;	0		Undefined
		&apos;	1		Basic module
		&apos;	2		Method reference as a string
	ServiceReference		As Object
	ServiceMethod			As String
	EventManager			As Boolean		&apos;	True if registered item is an event manager
End Type

Private vServicesArray		As Variant			&apos;	List of services registered by a library

REM ============================================================== PUBLIC METHODS

REM -----------------------------------------------------------------------------
Public Function CreateScriptService(Optional ByRef Service As Variant _
										, ParamArray pvArgs As Variant _
										) As Variant
&apos;&apos;&apos;	Create access to the services of a library for the benefit of a user script
&apos;&apos;&apos;	A service is to understand either:
&apos;&apos;&apos;		as a set of methods gathered in a Basic standard module
&apos;&apos;&apos;		or a set of methods and properties gathered in a Basic class module
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		Service: the name of the service in 2 parts &quot;library.service&quot;
&apos;&apos;&apos;			The library is a Basic library that must exist in the GlobalScope
&apos;&apos;&apos;				(default = &quot;ScriptForge&quot;)
&apos;&apos;&apos;			The service is one of the services registered by the library
&apos;&apos;&apos;				thru the RegisterScriptServices() routine
&apos;&apos;&apos;		pvArgs: a set of arguments passed to the constructor of the service
&apos;&apos;&apos;			This is only possible if the service refers to a Basic class module
&apos;&apos;&apos;	Returns
&apos;&apos;&apos;		The object containing either the reference of the Basic module
&apos;&apos;&apos;		or of the Basic class instance
&apos;&apos;&apos;			Both are Basic objects
&apos;&apos;&apos;		Returns Nothing if an error occurred.
&apos;&apos;&apos;			==&gt;&gt; NOTE: The error can be within the user script creating the new class instance
&apos;&apos;&apos;	Exceptions:
&apos;&apos;&apos;		SERVICESNOTLOADEDERROR			RegisterScriptService probable failure
&apos;&apos;&apos;		UNKNOWNSERVICEERROR				Service not found
&apos;&apos;&apos;	Examples
&apos;&apos;&apos;		CreateScriptService(&quot;Array&quot;)
&apos;&apos;&apos;					=&gt; Refers to &quot;ScriptForge.Array&quot;, i.e. the ScriptForge.SF_Array module
&apos;&apos;&apos;		CreateScriptService(&quot;ScriptForge.Dictionary&quot;, ...)
&apos;&apos;&apos;					=&gt; Returns a new empty dictionary; &quot;ScriptForge.&quot; is optional
&apos;&apos;&apos;		CreateScriptService(&quot;SFDocuments.Calc&quot;, ...)
&apos;&apos;&apos;					=&gt; Refers to the Calc service, implemented in the SFDocuments library
&apos;&apos;&apos;		CreateScriptService(&quot;Dialog&quot;, dlgName, ...)
&apos;&apos;&apos;					=&gt; Returns a Dialog instance referring to the dlgName dialog
&apos;&apos;&apos;		CreateScriptService(&quot;SFDocuments.Event&quot;, oEvent)
&apos;&apos;&apos;					=&gt; Refers to the Document service instance, implemented in the SFDocuments library, having triggered the event

Dim vScriptService As Variant		&apos;	Return value
Dim vServiceItem As Variant			&apos;	A single service (see _Service type definition)
Dim vServicesList As Variant		&apos;	Output of RegisterScriptServices
Dim vSplit As Variant				&apos;	Array to split argument in
Dim sLibrary As String				&apos;	Library part of the argument
Dim sService As String				&apos;	Service part of the argument
Dim vLibrary As Variant				&apos;	Dictionary of libraries
Dim vService As Variant				&apos;	An individual service object
Const cstThisSub = &quot;SF_Services.CreateScriptService&quot;
Const cstSubArgs = &quot;Service, arg0[, arg1] ...&quot;

	&apos;	Save Err, Erl, .. values before any On Error ... statement
	SF_Exception._CaptureSystemError()
	If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
	Set vScriptService = Nothing

Check:
	If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
		If Not SF_Utils._Validate(Service, &quot;Service&quot;, V_STRING) Then GoTo Catch
		If Len(Service) = 0 Then GoTo CatchNotFound
	End If

Try:
	&apos;	Initialize the list of services when CreateScriptService called for the very 1st time
	If IsEmpty(_SF_.ServicesList) Then _SF_.ServicesList = SF_Services._NewDictionary()

	&apos;	Simple parsing of argument
	vSplit = Split(Service, &quot;.&quot;)
	If UBound(vSplit) &gt; 1 Then GoTo CatchNotFound
	If UBound(vSplit) = 0 Then
		sLibrary = &quot;ScriptForge&quot;	&apos;	Yes, the default value !
		sService = vSplit(0)
		&apos;	Accept other default values for associated libraries
		Select Case LCase(sService)
			Case &quot;document&quot;, &quot;calc&quot;, &quot;writer&quot;, &quot;base&quot;, &quot;formdocument&quot;, &quot;documentevent&quot;, &quot;formevent&quot;
														sLibrary = &quot;SFDocuments&quot;
			Case &quot;dialog&quot;, &quot;dialogevent&quot;, &quot;newdialog&quot;
														sLibrary = &quot;SFDialogs&quot;
			Case &quot;database&quot;, &quot;datasheet&quot;			:	sLibrary = &quot;SFDatabases&quot;
			Case &quot;unittest&quot;							:	sLibrary = &quot;SFUnitTests&quot;
			Case &quot;contextmenu&quot;, &quot;menu&quot;, &quot;popupmenu&quot;, &quot;toolbar&quot;, &quot;toolbarbutton&quot;
														sLibrary = &quot;SFWidgets&quot;
			Case Else
		End Select
	Else
		sLibrary = vSplit(0)
		sService = vSplit(1)
	End If

	With _SF_.ServicesList

	&apos;	Load the set of services from the library, if not yet done
		If Not .Exists(sLibrary) Then
			If Not SF_Services._LoadLibraryServices(sLibrary) Then GoTo CatchNotLoaded
		End If

	&apos;	Find and return the requested service
		vServicesList = .Item(sLibrary)
		If Not vServicesList.Exists(sService) Then GoTo CatchNotFound
		vServiceItem = vServicesList.Item(sService)
		Select Case vServiceItem.ServiceType
			Case 1			&apos;	Basic module
				vScriptService = vServiceItem.ServiceReference
			Case 2			&apos;	Method to call
				If sLibrary = &quot;ScriptForge&quot; Then	&apos;	Direct call
					Select Case UCase(sService)
						Case &quot;DICTIONARY&quot;	:	vScriptService = SF_Services._NewDictionary(pvArgs)
						Case &quot;L10N&quot;			:	vScriptService = SF_Services._NewL10N(pvArgs)
						Case &quot;TIMER&quot;		:	vScriptService = SF_Services._NewTimer(pvArgs)
						Case Else
					End Select
				Else								&apos;	Call via script provider
					Set vService = SF_Session._GetScript(&quot;Basic&quot;, SF_Session.SCRIPTISAPPLICATION, vServiceItem.ServiceMethod)
					vScriptService = vService.Invoke(Array(pvArgs()), Array(), Array())
				End If
			Case Else
		End Select

	End With

Finally:
	CreateScriptService = vScriptService
	SF_Utils._ExitFunction(cstThisSub)
	Exit Function
Catch:
	GoTo Finally
CatchNotFound:
	SF_Exception.RaiseFatal(UNKNOWNSERVICEERROR, &quot;Service&quot;, Service, sLibrary, sService)
	GoTo Finally
CatchNotLoaded:
	SF_Exception.RaiseFatal(SERVICESNOTLOADEDERROR, &quot;Service&quot;, Service, sLibrary)
	GoTo Finally
End Function	&apos;	ScriptForge.SF_Services.CreateScriptService

REM -----------------------------------------------------------------------------
Public Function RegisterEventManager(Optional ByVal ServiceName As Variant _
									, Optional ByRef ServiceReference As Variant _
									) As Boolean
&apos;&apos;&apos;	Register into ScriptForge a new event entry for the library
&apos;&apos;&apos;	from which this method is called
&apos;&apos;&apos;	MUST BE CALLED ONLY from a specific RegisterScriptServices() method
&apos;&apos;&apos;	Usually the method should be called only once by library
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		ServiceName: the name of the service as a string. It the service exists
&apos;&apos;&apos;			already for the library the method overwrites the existing entry
&apos;&apos;&apos;		ServiceReference: the function which will identify the source of the triggered event
&apos;&apos;&apos;				something like: &quot;libraryname.modulename.function&quot;
&apos;&apos;&apos;	Returns:
&apos;&apos;&apos;		True if successful
&apos;&apos;&apos;	Example:
&apos;&apos;&apos;		&apos;	Code snippet stored in a module contained in the SFDocuments library
&apos;&apos;&apos;		Sub RegisterScriptServices()
&apos;&apos;&apos;			&apos;	Register the events manager of the library
&apos;&apos;&apos;			RegisterEventManager(&quot;DocumentEvent&quot;, &quot;SFDocuments.SF_Register._EventManager&quot;)
&apos;&apos;&apos;		End Sub
&apos;&apos;&apos;		&apos;	Code snippet stored in a user script
&apos;&apos;&apos;		Sub Trigger(poEvent As Object)	&apos;	Triggered by a DOCUMENTEVENT event
&apos;&apos;&apos;		Dim myDoc As Object
&apos;&apos;&apos;			&apos;	To get the document concerned by the event:
&apos;&apos;&apos;			Set myDoc = CreateScriptService(&quot;SFDocuments.DocumentEvent&quot;, poEvent)
&apos;&apos;&apos;		End Sub

Dim bRegister As Boolean			&apos;	Return value
Const cstThisSub = &quot;SF_Services.RegisterEventManager&quot;
Const cstSubArgs = &quot;ServiceName, ServiceReference&quot;

	If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
	bRegister = False

Check:
	If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
		If Not SF_Utils._Validate(ServiceName, &quot;ServiceName&quot;, V_STRING) Then GoTo Finally
		If Not SF_Utils._Validate(ServiceReference, &quot;ServiceReference&quot;,V_STRING) Then GoTo Finally
	End If

Try:
	bRegister = _AddToServicesArray(ServiceName, ServiceReference, True)
	
Finally:
	RegisterEventManager = bRegister
	SF_Utils._ExitFunction(cstThisSub)
	Exit Function
Catch:
	GoTo Finally
End Function		&apos;	ScriptForge.SF_Services.RegisterEventManager

REM -----------------------------------------------------------------------------
Public Function RegisterService(Optional ByVal ServiceName As Variant _
									, Optional ByRef ServiceReference As Variant _
									) As Boolean
&apos;&apos;&apos;	Register into ScriptForge a new service entry for the library
&apos;&apos;&apos;	from which this method is called
&apos;&apos;&apos;	MUST BE CALLED ONLY from a specific RegisterScriptServices() method
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		ServiceName: the name of the service as a string. It the service exists
&apos;&apos;&apos;			already for the library the method overwrites the existing entry
&apos;&apos;&apos;		ServiceReference: either
&apos;&apos;&apos;			- the Basic module that implements the methods of the service
&apos;&apos;&apos;				something like: GlobalScope.Library.Module
&apos;&apos;&apos;			- an instance of the class implementing the methods and properties of the service
&apos;&apos;&apos;				something like: &quot;libraryname.modulename.function&quot;
&apos;&apos;&apos;	Returns:
&apos;&apos;&apos;		True if successful

Dim bRegister As Boolean			&apos;	Return value
Const cstThisSub = &quot;SF_Services.RegisterService&quot;
Const cstSubArgs = &quot;ServiceName, ServiceReference&quot;

	If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
	bRegister = False

Check:
	If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
		If Not SF_Utils._Validate(ServiceName, &quot;ServiceName&quot;, V_STRING) Then GoTo Finally
		If Not SF_Utils._Validate(ServiceReference, &quot;ServiceReference&quot;, Array(V_STRING, V_OBJECT)) Then GoTo Finally
	End If

Try:
	bRegister = _AddToServicesArray(ServiceName, ServiceReference, False)
	
Finally:
	RegisterService = bRegister
	SF_Utils._ExitFunction(cstThisSub)
	Exit Function
Catch:
	GoTo Finally
End Function		&apos;	ScriptForge.SF_Services.RegisterService

REM -----------------------------------------------------------------------------
Public Sub RegisterScriptServices() As Variant
&apos;&apos;&apos;	Register into ScriptForge the list of the services implemented by the current library
&apos;&apos;&apos;	Each library pertaining to the framework must implement its own version of this method
&apos;&apos;&apos;	This method may be stored in any standard (i.e. not class-) module
&apos;&apos;&apos;
&apos;&apos;&apos;	Each individual service is registered by calling the RegisterService() method
&apos;&apos;&apos;
&apos;&apos;&apos;	The current version is given as an example
&apos;&apos;&apos;
	With GlobalScope.ScriptForge.SF_Services
		.RegisterService(&quot;Array&quot;, GlobalScope.ScriptForge.SF_Array)					&apos;	Reference to the Basic module
		.RegisterService(&quot;Dictionary&quot;, &quot;ScriptForge.SF_Services._NewDictionary&quot;)	&apos;	Reference to the function initializing the service
		.RegisterService(&quot;Exception&quot;, GlobalScope.ScriptForge.SF_Exception)
		.RegisterService(&quot;FileSystem&quot;, GlobalScope.ScriptForge.SF_FileSystem)
		.RegisterService(&quot;L10N&quot;, &quot;ScriptForge.SF_Services._NewL10N&quot;)
		.RegisterService(&quot;Platform&quot;, GlobalScope.ScriptForge.SF_Platform)
		.RegisterService(&quot;Region&quot;, GlobalScope.ScriptForge.SF_Region)
		.RegisterService(&quot;Session&quot;, GlobalScope.ScriptForge.SF_Session)
		.RegisterService(&quot;SharedMemory&quot;, GlobalScope.ScriptForge.SF_SharedMemory)
		.RegisterService(&quot;String&quot;, GlobalScope.ScriptForge.SF_String)
		.RegisterService(&quot;Timer&quot;, &quot;ScriptForge.SF_Services._NewTimer&quot;)
		.RegisterService(&quot;UI&quot;, GlobalScope.ScriptForge.SF_UI)
		&apos;TODO
	End With

End Sub			&apos;	ScriptForge.SF_Services.RegisterScriptServices

REM =========================================================== PRIVATE FUNCTIONS

REM -----------------------------------------------------------------------------
Private Function _AddToServicesArray(ByVal psServiceName As String _
										, ByRef pvServiceReference As Variant _
										, ByVal pbEvent As Boolean _
										) As Boolean
&apos;&apos;&apos;	Add the arguments as an additional row in vServicesArray (Public variable)
&apos;&apos;&apos;	Called from RegisterService and RegisterEvent methods

Dim bRegister As Boolean		&apos;	Return value
Dim lMax As Long				&apos;	Number of rows in vServicesArray

	bRegister = False

Check:
	&apos;	Ignore when method is not called from RegisterScriptServices()
	If IsEmpty(vServicesArray) Or IsNull(vServicesArray) Or Not IsArray(vServicesArray) Then GoTo Finally

Try:
	lMax = UBound(vServicesArray, 1) + 1
	If lMax &lt;= 0 Then
		ReDim vServicesArray(0 To 0, 0 To 2)
	Else
		ReDim Preserve vServicesArray(0 To lMax, 0 To 2)
	End If
	vServicesArray(lMax, 0) = psServiceName
	vServicesArray(lMax, 1) = pvServiceReference
	vServicesArray(lMax, 2) = pbEvent
	bRegister = True

Finally:
	_AddToServicesArray = bRegister
	Exit Function
End Function	&apos;	ScriptForge.SF_Services._AddToServicesArray

REM -----------------------------------------------------------------------------
Private Function _FindModuleFromMethod(ByVal psLibrary As String _
										, ByVal psMethod As String _
										) As String
&apos;&apos;&apos;	Find in the given library the name of the module containing
&apos;&apos;&apos;	the method given as 2nd argument (usually RegisterScriptServices)
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		psLibrary: the name of the Basic library
&apos;&apos;&apos;		psMethod: the method to locate
&apos;&apos;&apos;	Returns:
&apos;&apos;&apos;		The name of the module or a zero-length string if not found

Dim oBasic As Object					&apos;	com.sun.star.comp.scripting.ScriptProviderForBasic
Dim vCategories As Variant				&apos;	&quot;user&quot; or &quot;share&quot; library categories
Dim sCategory As String
Dim vLibraries As Variant				&apos;	Library names
Dim sLibrary As String
Dim vModules As Variant					&apos;	Module names
Dim sModule As String					&apos;	Return value
Dim vMethods As Variant					&apos;	Methods/properties/subs/functions
Dim sMethod As String
Dim i As Integer, k As Integer, l As Integer, m As Integer

	_FindModuleFromMethod = &quot;&quot;

	&apos;	Exploration is done via tree nodes, starting
	&apos;	from a com.sun.star.comp.scripting.ScriptProviderForBasic
	&apos;	See also bug report https://bugs.documentfoundation.org/show_bug.cgi?id=165147
	vCategories = Array(&quot;user&quot;, &quot;share&quot;)
	For i = 0 To UBound(vCategories)
		Set oBasic = CreateUnoService(&quot;com.sun.star.comp.scripting.ScriptProviderForBasic&quot;)
		sCategory = vCategories(i)
		oBasic.initialize(Array(sCategory))
		If oBasic.hasChildNodes() Then
			vLibraries = oBasic.getChildNodes()
			For k = 0 To UBound(vLibraries)
				sLibrary = vLibraries(k).getName()
				&apos;	Consider the given library only
				If sLibrary = psLibrary Then
					If vLibraries(k).hasChildNodes() Then
						vModules = vLibraries(k).getChildNodes()
						For l = 0 To UBound(vModules)
							sModule = vModules(l).getName()
							&apos;	Check if the module contains the targeted method
							If vModules(l).hasChildNodes() Then
								vMethods = vModules(l).getChildNodes()
								For m = 0 To UBound(vMethods)
									sMethod = vMethods(m).getName()
									If sMethod = psMethod Then
										_FindModuleFromMethod = sModule
										Exit Function
									End If
								Next m
							End If
						Next l
					End If
				End If
			Next k
		End If
	Next i

End Function	&apos;	ScriptForge.SF_Services._FindModuleFromMethod

REM -----------------------------------------------------------------------------
Private Function _LoadLibraryServices(ByVal psLibrary As String) As Boolean
&apos;&apos;&apos;	Execute psLibrary.RegisterScriptServices() and load its services into the persistent storage
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		psLibrary: the name of the Basic library
&apos;&apos;&apos;					Library will be loaded if not yet done
&apos;&apos;&apos;	Returns:
&apos;&apos;&apos;		True if success
&apos;&apos;&apos;		The list of services is loaded directly into the persistent storage


Dim vServicesList As Variant		&apos;	Dictionary of services
Dim vService As Variant				&apos;	Single service entry in dictionary
Dim vServiceItem As Variant			&apos;	Single service in vServicesArray
Dim sModule As String				&apos;	Name of module containing the RegisterScriptServices method
Dim i As Long
Const cstRegister = &quot;RegisterScriptServices&quot;

Try:
	_LoadLibraryServices = False
	
	vServicesArray = Array()

	If psLibrary = &quot;ScriptForge&quot; Then
		&apos;	Direct call
		ScriptForge.SF_Services.RegisterScriptServices()
	Else
		&apos;	Register services via script provider
		If GlobalScope.BasicLibraries.hasByName(psLibrary) Then
			If Not GlobalScope.BasicLibraries.isLibraryLoaded(psLibrary) Then
				GlobalScope.BasicLibraries.LoadLibrary(psLibrary)
			End If
		Else
			GoTo Finally
		End If
		sModule = SF_Services._FindModuleFromMethod(psLibrary, cstRegister)
		If Len(sModule) = 0 Then GoTo Finally
		SF_Session.ExecuteBasicScript(, psLibrary &amp; &quot;.&quot; &amp; sModule &amp; &quot;.&quot; &amp; cstRegister)
	End If

	&apos;	Store in persistent storage
	&apos;	- Create list of services for the current library
	Set vServicesList = SF_Services._NewDictionary()
	For i = 0 To UBound(vServicesArray, 1)
		Set vService = New _Service
		With vService
			.ServiceName = vServicesArray(i, 0)
			vServiceItem = vServicesArray(i, 1)
			If VarType(vServiceItem) = V_STRING Then
				.ServiceType = 2
				.ServiceMethod = vServiceItem
				Set .ServiceReference = Nothing
			Else	&apos;	OBJECT
				.ServiceType = 1
				.ServiceMethod = &quot;&quot;
				Set .ServiceReference = vServiceItem
			End If
			.EventManager = vServicesArray(i, 2)
		End With
		vServicesList.Add(vServicesArray(i, 0), vService)
	Next i
	&apos;	- Add the new dictionary to the persistent dictionary
	_SF_.ServicesList.Add(psLibrary, vServicesList)
	_LoadLibraryServices = True
	vServicesArray = Empty

Finally:
	Exit Function
End Function	&apos;	ScriptForge.SF_Services._LoadLibraryServices

REM -----------------------------------------------------------------------------
Public Function _NewDictionary(Optional ByVal pvArgs As Variant) As Variant
&apos;&apos;&apos;	Create a new instance of the SF_Dictionary class
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		[0] : If True, the keys are compared case-sensitively. Default = False
&apos;&apos;&apos;	Returns: the instance or Nothing

Dim oDict As Variant			&apos;	Return value
Dim bCaseSensitive As Boolean	&apos;	Keys comparison

	If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch

Check:
	If IsMissing(pvArgs) Then pvArgs = Array()
	If Not IsArray(pvArgs) Then pvArgs = Array(pvArgs)
	If UBound(pvArgs) &lt; 0 Then
		bCaseSensitive = False
	Else
		If Not SF_Utils._Validate(pvArgs(0), &quot;CaseSensitive (Arg0)&quot;, V_BOOLEAN) Then GoTo Catch
		bCaseSensitive = pvArgs(0)
	End If

Try:
	Set oDict = New SF_Dictionary
	Set oDict.[Me] = oDict
	oDict.CaseSensitive = bCaseSensitive

Finally:
	Set _NewDictionary = oDict
	Exit Function
Catch:
	Set oDict = Nothing
	GoTo Finally
End Function	&apos;	ScriptForge.SF_Services._NewDictionary

REM -----------------------------------------------------------------------------
Public Function _NewL10N(Optional ByVal pvArgs As Variant) As Variant
&apos;&apos;&apos;	Create a new instance of the SF_L10N class
&apos;	Args:
&apos;&apos;&apos;		FolderName: the folder containing the PO files in SF_FileSystem.FileNaming notation
&apos;&apos;&apos;		Locale: locale of user session (default) or any other valid la{nguage]-CO[UNTRY] combination
&apos;&apos;&apos;			The country part is optional. Valid are f.i. &quot;fr&quot;, &quot;fr-CH&quot;, &quot;en-US&quot;
&apos;&apos;&apos;		Encoding: The character set that should be used
&apos;&apos;&apos;				Use one of the Names listed in https://www.iana.org/assignments/character-sets/character-sets.xhtml
&apos;&apos;&apos;				Note that LibreOffice probably does not implement all existing sets
&apos;&apos;&apos;				Default = UTF-8
&apos;&apos;&apos;		Locale2: fallback Locale to select if Locale po file does not exist (typically &quot;en-US&quot;)
&apos;&apos;&apos;		Encoding2: Encoding of the 2nd Locale file
&apos;&apos;&apos;	Returns: the instance or Nothing
&apos;&apos;&apos;	Exceptions:
&apos;&apos;&apos;		UNKNOWNFILEERROR		The PO file does not exist

Dim oL10N As Variant		&apos;	Return value
Dim sFolderName	As String	&apos;	Folder containing the PO files
Dim sLocale As String		&apos;	Passed argument or that of the user session
Dim sLocale2 As String		&apos;	Alias for Locale2
Dim oLocale As Variant		&apos;	com.sun.star.lang.Locale
Dim sPOFile As String		&apos;	PO file must exist
Dim sEncoding As String		&apos;	Alias for Encoding
Dim sEncoding2 As String	&apos;	Alias for Encoding2

	If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch

Check:
	If IsMissing(pvArgs) Then pvArgs = Array()
	sPOFile = &quot;&quot;
	sEncoding = &quot;&quot;
	If UBound(pvArgs) &gt;= 0 Then
		If Not SF_Utils._ValidateFile(pvArgs(0), &quot;Folder (Arg0)&quot;, , True) Then GoTo Catch
		sFolderName = pvArgs(0)
		sLocale = &quot;&quot;
		If UBound(pvArgs) &gt;= 1 Then
			If Not SF_Utils._Validate(pvArgs(1), &quot;Locale (Arg1)&quot;, V_STRING) Then GoTo Catch
			sLocale = pvArgs(1)
		End If
		If Len(sLocale) = 0 Then	&apos;	Called from Python, the Locale argument may be the zero-length string
			Set oLocale = SF_Utils._GetUNOService(&quot;OfficeLocale&quot;)
			sLocale = oLocale.Language &amp; &quot;-&quot; &amp; oLocale.Country
		End If
		If UBound(pvArgs) &gt;= 2 Then
			If IsMissing(pvArgs(2)) Or IsEmpty(pvArgs(2)) Then pvArgs(2) = &quot;UTF-8&quot;
			If Not SF_Utils._Validate(pvArgs(2), &quot;Encoding (Arg2)&quot;, V_STRING) Then GoTo Catch
			sEncoding = pvArgs(2)
		Else
			sEncoding = &quot;UTF-8&quot;
		End If
		sLocale2 = &quot;&quot;
		If UBound(pvArgs) &gt;= 3 Then
			If Not SF_Utils._Validate(pvArgs(3), &quot;Locale2 (Arg3)&quot;, V_STRING) Then GoTo Catch
			sLocale2 = pvArgs(3)
		End If
		If UBound(pvArgs) &gt;= 4 Then
			If Not SF_Utils._Validate(pvArgs(4), &quot;Encoding2 (Arg4)&quot;, V_STRING) Then GoTo Catch
			sEncoding2 = pvArgs(4)
		Else
			sEncoding2 = &quot;UTF-8&quot;
		End If
		If Len(sFolderName) &gt; 0 Then
			sPOFile = SF_FileSystem.BuildPath(sFolderName, sLocale &amp; &quot;.po&quot;)
			If Not SF_FileSystem.FileExists(sPOFile) Then
				If Len(sLocale2) = 0 Then GoTo CatchNotExists	&apos;	No fallback =&gt; error
				&apos;	Try the fallback
				sPOFile = SF_FileSystem.BuildPath(sFolderName, sLocale2 &amp; &quot;.po&quot;)
				If Not SF_FileSystem.FileExists(sPOFile) Then GoTo CatchNotExists
				sEncoding = sEncoding2
			End If
		End If
	End If

Try:
	Set oL10N = New SF_L10N
	Set oL10N.[Me] = oL10N
	oL10N._Initialize(sPOFile, sEncoding)

Finally:
	Set _NewL10N = oL10N
	Exit Function
Catch:
	Set oL10N = Nothing
	GoTo Finally
CatchNotExists:
	SF_Exception.RaiseFatal(UNKNOWNFILEERROR, &quot;FileName&quot;, sPOFile)
	GoTo Finally
End Function	&apos;	ScriptForge.SF_Services._NewL10N

REM -----------------------------------------------------------------------------
Public Function _NewTimer(Optional ByVal pvArgs As Variant) As Variant
&apos;&apos;&apos;	Create a new instance of the SF_Timer class
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		[0] : If True, start the timer immediately
&apos;&apos;&apos;	Returns: the instance or Nothing

Dim oTimer As Variant		&apos;	Return value
Dim bStart As Boolean		&apos;	Automatic start ?

	If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch

Check:
	If IsMissing(pvArgs) Then pvArgs = Array()
	If UBound(pvArgs) &lt; 0 Then
		bStart = False
	Else
		If Not SF_Utils._Validate(pvArgs(0), &quot;Start (Arg0)&quot;, V_BOOLEAN) Then GoTo Catch
		bStart = pvArgs(0)
	End If
Try:
	Set oTimer = New SF_Timer
	Set oTimer.[Me] = oTimer
	If bStart Then oTimer.Start()

Finally:
	Set _NewTimer = oTimer
	Exit Function
Catch:
	Set oTimer = Nothing
	GoTo Finally
End Function	&apos;	ScriptForge.SF_Services._NewTimer

REM ============================================== END OF SCRIPTFORGE.SF_SERVICES
</script:module>