diff options
author | Jean-Pierre Ledure <jp@ledure.be> | 2021-03-04 17:19:27 +0100 |
---|---|---|
committer | Jean-Pierre Ledure <jp@ledure.be> | 2021-03-05 10:26:51 +0100 |
commit | f66a479225b35e9f1fd3621eef7496462088308a (patch) | |
tree | 8499d1565c6f9ac39355e3a36aac84abfd8b2d1e /wizards/source/scriptforge | |
parent | a04dde6b9c6d625ac80acbad1e599dbd8edf3579 (diff) |
ScriptForge - (scriptforge.py) Python-Basic machinery
Python scripts can now invoke usual Basic builtin functions
Example:
from ScriptForge import CreateScriptService
bas = CreateScriptService('Basic')
bas.MsgBox('This is the text to be displayed', bas.MB_ICONEXCLAMATION)
Python scripts can use most ScriptForge services written in Basic
Example:
from scriptforge import CreateScriptService
FSO = CreateScriptService('FileSystem')
a = FSO.BuildPath('/tmp', 'xyz')
Syntax and semantic are as close as possible to the Basic syntax
Implemented are a SFServices class and its subclasses representing each
a ScriptForge service and where the interfaces are defined (properties,
methods and arguments)
Their execution goes through the "machinery", i.e. a set of python
and basic routines that manage the call from the python process to
the appropriate service in an as much agnostic and generic way
Only a limited set of services are implemented so far: SF_FileSystem
(partially) and SF_Timer: they served as prototypes and initial tests
Change-Id: I0b383b59359c12710e7165139e498cca5a7856bb
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/111971
Tested-by: Jean-Pierre Ledure <jp@ledure.be>
Tested-by: Jenkins
Reviewed-by: Jean-Pierre Ledure <jp@ledure.be>
Diffstat (limited to 'wizards/source/scriptforge')
-rw-r--r-- | wizards/source/scriptforge/SF_FileSystem.xba | 17 | ||||
-rw-r--r-- | wizards/source/scriptforge/SF_PythonHelper.xba | 752 | ||||
-rw-r--r-- | wizards/source/scriptforge/SF_Root.xba | 49 | ||||
-rw-r--r-- | wizards/source/scriptforge/SF_Session.xba | 2 | ||||
-rw-r--r-- | wizards/source/scriptforge/SF_Timer.xba | 4 | ||||
-rw-r--r-- | wizards/source/scriptforge/po/ScriptForge.pot | 6 | ||||
-rw-r--r-- | wizards/source/scriptforge/po/en.po | 6 | ||||
-rw-r--r-- | wizards/source/scriptforge/python/scriptforge.py | 676 | ||||
-rw-r--r-- | wizards/source/scriptforge/script.xlb | 1 |
9 files changed, 1494 insertions, 19 deletions
diff --git a/wizards/source/scriptforge/SF_FileSystem.xba b/wizards/source/scriptforge/SF_FileSystem.xba index 7fd5e8fff562..f626eba6fd92 100644 --- a/wizards/source/scriptforge/SF_FileSystem.xba +++ b/wizards/source/scriptforge/SF_FileSystem.xba @@ -1091,14 +1091,14 @@ Check: Try: Select Case UCase(PropertyName) - Case "ConfigFolder" : GetProperty = ConfigFolder - Case "ExtensionsFolder" : GetProperty = ExtensionsFolder - Case "FileNaming" : GetProperty = FileNaming - Case "HomeFolder" : GetProperty = HomeFolder - Case "InstallFolder" : GetProperty = InstallFolder - Case "TemplatesFolder" : GetProperty = TemplatesFolder - Case "TemporaryFolder" : GetProperty = TemporaryFolder - Case "UserTemplatesFolder" : GetProperty = UserTemplatesFolder + Case UCase("ConfigFolder") : GetProperty = ConfigFolder + Case UCase("ExtensionsFolder") : GetProperty = ExtensionsFolder + Case UCase("FileNaming") : GetProperty = FileNaming + Case UCase("HomeFolder") : GetProperty = HomeFolder + Case UCase("InstallFolder") : GetProperty = InstallFolder + Case UCase("TemplatesFolder") : GetProperty = TemplatesFolder + Case UCase("TemporaryFolder") : GetProperty = TemporaryFolder + Case UCase("UserTemplatesFolder") : GetProperty = UserTemplatesFolder Case Else End Select @@ -1611,6 +1611,7 @@ Check: Try: Select Case UCase(PropertyName) + Case UCase("FileNaming") : FileNaming = Value Case Else End Select diff --git a/wizards/source/scriptforge/SF_PythonHelper.xba b/wizards/source/scriptforge/SF_PythonHelper.xba new file mode 100644 index 000000000000..443a75d4afdb --- /dev/null +++ b/wizards/source/scriptforge/SF_PythonHelper.xba @@ -0,0 +1,752 @@ +<?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_PythonHelper" 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 + +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +''' SF_PythonHelper (aka Basic) +''' =============== +''' Singleton class implementing the "ScriptForge.Basic" service +''' Implemented as a usual Basic module +''' +''' The "Basic" service must be called ONLY from a PYTHON script +''' Service invocations: Next Python code lines are equivalent: +''' bas = CreateScriptService('ScriptForge.Basic') +''' bas = CreateScriptService('Basic') +''' +''' This service proposes a collection of methods to be executed in a Python context +''' to simulate the exact behaviour of the identical Basic buitin method. +''' Typical example: +''' bas.MsgBox('This has to be displayed in a message box') +''' +''' The service includes also an agnostic "Python Dispatcher" function. +''' It dispatches Python script requests to execute Basic services to the +''' appropriate properties and methods via dynamic call techniques +''' +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +REM ================================================================== EXCEPTIONS + +REM ============================================================ MODULE CONSTANTS + +REM ===================================================== CONSTRUCTOR/DESTRUCTOR + +REM ----------------------------------------------------------------------------- +Public Function Dispose() As Variant + Set Dispose = Nothing +End Function ' ScriptForge.SF_PythonHelper Explicit destructor + +REM ================================================================== PROPERTIES + +REM ----------------------------------------------------------------------------- +Property Get ObjectType As String +''' Only to enable object representation + ObjectType = "SF_PythonHelper" +End Property ' ScriptForge.SF_PythonHelper.ObjectType + +REM ----------------------------------------------------------------------------- +Property Get ServiceName As String +''' Internal use + ServiceName = "ScriptForge.Basic" +End Property ' ScriptForge.SF_PythonHelper.ServiceName + +REM ============================================================== PUBLIC METHODS + +REM ----------------------------------------------------------------------------- +Public Function PyConvertFromUrl(ByVal FileName As Variant) As String +''' Convenient function to replicate ConvertFromUrl() in Python scripts +''' Args: +''' FileName: a string representing a file in URL format +''' Returns: +''' The same file name in native operating system notation +''' Example: (Python code) +''' a = bas.ConvertFromUrl('file:////boot.sys') + +Dim sFileName As String ' Return value +Const cstThisSub = "Basic.ConvertFromUrl" +Const cstSubArgs = "filename" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sFileName = "" + +Check: + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) + +Try: + sFileName = ConvertFromUrl(FileName) + +Finally: + PyConvertFromUrl = sFileName + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_PythonHelper.PyConvertFromUrl + +REM ----------------------------------------------------------------------------- +Public Function PyConvertToUrl(ByVal FileName As Variant) As String +''' Convenient function to replicate ConvertToUrl() in Python scripts +''' Args: +''' FileName: a string representing a file in native operating system notation +''' Returns: +''' The same file name in URL format +''' Example: (Python code) +''' a = bas.ConvertToUrl('C:\boot.sys') + +Dim sFileName As String ' Return value +Const cstThisSub = "Basic.ConvertToUrl" +Const cstSubArgs = "filename" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sFileName = "" + +Check: + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) + +Try: + sFileName = ConvertToUrl(FileName) + +Finally: + PyConvertToUrl = sFileName + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_PythonHelper.PyConvertToUrl + +REM ----------------------------------------------------------------------------- +Public Function PyCreateUnoService(ByVal UnoService As Variant) As Variant +''' Convenient function to replicate CreateUnoService() in Python scripts +''' Args: +''' UnoService: a string representing the service to create +''' Returns: +''' A UNO object +''' Example: (Python code) +''' a = bas.CreateUnoService('com.sun.star.i18n.CharacterClassification') + +Dim vUno As Variant ' Return value +Const cstThisSub = "Basic.CreateUnoService" +Const cstSubArgs = "unoservice" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + Set vUno = Nothing + +Check: + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) + +Try: + Set vUno = CreateUnoService(UnoService) + +Finally: + Set PyCreateUnoService = vUno + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_PythonHelper.PyCreateUnoService + +REM ----------------------------------------------------------------------------- +Public Function PyDateAdd(ByVal Add As Variant _ + , ByVal Count As Variant _ + , ByVal DateArg As Variant _ + ) As Variant +''' Convenient function to replicate DateAdd() in Python scripts +''' Args: +''' Add: The unit to add +''' Count: how many times to add (might be negative) +''' DateArg: a date as a string in iso format +''' Returns: +''' The new date date as a string in iso format +''' Example: (Python code) +''' a = bas.DateAdd('d', 1, bas.Now()) ' Tomorrow + +Dim vNewDate As Variant ' Return value +Dim vDate As Date ' Alias of DateArg +Const cstThisSub = "Basic.DateAdd" +Const cstSubArgs = "add, count, datearg" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + vNewDate = "" + +Check: + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) + +Try: + vDate = SF_Utils._CStrToDate(DateArg) + vNewDate = DateAdd(Add, Count, vDate) + +Finally: + If VarType(vNewDate) = V_DATE Then PyDateAdd = SF_Utils._CDateToIso(vNewDate) Else PyDateAdd = vNewDate + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_PythonHelper.PyDateAdd + +REM ----------------------------------------------------------------------------- +Public Function PyDateDiff(ByVal Add As Variant _ + , ByVal Date1 As Variant _ + , ByVal Date2 As Variant _ + , ByVal WeekStart As Variant _ + , ByVal YearStart As Variant _ + ) As Long +''' Convenient function to replicate DateDiff() in Python scripts +''' Args: +''' Add: The unit of the date interval +''' Date1, Date2: the two dates to be compared +''' WeekStart: the starting day of a week +''' YearStart: the starting week of a year +''' Returns: +''' The number of intervals expressed in Adds +''' Example: (Python code) +''' a = bas.DateDiff('d', bas.DateAdd('d', 1, bas.Now()), bas.Now()) ' -1 day + +Dim lDiff As Long ' Return value +Dim vDate1 As Date ' Alias of Date1 +Dim vDate2 As Date ' Alias of Date2 +Const cstThisSub = "Basic.DateDiff" +Const cstSubArgs = "add, date1, date2, [weekstart=1], [yearstart=1]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + lDiff = 0 + +Check: + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) + +Try: + vDate1 = SF_Utils._CStrToDate(Date1) + vDate2 = SF_Utils._CStrToDate(Date2) + lDiff = DateDiff(Add, vDate1, vDate2, WeekStart, YearStart) + + +Finally: + PyDateDiff = lDiff + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_PythonHelper.PyDateDiff + +REM ----------------------------------------------------------------------------- +Public Function PyDatePart(ByVal Add As Variant _ + , ByVal DateArg As Variant _ + , ByVal WeekStart As Variant _ + , ByVal YearStart As Variant _ + ) As Long +''' Convenient function to replicate DatePart() in Python scripts +''' Args: +''' Add: The unit of the date interval +''' DateArg: The date from which to extract a part +''' WeekStart: the starting day of a week +''' YearStart: the starting week of a year +''' Returns: +''' The specified part of the date +''' Example: (Python code) +''' a = bas.DatePart('y', bas.Now()) ' day of year + +Dim lPart As Long ' Return value +Dim vDate As Date ' Alias of DateArg +Const cstThisSub = "Basic.DatePart" +Const cstSubArgs = "add, datearg, [weekstart=1], [yearstart=1]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + lPart = 0 + +Check: + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) + +Try: + vDate = SF_Utils._CStrToDate(DateArg) + lPart = DatePart(Add, vDate, WeekStart, YearStart) + + +Finally: + PyDatePart = lPart + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_PythonHelper.PyDatePart + +REM ----------------------------------------------------------------------------- +Public Function PyDateValue(ByVal DateArg As Variant) As Variant +''' Convenient function to replicate DateValue() in Python scripts +''' Args: +''' DateArg: a date as a string +''' Returns: +''' The converted date as a string in iso format +''' Example: (Python code) +''' a = bas.DateValue('2021-02-18') + +Dim vDate As Variant ' Return value +Const cstThisSub = "Basic.DateValue" +Const cstSubArgs = "datearg" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + vDate = "" + +Check: + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) + +Try: + vDate = DateValue(DateArg) + +Finally: + If VarType(vDate) = V_DATE Then PyDateValue = SF_Utils._CDateToIso(vDate) Else PyDateValue = vDate + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_PythonHelper.PyDateValue + +REM ----------------------------------------------------------------------------- +Public Function PyFormat(ByVal Value As Variant _ + , ByVal Pattern As Variant _ + ) As String +''' Convenient function to replicate Format() in Python scripts +''' Args: +''' Value: a date or a number +''' Pattern: the format to apply +''' Returns: +''' The formatted value +''' Example: (Python code) +''' MsgBox bas.Format(6328.2, '##,##0.00') + +Dim sFormat As String ' Return value +Dim vValue As Variant ' Alias of Value +Const cstThisSub = "Basic.Format" +Const cstSubArgs = "value, oattern" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sFormat = "" + +Check: + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) + +Try: + If VarType(Value) = V_DATE Then vValue = SF_Utils._CStrToDate(Value) ELse vValue = Value + If IsEmpty(Pattern) Or Len(Pattern) = 0 Then sFormat = Str(vValue) Else sFormat = Format(vValue, Pattern) + + +Finally: + PyFormat = sFormat + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_PythonHelper.PyFormat + +REM ----------------------------------------------------------------------------- +Public Function PyGetGuiType() As Integer +''' Convenient function to replicate GetGuiType() in Python scripts +''' Args: +''' Returns: +''' The GetGuiType value +''' Example: (Python code) +''' MsgBox bas.GetGuiType() + +Const cstThisSub = "Basic.GetGuiType" +Const cstSubArgs = "" + +Check: + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) + +Try: + PyGetGuiType = GetGuiType() + + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +End Function ' ScriptForge.SF_PythonHelper.PyGetGuiType + +REM ----------------------------------------------------------------------------- +Public Function PyGetSystemTicks() As Long +''' Convenient function to replicate GetSystemTicks() in Python scripts +''' Args: +''' Returns: +''' The GetSystemTicks value +''' Example: (Python code) +''' MsgBox bas.GetSystemTicks() + +Const cstThisSub = "Basic.GetSystemTicks" +Const cstSubArgs = "" + +Check: + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) + +Try: + PyGetSystemTicks = GetSystemTicks() + + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +End Function ' ScriptForge.SF_PythonHelper.PyGetSystemTicks + +REM ----------------------------------------------------------------------------- +Public Function PyGlobalScope(ByVal Library As Variant) As Object +''' Convenient function to replicate GlobalScope() in Python scripts +''' Args: +''' Library: "Basic" or "Dialog" +''' Returns: +''' The GlobalScope value +''' Example: (Python code) +''' MsgBox bas.GlobalScope.BasicLibraries() + +Const cstThisSub = "Basic.GlobalScope.BasicLibraries" ' or DialogLibraries +Const cstSubArgs = "" + +Check: + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) + +Try: + Select Case Library + Case "Basic" + PyGlobalScope = GlobalScope.BasicLibraries() + Case "Dialog" + PyGlobalScope = GlobalScope.DialogLibraries() + Case Else + End Select + +Finally: + SF_Utils._ExitFunction(cstThisSub) + Exit Function +End Function ' ScriptForge.SF_PythonHelper.PyGlobalScope + +REM ----------------------------------------------------------------------------- +Public Function PyInputBox(ByVal Msg As Variant _ + , ByVal Title As Variant _ + , ByVal Default As Variant _ + , Optional ByVal XPos As Variant _ + , Optional ByVal YPos As Variant _ + ) As String +''' Convenient function to replicate InputBox() in Python scripts +''' Args: +''' Msg: String expression displayed as the message in the dialog box +''' Title: String expression displayed in the title bar of the dialog box +''' Default: String expression displayed in the text box as default if no other input is given +''' XPos: Integer expression that specifies the horizontal position of the dialog +''' YPos: Integer expression that specifies the vertical position of the dialog +''' If XPos and YPos are omitted, the dialog is centered on the screen +''' The position is specified in twips. +''' Returns: +''' The entered value or "" if the user pressed the Cancel button +''' Example: (Python code) +''' a = bas.InputBox ('Please enter a phrase:', 'Dear User') + +Dim sInput As String ' Return value +Const cstThisSub = "Basic.InputBox" +Const cstSubArgs = "msg, [title=''], [default=''], [xpos], [ypos]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sInput = "" + +Check: + If IsMissing(YPos) Then YPos = 1 + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) + +Try: + If IsMissing(XPos) Then + sInput = InputBox(Msg, Title, Default) + Else + sInput = InputBox(Msg, Title, Default, XPos, YPos) + End If + +Finally: + PyInputBox = sInput + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_PythonHelper.PyInputBox + +REM ----------------------------------------------------------------------------- +Public Function PyMsgBox(ByVal Text As Variant _ + , ByVal DialogType As Variant _ + , ByVal DialogTitle As Variant _ + ) As Integer +''' Convenient function to replicate MsgBox() in Python scripts +''' Args: +''' Text: String expression displayed as a message in the dialog box +''' DialogType: Any integer expression that defines the number and type of buttons or icons displayed +''' DialogTitle: String expression displayed in the title bar of the dialog +''' Returns: +''' The pressed button +''' Example: (Python code) +''' a = bas.MsgBox ('Please press a button:', bas.MB_EXCLAMATION, 'Dear User') + +Dim sMsg As String ' Return value +Const cstThisSub = "Basic.MsgBox" +Const cstSubArgs = "text, [dialogtype=0], [dialogtitle]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + sMsg = "" + +Check: + SF_Utils._EnterFunction(cstThisSub, cstSubArgs) + +Try: + sMsg = MsgBox(Text, DialogType, DialogTitle) + +Finally: + PyMsgBox = sMsg + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_PythonHelper.PyMsgBox + +REM ============================================================= PRIVATE METHODS + +REM ----------------------------------------------------------------------------- +Public Function _PythonDispatcher(ByRef BasicObject As Variant _ + , ByVal CallType As Variant _ + , ByVal Script As Variant _ + , ParamArray Args() As Variant _ + ) As Variant +''' Called from Python only +''' The method calls the method Script associated with the BasicObject class or module +''' with the given arguments +''' The invocation of the method can be a Property Get, Property Let or a usual call +''' NB: arguments and return values must not be 2D arrays +''' The implementation intends to be as AGNOSTIC as possible in termes of objects nature and methods called +''' Args: +''' BasicObject: a module or a class instance - May also be the reserved string: "SF_Services" +''' CallType: one of the constants applicable to a CallByName statement + optional protocol flags +''' Script: the name of the method or property +''' Args: the arguments to pass to the method. Input arguments can contain symbolic constants for Null, Missing, etc. +''' Returns: +''' A 1D array: +''' [0] The returned value - scalar, object or 1D array +''' [1] The VarType() of the returned value +''' Null, Empty and Nothing have different vartypes but return all None to Python +''' Additionally, when array: +''' [2] Number of dimensions in Basic +''' Additionally, when Basic object: +''' [2] Module (1), Class instance (2) or UNO (3) +''' [3] The object's ObjectType +''' [4] The object's service name +''' [5] The object's name +''' When an error occurs Python receives None as a scalar. This determines the occurence of a failure + +Dim vReturn As Variant ' The value returned by the invoked property or method +Dim vReturnArray As Variant ' Return value +Dim vBasicObject As Variant ' Alias of BasicObject to avoid "Object reference not set" error +Dim iNbArgs As Integer ' Number of valid input arguments +Dim vArg As Variant ' Alias for a single argument +Dim vArgs() As Variant ' Alias for Args() +Dim sScript As String ' Argument of ExecuteBasicScript() +Dim vParams As Variant ' Array of arguments to pass to a ParamArray +Dim sObjectType As String ' Alias of object.ObjectType +Dim bBasicClass As Boolean ' True when BasicObject is a class +Dim sLibrary As String ' Library where the object belongs to +Dim bUno As Boolean ' Return value is a UNO object +Dim sess As Object : Set sess = ScriptForge.SF_Session +Dim i As Long + +' Conventional special input or output values +Const cstNoArgs = "+++NOARGS+++", cstSymEmpty = "+++EMPTY+++", cstSymNull = "+++NULL+++", cstSymMissing = "+++MISSING+++" + +' https://support.office.com/en-us/article/CallByName-fonction-49ce9475-c315-4f13-8d35-e98cfe98729a +' Determines the CallType +Const vbGet = 2, vbLet = 4, vbMethod = 1, vbSet = 8 +' Protocol flags +Const cstArgArray = 512 ' 1st argument can be a 2D array +Const cstRetArray = 1024 ' Return value can be an array +Const cstUno = 256 ' Return value can be a UNO object +' Object nature in returned array +Const objMODULE = 1, objCLASS = 2, objUNO = 3 + +Check: + 'If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + _PythonDispatcher = Null + + ' Ignore Null basic objects (Null = Null or Nothing) + If IsNull(BasicObject) Or IsEmpty(BasicObject) Then GoTo Catch + + ' Reinterprete arguments one by one into vArgs, examine iso-dates and conventional NoArgs/Empty/Null values + iNbArgs = -1 + vArgs = Array() + If UBound(Args) >= 0 Then + For i = 0 To UBound(Args) + vArg = Args(i) + If i = 0 And VarType(vArg) = V_STRING Then + If vArg = cstNoArgs Then Exit For + End If + If VarType(vArg) = V_STRING Then + If vArg = cstSymEmpty Then + vArg = Empty + ElseIf vArg = cstSymNull Then + vArg = Null + ElseIf vArg = cstSymMissing Then + Exit For ' Next arguments must be missing also + Else + vArg = SF_Utils._CStrToDate(vArg) + If vArg < 0 Then vArg = Args(i) 'Conversion of iso format failed => forget + End If + End If + iNbArgs = iNbArgs + 1 + + ReDim Preserve vArgs(iNbArgs) + vArgs(iNbArgs) = vArg + Next i + End If + +Try: + ' Dispatching strategy: based on next constraints + ' (1) Bug https://bugs.documentfoundation.org/show_bug.cgi?id=138155 + ' The CallByName function fails when returning an array + ' (2) Python has tuples and tuple of tuples, not 2D arrays + ' (3) Passing 2D arrays through a script provider always transform it into a sequence of sequences + ' 1. Methods in usual modules are called by ExecuteBasicScript() except if they use a ParamArray + ' 2. Properies in any service are got and set with obj.GetProperty/SetProperty(...) + ' 3. Methods in class modules are invoked with CallByName + ' 4. Methods in class modules using a 2D array or returning arrays, or methods using ParamArray, +''' are hardcoded as exceptions or are not implemented + ' 5. Methods returning a 1D array when no arguments and a scalar otherwise (e.g. SF_Dialog.Controls()) + ' may be considered as properties when no argument +' Requires Python and Basic update in the concerned library but is transparent for this dispatcher + + Select case VarType(BasicObject) + Case V_STRING + ' Special entry for CreateScriptService() + vBasicObject = BasicObject + If vBasicObject = "SF_Services" Then + If UBound(vArgs) = 0 Then vParams = Array() Else vParams = SF_Array.Slice(vArgs, 1) + Select Case UBound(vParams) + Case -1 : vReturn = SF_Services.CreateScriptService(vArgs(0)) + Case 0 : vReturn = SF_Services.CreateScriptService(vArgs(0), vParams(0)) + Case 1 : vReturn = SF_Services.CreateScriptService(vArgs(0), vParams(0), vParams(1)) + Case 2 : vReturn = SF_Services.CreateScriptService(vArgs(0), vParams(0), vParams(1), vParams(2)) + Case 3 : vReturn = SF_Services.CreateScriptService(vArgs(0), vParams(0), vParams(1), vParams(2), vParams(3)) + Case 4 : vReturn = SF_Services.CreateScriptService(vArgs(0), vParams(0), vParams(1), vParams(2), vParams(3), vParams(4)) + End Select + End If + If VarType(vReturn) = V_OBJECT And Not IsNull(vReturn) Then + vBasicObject = vReturn + sObjectType = vBasicObject.ObjectType + bBasicClass = ( Left(sObjectType, 3) <> "SF_" ) + End If + + ' Implement dispatching strategy + Case V_INTEGER + If BasicObject < 0 Or Not IsArray(_SF_.PythonStorage) Then GoTo Catch + If BasicObject > UBound(_SF_.PythonStorage) Then GoTo Catch + vBasicObject = _SF_.PythonStorage(BasicObject) + sObjectType = vBasicObject.ObjectType + ' Basic modules have type = "SF_*" + bBasicClass = ( Left(sObjectType, 3) <> "SF_" ) + sLibrary = Split(vBasicObject.ServiceName, ".")(0) + + ' Methods in usual modules are called by ExecuteBasicScript() except if they use a ParamArray + If Not bBasicClass And (CallType And vbMethod) = vbMethod Then + sScript = sLibrary & "." & sObjectType & "." & Script + Select Case UBound(vArgs) + Case -1 : vReturn = sess.ExecuteBasicScript(, sScript) + Case 0 : vReturn = sess.ExecuteBasicScript(, sScript, vArgs(0)) + Case 1 : vReturn = sess.ExecuteBasicScript(, sScript, vArgs(0), vArgs(1)) + Case 2 : vReturn = sess.ExecuteBasicScript(, sScript, vArgs(0), vArgs(1), vArgs(2)) + Case 3 : vReturn = sess.ExecuteBasicScript(, sScript, vArgs(0), vArgs(1), vArgs(2), vArgs(3)) + Case 4 : vReturn = sess.ExecuteBasicScript(, sScript, vArgs(0), vArgs(1), vArgs(2), vArgs(3), vArgs(4)) + Case 5 : vReturn = sess.ExecuteBasicScript(, sScript, vArgs(0), vArgs(1), vArgs(2), vArgs(3), vArgs(4), vArgs(5)) + Case 6 : vReturn = sess.ExecuteBasicScript(, sScript, vArgs(0), vArgs(1), vArgs(2), vArgs(3), vArgs(4), vArgs(5), vArgs(6)) + Case 7 : vReturn = sess.ExecuteBasicScript(, sScript, vArgs(0), vArgs(1), vArgs(2), vArgs(3), vArgs(4), vArgs(5), vArgs(6), vArgs(7)) + End Select + + ' Properies in any service are got and set with obj.GetProperty/SetProperty(...) + ElseIf (CallType And vbGet) = vbGet Then + ' vReturn = sess.ExecuteBasicScript(, sLibrary & "." & sObjectType & ".GetProperty", Script) + vReturn = vBasicObject.GetProperty(Script) + ElseIf (CallType And vbLet) = vbLet Then + ' vReturn = sess.ExecuteBasicScript(, sLibrary & "." & sObjectType & ".SetProperty", Script, vArgs(0)) + vReturn = vBasicObject.SetProperty(Script, vArgs(0)) + + ' Methods in class modules using a 2D array or returning arrays are hardcoded as exceptions + ElseIf ((CallType And vbMethod) + (CallType And cstArgArray)) = vbMethod + cstArgArray Or _ + ((CallType And vbMethod) + (CallType And cstRetArray)) = vbMethod + cstRetArray Then + Select Case sLibrary + End Select + + ' Methods in class modules are invoked with CallByName + ElseIf bBasicClass And ((CallType And vbMethod) = vbMethod) Then + Select Case UBound(vArgs) + Case -1 : vReturn = CallByName(vBasicObject, Script, vbMethod) + ' Special case: Dispose() must update the cache for class objects created in Python scripts + If Script = "Dispose" Then Set _SF_.PythonStorage(BasicObject) = Nothing + Case 0 : vReturn = CallByName(vBasicObject, Script, vbMethod, vArgs(0)) + Case 1 : vReturn = CallByName(vBasicObject, Script, vbMethod, vArgs(0), vArgs(1)) + Case 2 : vReturn = CallByName(vBasicObject, Script, vbMethod, vArgs(0), vArgs(1), vArgs(2)) + Case 3 : vReturn = CallByName(vBasicObject, Script, vbMethod, vArgs(0), vArgs(1), vArgs(2), vArgs(3)) + Case 4 : vReturn = CallByName(vBasicObject, Script, vbMethod, vArgs(0), vArgs(1), vArgs(2), vArgs(3), vArgs(4)) + Case 5 : vReturn = CallByName(vBasicObject, Script, vbMethod, vArgs(0), vArgs(1), vArgs(2), vArgs(3), vArgs(4), vArgs(5)) + Case 6 : vReturn = CallByName(vBasicObject, Script, vbMethod, vArgs(0), vArgs(1), vArgs(2), vArgs(3), vArgs(4), vArgs(5), vArgs(6)) + Case 7 : vReturn = CallByName(vBasicObject, Script, vbMethod, vArgs(0), vArgs(1), vArgs(2), vArgs(3), vArgs(4), vArgs(5), vArgs(6), vArgs(7)) + End Select + End If + Case Else + End Select + + ' Format the returned array + vReturnArray = Array() + ' Distinguish: Basic object + ' UNO object + ' Array + ' Scalar + If IsArray(vReturn) Then + ReDim vReturnArray(0 To 2) + vReturnArray(0) = vReturn ' 2D arrays are flattened by the script provider when returning to Python + vReturnArray(1) = VarType(vReturn) + vReturnArray(2) = SF_Array.CountDims(vReturn) + ElseIf VarType(vReturn) = V_OBJECT And Not IsNull(vReturn) Then + ' Uno or not Uno ?BuildPath + bUno = False + If (CallType And cstUno) = cstUno Then ' UNO considered only when pre-announced in CallType + If Len(sess.UnoObjectType(vReturn)) > 0 Then bUno = True + End If + If bUno Then + ReDim vReturnArray(0 To 2) + Set vReturnArray(0) = vReturn + Else + ReDim vReturnArray(0 To 5) + vReturnArray(0) = _SF_._AddToPythonSTorage(vReturn) + End If + vReturnArray(1) = V_OBJECT + vReturnArray(2) = Iif(bUno, objUNO, Iif(bBasicClass, objCLASS, objMODULE)) + If Not bUno Then + vReturnArray(3) = vReturn.ObjectType + vReturnArray(4) = vReturn.ServiceName + If SF_Array.Contains(vReturn.Properties(), "Name", SortOrder := "ASC") Then vReturnArray(5) = vReturn.Name Else vReturnArray(5) = "" + End If + Else ' Scalar or Nothing + ReDim vReturnArray(0 To 1) + vReturnArray(0) = vReturn + vReturnArray(1) = VarType(vReturn) + End If + + _PythonDispatcher = vReturnArray + +Finally: + Exit Function +Catch: + GoTo Finally +End Function ' ScriptForge.SF_PythonHelper._PythonDispatcher + +REM ----------------------------------------------------------------------------- +Private Function _Repr() As String +''' Convert the Basic instance to a readable string, typically for debugging purposes (DebugPrint ...) +''' Args: +''' Return: +''' "[PythonHelper]" + + _Repr = "[PythonHelper]" + +End Function ' ScriptForge.SF_PythonHelper._Repr + +REM ================================================= END OF SCRIPTFORGE.SF_PythonHelper +</script:module>
\ No newline at end of file diff --git a/wizards/source/scriptforge/SF_Root.xba b/wizards/source/scriptforge/SF_Root.xba index 334e4798018c..379cb3586a2b 100644 --- a/wizards/source/scriptforge/SF_Root.xba +++ b/wizards/source/scriptforge/SF_Root.xba @@ -73,6 +73,7 @@ Private Interface As Object ' ScriptForge own L10N service Private OSName As String ' WIN, LINUX, MACOS Private SFDialogs As Variant ' Persistent storage for the SFDialogs library Private SFForms As Variant ' Persistent storage for the SF_Form class in the SFDocuments library +Private PythonStorage As Variant ' Persistent storage for the objects created and processed in Python REM ====================================================== CONSTRUCTOR/DESTRUCTOR @@ -120,6 +121,7 @@ Private Sub Class_Initialize() OSName = "" SFDialogs = Empty SFForms = Empty + PythonStorage = Empty End Sub ' ScriptForge.SF_Root Constructor REM ----------------------------------------------------------------------------- @@ -169,6 +171,49 @@ Dim sLine As String ' Alias of psLine End Sub ' ScriptForge.SF_Root._AddToConsole REM ----------------------------------------------------------------------------- +Public Function _AddToPythonStorage(ByRef poObject As Object) As Long +''' Insert a newly created object in the Python persistent storage +''' and return the index of the used entry +''' The persistent storage is a simple array of objects +''' Args: +''' poObject: the object to insert + +Dim lIndex As Long ' Return value +Dim lSize As Long ' UBound of the persistent storage +Dim i As Long + +Check: + lIndex = -1 + If IsNull(poObject) Then Exit Function + On Local Error GoTo Finally + If IsEmpty(PythonStorage) Then PythonStorage = Array() + lSize = UBound(PythonStorage) + +Try: + ' Can an empty entry be reused ? + For i = 0 To lSize + If IsNull(PythonStorage(i)) Then + lIndex = i + Exit For + End If + Next i + + ' Resize Python storage if no empty space + If lIndex < 0 Then + lSize = lSize + 1 + ReDim Preserve PythonStorage(0 To lSize) + lIndex = lSize + End If + + ' Insert new object + Set PythonStorage(lIndex) = poObject + +Finally: + _AddToPythonStorage = lIndex + Exit Function +End Function ' ScriptForge.SF_Root._AddToPythonStorage + +REM ----------------------------------------------------------------------------- Public Sub _LoadLocalizedInterface(Optional ByVal psMode As String) ''' Build the user interface in a persistent L10N object ''' Executed - only once - at first ScriptForge invocation by a user script @@ -380,13 +425,13 @@ Try: ' SF_Array.ExtractColumn, ExtractRow .AddText( Context := "ARRAYINDEX2" _ , MsgId := "The given slice limits do not fit within the bounds of the array.\n\n" _ - & "\t« Array_2D » = %1\n" _ + & "\t« Array_1D » = %1\n" _ & "\t« From » = %2\n" _ & "\t« UpTo » = %3" _ , Comment := "SF_Array.ExtractColumn (...) error message\n" _ & "%1: 'Column' or 'Row' of a matrix\n" _ & "%2, %3: array contents\n" _ - & "'Array_2D', 'From' and 'UpTo' should not be translated" _ + & "'Array_1D', 'From' and 'UpTo' should not be translated" _ ) ' SF_Array.ImportFromCSVFile .AddText( Context := "CSVPARSING" _ diff --git a/wizards/source/scriptforge/SF_Session.xba b/wizards/source/scriptforge/SF_Session.xba index 84351de24add..2a56e91f1a55 100644 --- a/wizards/source/scriptforge/SF_Session.xba +++ b/wizards/source/scriptforge/SF_Session.xba @@ -166,7 +166,7 @@ Try: ' Execute script Set oScript = SF_Session._GetScript("Basic", Scope, Script) On Local Error GoTo CatchExec - If Not IsNull(oScript) Then vReturn = oScript.Invoke(pvArgs(), Array(), Array()) + If Not IsNull(oScript) Then vReturn = oScript.Invoke(pvArgs, Array(), Array()) Finally: ExecuteBasicScript = vReturn diff --git a/wizards/source/scriptforge/SF_Timer.xba b/wizards/source/scriptforge/SF_Timer.xba index 3bdcaa6b701e..f1c718fca7b1 100644 --- a/wizards/source/scriptforge/SF_Timer.xba +++ b/wizards/source/scriptforge/SF_Timer.xba @@ -417,9 +417,9 @@ Dim cstSubArgs As String End Select _PropertyGet = Fix(dDuration * 1000 / DSECOND) / 1000 Case UCase("IsStarted") - _PropertyGet = ( _TimerStatus = STATUSSTARTED Or _TimerStatus = STATUSSUSPENDED ) + _PropertyGet = CBool( _TimerStatus = STATUSSTARTED Or _TimerStatus = STATUSSUSPENDED ) Case UCase("IsSuspended") - _PropertyGet = ( _TimerStatus = STATUSSUSPENDED ) + _PropertyGet = CBool( _TimerStatus = STATUSSUSPENDED ) Case UCase("SuspendDuration") Select Case _TimerStatus Case STATUSINACTIVE : dDuration = 0.0 diff --git a/wizards/source/scriptforge/po/ScriptForge.pot b/wizards/source/scriptforge/po/ScriptForge.pot index a4c6ff514bc4..06f0c77c6e1a 100644 --- a/wizards/source/scriptforge/po/ScriptForge.pot +++ b/wizards/source/scriptforge/po/ScriptForge.pot @@ -14,7 +14,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: https://bugs.libreoffice.org/enter_bug.cgi?product=LibreOffice&bug_status=UNCONFIRMED&component=UI\n" -"POT-Creation-Date: 2021-02-03 15:55:36\n" +"POT-Creation-Date: 2021-03-04 16:31:25\n" "PO-Revision-Date: YYYY-MM-DD HH:MM:SS\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <EMAIL@ADDRESS>\n" @@ -263,13 +263,13 @@ msgstr "" #. SF_Array.ExtractColumn (...) error message #. %1: 'Column' or 'Row' of a matrix #. %2, %3: array contents -#. 'Array_2D', 'From' and 'UpTo' should not be translated +#. 'Array_1D', 'From' and 'UpTo' should not be translated #, kde-format msgctxt "ARRAYINDEX2" msgid "" "The given slice limits do not fit within the bounds of the array.\n" "\n" -" « Array_2D » = %1\n" +" « Array_1D » = %1\n" " « From » = %2\n" " « UpTo » = %3" msgstr "" diff --git a/wizards/source/scriptforge/po/en.po b/wizards/source/scriptforge/po/en.po index a4c6ff514bc4..06f0c77c6e1a 100644 --- a/wizards/source/scriptforge/po/en.po +++ b/wizards/source/scriptforge/po/en.po @@ -14,7 +14,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: https://bugs.libreoffice.org/enter_bug.cgi?product=LibreOffice&bug_status=UNCONFIRMED&component=UI\n" -"POT-Creation-Date: 2021-02-03 15:55:36\n" +"POT-Creation-Date: 2021-03-04 16:31:25\n" "PO-Revision-Date: YYYY-MM-DD HH:MM:SS\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <EMAIL@ADDRESS>\n" @@ -263,13 +263,13 @@ msgstr "" #. SF_Array.ExtractColumn (...) error message #. %1: 'Column' or 'Row' of a matrix #. %2, %3: array contents -#. 'Array_2D', 'From' and 'UpTo' should not be translated +#. 'Array_1D', 'From' and 'UpTo' should not be translated #, kde-format msgctxt "ARRAYINDEX2" msgid "" "The given slice limits do not fit within the bounds of the array.\n" "\n" -" « Array_2D » = %1\n" +" « Array_1D » = %1\n" " « From » = %2\n" " « UpTo » = %3" msgstr "" diff --git a/wizards/source/scriptforge/python/scriptforge.py b/wizards/source/scriptforge/python/scriptforge.py new file mode 100644 index 000000000000..189a7f3049a4 --- /dev/null +++ b/wizards/source/scriptforge/python/scriptforge.py @@ -0,0 +1,676 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020-2021 Jean-Pierre LEDURE, Alain ROMEDENNE + +# ===================================================================================================================== +# === The ScriptForge library and its associated libraries are part of the LibreOffice project. === +# === Full documentation is available on https://help.libreoffice.org/ === +# ===================================================================================================================== + +# ScriptForge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +# ScriptForge is free software; you can redistribute it and/or modify it under the terms of either (at your option): + +# 1) 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/ . + +# 2) The GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. If a copy of the LGPL was not +# distributed with this file, see http://www.gnu.org/licenses/ . + +""" + ScriptForge libraries are an extensible and robust collection of macro scripting resources for LibreOffice + to be invoked from user Basic or Python macros. Users familiar with other BASIC macro variants often face hard + times to dig into the extensive LibreOffice Application Programming Interface even for the simplest operations. + By collecting most-demanded document operations in a set of easy to use, easy to read routines, users can now + program document macros with much less hassle and get quicker results. + + ScriptForge abundant methods are organized in reusable modules that cleanly isolate Basic/Python programming + language constructs from ODF document content accesses and user interface(UI) features. + + The scriptforge.py module + - implements a protocol between Python (user) scripts and the ScriptForge Basic library + - contains the interfaces (classes and attributes) to be used in Python user scripts + to run the services implemented in the standard libraries shipped with LibreOffice + + Usage: + + When Python and LibreOffice run in the same process (usual case): either + from scriptforge import * # or, better ... + from scriptforge import CreateScriptService + + When Python and LibreOffice are started in separate processes, + LibreOffice being started from console ... (example for Linux with port = 2021) + ./soffice --accept='socket,host=localhost,port=2021;urp;' + then use next statement: + from scriptforge import * # or, better ... + from scriptforge import CreateScriptService, ScriptForge + ScriptForge(hostname = 'localhost', port = 2021) + + Specific documentation about the use of ScriptForge from Python scripts: + TBD + """ + +import uno + +from platform import system as _opsys +import datetime +import os + + +class _Singleton(type): + """ + A Singleton metaclass design pattern + Credits: « Python in a Nutshell » by Alex Martelli, O'Reilly + """ + instances = {} + + def __call__(cls, *args, **kwargs): + if cls not in cls.instances: + cls.instances[cls] = super(_Singleton, cls).__call__(*args, **kwargs) + return cls.instances[cls] + + +# ##################################################################################################################### +# ScriptForge CLASS ### +# ##################################################################################################################### + +class ScriptForge(object, metaclass = _Singleton): + """ + The ScriptForge (singleton) class encapsulates the core of the ScriptForge run-time + - Bridge with the LibreOffice process + - Implementation of the inter-language protocol with the Basic libraries + - Identification of the available services interfaces + - Dispatching of services + - Coexistence with UNO + + It embeds the Service class that manages the protocol with Basic + """ + + # ######################################################################### + # Class attributes + # ######################################################################### + hostname = '' + port = 0 + componentcontext = None + scriptprovider = None + + # ######################################################################### + # Class constants + # ######################################################################### + library = 'ScriptForge' + Version = '7.2' # Actual version number + # + # Basic dispatcher for Python scripts + basicdispatcher = 'ScriptForge.SF_PythonHelper._PythonDispatcher' + # + # VarType() constants + V_EMPTY, V_NULL, V_INTEGER, V_LONG, V_SINGLE, V_DOUBLE = 0, 1, 2, 3, 4, 5 + V_CURRENCY, V_DATE, V_STRING, V_OBJECT, V_BOOLEAN = 6, 7, 8, 9, 11 + V_VARIANT, V_ARRAY, V_ERROR, V_UNO = 12, 8192, -1, 16 + # Object types + objMODULE, objCLASS, objUNO = 1, 2, 3 + # Special argument symbols + cstSymEmpty, cstSymNull, cstSymMissing = '+++EMPTY+++', '+++NULL+++', '+++MISSING+++' + + def __init__(self, hostname = '', port = 0): + """ + Because singleton, constructor is executed only once while Python active + Arguments are mandatory when Python and LibreOffice run in separate processes + :param hostname: probably 'localhost' + :param port: port number + """ + ScriptForge.hostname = hostname + ScriptForge.port = port + # Determine main pyuno entry points + ScriptForge.componentcontext = self.ConnectToLOProcess(hostname, port) # com.sun.star.uno.XComponentContext + ScriptForge.scriptprovider = self.ScriptProvider(self.componentcontext) # ...script.provider.XScriptProvider + # + # Establish a list of the available services as a dictionary (servicename, serviceclass) + ScriptForge.serviceslist = dict((cls.servicename, cls) for cls in SFServices.__subclasses__()) + ScriptForge.servicesdispatcher = None + + @classmethod + def ConnectToLOProcess(cls, hostname = '', port = 0): + """ + Called by the ScriptForge class constructor to establish the connection with + the requested LibreOffice instance + The default arguments are for the usual interactive mode + + :param hostname: probably 'localhost' or '' + :param port: port number or 0 + :return: the derived component context + """ + if len(hostname) > 0 and port > 0: # Explicit connection request via socket + ctx = uno.getComponentContext() # com.sun.star.uno.XComponentContext + resolver = ctx.ServiceManager.createInstanceWithContext( + 'com.sun.star.bridge.UnoUrlResolver', ctx) # com.sun.star.comp.bridge.UnoUrlResolver + try: + conn = 'socket,host=%s,port=%d' % (hostname, port) + url = 'uno:%s;urp;StarOffice.ComponentContext' % conn + ctx = resolver.resolve(url) + except Exception: # thrown when LibreOffice specified instance isn't started + raise ConnectionError( + 'Connection to LibreOffice failed (host = ' + hostname + ', port = ' + str(port) + ')') + return ctx + elif len(hostname) == 0 and port == 0: # Usual interactive mode + return uno.getComponentContext() + else: + raise SystemExit('The creation of the ScriptForge() instance got invalid arguments: ' + + '(host = ' + hostname + ', port = ' + str(port) + ')') + + @classmethod + def ScriptProvider(cls, context = None): + """ + Returns the general script provider + """ + servicemanager = context.ServiceManager # com.sun.star.lang.XMultiComponentFactory + masterscript = servicemanager.createInstanceWithContext( + "com.sun.star.script.provider.MasterScriptProviderFactory", context) + return masterscript.createScriptProvider("") + + @classmethod + def InvokeSimpleScript(cls, script, *args): + """ + Create a UNO object corresponding with the given Python or Basic script + The execution is done with the invoke() method applied on the created object + Implicit scope: Extensions and documents are excluded. Either + "application" a shared library (BASIC) + "share" a library of LibreOffice Macros (PYTHON) + :param script: Either + [library.]module.method - Must not be a class module or method + [directory/]module.py$method + :return: the script object as a com.sun.star.script.provider.XScript UNO object + """ + # Compute the URI specification described in + # https://wiki.openoffice.org/wiki/Documentation/DevGuide/Scripting/Scripting_Framework_URI_Specification + if cls.servicesdispatcher is not None and script == ScriptForge.basicdispatcher: + xscript = cls.servicesdispatcher + elif len(script) > 0: + if '.py$' in script.lower(): # Python + uri = 'vnd.sun.star.script:' + script + '?language=Python&location=share' + else: # Basic + lib = '' + if len(script.split('.')) < 3: + lib = cls.library + '.' + uri = 'vnd.sun.star.script:' + lib + script + '?language=Basic&location=application' + # Get the script object + try: + xscript = cls.scriptprovider.getScript(uri) + except Exception: + raise SystemExit('The script ' + "'" + script + "'" + + ' could not be located in your LibreOffice installation') + else: # Should not happen + return None + # Execute the script with the given arguments + # Packaging for script provider depends on presence of ParamArray arguments in the called Basic script + if script == ScriptForge.basicdispatcher: + # At 1st execution, buffer xscript + if cls.servicesdispatcher is None: + cls.servicesdispatcher = xscript + scriptreturn = xscript.invoke(args[0], (), ()) + else: + scriptreturn = xscript.invoke(args, (), ()) + # + return scriptreturn[0] # Updatable arguments passed by reference are ignored + + @classmethod + def InvokeBasicService(cls, basicobject, flags, method, *args): + """ + Execute a given Basic script and interprete its result + This method has as counterpart the ScriptForge.SF_PythonHelper._PythonDispatcher() Basic method + :param basicobject: a Service subclass + :param flags: see the vb* and flg* constants below + :param method: the name of the method or property to invoke, as a string + :param args: the arguments of the method. Symbolic cst* constants may be necessary + :return: The invoked Basic counterpart script (with InvokeSimpleScript()) will return a tuple + [0] The returned value - scalar, object reference or a tuple + [1] The Basic VarType() of the returned value + Null, Empty and Nothing have different vartypes but return all None to Python + Additionally, when [0] is a tuple: + [2] Number of dimensions in Basic + Additionally, when [0] is a UNO or Basic object: + [2] Module (1), Class instance (2) or UNO (3) + [3] The object's ObjectType + [4] The object's ServiceName + [5] The object's name + When an error occurs Python receives None as a scalar. This determines the occurence of a failure + The method returns either + - the 0th element of the tuple when scalar, tuple or UNO object + - a new Service() object or one of its subclasses otherwise + """ + # Constants + script = ScriptForge.basicdispatcher + cstNoArgs = '+++NOARGS+++' + cstValue, cstVarType, cstDims, cstClass, cstType, cstService, cstName = 0, 1, 2, 2, 3, 4, 5 + + # + # Run the basic script + # The targeted script has a ParamArray argument. Do not change next 4 lines except if you know what you do ! + if len(args) == 0: + args = (basicobject,) + (flags,) + (method,) + (cstNoArgs,) + else: + args = (basicobject,) + (flags,) + (method,) + args + returntuple = cls.InvokeSimpleScript(script, args) + # + # Interprete the result + # Did an error occur in the Basic world ? + if not isinstance(returntuple, (tuple, list)): + raise RuntimeError("The execution of the method '" + method + "' failed. Execution stops.") + # + # Analyze the returned tuple + if returntuple[cstVarType] == ScriptForge.V_OBJECT and len(returntuple) > cstClass: # Avoid Nothing + if returntuple[cstClass] == ScriptForge.objUNO: + pass + else: + # Create the new class instance of the right subclass of Service() + servname = returntuple[cstService] + for subcls in SFServices.__subclasses__(): + if servname == subcls.servicename: + return subcls(returntuple[cstValue], returntuple[cstType], returntuple[cstClass], + returntuple[cstName]) + # When service not found + raise RuntimeError("The service '" + servname + "' is not available in Python. Execution stops.") + elif returntuple[cstVarType] >= ScriptForge.V_ARRAY: + pass + else: # All scalar values + pass + return returntuple[cstValue] + + +# ##################################################################################################################### +# SFServices CLASS (ScriptForge services superclass) ### +# ##################################################################################################################### + +class SFServices(object): + """ + Generic implementation of a parent Service class + Every service must subclass this class to be recognized as a valid service + A service instance is created by the CreateScriptService method + It can have a mirror in the Basic world or be totally defined in Python + + Every subclass must initialize 2 class properties: + servicename (e.g. ScriptForge.FileSystem, ScriptForge.Basic) + serviceimplementation: either 'python' or 'basic' + This is sufficient to register the set of services in the Python world + + The communication with Basic is managed by 2 ScriptForge() methods: + InvokeSimpleScript(): low level invocation of a Basic script. This script must be located + in a usual Basic module. The result is passed as-is + InvokeSBasicService(): the result comes back encapsulated with additional info + The result is interpreted in the method + The invoked script can be a property or a method of a Basic class module + It is up to every service method to determine which method to use + + For Basic services only: + Each instance is identified by its + - object reference: the real Basic object embedded as a UNO wrapper object + - objecttype ('SF_String', 'DICTIONARY', ...) + - name (form, control, ... name) - may be blank + + The role of the Service() superclass is mainly to propose a generic properties management + Properties are got and set following next strategy: + 1. Property names are controlled strictly ('Value' and not 'value') + 2. Getting a property value for the first time is always done via a Basic call + 3. Next occurrences are fetched from the Python dictionary of the instance if the property + is read-only, otherwise via a Basic call + 4. Read-only properties may be modified or deleted exceptionally by the class + when self.internal == True. The latter must immediately be reset after use + + Each subclass must define its interface with the user scripts: + 1. The properties + a dictionary named 'serviceProperties' with keys = (camel-cased) property names and value = boolean + True = editable, False = read-only + a list named 'localProperties' reserved to properties for internal use + e.g. oDlg.Controls() is a method that uses '_Controls' to hold the list of available controls + serviceProperties are buffered in Python after their 1st get request to Basic + Only if there is a need to go to Basic at each get, then declare the property explicitly: + @property + def myProperty(self): + return self.GetProperty('myProperty') + 2 The methods + a usual def: statement + def myMethod(self, arg1, arg2 = ''): + return self.Execute(self.vbMethod, 'myMethod', arg1, arg2) + Method names are camel-cased, arguments are lower-cased + All arguments must be present and initialized before the call to Basic, if any + """ + # Python-Basic protocol constants and flags + vbGet, vbLet, vbMethod, vbSet = 2, 4, 1, 8 # CallByName constants + flgArrayArg = 512 # 1st argument can be a 2D array + flgArrayRet = 1024 # Invoked service method can return an array + flgUno = 256 # Invoked service method/property can return a UNO object + # Basic class type + moduleClass, moduleStandard = 2, 1 + # + # To operate dynamic property getting/setting it is necessary to + # enumerate all types of properties and adapt __getattr__() and __setattr__() according to their type + internal_attributes = ('objectreference', 'objecttype', 'name', 'internal', 'servicename', + 'serviceimplementation', 'classmodule', 'EXEC', 'SIMPLEEXEC') + + def __init__(self, reference = -1, objtype = None, classmodule = 0, name = ''): + """ + Trivial initialization of internal properties + If the subclass has its own __init()__ method, a call to this one should be its first statement. + Afterwards localProperties should be filled with the list of its own propertties + """ + self.objectreference = reference # the index in the Python storage where the Basic object is stored + self.objecttype = objtype # ('SF_String', 'DICTIONARY', ...) + self.classmodule = classmodule # Module (1), Class instance (2) + self.name = name # '' when no name + self.internal = False # True to exceptionally allow assigning a new value to a read-only property + self.localProperties = () # the properties reserved for internal use (often empty) + self.SIMPLEEXEC = ScriptForge.InvokeSimpleScript # Shortcuts to script provider interfaces + self.EXEC = ScriptForge.InvokeBasicService + + def __getattr__(self, name): + """ + Executed for EVERY property reference if name not yet in the instance dict + At the 1st get, the property value is always got from Basic + """ + if self.serviceimplementation == 'basic': + if name in ('serviceProperties', 'localProperties', 'internal_attributes'): + pass + elif name in self.serviceProperties: + # Get Property from Basic + return self.GetProperty(name) + # Execute the usual attributes getter + return super(SFServices, self).__getattribute__(name) + + def __setattr__(self, name, value): + """ + Executed for EVERY property assignment, including in __init__() !! + Setting a property requires for serviceProperties() to be executed in Basic + """ + if self.serviceimplementation == 'basic': + if name in ('serviceProperties', 'localProperties', 'internal_attributes'): + pass + elif name[0:2] == '__' or name in self.internal_attributes or name in self.localProperties: + pass + elif name in self.serviceProperties: + if self.internal: # internal = True forces property local setting even if property is read-only + pass + elif self.serviceProperties[name] is True: # True == Editable + self.SetProperty(name, value) + else: + raise AttributeError( + "type object '" + self.objecttype + "' has no editable property '" + name + "'") + else: + raise AttributeError("type object '" + self.objecttype + "' has no property '" + name + "'") + object.__setattr__(self, name, value) + return + + def __repr__(self): + return self.serviceimplementation + '/' + self.servicename + '/' + str(self.objectreference) + '/' + \ + super(SFServices, self).__repr__() + + def Dispose(self): + if self.serviceimplementation == 'basic': + if self.classmodule == self.moduleClass and self.objectreference >= 0: + self.Execute(self.vbMethod, 'Dispose') + self.objectreference = -1 + + def Execute(self, flags = 0, methodname = '', *args): + if flags == 0: + flags = self.vbMethod + if len(methodname) > 0: + return self.EXEC(self.objectreference, flags, methodname, *args) + + def GetProperty(self, propertyname): + """ + Get the given property from the Basic world + """ + return self.EXEC(self.objectreference, self.vbGet, propertyname) + + def SetProperty(self, propertyname, value): + """ + Set the given property to a new value in the Basic world + """ + return self.EXEC(self.objectreference, self.vbLet, propertyname, value) + + +# ##################################################################################################################### +# SFScriptForge CLASS (alias of ScriptForge Basic library) ### +# ##################################################################################################################### +class SFScriptForge: + # ######################################################################### + # SF_Basic CLASS + # ######################################################################### + class SF_Basic(SFServices, metaclass = _Singleton): + """ + This service proposes a collection of Basic methods to be executed in a Python context + to simulate the exact syntax and behaviour of the identical Basic builtin method. + Typical example: + SF_Basic.MsgBox('This has to be displayed in a message box') + """ + # Mandatory class properties for service registration + serviceimplementation = 'python' + servicename = 'ScriptForge.Basic' + # Basic helper functions invocation + module = 'SF_PythonHelper' + # Message box constants + MB_ABORTRETRYIGNORE, MB_DEFBUTTON1, MB_DEFBUTTON2, MB_DEFBUTTON3 = 2, 128, 256, 512 + MB_ICONEXCLAMATION, MB_ICONINFORMATION, MB_ICONQUESTION, MB_ICONSTOP = 48, 64, 32, 16 + MB_OK, MB_OKCANCEL, MB_RETRYCANCEL, MB_YESNO, MB_YESNOCANCEL = 0, 1, 5, 4, 3 + IDABORT, IDCANCEL, IDIGNORE, IDNO, IDOK, IDRETRY, IDYES = 3, 2, 5, 7, 1, 4, 6 + + def ConvertFromUrl(self, filename): + return self.SIMPLEEXEC(self.module + '.PyConvertFromUrl', filename) + + def ConvertToUrl(self, filename): + return self.SIMPLEEXEC(self.module + '.PyConvertToUrl', filename) + + def CreateUnoService(self, unoservice): + return self.SIMPLEEXEC(self.module + '.PyCreateUnoService', unoservice) + + def DateAdd(self, add, count, datearg): + if isinstance(datearg, datetime.datetime): + datearg = datearg.isoformat() + dateadd = self.SIMPLEEXEC(self.module + '.PyDateAdd', add, count, datearg) + return datetime.datetime.fromisoformat(dateadd) + + def DateDiff(self, add, date1, date2, weekstart = 1, yearstart = 1): + if isinstance(date1, datetime.datetime): + date1 = date1.isoformat() + if isinstance(date2, datetime.datetime): + date2 = date2.isoformat() + return self.SIMPLEEXEC(self.module + '.PyDateDiff', add, date1, date2, weekstart, yearstart) + + def DatePart(self, add, datearg, weekstart = 1, yearstart = 1): + if isinstance(datearg, datetime.datetime): + datearg = datearg.isoformat() + return self.SIMPLEEXEC(self.module + '.PyDatePart', add, datearg, weekstart, yearstart) + + def DateValue(self, datearg): + if isinstance(datearg, datetime.datetime): + datearg = datearg.isoformat() + datevalue = self.SIMPLEEXEC(self.module + '.PyDateValue', datearg) + return datetime.datetime.fromisoformat(datevalue) + + def Format(self, value, pattern = ''): + if isinstance(value, datetime.datetime): + value = value.isoformat() + return self.SIMPLEEXEC(self.module + '.PyFormat', value, pattern) + + def GetGuiType(self): + return self.SIMPLEEXEC(self.module + '.PyGetGuiType') + + def GetSystemTicks(self): + return self.SIMPLEEXEC(self.module + '.PyGetSystemTicks') + + @staticmethod + def GetDefaultContext(): + return ScriptForge.componentcontext + + @staticmethod + def GetPathSeparator(): + return os.sep + + class GlobalScope(object, metaclass = _Singleton): + @classmethod # Mandatory because the GlobalScope class is normally not instantiated + def BasicLibraries(cls): + return SFScriptForge.SF_Basic().SIMPLEEXEC(SFScriptForge.SF_Basic.module + '.PyGlobalScope', 'Basic') + + @classmethod + def DialogLibraries(cls): + return SFScriptForge.SF_Basic().SIMPLEEXEC(SFScriptForge.SF_Basic.module + '.PyGlobalScope', 'Dialog') + + def InputBox(self, msg, title = '', default = '', xpos = -1, ypos = -1): + if xpos < 0 or ypos < 0: + return self.SIMPLEEXEC(self.module + '.PyInputBox', msg, title, default) + return self.SIMPLEEXEC(self.module + '.PyInputBox', msg, title, default, xpos, ypos) + + def MsgBox(self, text, dialogtype = 0, dialogtitle = ''): + return self.SIMPLEEXEC(self.module + '.PyMsgBox', text, dialogtype, dialogtitle) + + @staticmethod + def Now(): + return datetime.datetime.now() + + @staticmethod + def RGB(red, green, blue): + return int('%02x%02x%02x' % (red, green, blue), 16) + + def Xray(self, unoobject = None): + return self.SIMPLEEXEC('XrayTool._main.xray', unoobject) + + # ######################################################################### + # SF_String CLASS + # ######################################################################### + class SF_String(SFServices, metaclass = _Singleton): + """ + A collection of methods focussed on string manipulation, user input validation, + regular expressions, encodings, parsing and hashing algorithms. + Many of them are less efficient than their Python equivalents. + """ + # Mandatory class properties for service registration + serviceimplementation = 'basic' + servicename = 'ScriptForge.String' + + # ######################################################################### + # SF_FileSystem CLASS + # ######################################################################### + class SF_FileSystem(SFServices, metaclass = _Singleton): + """ + The "FileSystem" service includes common file and folder handling routines. + """ + # Mandatory class properties for service registration + serviceimplementation = 'basic' + servicename = 'ScriptForge.FileSystem' + serviceProperties = dict(FileNaming = True, ConfigFolder = False, ExtensionsFolder = False, HomeFolder = False, + InstallFolder = False, TemplatesFolder = False, TemporaryFolder = False, + UserTemplatesFolder = False) + + @property + def ConfigFolder(self): + return self.GetProperty('ConfigFolder') + + def BuildPath(self, foldername, name): + return self.Execute(self.vbMethod, 'BuildPath', foldername, name) + + def FolderExists(self, foldername): + return self.Execute(self.vbMethod, 'FolderExists', foldername) + + # ######################################################################### + # SF_Timer CLASS + # ######################################################################### + class SF_Timer(SFServices): + """ + The "Timer" service measures the amount of time it takes to run user scripts.. + """ + # Mandatory class properties for service registration + serviceimplementation = 'basic' + servicename = 'ScriptForge.Timer' + serviceProperties = dict(Duration = False, IsStarted = False, IsSuspended = False, + SuspendDuration = False, TotalDuration = False) + + @property + def Duration(self): + return self.GetProperty('Duration') + + @property + def IsStarted(self): + return self.GetProperty('IsStarted') + + @property + def SuspendDuration(self): + return self.GetProperty('SuspendDuration') + + @property + def TotalDuration(self): + return self.GetProperty('TotalDuration') + + def Continue(self): + return self.Execute(self.vbMethod, 'Continue') + + def Restart(self): + return self.Execute(self.vbMethod, 'Restart') + + def Start(self): + return self.Execute(self.vbMethod, 'Start') + + def Suspend(self): + return self.Execute(self.vbMethod, 'Suspend') + + def Terminate(self): + return self.Execute(self.vbMethod, 'Terminate') + + +# ##############################################False####################################################################### +# CreateScriptService() ### +# ##################################################################################################################### +def CreateScriptService(service, *args): + """ + A service being the name of a collection of properties and methods, + this method returns the Python object mirror of the Basic object implementing + the requested service + As an exception to above, 'Basic' is accepted as a shortcut to the Basic service + which is implemented in Python + :param service: the name of the service as a string 'library.service' - cased exactly + :param args: the arguments to pass to the service constructor + :return: the service as a Python object + """ + # Init at each CreateScriptService() invocation + # CreateScriptService is usually the first statement in user scripts requesting ScriptForge services + # ScriptForge() is optional in user scripts when Python process inside LibreOffice process + ScriptForge() + + def ResolveSynonyms(servicename): + """ + Synonyms within service names implemented in Python are resolved here + :param servicename: The short name of the service + :return: The official service name + """ + if servicename.lower() in ('basic', 'scriptforge.basic'): + return 'ScriptForge.Basic' + return servicename + + # + # Check the list of available services to examine if the requested service is within the Python world + scriptservice = ResolveSynonyms(service) + if scriptservice in ScriptForge.serviceslist: + serv = ScriptForge.serviceslist[scriptservice] + if serv.serviceimplementation == 'python': + return serv() + # The requested service is to be found in the Basic world + if len(args) == 0: + serv = ScriptForge.InvokeBasicService('SF_Services', SFServices.vbMethod, 'CreateScriptService', service) + else: + serv = ScriptForge.InvokeBasicService('SF_Services', SFServices.vbMethod, 'CreateScriptService', service, *args) + return serv + +# ##################################################################################################################### +# Services shortcuts ### +# ##################################################################################################################### +# SF_Basic = CreateScriptService('SFPython.Basic') +# SF_String = _ScriptForge.SF_String + + +# ###################################################################### +# lists the scripts, that shall be visible inside the Basic/Python IDE +# ###################################################################### + +g_exportedScripts = ()
\ No newline at end of file diff --git a/wizards/source/scriptforge/script.xlb b/wizards/source/scriptforge/script.xlb index d4c21b652ebe..fd4045f34666 100644 --- a/wizards/source/scriptforge/script.xlb +++ b/wizards/source/scriptforge/script.xlb @@ -18,4 +18,5 @@ <library:element library:name="SF_Exception"/> <library:element library:name="SF_UI"/> <library:element library:name="SF_Platform"/> + <library:element library:name="SF_PythonHelper"/> </library:library>
\ No newline at end of file |