summaryrefslogtreecommitdiffstats
path: root/wizards/source/scriptforge
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--wizards/source/scriptforge/SF_Array.xba2608
-rw-r--r--wizards/source/scriptforge/SF_Dictionary.xba959
-rw-r--r--wizards/source/scriptforge/SF_Exception.xba1381
-rw-r--r--wizards/source/scriptforge/SF_FileSystem.xba2128
-rw-r--r--wizards/source/scriptforge/SF_L10N.xba825
-rw-r--r--wizards/source/scriptforge/SF_Platform.xba451
-rw-r--r--wizards/source/scriptforge/SF_PythonHelper.xba967
-rw-r--r--wizards/source/scriptforge/SF_Region.xba861
-rw-r--r--wizards/source/scriptforge/SF_Root.xba1070
-rw-r--r--wizards/source/scriptforge/SF_Services.xba639
-rw-r--r--wizards/source/scriptforge/SF_Session.xba1076
-rw-r--r--wizards/source/scriptforge/SF_String.xba2734
-rw-r--r--wizards/source/scriptforge/SF_TextStream.xba702
-rw-r--r--wizards/source/scriptforge/SF_Timer.xba466
-rw-r--r--wizards/source/scriptforge/SF_UI.xba1350
-rw-r--r--wizards/source/scriptforge/SF_Utils.xba1113
-rw-r--r--wizards/source/scriptforge/_CodingConventions.xba100
-rw-r--r--wizards/source/scriptforge/_ModuleModel.xba221
-rw-r--r--wizards/source/scriptforge/__License.xba25
-rw-r--r--wizards/source/scriptforge/dialog.xlb6
-rw-r--r--wizards/source/scriptforge/dlgConsole.xdl14
-rw-r--r--wizards/source/scriptforge/dlgProgress.xdl11
-rw-r--r--wizards/source/scriptforge/po/ScriptForge.pot975
-rw-r--r--wizards/source/scriptforge/po/en.po975
-rw-r--r--wizards/source/scriptforge/po/pt.po1141
-rw-r--r--wizards/source/scriptforge/python/ScriptForgeHelper.py317
-rw-r--r--wizards/source/scriptforge/python/scriptforge.py2539
-rw-r--r--wizards/source/scriptforge/script.xlb23
28 files changed, 25677 insertions, 0 deletions
diff --git a/wizards/source/scriptforge/SF_Array.xba b/wizards/source/scriptforge/SF_Array.xba
new file mode 100644
index 000000000..49bdab147
--- /dev/null
+++ b/wizards/source/scriptforge/SF_Array.xba
@@ -0,0 +1,2608 @@
+<?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_Array" 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_Array
+&apos;&apos;&apos; ========
+&apos;&apos;&apos; Singleton class implementing the &quot;ScriptForge.Array&quot; service
+&apos;&apos;&apos; Implemented as a usual Basic module
+&apos;&apos;&apos; Only 1D or 2D arrays are considered. Arrays with more than 2 dimensions are rejected
+&apos;&apos;&apos; With the noticeable exception of the CountDims method (&gt;2 dims allowed)
+&apos;&apos;&apos; The first argument of almost every method is the array to consider
+&apos;&apos;&apos; It is always passed by reference and left unchanged
+&apos;&apos;&apos;
+&apos;&apos;&apos; Detailed user documentation:
+&apos;&apos;&apos; https://help.libreoffice.org/latest/en-US/text/sbasic/shared/03/sf_array.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 ARRAYSEQUENCEERROR = &quot;ARRAYSEQUENCEERROR&quot; &apos; Incoherent arguments
+Const ARRAYINSERTERROR = &quot;ARRAYINSERTERROR&quot; &apos; Matrix and vector have incompatible sizes
+Const ARRAYINDEX1ERROR = &quot;ARRAYINDEX1ERROR&quot; &apos; Given index does not fit in array bounds
+Const ARRAYINDEX2ERROR = &quot;ARRAYINDEX2ERROR&quot; &apos; Given indexes do not fit in array bounds
+Const CSVPARSINGERROR = &quot;CSVPARSINGERROR&quot; &apos; Parsing error detected while parsing a csv file
+Const CSVOVERFLOWWARNING = &quot;CSVOVERFLOWWARNING&quot; &apos; Array becoming too big, import process of csv file is interrupted
+
+REM ============================================================ MODULE CONSTANTS
+
+Const MAXREPR = 50 &apos; Maximum length to represent an array in the console
+
+REM ===================================================== CONSTRUCTOR/DESTRUCTOR
+
+REM -----------------------------------------------------------------------------
+Public Function Dispose() As Variant
+ Set Dispose = Nothing
+End Function &apos; ScriptForge.SF_Array Explicit destructor
+
+REM ================================================================== PROPERTIES
+
+REM -----------------------------------------------------------------------------
+Property Get ObjectType As String
+&apos;&apos;&apos; Only to enable object representation
+ ObjectType = &quot;SF_Array&quot;
+End Property &apos; ScriptForge.SF_Array.ObjectType
+
+REM -----------------------------------------------------------------------------
+Property Get ServiceName As String
+&apos;&apos;&apos; Internal use
+ ServiceName = &quot;ScriptForge.Array&quot;
+End Property &apos; ScriptForge.SF_Array.ServiceName
+
+REM ============================================================== PUBLIC METHODS
+
+REM -----------------------------------------------------------------------------
+Public Function Append(Optional ByRef Array_1D As Variant _
+ , ParamArray pvArgs() As Variant _
+ ) As Variant
+&apos;&apos;&apos; Append at the end of the input array the items listed as arguments
+&apos;&apos;&apos; Arguments are appended blindly
+&apos;&apos;&apos; each of them might be a scalar of any type or a subarray
+&apos;&apos;&apos; Args
+&apos;&apos;&apos; Array_1D: the pre-existing array, may be empty
+&apos;&apos;&apos; pvArgs: a list of items to append to Array_1D
+&apos;&apos;&apos; Return:
+&apos;&apos;&apos; the new extended array. Its LBound is identical to that of Array_1D
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Array.Append(Array(1, 2, 3), 4, 5) returns (1, 2, 3, 4, 5)
+
+Dim vAppend As Variant &apos; Return value
+Dim lNbArgs As Long &apos; Number of elements to append
+Dim lMax As Long &apos; UBound of input array
+Dim i As Long
+Const cstThisSub = &quot;Array.Append&quot;
+Const cstSubArgs = &quot;Array_1D, arg0[, arg1] ...&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vAppend = Array()
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
+ End If
+
+Try:
+ lMax = UBound(Array_1D)
+ lNbArgs = UBound(pvArgs) + 1 &apos; pvArgs is always zero-based
+ If lMax &lt; LBound(Array_1D) Then &apos; Initial array is empty
+ If lNbArgs &gt; 0 Then
+ ReDim vAppend(0 To lNbArgs - 1)
+ End If
+ Else
+ vAppend() = Array_1D()
+ If lNbArgs &gt; 0 Then
+ ReDim Preserve vAppend(LBound(Array_1D) To lMax + lNbArgs)
+ End If
+ End If
+ For i = 1 To lNbArgs
+ vAppend(lMax + i) = pvArgs(i - 1)
+ Next i
+
+Finally:
+ Append = vAppend()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.Append
+
+REM -----------------------------------------------------------------------------
+Public Function AppendColumn(Optional ByRef Array_2D As Variant _
+ , Optional ByRef Column As Variant _
+ ) As Variant
+&apos;&apos;&apos; AppendColumn appends to the right side of a 2D array a new Column
+&apos;&apos;&apos; Args
+&apos;&apos;&apos; Array_2D: the pre-existing array, may be empty
+&apos;&apos;&apos; If the array has 1 dimension, it is considered as the 1st Column of the resulting 2D array
+&apos;&apos;&apos; Column: a 1D array with as many items as there are rows in Array_2D
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; the new extended array. Its LBounds are identical to that of Array_2D
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; ARRAYINSERTERROR
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Array.AppendColumn(Array(1, 2, 3), Array(4, 5, 6)) returns ((1, 4), (2, 5), (3, 6))
+&apos;&apos;&apos; x = SF_Array.AppendColumn(Array(), Array(1, 2, 3)) =&gt; ∀ i ∈ {0 ≤ i ≤ 2} : x(0, i) ≡ i
+
+Dim vAppendColumn As Variant &apos; Return value
+Dim iDims As Integer &apos; Dimensions of Array_2D
+Dim lMin1 As Long &apos; LBound1 of input array
+Dim lMax1 As Long &apos; UBound1 of input array
+Dim lMin2 As Long &apos; LBound2 of input array
+Dim lMax2 As Long &apos; UBound2 of input array
+Dim lMin As Long &apos; LBound of Column array
+Dim lMax As Long &apos; UBound of Column array
+Dim i As Long
+Dim j As Long
+Const cstThisSub = &quot;Array.AppendColumn&quot;
+Const cstSubArgs = &quot;Array_2D, Column&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vAppendColumn = Array()
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;) Then GoTo Finally &apos;Initial check: not missing and array
+ If Not SF_Utils._ValidateArray(Column, &quot;Column&quot;, 1) Then GoTo Finally
+ End If
+ iDims = SF_Array.CountDims(Array_2D)
+ If iDims &gt; 2 Then
+ If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally &apos;2nd check to manage error
+ End If
+
+Try:
+ lMin = LBound(Column)
+ lMax = UBound(Column)
+
+ &apos; Compute future dimensions of output array
+ Select Case iDims
+ Case 0 : lMin1 = lMin : lMax1 = lMax
+ lMin2 = 0 : lMax2 = -1
+ Case 1 : lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
+ lMin2 = 0 : lMax2 = 0
+ Case 2 : lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
+ lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
+ End Select
+ If iDims &gt; 0 And lMax - lMin &lt;&gt; lMax1 - lMin1 Then GoTo CatchColumn
+ ReDim vAppendColumn(lMin1 To lMax1, lMin2 To lMax2 + 1)
+
+ &apos; Copy input array to output array
+ For i = lMin1 To lMax1
+ For j = lMin2 To lMax2
+ If iDims = 2 Then vAppendColumn(i, j) = Array_2D(i, j) Else vAppendColumn(i, j) = Array_2D(i)
+ Next j
+ Next i
+ &apos; Copy new Column
+ For i = lMin1 To lMax1
+ vAppendColumn(i, lMax2 + 1) = Column(i)
+ Next i
+
+Finally:
+ AppendColumn = vAppendColumn()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchColumn:
+ SF_Exception.RaiseFatal(ARRAYINSERTERROR, &quot;Column&quot;, SF_Array._Repr(Array_2D), SF_Utils._Repr(Column, MAXREPR))
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.AppendColumn
+
+REM -----------------------------------------------------------------------------
+Public Function AppendRow(Optional ByRef Array_2D As Variant _
+ , Optional ByRef Row As Variant _
+ ) As Variant
+&apos;&apos;&apos; AppendRow appends below a 2D array a new row
+&apos;&apos;&apos; Args
+&apos;&apos;&apos; Array_2D: the pre-existing array, may be empty
+&apos;&apos;&apos; If the array has 1 dimension, it is considered as the 1st row of the resulting 2D array
+&apos;&apos;&apos; Row: a 1D array with as many items as there are columns in Array_2D
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; the new extended array. Its LBounds are identical to that of Array_2D
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; ARRAYINSERTERROR
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Array.AppendRow(Array(1, 2, 3), Array(4, 5, 6)) returns ((1, 2, 3), (4, 5, 6))
+&apos;&apos;&apos; x = SF_Array.AppendRow(Array(), Array(1, 2, 3)) =&gt; ∀ i ∈ {0 ≤ i ≤ 2} : x(i, 0) ≡ i
+
+Dim vAppendRow As Variant &apos; Return value
+Dim iDims As Integer &apos; Dimensions of Array_2D
+Dim lMin1 As Long &apos; LBound1 of input array
+Dim lMax1 As Long &apos; UBound1 of input array
+Dim lMin2 As Long &apos; LBound2 of input array
+Dim lMax2 As Long &apos; UBound2 of input array
+Dim lMin As Long &apos; LBound of row array
+Dim lMax As Long &apos; UBound of row array
+Dim i As Long
+Dim j As Long
+Const cstThisSub = &quot;Array.AppendRow&quot;
+Const cstSubArgs = &quot;Array_2D, Row&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vAppendRow = Array()
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;) Then GoTo Finally &apos;Initial check: not missing and array
+ If Not SF_Utils._ValidateArray(Row, &quot;Row&quot;, 1) Then GoTo Finally
+ End If
+ iDims = SF_Array.CountDims(Array_2D)
+ If iDims &gt; 2 Then
+ If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally &apos;2nd check to manage error
+ End If
+
+Try:
+ lMin = LBound(Row)
+ lMax = UBound(Row)
+
+ &apos; Compute future dimensions of output array
+ Select Case iDims
+ Case 0 : lMin1 = 0 : lMax1 = -1
+ lMin2 = lMin : lMax2 = lMax
+ Case 1 : lMin1 = 0 : lMax1 = 0
+ lMin2 = LBound(Array_2D, 1) : lMax2 = UBound(Array_2D, 1)
+ Case 2 : lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
+ lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
+ End Select
+ If iDims &gt; 0 And lMax - lMin &lt;&gt; lMax2 - lMin2 Then GoTo CatchRow
+ ReDim vAppendRow(lMin1 To lMax1 + 1, lMin2 To lMax2)
+
+ &apos; Copy input array to output array
+ For i = lMin1 To lMax1
+ For j = lMin2 To lMax2
+ If iDims = 2 Then vAppendRow(i, j) = Array_2D(i, j) Else vAppendRow(i, j) = Array_2D(j)
+ Next j
+ Next i
+ &apos; Copy new row
+ For j = lMin2 To lMax2
+ vAppendRow(lMax1 + 1, j) = Row(j)
+ Next j
+
+Finally:
+ AppendRow = vAppendRow()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchRow:
+ SF_Exception.RaiseFatal(ARRAYINSERTERROR, &quot;Row&quot;, SF_Array._Repr(Array_2D), SF_Utils._Repr(Row, MAXREPR))
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.AppendRow
+
+REM -----------------------------------------------------------------------------
+Public Function Contains(Optional ByRef Array_1D As Variant _
+ , Optional ByVal ToFind As Variant _
+ , Optional ByVal CaseSensitive As Variant _
+ , Optional ByVal SortOrder As Variant _
+ ) As Boolean
+&apos;&apos;&apos; Check if a 1D array contains the ToFind number, string or date
+&apos;&apos;&apos; The comparison between strings can be done case-sensitive or not
+&apos;&apos;&apos; If the array is sorted then
+&apos;&apos;&apos; the array must be filled homogeneously, i.e. all items must be of the same type
+&apos;&apos;&apos; Empty and Null items are forbidden
+&apos;&apos;&apos; a binary search is done
+&apos;&apos;&apos; Otherwise the array is scanned from top. Null or Empty items are simply ignored
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array_1D: the array to scan
+&apos;&apos;&apos; ToFind: a number, a date or a string to find
+&apos;&apos;&apos; CaseSensitive: Only for string comparisons, default = False
+&apos;&apos;&apos; SortOrder: &quot;ASC&quot;, &quot;DESC&quot; or &quot;&quot; (= not sorted, default)
+&apos;&apos;&apos; Return: True when found
+&apos;&apos;&apos; Result is unpredictable when array is announced sorted and is in reality not
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Array.Contains(Array(&quot;A&quot;,&quot;B&quot;,&quot;c&quot;,&quot;D&quot;), &quot;C&quot;, SortOrder := &quot;ASC&quot;) returns True
+&apos;&apos;&apos; SF_Array.Contains(Array(&quot;A&quot;,&quot;B&quot;,&quot;c&quot;,&quot;D&quot;), &quot;C&quot;, CaseSensitive := True) returns False
+
+Dim bContains As Boolean &apos; Return value
+Dim iToFindType As Integer &apos; VarType of ToFind
+Const cstThisSub = &quot;Array.Contains&quot;
+Const cstSubArgs = &quot;Array_1D, ToFind, [CaseSensitive=False], [SortOrder=&quot;&quot;&quot;&quot;|&quot;&quot;ASC&quot;&quot;|&quot;&quot;DESC&quot;&quot;]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+
+ bContains = False
+
+Check:
+ If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False
+ If IsMissing(SortOrder) Or IsEmpty(SortOrder) Then SortOrder = &quot;&quot;
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(SortOrder, &quot;SortOrder&quot;, V_STRING, Array(&quot;ASC&quot;, &quot;DESC&quot;, &quot;&quot;)) Then GoTo Finally
+ If Not SF_Utils._Validate(ToFind, &quot;ToFind&quot;, Array(V_STRING, V_DATE, V_NUMERIC)) Then GoTo Finally
+ iToFindType = SF_Utils._VarTypeExt(ToFind)
+ If SortOrder &lt;&gt; &quot;&quot; Then
+ If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1, iToFindType) Then GoTo Finally
+ Else
+ If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
+ End If
+ If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
+ End If
+
+Try:
+ bContains = SF_Array._FindItem(Array_1D, ToFind, CaseSensitive, SortOrder)(0)
+
+Finally:
+ Contains = bContains
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.Contains
+
+REM -----------------------------------------------------------------------------
+Public Function ConvertToDictionary(Optional ByRef Array_2D As Variant) As Variant
+&apos;&apos;&apos; Store the content of a 2-columns array into a dictionary
+&apos;&apos;&apos; Key found in 1st column, Item found in 2nd
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array_2D: 1st column must contain exclusively non zero-length strings
+&apos;&apos;&apos; 1st column may not be sorted
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; a ScriptForge dictionary object
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos;
+
+Dim oDict As Variant &apos; Return value
+Dim i As Long
+Const cstThisSub = &quot;Dictionary.ConvertToArray&quot;
+Const cstSubArgs = &quot;Array_2D&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2, V_STRING, True) Then GoTo Finally
+ End If
+
+Try:
+ Set oDict = SF_Services.CreateScriptService(&quot;Dictionary&quot;)
+ For i = LBound(Array_2D, 1) To UBound(Array_2D, 1)
+ oDict.Add(Array_2D(i, 0), Array_2D(i, 1))
+ Next i
+
+ ConvertToDictionary = oDict
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.ConvertToDictionary
+
+REM -----------------------------------------------------------------------------
+Public Function Copy(Optional ByRef Array_ND As Variant) As Variant
+&apos;&apos;&apos; Duplicate a 1D or 2D array
+&apos;&apos;&apos; A usual assignment copies an array by reference, i.e. shares the same memory location
+&apos;&apos;&apos; Dim a, b
+&apos;&apos;&apos; a = Array(1, 2, 3)
+&apos;&apos;&apos; b = a
+&apos;&apos;&apos; a(2) = 30
+&apos;&apos;&apos; MsgBox b(2) &apos; 30
+&apos;&apos;&apos; Args
+&apos;&apos;&apos; Array_ND: the array to copy, may be empty
+&apos;&apos;&apos; Return:
+&apos;&apos;&apos; the copied array. Subarrays however still remain assigned by reference
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Array.Copy(Array(1, 2, 3)) returns (1, 2, 3)
+
+Dim vCopy As Variant &apos; Return value
+Dim iDims As Integer &apos; Number of dimensions of the input array
+Const cstThisSub = &quot;Array.Copy&quot;
+Const cstSubArgs = &quot;Array_ND&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vCopy = Array()
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_ND, &quot;Array_ND&quot;) Then GoTo Finally
+ iDims = SF_Array.CountDims(Array_ND)
+ If iDims &gt; 2 Then
+ If Not SF_Utils._ValidateArray(Array_ND, &quot;Array_ND&quot;, 2) Then GoTo Finally
+ End If
+ End If
+
+Try:
+ Select Case iDims
+ Case 0
+ Case 1
+ vCopy = Array_ND
+ ReDim Preserve vCopy(LBound(Array_ND) To UBound(Array_ND))
+ Case 2
+ vCopy = Array_ND
+ ReDim Preserve vCopy(LBound(Array_ND, 1) To UBound(Array_ND, 1), LBound(Array_ND, 2) To UBound(Array_ND, 2))
+ End Select
+
+Finally:
+ Copy = vCopy()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.Copy
+
+REM -----------------------------------------------------------------------------
+Public Function CountDims(Optional ByRef Array_ND As Variant) As Integer
+&apos;&apos;&apos; Count the number of dimensions of an array - may be &gt; 2
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array_ND: the array to be examined
+&apos;&apos;&apos; Return: the number of dimensions: -1 = not array, 0 = uninitialized array, else &gt;= 1
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; Dim a(1 To 10, -3 To 12, 5)
+&apos;&apos;&apos; CountDims(a) returns 3
+
+Dim iDims As Integer &apos; Return value
+Dim lMax As Long &apos; Storage for UBound of each dimension
+Const cstThisSub = &quot;Array.CountDims&quot;
+Const cstSubArgs = &quot;Array_ND&quot;
+
+Check:
+ iDims = -1
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If IsMissing(Array_ND) Then &apos; To have missing exception processed
+ If Not SF_Utils._ValidateArray(Array_ND, &quot;Array_ND&quot;) Then GoTo Finally
+ End If
+ End If
+
+Try:
+ On Local Error Goto ErrHandler
+ &apos; Loop, increasing the dimension index (i) until an error occurs.
+ &apos; An error will occur when i exceeds the number of dimensions in the array. Returns i - 1.
+ iDims = 0
+ If Not IsArray(Array_ND) Then
+ Else
+ Do
+ iDims = iDims + 1
+ lMax = UBound(Array_ND, iDims)
+ Loop Until (Err &lt;&gt; 0)
+ End If
+
+ ErrHandler:
+ On Local Error GoTo 0
+
+ iDims = iDims - 1
+ If iDims = 1 Then
+ If LBound(Array_ND, 1) &gt; UBound(Array_ND, 1) Then iDims = 0
+ End If
+
+Finally:
+ CountDims = iDims
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+End Function &apos; ScriptForge.SF_Array.CountDims
+
+REM -----------------------------------------------------------------------------
+Public Function Difference(Optional ByRef Array1_1D As Variant _
+ , Optional ByRef Array2_1D As Variant _
+ , Optional ByVal CaseSensitive As Variant _
+ ) As Variant
+&apos;&apos;&apos; Build a set being the Difference of the two input arrays, i.e. items are contained in 1st array and NOT in 2nd
+&apos;&apos;&apos; both input arrays must be filled homogeneously, i.e. all items must be of the same type
+&apos;&apos;&apos; Empty and Null items are forbidden
+&apos;&apos;&apos; The comparison between strings is case sensitive or not
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array1_1D: a 1st input array
+&apos;&apos;&apos; Array2_1D: a 2nd input array
+&apos;&apos;&apos; CaseSensitive: default = False
+&apos;&apos;&apos; Returns: a zero-based array containing unique items from the 1st array not present in the 2nd
+&apos;&apos;&apos; The output array is sorted in ascending order
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Array.Difference(Array(&quot;A&quot;, &quot;C&quot;, &quot;A&quot;, &quot;b&quot;, &quot;B&quot;), Array(&quot;C&quot;, &quot;Z&quot;, &quot;b&quot;), True) returns (&quot;A&quot;, &quot;B&quot;)
+
+Dim vDifference() As Variant &apos; Return value
+Dim vSorted() As Variant &apos; The 2nd input array after sort
+Dim iType As Integer &apos; VarType of elements in input arrays
+Dim lMin1 As Long &apos; LBound of 1st input array
+Dim lMax1 As Long &apos; UBound of 1st input array
+Dim lMin2 As Long &apos; LBound of 2nd input array
+Dim lMax2 As Long &apos; UBound of 2nd input array
+Dim lSize As Long &apos; Number of Difference items
+Dim vItem As Variant &apos; One single item in the array
+Dim i As Long
+Const cstThisSub = &quot;Array.Difference&quot;
+Const cstSubArgs = &quot;Array1_1D, Array2_1D, [CaseSensitive=False]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vDifference = Array()
+
+Check:
+ If IsMissing(CaseSensitive) Then CaseSensitive = False
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array1_1D, &quot;Array1_1D&quot;, 1, 0, True) Then GoTo Finally
+ iType = SF_Utils._VarTypeExt(Array1_1D(LBound(Array1_1D)))
+ If Not SF_Utils._ValidateArray(Array2_1D, &quot;Array2_1D&quot;, 1, iType, True) Then GoTo Finally
+ If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
+ End If
+
+Try:
+ lMin1 = LBound(Array1_1D) : lMax1 = UBound(Array1_1D)
+ lMin2 = LBound(Array2_1D) : lMax2 = UBound(Array2_1D)
+
+ &apos; If 1st array is empty, do nothing
+ If lMax1 &lt; lMin1 Then
+ ElseIf lMax2 &lt; lMin2 Then &apos; only 2nd array is empty
+ vUnion = SF_Array.Unique(Array1_1D, CaseSensitive)
+ Else
+
+ &apos; First sort the 2nd array
+ vSorted = SF_Array.Sort(Array2_1D, &quot;ASC&quot;, CaseSensitive)
+
+ &apos; Resize the output array to the size of the 1st array
+ ReDim vDifference(0 To (lMax1 - lMin1))
+ lSize = -1
+
+ &apos; Fill vDifference one by one with items present only in 1st set
+ For i = lMin1 To lMax1
+ vItem = Array1_1D(i)
+ If Not SF_Array.Contains(vSorted, vItem, CaseSensitive, &quot;ASC&quot;) Then
+ lSize = lSize + 1
+ vDifference(lSize) = vItem
+ End If
+ Next i
+
+ &apos; Remove unfilled entries and duplicates
+ If lSize &gt;= 0 Then
+ ReDim Preserve vDifference(0 To lSize)
+ vDifference() = SF_Array.Unique(vDifference, CaseSensitive)
+ Else
+ vDifference = Array()
+ End If
+ End If
+
+Finally:
+ Difference = vDifference()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.Difference
+
+REM -----------------------------------------------------------------------------
+Public Function ExportToTextFile(Optional ByRef Array_1D As Variant _
+ , Optional ByVal FileName As Variant _
+ , Optional ByVal Encoding As Variant _
+ ) As Boolean
+&apos;&apos;&apos; Write all items of the array sequentially to a text file
+&apos;&apos;&apos; If the file exists already, it will be overwritten without warning
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array_1D: the array to export
+&apos;&apos;&apos; FileName: the full name (path + file) in SF_FileSystem.FileNaming notation
+&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 does not implement all existing sets
+&apos;&apos;&apos; Default = UTF-8
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if successful
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Array.ExportToTextFile(Array(&quot;A&quot;,&quot;B&quot;,&quot;C&quot;,&quot;D&quot;), &quot;C:\Temp\A short file.txt&quot;)
+
+Dim bExport As Boolean &apos; Return value
+Dim oFile As Object &apos; Output file handler
+Dim sLine As String &apos; A single line
+Const cstThisSub = &quot;Array.ExportToTextFile&quot;
+Const cstSubArgs = &quot;Array_1D, FileName&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bExport = False
+
+Check:
+ If IsMissing(Encoding) Or IsEmpty(Encoding) Then Encoding = &quot;UTF-8&quot;
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1, V_STRING, True) Then GoTo Finally
+ If Not SF_Utils._ValidateFile(FileName, &quot;FileName&quot;) Then GoTo Finally
+ If Not SF_Utils._Validate(Encoding, &quot;Encoding&quot;, V_STRING) Then GoTo Finally
+ End If
+
+Try:
+ Set oFile = SF_FileSystem.CreateTextFile(FileName, Overwrite := True, Encoding := Encoding)
+ If Not IsNull(oFile) Then
+ With oFile
+ For Each sLine In Array_1D
+ .WriteLine(sLine)
+ Next sLine
+ .CloseFile()
+ End With
+ End If
+
+ bExport = True
+
+Finally:
+ If Not IsNull(oFile) Then Set oFile = oFile.Dispose()
+ ExportToTextFile = bExport
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.ExportToTextFile
+
+REM -----------------------------------------------------------------------------
+Public Function ExtractColumn(Optional ByRef Array_2D As Variant _
+ , Optional ByVal ColumnIndex As Variant _
+ ) As Variant
+&apos;&apos;&apos; ExtractColumn extracts from a 2D array a specific column
+&apos;&apos;&apos; Args
+&apos;&apos;&apos; Array_2D: the array from which to extract
+&apos;&apos;&apos; ColumnIndex: the column to extract - must be in the interval [LBound, UBound]
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; the extracted column. Its LBound and UBound are identical to that of the 1st dimension of Array_2D
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; ARRAYINDEX1ERROR
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; |1, 2, 3|
+&apos;&apos;&apos; SF_Array.ExtractColumn( |4, 5, 6|, 2) returns (3, 6, 9)
+&apos;&apos;&apos; |7, 8, 9|
+
+Dim vExtractColumn As Variant &apos; Return value
+Dim lMin1 As Long &apos; LBound1 of input array
+Dim lMax1 As Long &apos; UBound1 of input array
+Dim lMin2 As Long &apos; LBound1 of input array
+Dim lMax2 As Long &apos; UBound1 of input array
+Dim i As Long
+Const cstThisSub = &quot;Array.ExtractColumn&quot;
+Const cstSubArgs = &quot;Array_2D, ColumnIndex&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vExtractColumn = Array()
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally
+ If Not SF_Utils._Validate(ColumnIndex, &quot;ColumnIndex&quot;, V_NUMERIC) Then GoTo Finally
+ End If
+
+Try:
+ &apos; Compute future dimensions of output array
+ lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
+ If ColumnIndex &lt; lMin2 Or ColumnIndex &gt; lMax2 Then GoTo CatchIndex
+ lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
+ ReDim vExtractColumn(lMin1 To lMax1)
+
+ &apos; Copy Column of input array to output array
+ For i = lMin1 To lMax1
+ vExtractColumn(i) = Array_2D(i, ColumnIndex)
+ Next i
+
+Finally:
+ ExtractColumn = vExtractColumn()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchIndex:
+ SF_Exception.RaiseFatal(ARRAYINDEX1ERROR, &quot;ColumnIndex&quot;, SF_Array._Repr(Array_2D), ColumnIndex)
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.ExtractColumn
+
+REM -----------------------------------------------------------------------------
+Public Function ExtractRow(Optional ByRef Array_2D As Variant _
+ , Optional ByVal RowIndex As Variant _
+ ) As Variant
+&apos;&apos;&apos; ExtractRow extracts from a 2D array a specific row
+&apos;&apos;&apos; Args
+&apos;&apos;&apos; Array_2D: the array from which to extract
+&apos;&apos;&apos; RowIndex: the row to extract - must be in the interval [LBound, UBound]
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; the extracted row. Its LBound and UBound are identical to that of the 2nd dimension of Array_2D
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; ARRAYINDEX1ERROR
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; |1, 2, 3|
+&apos;&apos;&apos; SF_Array.ExtractRow(|4, 5, 6|, 2) returns (7, 8, 9)
+&apos;&apos;&apos; |7, 8, 9|
+
+Dim vExtractRow As Variant &apos; Return value
+Dim lMin1 As Long &apos; LBound1 of input array
+Dim lMax1 As Long &apos; UBound1 of input array
+Dim lMin2 As Long &apos; LBound1 of input array
+Dim lMax2 As Long &apos; UBound1 of input array
+Dim i As Long
+Const cstThisSub = &quot;Array.ExtractRow&quot;
+Const cstSubArgs = &quot;Array_2D, RowIndex&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vExtractRow = Array()
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally
+ If Not SF_Utils._Validate(RowIndex, &quot;RowIndex&quot;, V_NUMERIC) Then GoTo Finally
+ End If
+
+Try:
+ &apos; Compute future dimensions of output array
+ lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
+ If RowIndex &lt; lMin1 Or RowIndex &gt; lMax1 Then GoTo CatchIndex
+ lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
+ ReDim vExtractRow(lMin2 To lMax2)
+
+ &apos; Copy row of input array to output array
+ For i = lMin2 To lMax2
+ vExtractRow(i) = Array_2D(RowIndex, i)
+ Next i
+
+Finally:
+ ExtractRow = vExtractRow()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchIndex:
+ SF_Exception.RaiseFatal(ARRAYINDEX1ERROR, &quot;RowIndex&quot;, SF_Array._Repr(Array_2D), RowIndex)
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.ExtractRow
+
+REM -----------------------------------------------------------------------------
+Public Function Flatten(Optional ByRef Array_1D As Variant) As Variant
+&apos;&apos;&apos; Stack all items and all items in subarrays into one array without subarrays
+&apos;&apos;&apos; Args
+&apos;&apos;&apos; Array_1D: the pre-existing array, may be empty
+&apos;&apos;&apos; Return:
+&apos;&apos;&apos; The new flattened array. Its LBound is identical to that of Array_1D
+&apos;&apos;&apos; If one of the subarrays has a number of dimensions &gt; 1 Then that subarray is left unchanged
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Array.Flatten(Array(1, 2, Array(3, 4, 5)) returns (1, 2, 3, 4, 5)
+
+Dim vFlatten As Variant &apos; Return value
+Dim lMin As Long &apos; LBound of input array
+Dim lMax As Long &apos; UBound of input array
+Dim lIndex As Long &apos; Index in output array
+Dim vItem As Variant &apos; Array single item
+Dim iDims As Integer &apos; Array number of dimensions
+Dim lEmpty As Long &apos; Number of empty subarrays
+Dim i As Long
+Dim j As Long
+Const cstThisSub = &quot;Array.Flatten&quot;
+Const cstSubArgs = &quot;Array_1D&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vFlatten = Array()
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
+ End If
+
+Try:
+ If UBound(Array_1D) &gt;= LBound(Array_1D) Then
+ lMin = LBound(Array_1D) : lMax = UBound(Array_1D)
+ ReDim vFlatten(lMin To lMax) &apos; Initial minimal sizing
+ lEmpty = 0
+ lIndex = lMin - 1
+ For i = lMin To lMax
+ vItem = Array_1D(i)
+ If IsArray(vItem) Then
+ iDims = SF_Array.CountDims(vItem)
+ Select Case iDims
+ Case 0 &apos; Empty arrays are ignored
+ lEmpty = lEmpty + 1
+ Case 1 &apos; Only 1D subarrays are flattened
+ ReDim Preserve vFlatten(lMin To UBound(vFlatten) + UBound(vItem) - LBound(vItem))
+ For j = LBound(vItem) To UBound(vItem)
+ lIndex = lIndex + 1
+ vFlatten(lIndex) = vItem(j)
+ Next j
+ Case &gt; 1 &apos; Other arrays are left unchanged
+ lIndex = lIndex + 1
+ vFlatten(lIndex) = vItem
+ End Select
+ Else
+ lIndex = lIndex + 1
+ vFlatten(lIndex) = vItem
+ End If
+ Next i
+ End If
+ &apos; Reduce size of output if Array_1D is populated with some empty arrays
+ If lEmpty &gt; 0 Then
+ If lIndex - lEmpty &lt; lMin Then
+ vFlatten = Array()
+ Else
+ ReDim Preserve vFlatten(lMin To UBound(vFlatten) - lEmpty)
+ End If
+ End If
+
+Finally:
+ Flatten = vFlatten()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.Flatten
+
+REM -----------------------------------------------------------------------------
+Public Function GetProperty(Optional ByVal PropertyName As Variant) As Variant
+&apos;&apos;&apos; Return the actual value of the given property
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; PropertyName: the name of the property as a string
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The actual value of the property
+&apos;&apos;&apos; Exceptions
+&apos;&apos;&apos; ARGUMENTERROR The property does not exist
+
+Const cstThisSub = &quot;Array.GetProperty&quot;
+Const cstSubArgs = &quot;PropertyName&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ GetProperty = Null
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
+ End If
+
+Try:
+ Select Case UCase(PropertyName)
+ Case Else
+ End Select
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.GetProperty
+
+REM -----------------------------------------------------------------------------
+Public Function ImportFromCSVFile(Optional ByRef FileName As Variant _
+ , Optional ByVal Delimiter As Variant _
+ , Optional ByVal DateFormat As Variant _
+ , Optional ByVal _IsoDate As Variant _
+ ) As Variant
+&apos;&apos;&apos; Import the data contained in a comma-separated values (CSV) file
+&apos;&apos;&apos; The comma may be replaced by any character
+&apos;&apos;&apos; Each line in the file contains a full record
+&apos;&apos;&apos; Line splitting is not allowed)
+&apos;&apos;&apos; However sequences like \n, \t, ... are left unchanged. Use SF_String.Unescape() to manage them
+&apos;&apos;&apos; A special mechanism is implemented to load dates
+&apos;&apos;&apos; The applicable CSV format is described in https://tools.ietf.org/html/rfc4180
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; FileName: the name of the text file containing the data expressed as given by the current FileNaming
+&apos;&apos;&apos; property of the SF_FileSystem service. Default = both URL format or native format
+&apos;&apos;&apos; Delimiter: Default = &quot;,&quot;. Other usual options are &quot;;&quot; and the tab character
+&apos;&apos;&apos; DateFormat: either YYYY-MM-DD, DD-MM-YYYY or MM-DD-YYYY
+&apos;&apos;&apos; The dash (-) may be replaced by a dot (.), a slash (/) or a space
+&apos;&apos;&apos; Other date formats will be ignored
+&apos;&apos;&apos; If &quot;&quot; (default), dates will be considered as strings
+&apos;&apos;&apos; _IsoDate: when True, the execution is initiated from Python, do not convert dates to Date variables. Internal use only
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; A 2D-array with each row corresponding with a single record read in the file
+&apos;&apos;&apos; and each column corresponding with a field of the record
+&apos;&apos;&apos; No check is made about the coherence of the field types across columns
+&apos;&apos;&apos; A best guess will be made to identify numeric and date types
+&apos;&apos;&apos; If a line contains less or more fields than the first line in the file,
+&apos;&apos;&apos; an exception will be raised. Empty lines however are simply ignored
+&apos;&apos;&apos; If the size of the file exceeds the number of items limit, a warning is raised
+&apos;&apos;&apos; and the array is truncated
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; CSVPARSINGERROR Given file is not formatted as a csv file
+&apos;&apos;&apos; CSVOVERFLOWWARNING Maximum number of allowed items exceeded
+
+Dim vArray As Variant &apos; Returned array
+Dim lCol As Long &apos; Index of last column of vArray
+Dim lRow As Long &apos; Index of current row of vArray
+Dim lFileSize As Long &apos; Number of records found in the file
+Dim vCsv As Object &apos; CSV file handler
+Dim sLine As String &apos; Last read line
+Dim vLine As Variant &apos; Array of fields of last read line
+Dim sItem As String &apos; Individual item in the file
+Dim vItem As Variant &apos; Individual item in the output array
+Dim iPosition As Integer &apos; Date position in individual item
+Dim iYear As Integer, iMonth As Integer, iDay As Integer
+ &apos; Date components
+Dim i As Long
+Const cstItemsLimit = 250000 &apos; Maximum number of admitted items
+Const cstThisSub = &quot;Array.ImportFromCSVFile&quot;
+Const cstSubArgs = &quot;FileName, [Delimiter=&quot;&quot;,&quot;&quot;], [DateFormat=&quot;&quot;&quot;&quot;]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vArray = Array()
+
+Check:
+ If IsMissing(Delimiter) Or IsEmpty(Delimiter) Then Delimiter = &quot;,&quot;
+ If IsMissing(DateFormat) Or IsEmpty(DateFormat) Then DateFormat = &quot;&quot;
+ If IsMissing(_IsoDate) Or IsEmpty(_IsoDate) Then _IsoDate = False
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateFile(FileName, &quot;FileName&quot;) Then GoTo Finally
+ If Not SF_Utils._Validate(Delimiter, &quot;Delimiter&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(DateFormat, &quot;DateFormat&quot;, V_STRING) Then GoTo Finally
+ End If
+ If Len(Delimiter) = 0 Then Delimiter = &quot;,&quot;
+
+Try:
+ &apos; Counts the lines present in the file to size the final array
+ &apos; Very beneficial for large files, better than multiple ReDims
+ &apos; Small overhead for small files
+ lFileSize = SF_FileSystem._CountTextLines(FileName, False)
+ If lFileSize &lt;= 0 Then GoTo Finally
+
+ &apos; Reread file line by line
+ Set vCsv = SF_FileSystem.OpenTextFile(FileName, IOMode := SF_FileSystem.ForReading)
+ If IsNull(vCsv) Then GoTo Finally &apos; Open error
+ lRow = -1
+ With vCsv
+ Do While Not .AtEndOfStream
+ sLine = .ReadLine()
+ If Len(sLine) &gt; 0 Then &apos; Ignore empty lines
+ If InStr(sLine, &quot;&quot;&quot;&quot;) &gt; 0 Then vLine = SF_String.SplitNotQuoted(sLine, Delimiter) Else vLine = Split(sLine, Delimiter) &apos; Simple split when relevant
+ lRow = lRow + 1
+ If lRow = 0 Then &apos; Initial sizing of output array
+ lCol = UBound(vLine)
+ ReDim vArray(0 To lFileSize - 1, 0 To lCol)
+ ElseIf UBound(vLine) &lt;&gt; lCol Then
+ GoTo CatchCSVFormat
+ End If
+ &apos; Check type and copy all items of the line
+ For i = 0 To lCol
+ If Left(vLine(i), 1) = &quot;&quot;&quot;&quot; Then sItem = SF_String.Unquote(vLine(i)) Else sItem = vLine(i) &apos; Unquote only when useful
+ &apos; Interpret the individual line item
+ Select Case True
+ Case IsNumeric(sItem)
+ If InStr(sItem, &quot;.&quot;) + InStr(1, sItem, &quot;e&quot;, 1) &gt; 0 Then vItem = Val(sItem) Else vItem = CLng(sItem)
+ Case DateFormat &lt;&gt; &quot;&quot; And Len(sItem) = Len(DateFormat)
+ If SF_String.IsADate(sItem, DateFormat) Then
+ iPosition = InStr(DateFormat, &quot;YYYY&quot;) : iYear = CInt(Mid(sItem, iPosition, 4))
+ iPosition = InStr(DateFormat, &quot;MM&quot;) : iMonth = CInt(Mid(sItem, iPosition, 2))
+ iPosition = InStr(DateFormat, &quot;DD&quot;) : iDay = CInt(Mid(sItem, iPosition, 2))
+ vItem = DateSerial(iYear, iMonth, iDay)
+ If _IsoDate Then vItem = SF_Utils._CDateToIso(vItem) &apos; Called from Python
+ Else
+ vItem = sItem
+ End If
+ Case Else : vItem = sItem
+ End Select
+ vArray(lRow, i) = vItem
+ Next i
+ End If
+ &apos; Provision to avoid very large arrays and their sometimes erratic behaviour
+ If (lRow + 2) * (lCol + 1) &gt; cstItemsLimit Then
+ ReDim Preserve vArray(0 To lRow, 0 To lCol)
+ GoTo CatchOverflow
+ End If
+ Loop
+ End With
+
+Finally:
+ If Not IsNull(vCsv) Then
+ vCsv.CloseFile()
+ Set vCsv = vCsv.Dispose()
+ End If
+ ImportFromCSVFile = vArray
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchCSVFormat:
+ SF_Exception.RaiseFatal(CSVPARSINGERROR, FileName, vCsv.Line, sLine)
+ GoTo Finally
+CatchOverflow:
+ &apos;TODO SF_Exception.RaiseWarning(SF_Exception.CSVOVERFLOWWARNING, cstThisSub)
+ &apos;MsgBox &quot;TOO MUCH LINES !!&quot;
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.ImportFromCSVFile
+
+REM -----------------------------------------------------------------------------
+Public Function IndexOf(Optional ByRef Array_1D As Variant _
+ , Optional ByVal ToFind As Variant _
+ , Optional ByVal CaseSensitive As Variant _
+ , Optional ByVal SortOrder As Variant _
+ ) As Long
+&apos;&apos;&apos; Finds in a 1D array the ToFind number, string or date
+&apos;&apos;&apos; ToFind must exist within the array.
+&apos;&apos;&apos; The comparison between strings can be done case-sensitively or not
+&apos;&apos;&apos; If the array is sorted then
+&apos;&apos;&apos; the array must be filled homogeneously, i.e. all items must be of the same type
+&apos;&apos;&apos; Empty and Null items are forbidden
+&apos;&apos;&apos; a binary search is done
+&apos;&apos;&apos; Otherwise the array is scanned from top. Null or Empty items are simply ignored
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array_1D: the array to scan
+&apos;&apos;&apos; ToFind: a number, a date or a string to find
+&apos;&apos;&apos; CaseSensitive: Only for string comparisons, default = False
+&apos;&apos;&apos; SortOrder: &quot;ASC&quot;, &quot;DESC&quot; or &quot;&quot; (= not sorted, default)
+&apos;&apos;&apos; Return: the index of the found item, LBound - 1 if not found
+&apos;&apos;&apos; Result is unpredictable when array is announced sorted and is in reality not
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Array.IndexOf(Array(&quot;A&quot;,&quot;B&quot;,&quot;c&quot;,&quot;D&quot;), &quot;C&quot;, SortOrder := &quot;ASC&quot;) returns 2
+&apos;&apos;&apos; SF_Array.IndexOf(Array(&quot;A&quot;,&quot;B&quot;,&quot;c&quot;,&quot;D&quot;), &quot;C&quot;, CaseSensitive := True) returns -1
+
+Dim vFindItem As Variant &apos; 2-items array (0) = True if found, (1) = Index where found
+Dim lIndex As Long &apos; Return value
+Dim iToFindType As Integer &apos; VarType of ToFind
+Const cstThisSub = &quot;Array.IndexOf&quot;
+Const cstSubArgs = &quot;Array_1D, ToFind, [CaseSensitive=False], [SortOrder=&quot;&quot;&quot;&quot;|&quot;&quot;ASC&quot;&quot;|&quot;&quot;DESC&quot;&quot;]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+
+ lIndex = -1
+
+Check:
+ If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False
+ If IsMissing(SortOrder) Or IsEmpty(SortOrder) Then SortOrder = &quot;&quot;
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(SortOrder, &quot;SortOrder&quot;, V_STRING, Array(&quot;ASC&quot;, &quot;DESC&quot;, &quot;&quot;)) Then GoTo Finally
+ If Not SF_Utils._Validate(ToFind, &quot;ToFind&quot;, Array(V_STRING, V_DATE, V_NUMERIC)) Then GoTo Finally
+ iToFindType = SF_Utils._VarTypeExt(ToFind)
+ If SortOrder &lt;&gt; &quot;&quot; Then
+ If Not SF_Utils._ValidateArray(Array_1D, &quot;Array&quot;, 1, iToFindType) Then GoTo Finally
+ Else
+ If Not SF_Utils._ValidateArray(Array_1D, &quot;Array&quot;, 1) Then GoTo Finally
+ End If
+ If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
+ End If
+
+Try:
+ vFindItem = SF_Array._FindItem(Array_1D, ToFind, CaseSensitive, SortOrder)
+ If vFindItem(0) = True Then lIndex = vFindItem(1) Else lIndex = LBound(Array_1D) - 1
+
+Finally:
+ IndexOf = lIndex
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.IndexOf
+
+REM -----------------------------------------------------------------------------
+Public Function Insert(Optional ByRef Array_1D As Variant _
+ , Optional ByVal Before As Variant _
+ , ParamArray pvArgs() As Variant _
+ ) As Variant
+&apos;&apos;&apos; Insert before the index Before of the input array the items listed as arguments
+&apos;&apos;&apos; Arguments are inserted blindly
+&apos;&apos;&apos; each of them might be a scalar of any type or a subarray
+&apos;&apos;&apos; Args
+&apos;&apos;&apos; Array_1D: the pre-existing array, may be empty
+&apos;&apos;&apos; Before: the index before which to insert; must be in the interval [LBound, UBound + 1]
+&apos;&apos;&apos; pvArgs: a list of items to Insert inside Array_1D
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; the new rxtended array. Its LBound is identical to that of Array_1D
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; ARRAYINSERTERROR
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Array.Insert(Array(1, 2, 3), 2, 4, 5) returns (1, 2, 4, 5, 3)
+
+Dim vInsert As Variant &apos; Return value
+Dim lNbArgs As Long &apos; Number of elements to Insert
+Dim lMin As Long &apos; LBound of input array
+Dim lMax As Long &apos; UBound of input array
+Dim i As Long
+Const cstThisSub = &quot;Array.Insert&quot;
+Const cstSubArgs = &quot;Array_1D, Before, arg0[, arg1] ...&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vInsert = Array()
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
+ If Not SF_Utils._Validate(Before, &quot;Before&quot;, V_NUMERIC) Then GoTo Finally
+ If Before &lt; LBound(Array_1D) Or Before &gt; UBound(Array_1D) + 1 Then GoTo CatchArgument
+ End If
+
+Try:
+ lNbArgs = UBound(pvArgs) + 1 &apos; pvArgs is always zero-based
+ lMin = LBound(Array_1D) &apos; = LBound(vInsert)
+ lMax = UBound(Array_1D) &apos; &lt;&gt; UBound(vInsert)
+ If lNbArgs &gt; 0 Then
+ ReDim vInsert(lMin To lMax + lNbArgs)
+ For i = lMin To UBound(vInsert)
+ If i &lt; Before Then
+ vInsert(i) = Array_1D(i)
+ ElseIf i &lt; Before + lNbArgs Then
+ vInsert(i) = pvArgs(i - Before)
+ Else
+ vInsert(i) = Array_1D(i - lNbArgs)
+ End If
+ Next i
+ Else
+ vInsert() = Array_1D()
+ End If
+
+Finally:
+ Insert = vInsert()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchArgument:
+ &apos;TODO SF_Exception.RaiseFatal(ARRAYINSERTERROR, cstThisSub)
+ MsgBox &quot;INVALID ARGUMENT VALUE !!&quot;
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.Insert
+
+REM -----------------------------------------------------------------------------
+Public Function InsertSorted(Optional ByRef Array_1D As Variant _
+ , Optional ByVal Item As Variant _
+ , Optional ByVal SortOrder As Variant _
+ , Optional ByVal CaseSensitive As Variant _
+ ) As Variant
+&apos;&apos;&apos; Insert in a sorted array a new item on its place
+&apos;&apos;&apos; the array must be filled homogeneously, i.e. all items must be of the same type
+&apos;&apos;&apos; Empty and Null items are forbidden
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array_1D: the array to sort
+&apos;&apos;&apos; Item: the scalar value to insert, same type as the existing array items
+&apos;&apos;&apos; SortOrder: &quot;ASC&quot; (default) or &quot;DESC&quot;
+&apos;&apos;&apos; CaseSensitive: Default = False
+&apos;&apos;&apos; Returns: the extended sorted array with same LBound as input array
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; InsertSorted(Array(&quot;A&quot;, &quot;C&quot;, &quot;a&quot;, &quot;b&quot;), &quot;B&quot;, CaseSensitive := True) returns (&quot;A&quot;, &quot;B&quot;, &quot;C&quot;, &quot;a&quot;, &quot;b&quot;)
+
+Dim vSorted() As Variant &apos; Return value
+Dim iType As Integer &apos; VarType of elements in input array
+Dim lMin As Long &apos; LBound of input array
+Dim lMax As Long &apos; UBound of input array
+Dim lIndex As Long &apos; Place where to insert new item
+Const cstThisSub = &quot;Array.InsertSorted&quot;
+Const cstSubArgs = &quot;Array_1D, Item, [SortOrder=&quot;&quot;ASC&quot;&quot;|&quot;&quot;DESC&quot;&quot;], [CaseSensitive=False]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vSorted = Array()
+
+Check:
+ If IsMissing(SortOrder) Or IsEmpty(SortOrder) Then SortOrder = &quot;ASC&quot;
+ If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1, 0) Then GoTo Finally
+ If LBound(Array_1D) &lt;= UBound(Array_1D) Then
+ iType = SF_Utils._VarTypeExt(Array_1D(LBound(Array_1D)))
+ If Not SF_Utils._Validate(Item, &quot;Item&quot;, iType) Then GoTo Finally
+ Else
+ If Not SF_Utils._Validate(Item, &quot;Item&quot;, Array(V_STRING, V_DATE, V_NUMERIC)) Then GoTo Finally
+ End If
+ If Not SF_Utils._Validate(SortOrder, &quot;SortOrder&quot;, V_STRING, Array(&quot;ASC&quot;,&quot;DESC&quot;)) Then GoTo Finally
+ If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
+ End If
+
+Try:
+ lMin = LBound(Array_1D)
+ lMax = UBound(Array_1D)
+ lIndex = SF_Array._FindItem(Array_1D, Item, CaseSensitive, SortOrder)(1)
+ vSorted = SF_Array.Insert(Array_1D, lIndex, Item)
+
+Finally:
+ InsertSorted = vSorted()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.InsertSorted
+
+REM -----------------------------------------------------------------------------
+Public Function Intersection(Optional ByRef Array1_1D As Variant _
+ , Optional ByRef Array2_1D As Variant _
+ , Optional ByVal CaseSensitive As Variant _
+ ) As Variant
+&apos;&apos;&apos; Build a set being the intersection of the two input arrays, i.e. items are contained in both arrays
+&apos;&apos;&apos; both input arrays must be filled homogeneously, i.e. all items must be of the same type
+&apos;&apos;&apos; Empty and Null items are forbidden
+&apos;&apos;&apos; The comparison between strings is case sensitive or not
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array1_1D: a 1st input array
+&apos;&apos;&apos; Array2_1D: a 2nd input array
+&apos;&apos;&apos; CaseSensitive: default = False
+&apos;&apos;&apos; Returns: a zero-based array containing unique items stored in both input arrays
+&apos;&apos;&apos; The output array is sorted in ascending order
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; Intersection(Array(&quot;A&quot;, &quot;C&quot;, &quot;A&quot;, &quot;b&quot;, &quot;B&quot;), Array(&quot;C&quot;, &quot;Z&quot;, &quot;b&quot;), True) returns (&quot;C&quot;, &quot;b&quot;)
+
+Dim vIntersection() As Variant &apos; Return value
+Dim vSorted() As Variant &apos; The shortest input array after sort
+Dim iType As Integer &apos; VarType of elements in input arrays
+Dim lMin1 As Long &apos; LBound of 1st input array
+Dim lMax1 As Long &apos; UBound of 1st input array
+Dim lMin2 As Long &apos; LBound of 2nd input array
+Dim lMax2 As Long &apos; UBound of 2nd input array
+Dim lMin As Long &apos; LBound of unsorted array
+Dim lMax As Long &apos; UBound of unsorted array
+Dim iShortest As Integer &apos; 1 or 2 depending on shortest input array
+Dim lSize As Long &apos; Number of Intersection items
+Dim vItem As Variant &apos; One single item in the array
+Dim i As Long
+Const cstThisSub = &quot;Array.Intersection&quot;
+Const cstSubArgs = &quot;Array1_1D, Array2_1D, [CaseSensitive=False]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vIntersection = Array()
+
+Check:
+ If IsMissing(CaseSensitive) Then CaseSensitive = False
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array1_1D, &quot;Array1_1D&quot;, 1, 0, True) Then GoTo Finally
+ iType = SF_Utils._VarTypeExt(Array1_1D(LBound(Array1_1D)))
+ If Not SF_Utils._ValidateArray(Array2_1D, &quot;Array2_1D&quot;, 1, iType, True) Then GoTo Finally
+ If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
+ End If
+
+Try:
+ lMin1 = LBound(Array1_1D) : lMax1 = UBound(Array1_1D)
+ lMin2 = LBound(Array2_1D) : lMax2 = UBound(Array2_1D)
+
+ &apos; If one of both arrays is empty, do nothing
+ If lMax1 &gt;= lMin1 And lMax2 &gt;= lMin2 Then
+
+ &apos; First sort the shortest array
+ If lMax1 - lMin1 &lt;= lMax2 - lMin2 Then
+ iShortest = 1
+ vSorted = SF_Array.Sort(Array1_1D, &quot;ASC&quot;, CaseSensitive)
+ lMin = lMin2 : lMax = lMax2 &apos; Bounds of unsorted array
+ Else
+ iShortest = 2
+ vSorted = SF_Array.Sort(Array2_1D, &quot;ASC&quot;, CaseSensitive)
+ lMin = lMin1 : lMax = lMax1 &apos; Bounds of unsorted array
+ End If
+
+ &apos; Resize the output array to the size of the shortest array
+ ReDim vIntersection(0 To (lMax - lMin))
+ lSize = -1
+
+ &apos; Fill vIntersection one by one only with items present in both sets
+ For i = lMin To lMax
+ If iShortest = 1 Then vItem = Array2_1D(i) Else vItem = Array1_1D(i) &apos; Pick in unsorted array
+ If SF_Array.Contains(vSorted, vItem, CaseSensitive, &quot;ASC&quot;) Then
+ lSize = lSize + 1
+ vIntersection(lSize) = vItem
+ End If
+ Next i
+
+ &apos; Remove unfilled entries and duplicates
+ If lSize &gt;= 0 Then
+ ReDim Preserve vIntersection(0 To lSize)
+ vIntersection() = SF_Array.Unique(vIntersection, CaseSensitive)
+ Else
+ vIntersection = Array()
+ End If
+ End If
+
+Finally:
+ Intersection = vIntersection()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.Intersection
+
+REM -----------------------------------------------------------------------------
+Public Function Join2D(Optional ByRef Array_2D As Variant _
+ , Optional ByVal ColumnDelimiter As Variant _
+ , Optional ByVal RowDelimiter As Variant _
+ , Optional ByVal Quote As Variant _
+ ) As String
+&apos;&apos;&apos; Join a two-dimensional array with two delimiters, one for columns, one for rows
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array_2D: each item must be either a String, a number, a Date or a Boolean
+&apos;&apos;&apos; ColumnDelimiter: delimits each column (default = Tab/Chr(9))
+&apos;&apos;&apos; RowDelimiter: delimits each row (default = LineFeed/Chr(10))
+&apos;&apos;&apos; Quote: if True, protect strings with double quotes (default = False)
+&apos;&apos;&apos; Return:
+&apos;&apos;&apos; A string after conversion of numbers and dates
+&apos;&apos;&apos; Invalid items are replaced by a zero-length string
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; | 1, 2, &quot;A&quot;, [2020-02-29], 5 |
+&apos;&apos;&apos; SF_Array.Join_2D( | 6, 7, &quot;this is a string&quot;, 9, 10 | , &quot;,&quot;, &quot;/&quot;)
+&apos;&apos;&apos; &apos; &quot;1,2,A,2020-02-29 00:00:00,5/6,7,this is a string,9,10&quot;
+
+Dim sJoin As String &apos; The return value
+Dim sItem As String &apos; The string representation of a single item
+Dim vItem As Variant &apos; Single item
+Dim lMin1 As Long &apos; LBound1 of input array
+Dim lMax1 As Long &apos; UBound1 of input array
+Dim lMin2 As Long &apos; LBound2 of input array
+Dim lMax2 As Long &apos; UBound2 of input array
+Dim i As Long
+Dim j As Long
+Const cstThisSub = &quot;Array.Join2D&quot;
+Const cstSubArgs = &quot;Array_2D, [ColumnDelimiter=Chr(9)], [RowDelimiter=Chr(10)], [Quote=False]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sJoin = &quot;&quot;
+
+Check:
+ If IsMissing(ColumnDelimiter) Or IsEmpty(ColumnDelimiter) Then ColumnDelimiter = Chr(9)
+ If IsMissing(RowDelimiter) Or IsEmpty(RowDelimiter) Then RowDelimiter = Chr(10)
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally
+ If Not SF_Utils._Validate(ColumnDelimiter, &quot;ColumnDelimiter&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(RowDelimiter, &quot;RowDelimiter&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(Quote, &quot;Quote&quot;, V_BOOLEAN) Then GoTo Finally
+ End If
+
+Try:
+ lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
+ lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
+ If lMin1 &lt;= lMax1 Then
+ For i = lMin1 To lMax1
+ For j = lMin2 To lMax2
+ vItem = Array_2D(i, j)
+ Select Case SF_Utils._VarTypeExt(vItem)
+ Case V_STRING : If Quote Then sItem = SF_String.Quote(vItem) Else sItem = vItem
+ Case V_NUMERIC, V_DATE : sItem = SF_Utils._Repr(vItem)
+ Case V_BOOLEAN : sItem = Iif(vItem, &quot;True&quot;, &quot;False&quot;) &apos;TODO: L10N
+ Case Else : sItem = &quot;&quot;
+ End Select
+ sJoin = sJoin &amp; sItem &amp; Iif(j &lt; lMax2, ColumnDelimiter, &quot;&quot;)
+ Next j
+ sJoin = sJoin &amp; Iif(i &lt; lMax1, RowDelimiter, &quot;&quot;)
+ Next i
+ End If
+
+Finally:
+ Join2D = sJoin
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.Join2D
+
+REM -----------------------------------------------------------------------------
+Public Function Methods() As Variant
+&apos;&apos;&apos; Return the list of public methods of the Array service as an array
+
+ Methods = Array( _
+ &quot;Append&quot; _
+ , &quot;AppendColumn&quot; _
+ , &quot;AppendRow&quot; _
+ , &quot;Contains&quot; _
+ , &quot;ConvertToDictionary&quot; _
+ , &quot;CountDims&quot; _
+ , &quot;Difference&quot; _
+ , &quot;ExportToTextFile&quot; _
+ , &quot;ExtractColumn&quot; _
+ , &quot;ExtractRow&quot; _
+ , &quot;Flatten&quot; _
+ , &quot;ImportFromCSVFile&quot; _
+ , &quot;IndexOf&quot; _
+ , &quot;Insert&quot; _
+ , &quot;InsertSorted&quot; _
+ , &quot;Intersection&quot; _
+ , &quot;Join2D&quot; _
+ , &quot;Prepend&quot; _
+ , &quot;PrependColumn&quot; _
+ , &quot;PrependRow&quot; _
+ , &quot;RangeInit&quot; _
+ , &quot;Reverse&quot; _
+ , &quot;Shuffle&quot; _
+ , &quot;Sort&quot; _
+ , &quot;SortColumns&quot; _
+ , &quot;SortRows&quot; _
+ , &quot;Transpose&quot; _
+ , &quot;TrimArray&quot; _
+ , &quot;Union&quot; _
+ , &quot;Unique&quot; _
+ )
+
+End Function &apos; ScriptForge.SF_Array.Methods
+
+REM -----------------------------------------------------------------------------
+Public Function Prepend(Optional ByRef Array_1D As Variant _
+ , ParamArray pvArgs() As Variant _
+ ) As Variant
+&apos;&apos;&apos; Prepend at the beginning of the input array the items listed as arguments
+&apos;&apos;&apos; Arguments are Prepended blindly
+&apos;&apos;&apos; each of them might be a scalar of any type or a subarray
+&apos;&apos;&apos; Args
+&apos;&apos;&apos; Array_1D: the pre-existing array, may be empty
+&apos;&apos;&apos; pvArgs: a list of items to Prepend to Array_1D
+&apos;&apos;&apos; Return: the new rxtended array. Its LBound is identical to that of Array_1D
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Array.Prepend(Array(1, 2, 3), 4, 5) returns (4, 5, 1, 2, 3)
+
+Dim vPrepend As Variant &apos; Return value
+Dim lNbArgs As Long &apos; Number of elements to Prepend
+Dim lMin As Long &apos; LBound of input array
+Dim lMax As Long &apos; UBound of input array
+Dim i As Long
+Const cstThisSub = &quot;Array.Prepend&quot;
+Const cstSubArgs = &quot;Array_1D, arg0[, arg1] ...&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vPrepend = Array()
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
+ End If
+
+Try:
+ lNbArgs = UBound(pvArgs) + 1 &apos; pvArgs is always zero-based
+ lMin = LBound(Array_1D) &apos; = LBound(vPrepend)
+ lMax = UBound(Array_1D) &apos; &lt;&gt; UBound(vPrepend)
+ If lMax &lt; LBound(Array_1D) And lNbArgs &gt; 0 Then &apos; Initial array is empty
+ ReDim vPrepend(0 To lNbArgs - 1)
+ Else
+ ReDim vPrepend(lMin To lMax + lNbArgs)
+ End If
+ For i = lMin To UBound(vPrepend)
+ If i &lt; lMin + lNbArgs Then vPrepend(i) = pvArgs(i - lMin) Else vPrepend(i) = Array_1D(i - lNbArgs)
+ Next i
+
+Finally:
+ Prepend = vPrepend
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.Prepend
+
+REM -----------------------------------------------------------------------------
+Public Function PrependColumn(Optional ByRef Array_2D As Variant _
+ , Optional ByRef Column As Variant _
+ ) As Variant
+&apos;&apos;&apos; PrependColumn prepends to the left side of a 2D array a new Column
+&apos;&apos;&apos; Args
+&apos;&apos;&apos; Array_2D: the pre-existing array, may be empty
+&apos;&apos;&apos; If the array has 1 dimension, it is considered as the last Column of the resulting 2D array
+&apos;&apos;&apos; Column: a 1D array with as many items as there are rows in Array_2D
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; the new rxtended array. Its LBounds are identical to that of Array_2D
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; ARRAYINSERTERROR
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Array.PrependColumn(Array(1, 2, 3), Array(4, 5, 6)) returns ((4, 1), (5, 2), (6, 3))
+&apos;&apos;&apos; x = SF_Array.PrependColumn(Array(), Array(1, 2, 3)) =&gt; ∀ i ∈ {0 ≤ i ≤ 2} : x(0, i) ≡ i
+
+Dim vPrependColumn As Variant &apos; Return value
+Dim iDims As Integer &apos; Dimensions of Array_2D
+Dim lMin1 As Long &apos; LBound1 of input array
+Dim lMax1 As Long &apos; UBound1 of input array
+Dim lMin2 As Long &apos; LBound2 of input array
+Dim lMax2 As Long &apos; UBound2 of input array
+Dim lMin As Long &apos; LBound of Column array
+Dim lMax As Long &apos; UBound of Column array
+Dim i As Long
+Dim j As Long
+Const cstThisSub = &quot;Array.PrependColumn&quot;
+Const cstSubArgs = &quot;Array_2D, Column&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vPrependColumn = Array()
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;) Then GoTo Finally &apos;Initial check: not missing and array
+ If Not SF_Utils._ValidateArray(Column, &quot;Column&quot;, 1) Then GoTo Finally
+ End If
+ iDims = SF_Array.CountDims(Array_2D)
+ If iDims &gt; 2 Then
+ If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally &apos;2nd check to manage error
+ End If
+
+Try:
+ lMin = LBound(Column)
+ lMax = UBound(Column)
+
+ &apos; Compute future dimensions of output array
+ Select Case iDims
+ Case 0 : lMin1 = lMin : lMax1 = lMax
+ lMin2 = 0 : lMax2 = -1
+ Case 1 : lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
+ lMin2 = 0 : lMax2 = 0
+ Case 2 : lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
+ lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
+ End Select
+ If iDims &gt; 0 And lMax - lMin &lt;&gt; lMax1 - lMin1 Then GoTo CatchColumn
+ ReDim vPrependColumn(lMin1 To lMax1, lMin2 To lMax2 + 1)
+
+ &apos; Copy input array to output array
+ For i = lMin1 To lMax1
+ For j = lMin2 + 1 To lMax2 + 1
+ If iDims = 2 Then vPrependColumn(i, j) = Array_2D(i, j - 1) Else vPrependColumn(i, j) = Array_2D(i)
+ Next j
+ Next i
+ &apos; Copy new Column
+ For i = lMin1 To lMax1
+ vPrependColumn(i, lMin2) = Column(i)
+ Next i
+
+Finally:
+ PrependColumn = vPrependColumn()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchColumn:
+ SF_Exception.RaiseFatal(ARRAYINSERTERROR, &quot;Column&quot;, SF_Array._Repr(Array_2D), SF_Utils._Repr(Column, MAXREPR))
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.PrependColumn
+
+REM -----------------------------------------------------------------------------
+Public Function PrependRow(Optional ByRef Array_2D As Variant _
+ , Optional ByRef Row As Variant _
+ ) As Variant
+&apos;&apos;&apos; PrependRow prepends on top of a 2D array a new row
+&apos;&apos;&apos; Args
+&apos;&apos;&apos; Array_2D: the pre-existing array, may be empty
+&apos;&apos;&apos; If the array has 1 dimension, it is considered as the last row of the resulting 2D array
+&apos;&apos;&apos; Row: a 1D array with as many items as there are columns in Array_2D
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; the new rxtended array. Its LBounds are identical to that of Array_2D
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; ARRAYINSERTERROR
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Array.PrependRow(Array(1, 2, 3), Array(4, 5, 6)) returns ((4, 5, 6), (1, 2, 3))
+&apos;&apos;&apos; x = SF_Array.PrependColumn(Array(), Array(1, 2, 3) =&gt; ∀ i ∈ {0 ≤ i ≤ 2} : x(i, 0) ≡ i
+
+Dim vPrependRow As Variant &apos; Return value
+Dim iDims As Integer &apos; Dimensions of Array_2D
+Dim lMin1 As Long &apos; LBound1 of input array
+Dim lMax1 As Long &apos; UBound1 of input array
+Dim lMin2 As Long &apos; LBound2 of input array
+Dim lMax2 As Long &apos; UBound2 of input array
+Dim lMin As Long &apos; LBound of row array
+Dim lMax As Long &apos; UBound of row array
+Dim i As Long
+Dim j As Long
+Const cstThisSub = &quot;Array.PrependRow&quot;
+Const cstSubArgs = &quot;Array_2D, Row&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vPrependRow = Array()
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;) Then GoTo Finally &apos;Initial check: not missing and array
+ If Not SF_Utils._ValidateArray(Row, &quot;Row&quot;, 1) Then GoTo Finally
+ End If
+ iDims = SF_Array.CountDims(Array_2D)
+ If iDims &gt; 2 Then
+ If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally &apos;2nd check to manage error
+ End If
+
+Try:
+ lMin = LBound(Row)
+ lMax = UBound(Row)
+
+ &apos; Compute future dimensions of output array
+ Select Case iDims
+ Case 0 : lMin1 = 0 : lMax1 = -1
+ lMin2 = lMin : lMax2 = lMax
+ Case 1 : lMin1 = 0 : lMax1 = 0
+ lMin2 = LBound(Array_2D, 1) : lMax2 = UBound(Array_2D, 1)
+ Case 2 : lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
+ lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
+ End Select
+ If iDims &gt; 0 And lMax - lMin &lt;&gt; lMax2 - lMin2 Then GoTo CatchRow
+ ReDim vPrependRow(lMin1 To lMax1 + 1, lMin2 To lMax2)
+
+ &apos; Copy input array to output array
+ For i = lMin1 + 1 To lMax1 + 1
+ For j = lMin2 To lMax2
+ If iDims = 2 Then vPrependRow(i, j) = Array_2D(i - 1, j) Else vPrependRow(i, j) = Array_2D(j)
+ Next j
+ Next i
+ &apos; Copy new row
+ For j = lMin2 To lMax2
+ vPrependRow(lMin1, j) = Row(j)
+ Next j
+
+Finally:
+ PrependRow = vPrependRow()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchRow:
+ SF_Exception.RaiseFatal(ARRAYINSERTERROR, &quot;Row&quot;, SF_Array._Repr(Array_2D), SF_Utils._Repr(Row, MAXREPR))
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.PrependRow
+
+REM -----------------------------------------------------------------------------
+Public Function Properties() As Variant
+&apos;&apos;&apos; Return the list or properties as an array
+
+ Properties = Array( _
+ )
+
+End Function &apos; ScriptForge.SF_Array.Properties
+
+REM -----------------------------------------------------------------------------
+Public Function RangeInit(Optional ByVal From As Variant _
+ , Optional ByVal UpTo As Variant _
+ , Optional ByVal ByStep As Variant _
+ ) As Variant
+&apos;&apos;&apos; Initialize a new zero-based array with numeric values
+&apos;&apos;&apos; Args: all numeric
+&apos;&apos;&apos; From: value of first item
+&apos;&apos;&apos; UpTo: last item should not exceed UpTo
+&apos;&apos;&apos; ByStep: difference between 2 successive items
+&apos;&apos;&apos; Return: the new array
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; ARRAYSEQUENCEERROR Wrong arguments, f.i. UpTo &lt; From with ByStep &gt; 0
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Array.RangeInit(10, 1, -1) returns (10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
+
+Dim lIndex As Long &apos; Index of array
+Dim lSize As Long &apos; UBound of resulting array
+Dim vCurrentItem As Variant &apos; Last stored item
+Dim vArray() &apos; The return value
+Const cstThisSub = &quot;Array.RangeInit&quot;
+Const cstSubArgs = &quot;From, UpTo, [ByStep = 1]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vArray = Array()
+
+Check:
+ If IsMissing(ByStep) Or IsEmpty(ByStep) Then ByStep = 1
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(From, &quot;From&quot;, V_NUMERIC) Then GoTo Finally
+ If Not SF_Utils._Validate(UpTo, &quot;UpTo&quot;, V_NUMERIC) Then GoTo Finally
+ If Not SF_Utils._Validate(ByStep, &quot;ByStep&quot;, V_NUMERIC) Then GoTo Finally
+ End If
+ If (From &lt; UpTo And ByStep &lt;= 0) Or (From &gt; UpTo And ByStep &gt;= 0) Then GoTo CatchSequence
+
+Try:
+ lSize = CLng(Abs((UpTo - From) / ByStep))
+ ReDim vArray(0 To lSize)
+ For lIndex = 0 To lSize
+ vArray(lIndex) = From + lIndex * ByStep
+ Next lIndex
+
+Finally:
+ RangeInit = vArray
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchSequence:
+ SF_Exception.RaiseFatal(ARRAYSEQUENCEERROR, From, UpTo, ByStep)
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.RangeInit
+
+REM -----------------------------------------------------------------------------
+Public Function Reverse(Optional ByRef Array_1D As Variant) As Variant
+&apos;&apos;&apos; Return the reversed 1D input array
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array_1D: the array to reverse
+&apos;&apos;&apos; Returns: the reversed array
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Array.Reverse(Array(1, 2, 3, 4)) returns (4, 3, 2, 1)
+
+Dim vReverse() As Variant &apos; Return value
+Dim lHalf As Long &apos; Middle of array
+Dim lMin As Long &apos; LBound of input array
+Dim lMax As Long &apos; UBound of input array
+Dim i As Long, j As Long
+Const cstThisSub = &quot;Array.Reverse&quot;
+Const cstSubArgs = &quot;Array_1D&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vReverse = Array()
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
+ End If
+
+Try:
+ lMin = LBound(Array_1D)
+ lMax = UBound(Array_1D)
+ ReDim vReverse(lMin To lMax)
+ lHalf = Int((lMax + lMin) / 2)
+ j = lMax
+ For i = lMin To lHalf
+ vReverse(i) = Array_1D(j)
+ vReverse(j) = Array_1D(i)
+ j = j - 1
+ Next i
+ &apos; Odd number of items
+ If IsEmpty(vReverse(lHalf + 1)) Then vReverse(lHalf + 1) = Array_1D(lHalf + 1)
+
+Finally:
+ Reverse = vReverse()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.Reverse
+
+REM -----------------------------------------------------------------------------
+Public Function SetProperty(Optional ByVal PropertyName As Variant _
+ , Optional ByRef Value As Variant _
+ ) As Boolean
+&apos;&apos;&apos; Set a new value to the given property
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; PropertyName: the name of the property as a string
+&apos;&apos;&apos; Value: its new value
+&apos;&apos;&apos; Exceptions
+&apos;&apos;&apos; ARGUMENTERROR The property does not exist
+
+Const cstThisSub = &quot;Array.SetProperty&quot;
+Const cstSubArgs = &quot;PropertyName, Value&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ SetProperty = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
+ End If
+
+Try:
+ Select Case UCase(PropertyName)
+ Case Else
+ End Select
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.SetProperty
+
+REM -----------------------------------------------------------------------------
+Public Function Shuffle(Optional ByRef Array_1D As Variant) As Variant
+&apos;&apos;&apos; Returns a random permutation of a 1D array
+&apos;&apos;&apos; https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array_1D: the array to shuffle
+&apos;&apos;&apos; Returns: the shuffled array
+
+Dim vShuffle() As Variant &apos; Return value
+Dim vSwapValue As Variant &apos; Intermediate value during swap
+Dim lMin As Long &apos; LBound of Array_1D
+Dim lCurrentIndex As Long &apos; Decremented from UBount to LBound
+Dim lRandomIndex As Long &apos; Random between LBound and lCurrentIndex
+Dim i As Long
+Const cstThisSub = &quot;Array.Shuffle&quot;
+Const cstSubArgs = &quot;Array_1D&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vShuffle = Array()
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
+ End If
+
+Try:
+ lMin = LBound(Array_1D)
+ lCurrentIndex = UBound(array_1D)
+ &apos; Initialize the output array
+ ReDim vShuffle(lMin To lCurrentIndex)
+ For i = lMin To lCurrentIndex
+ vShuffle(i) = Array_1D(i)
+ Next i
+ &apos; Now ... shuffle !
+ Do While lCurrentIndex &gt; lMin
+ lRandomIndex = Int(Rnd * (lCurrentIndex - lMin)) + lMin
+ vSwapValue = vShuffle(lCurrentIndex)
+ vShuffle(lCurrentIndex) = vShuffle(lRandomIndex)
+ vShuffle(lRandomIndex) = vSwapValue
+ lCurrentIndex = lCurrentIndex - 1
+ Loop
+
+Finally:
+ Shuffle = vShuffle()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.Shuffle
+
+REM -----------------------------------------------------------------------------
+Public Function Slice(Optional ByRef Array_1D As Variant _
+ , Optional ByVal From As Variant _
+ , Optional ByVal UpTo As Variant _
+ ) As Variant
+&apos;&apos;&apos; Returns a subset of a 1D array
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array_1D: the array to slice
+&apos;&apos;&apos; From: the lower index of the subarray to extract (included)
+&apos;&apos;&apos; UpTo: the upper index of the subarray to extract (included). Default = the last item of Array_1D
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The selected subarray with the same LBound as the input array.
+&apos;&apos;&apos; If UpTo &lt; From then the returned array is empty
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; ARRAYINDEX2ERROR Wrong values for From and/or UpTo
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; SF_Array.Slice(Array(1, 2, 3, 4, 5), 1, 3) returns (2, 3, 4)
+
+Dim vSlice() As Variant &apos; Return value
+Dim lMin As Long &apos; LBound of Array_1D
+Dim lIndex As Long &apos; Current index in output array
+Dim i As Long
+Const cstThisSub = &quot;Array.Slice&quot;
+Const cstSubArgs = &quot;Array_1D, From, [UpTo = UBound(Array_1D)]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vSlice = Array()
+
+Check:
+ If IsMissing(UpTo) Or IsEmpty(UpTo) Then UpTo = -1
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
+ If Not SF_Utils._Validate(From, &quot;From&quot;, V_NUMERIC) Then GoTo Finally
+ If Not SF_Utils._Validate(UpTo, &quot;UpTo&quot;, V_NUMERIC) Then GoTo Finally
+ End If
+ If UpTo = -1 Then UpTo = UBound(Array_1D)
+ If From &lt; LBound(Array_1D) Or From &gt; UBound(Array_1D) _
+ Or From &gt; UpTo Or UpTo &gt; UBound(Array_1D) Then GoTo CatchIndex
+
+Try:
+ If UpTo &gt;= From Then
+ lMin = LBound(Array_1D)
+ &apos; Initialize the output array
+ ReDim vSlice(lMin To lMin + UpTo - From)
+ lIndex = lMin - 1
+ For i = From To UpTo
+ lIndex = lIndex + 1
+ vSlice(lIndex) = Array_1D(i)
+ Next i
+ End If
+
+Finally:
+ Slice = vSlice()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchIndex:
+ SF_Exception.RaiseFatal(ARRAYINDEX2ERROR, SF_Array._Repr(Array_1D), From, UpTo)
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.Slice
+
+REM -----------------------------------------------------------------------------
+Public Function Sort(Optional ByRef Array_1D As Variant _
+ , Optional ByVal SortOrder As Variant _
+ , Optional ByVal CaseSensitive As Variant _
+ ) As Variant
+&apos;&apos;&apos; Sort a 1D array in ascending or descending order. String comparisons can be case-sensitive or not
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array_1D: the array to sort
+&apos;&apos;&apos; must be filled homogeneously by either strings, dates or numbers
+&apos;&apos;&apos; Null and Empty values are allowed
+&apos;&apos;&apos; SortOrder: &quot;ASC&quot; (default) or &quot;DESC&quot;
+&apos;&apos;&apos; CaseSensitive: Default = False
+&apos;&apos;&apos; Returns: the sorted array
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; Sort(Array(&quot;a&quot;, &quot;A&quot;, &quot;b&quot;, &quot;B&quot;, &quot;C&quot;), CaseSensitive := True) returns (&quot;A&quot;, &quot;B&quot;, &quot;C&quot;, &quot;a&quot;, &quot;b&quot;)
+
+Dim vSort() As Variant &apos; Return value
+Dim vIndexes() As Variant &apos; Indexes of sorted items
+Dim lMin As Long &apos; LBound of input array
+Dim lMax As Long &apos; UBound of input array
+Dim i As Long
+Const cstThisSub = &quot;Array.Sort&quot;
+Const cstSubArgs = &quot;Array_1D, [SortOrder=&quot;&quot;&quot;&quot;|&quot;&quot;ASC&quot;&quot;|&quot;&quot;DESC&quot;&quot;], [CaseSensitive=False]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vSort = Array()
+
+Check:
+ If IsMissing(SortOrder) Or IsEmpty(SortOrder) Then SortOrder = &quot;ASC&quot;
+ If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1, 0) Then GoTo Finally
+ If Not SF_Utils._Validate(SortOrder, &quot;SortOrder&quot;, V_STRING, Array(&quot;ASC&quot;,&quot;DESC&quot;)) Then GoTo Finally
+ If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
+ End If
+
+Try:
+ lMin = LBound(Array_1D)
+ lMax = UBound(Array_1D)
+ vIndexes() = SF_Array._HeapSort(Array_1D, ( SortOrder = &quot;ASC&quot; ), CaseSensitive)
+
+ &apos; Load output array
+ ReDim vSort(lMin To lMax)
+ For i = lMin To lMax
+ vSort(i) = Array_1D(vIndexes(i))
+ Next i
+
+Finally:
+ Sort = vSort()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.Sort
+
+REM -----------------------------------------------------------------------------
+Public Function SortColumns(Optional ByRef Array_2D As Variant _
+ , Optional ByVal RowIndex As Variant _
+ , Optional ByVal SortOrder As Variant _
+ , Optional ByVal CaseSensitive As Variant _
+ ) As Variant
+&apos;&apos;&apos; Returns a permutation of the columns of a 2D array, sorted on the values of a given row
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array_2D: the input array
+&apos;&apos;&apos; RowIndex: the index of the row to sort the columns on
+&apos;&apos;&apos; the row must be filled homogeneously by either strings, dates or numbers
+&apos;&apos;&apos; Null and Empty values are allowed
+&apos;&apos;&apos; SortOrder: &quot;ASC&quot; (default) or &quot;DESC&quot;
+&apos;&apos;&apos; CaseSensitive: Default = False
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; the array with permuted columns, LBounds and UBounds are unchanged
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; ARRAYINDEXERROR
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; | 5, 7, 3 | | 7, 5, 3 |
+&apos;&apos;&apos; SF_Array.SortColumns( | 1, 9, 5 |, 2, &quot;ASC&quot;) returns | 9, 1, 5 |
+&apos;&apos;&apos; | 6, 1, 8 | | 1, 6, 8 |
+
+Dim vSort() As Variant &apos; Return value
+Dim vRow() As Variant &apos; The row on which to sort the array
+Dim vIndexes() As Variant &apos; Indexes of sorted row
+Dim lMin1 As Long &apos; LBound1 of input array
+Dim lMax1 As Long &apos; UBound1 of input array
+Dim lMin2 As Long &apos; LBound2 of input array
+Dim lMax2 As Long &apos; UBound2 of input array
+Dim i As Long, j As Long
+Const cstThisSub = &quot;Array.SortColumn&quot;
+Const cstSubArgs = &quot;Array_2D, RowIndex, [SortOrder=&quot;&quot;&quot;&quot;|&quot;&quot;ASC&quot;&quot;|&quot;&quot;DESC&quot;&quot;], [CaseSensitive=False]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vSort = Array()
+
+Check:
+ If IsMissing(SortOrder) Or IsEmpty(SortOrder) Then SortOrder = &quot;ASC&quot;
+ If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally
+ If Not SF_Utils._Validate(RowIndex, &quot;RowIndex&quot;, V_NUMERIC) Then GoTo Finally
+ If Not SF_Utils._Validate(SortOrder, &quot;SortOrder&quot;, V_STRING, Array(&quot;ASC&quot;,&quot;DESC&quot;)) Then GoTo Finally
+ If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
+ End If
+
+Try:
+ lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
+ If RowIndex &lt; lMin1 Or RowIndex &gt; lMax1 Then GoTo CatchIndex
+ lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
+
+ &apos; Extract and sort the RowIndex-th row
+ vRow = SF_Array.ExtractRow(Array_2D, RowIndex)
+ If Not SF_Utils._ValidateArray(vRow, &quot;Row #&quot; &amp; CStr(RowIndex), 1, 0) Then GoTo Finally
+ vIndexes() = SF_Array._HeapSort(vRow, ( SortOrder = &quot;ASC&quot; ), CaseSensitive)
+
+ &apos; Load output array
+ ReDim vSort(lMin1 To lMax1, lMin2 To lMax2)
+ For i = lMin1 To lMax1
+ For j = lMin2 To lMax2
+ vSort(i, j) = Array_2D(i, vIndexes(j))
+ Next j
+ Next i
+
+Finally:
+ SortColumns = vSort()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchIndex:
+ &apos;TODO SF_Exception.RaiseFatal(ARRAYINDEXERROR, cstThisSub)
+ MsgBox &quot;INVALID INDEX VALUE !!&quot;
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.SortColumns
+
+REM -----------------------------------------------------------------------------
+Public Function SortRows(Optional ByRef Array_2D As Variant _
+ , Optional ByVal ColumnIndex As Variant _
+ , Optional ByVal SortOrder As Variant _
+ , Optional ByVal CaseSensitive As Variant _
+ ) As Variant
+&apos;&apos;&apos; Returns a permutation of the rows of a 2D array, sorted on the values of a given column
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array_2D: the input array
+&apos;&apos;&apos; ColumnIndex: the index of the column to sort the rows on
+&apos;&apos;&apos; the column must be filled homogeneously by either strings, dates or numbers
+&apos;&apos;&apos; Null and Empty values are allowed
+&apos;&apos;&apos; SortOrder: &quot;ASC&quot; (default) or &quot;DESC&quot;
+&apos;&apos;&apos; CaseSensitive: Default = False
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; the array with permuted Rows, LBounds and UBounds are unchanged
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; ARRAYINDEXERROR
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; | 5, 7, 3 | | 1, 9, 5 |
+&apos;&apos;&apos; SF_Array.SortRows( | 1, 9, 5 |, 0, &quot;ASC&quot;) returns | 5, 7, 3 |
+&apos;&apos;&apos; | 6, 1, 8 | | 6, 1, 8 |
+
+Dim vSort() As Variant &apos; Return value
+Dim vCol() As Variant &apos; The column on which to sort the array
+Dim vIndexes() As Variant &apos; Indexes of sorted row
+Dim lMin1 As Long &apos; LBound1 of input array
+Dim lMax1 As Long &apos; UBound1 of input array
+Dim lMin2 As Long &apos; LBound2 of input array
+Dim lMax2 As Long &apos; UBound2 of input array
+Dim i As Long, j As Long
+Const cstThisSub = &quot;Array.SortRow&quot;
+Const cstSubArgs = &quot;Array_2D, ColumnIndex, [SortOrder=&quot;&quot;&quot;&quot;|&quot;&quot;ASC&quot;&quot;|&quot;&quot;DESC&quot;&quot;], [CaseSensitive=False]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vSort = Array()
+
+Check:
+ If IsMissing(SortOrder) Or IsEmpty(SortOrder) Then SortOrder = &quot;ASC&quot;
+ If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally
+ If Not SF_Utils._Validate(ColumnIndex, &quot;ColumnIndex&quot;, V_NUMERIC) Then GoTo Finally
+ If Not SF_Utils._Validate(SortOrder, &quot;SortOrder&quot;, V_STRING, Array(&quot;ASC&quot;,&quot;DESC&quot;)) Then GoTo Finally
+ If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
+ End If
+
+Try:
+ lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
+ If ColumnIndex &lt; lMin2 Or ColumnIndex &gt; lMax2 Then GoTo CatchIndex
+ lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
+
+ &apos; Extract and sort the ColumnIndex-th column
+ vCol = SF_Array.ExtractColumn(Array_2D, ColumnIndex)
+ If Not SF_Utils._ValidateArray(vCol, &quot;Column #&quot; &amp; CStr(ColumnIndex), 1, 0) Then GoTo Finally
+ vIndexes() = SF_Array._HeapSort(vCol, ( SortOrder = &quot;ASC&quot; ), CaseSensitive)
+
+ &apos; Load output array
+ ReDim vSort(lMin1 To lMax1, lMin2 To lMax2)
+ For i = lMin1 To lMax1
+ For j = lMin2 To lMax2
+ vSort(i, j) = Array_2D(vIndexes(i), j)
+ Next j
+ Next i
+
+Finally:
+ SortRows = vSort()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchIndex:
+ &apos;TODO SF_Exception.RaiseFatal(ARRAYINDEXERROR, cstThisSub)
+ MsgBox &quot;INVALID INDEX VALUE !!&quot;
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.SortRows
+
+REM -----------------------------------------------------------------------------
+Public Function Transpose(Optional ByRef Array_2D As Variant) As Variant
+&apos;&apos;&apos; Swaps rows and columns in a 2D array
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array_2D: the array to transpose
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The transposed array
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; | 1, 2 | | 1, 3, 5 |
+&apos;&apos;&apos; SF_Array.Transpose( | 3, 4 | ) returns | 2, 4, 6 |
+&apos;&apos;&apos; | 5, 6 |
+
+Dim vTranspose As Variant &apos; Return value
+Dim lIndex As Long &apos; vTranspose index
+Dim lMin1 As Long &apos; LBound1 of input array
+Dim lMax1 As Long &apos; UBound1 of input array
+Dim lMin2 As Long &apos; LBound2 of input array
+Dim lMax2 As Long &apos; UBound2 of input array
+Dim i As Long, j As Long
+Const cstThisSub = &quot;Array.Transpose&quot;
+Const cstSubArgs = &quot;Array_2D&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vTranspose = Array()
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally
+ End If
+
+Try:
+ &apos; Resize the output array
+ lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
+ lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
+ If lMin1 &lt;= lMax1 Then
+ ReDim vTranspose(lMin2 To lMax2, lMin1 To lMax1)
+ End If
+
+ &apos; Transpose items
+ For i = lMin1 To lMax1
+ For j = lMin2 To lMax2
+ vTranspose(j, i) = Array_2D(i, j)
+ Next j
+ Next i
+
+Finally:
+ Transpose = vTranspose
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.Transpose
+
+REM -----------------------------------------------------------------------------
+Public Function TrimArray(Optional ByRef Array_1D As Variant) As Variant
+&apos;&apos;&apos; Remove from a 1D array all Null, Empty and zero-length entries
+&apos;&apos;&apos; Strings are trimmed as well
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array_1D: the array to scan
+&apos;&apos;&apos; Return: The trimmed array
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Array.TrimArray(Array(&quot;A&quot;,&quot;B&quot;,Null,&quot; D &quot;)) returns (&quot;A&quot;,&quot;B&quot;,&quot;D&quot;)
+
+Dim vTrimArray As Variant &apos; Return value
+Dim lIndex As Long &apos; vTrimArray index
+Dim lMin As Long &apos; LBound of input array
+Dim lMax As Long &apos; UBound of input array
+Dim vItem As Variant &apos; Single array item
+Dim i As Long
+Const cstThisSub = &quot;Array.TrimArray&quot;
+Const cstSubArgs = &quot;Array_1D&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vTrimArray = Array()
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
+ End If
+
+Try:
+ lMin = LBound(Array_1D)
+ lMax = UBound(Array_1D)
+ If lMin &lt;= lMax Then
+ ReDim vTrimArray(lMin To lMax)
+ End If
+ lIndex = lMin - 1
+
+ &apos; Load only valid items from Array_1D to vTrimArray
+ For i = lMin To lMax
+ vItem = Array_1D(i)
+ Select Case VarType(vItem)
+ Case V_EMPTY
+ Case V_NULL : vItem = Empty
+ Case V_STRING
+ vItem = Trim(vItem)
+ If Len(vItem) = 0 Then vItem = Empty
+ Case Else
+ End Select
+ If Not IsEmpty(vItem) Then
+ lIndex = lIndex + 1
+ vTrimArray(lIndex) = vItem
+ End If
+ Next i
+
+ &apos;Keep valid entries
+ If lMin &lt;= lIndex Then
+ ReDim Preserve vTrimArray(lMin To lIndex)
+ Else
+ vTrimArray = Array()
+ End If
+
+Finally:
+ TrimArray = vTrimArray
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.TrimArray
+
+REM -----------------------------------------------------------------------------
+Public Function Union(Optional ByRef Array1_1D As Variant _
+ , Optional ByRef Array2_1D As Variant _
+ , Optional ByVal CaseSensitive As Variant _
+ ) As Variant
+&apos;&apos;&apos; Build a set being the Union of the two input arrays, i.e. items are contained in any of both arrays
+&apos;&apos;&apos; both input arrays must be filled homogeneously, i.e. all items must be of the same type
+&apos;&apos;&apos; Empty and Null items are forbidden
+&apos;&apos;&apos; The comparison between strings is case sensitive or not
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array1_1D: a 1st input array
+&apos;&apos;&apos; Array2_1D: a 2nd input array
+&apos;&apos;&apos; CaseSensitive: default = False
+&apos;&apos;&apos; Returns: a zero-based array containing unique items stored in any of both input arrays
+&apos;&apos;&apos; The output array is sorted in ascending order
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Array.Union(Array(&quot;A&quot;, &quot;C&quot;, &quot;A&quot;, &quot;b&quot;, &quot;B&quot;), Array(&quot;C&quot;, &quot;Z&quot;, &quot;b&quot;), True) returns (&quot;A&quot;, &quot;B&quot;, &quot;C&quot;, &quot;Z&quot;, &quot;b&quot;)
+
+Dim vUnion() As Variant &apos; Return value
+Dim iType As Integer &apos; VarType of elements in input arrays
+Dim lMin1 As Long &apos; LBound of 1st input array
+Dim lMax1 As Long &apos; UBound of 1st input array
+Dim lMin2 As Long &apos; LBound of 2nd input array
+Dim lMax2 As Long &apos; UBound of 2nd input array
+Dim lSize As Long &apos; Number of Union items
+Dim i As Long
+Const cstThisSub = &quot;Array.Union&quot;
+Const cstSubArgs = &quot;Array1_1D, Array2_1D, [CaseSensitive=False]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vUnion = Array()
+
+Check:
+ If IsMissing(CaseSensitive) Then CaseSensitive = False
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array1_1D, &quot;Array1_1D&quot;, 1, 0, True) Then GoTo Finally
+ iType = SF_Utils._VarTypeExt(Array1_1D(LBound(Array1_1D)))
+ If Not SF_Utils._ValidateArray(Array2_1D, &quot;Array2_1D&quot;, 1, iType, True) Then GoTo Finally
+ If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
+ End If
+
+Try:
+ lMin1 = LBound(Array1_1D) : lMax1 = UBound(Array1_1D)
+ lMin2 = LBound(Array2_1D) : lMax2 = UBound(Array2_1D)
+
+ &apos; If both arrays are empty, do nothing
+ If lMax1 &lt; lMin1 And lMax2 &lt; lMin2 Then
+ ElseIf lMax1 &lt; lMin1 Then &apos; only 1st array is empty
+ vUnion = SF_Array.Unique(Array2_1D, CaseSensitive)
+ ElseIf lMax2 &lt; lMin2 Then &apos; only 2nd array is empty
+ vUnion = SF_Array.Unique(Array1_1D, CaseSensitive)
+ Else
+
+ &apos; Build union of both arrays
+ ReDim vUnion(0 To (lMax1 - lMin1) + (lMax2 - lMin2) + 1)
+ lSize = -1
+
+ &apos; Fill vUnion one by one only with items present in any set
+ For i = lMin1 To lMax1
+ lSize = lSize + 1
+ vUnion(lSize) = Array1_1D(i)
+ Next i
+ For i = lMin2 To lMax2
+ lSize = lSize + 1
+ vUnion(lSize) = Array2_1D(i)
+ Next i
+
+ &apos; Remove duplicates
+ vUnion() = SF_Array.Unique(vUnion, CaseSensitive)
+ End If
+
+Finally:
+ Union = vUnion()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.Union
+
+REM -----------------------------------------------------------------------------
+Public Function Unique(Optional ByRef Array_1D As Variant _
+ , Optional ByVal CaseSensitive As Variant _
+ ) As Variant
+&apos;&apos;&apos; Build a set of unique values derived from the input array
+&apos;&apos;&apos; the input array must be filled homogeneously, i.e. all items must be of the same type
+&apos;&apos;&apos; Empty and Null items are forbidden
+&apos;&apos;&apos; The comparison between strings is case sensitive or not
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array_1D: the input array with potential duplicates
+&apos;&apos;&apos; CaseSensitive: default = False
+&apos;&apos;&apos; Returns: the array without duplicates with same LBound as input array
+&apos;&apos;&apos; The output array is sorted in ascending order
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; Unique(Array(&quot;A&quot;, &quot;C&quot;, &quot;A&quot;, &quot;b&quot;, &quot;B&quot;), True) returns (&quot;A&quot;, &quot;B&quot;, &quot;C&quot;, &quot;b&quot;)
+
+Dim vUnique() As Variant &apos; Return value
+Dim vSorted() As Variant &apos; The input array after sort
+Dim lMin As Long &apos; LBound of input array
+Dim lMax As Long &apos; UBound of input array
+Dim lUnique As Long &apos; Number of unique items
+Dim vIndex As Variant &apos; Output of _FindItem() method
+Dim vItem As Variant &apos; One single item in the array
+Dim i As Long
+Const cstThisSub = &quot;Array.Unique&quot;
+Const cstSubArgs = &quot;Array_1D, [CaseSensitive=False]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vUnique = Array()
+
+Check:
+ If IsMissing(CaseSensitive) Then CaseSensitive = False
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1, 0, True) Then GoTo Finally
+ If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
+ End If
+
+Try:
+ lMin = LBound(Array_1D)
+ lMax = UBound(Array_1D)
+ If lMax &gt;= lMin Then
+ &apos; First sort the array
+ vSorted = SF_Array.Sort(Array_1D, &quot;ASC&quot;, CaseSensitive)
+ ReDim vUnique(lMin To lMax)
+ lUnique = lMin
+ &apos; Fill vUnique one by one ignoring duplicates
+ For i = lMin To lMax
+ vItem = vSorted(i)
+ If i = lMin Then
+ vUnique(i) = vItem
+ Else
+ If SF_Array._ValCompare(vItem, vSorted(i - 1), CaseSensitive) = 0 Then &apos; Ignore item
+ Else
+ lUnique = lUnique + 1
+ vUnique(lUnique) = vItem
+ End If
+ End If
+ Next i
+ &apos; Remove unfilled entries
+ ReDim Preserve vUnique(lMin To lUnique)
+ End If
+
+Finally:
+ Unique = vUnique()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.Unique
+
+REM ============================================================= PRIVATE METHODS
+
+REM -----------------------------------------------------------------------------
+Public Function _FindItem(ByRef pvArray_1D As Variant _
+ , ByVal pvToFind As Variant _
+ , ByVal pbCaseSensitive As Boolean _
+ , ByVal psSortOrder As String _
+ ) As Variant
+&apos;&apos;&apos; Check if a 1D array contains the ToFind number, string or date and return its index
+&apos;&apos;&apos; The comparison between strings can be done case-sensitively or not
+&apos;&apos;&apos; If the array is sorted then a binary search is done
+&apos;&apos;&apos; Otherwise the array is scanned from top. Null or Empty items are simply ignored
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; pvArray_1D: the array to scan
+&apos;&apos;&apos; pvToFind: a number, a date or a string to find
+&apos;&apos;&apos; pbCaseSensitive: Only for string comparisons, default = False
+&apos;&apos;&apos; psSortOrder: &quot;ASC&quot;, &quot;DESC&quot; or &quot;&quot; (= not sorted, default)
+&apos;&apos;&apos; Return: a (0:1) array
+&apos;&apos;&apos; (0) = True when found
+&apos;&apos;&apos; (1) = if found: index of item
+&apos;&apos;&apos; if not found: if sorted, index of next item in the array (might be = UBound + 1)
+&apos;&apos;&apos; if not sorted, meaningless
+&apos;&apos;&apos; Result is unpredictable when array is announced sorted and is in reality not
+&apos;&apos;&apos; Called by Contains, IndexOf and InsertSorted. Also called by SF_Dictionary
+
+Dim bContains As Boolean &apos; True if match found
+Dim iToFindType As Integer &apos; VarType of pvToFind
+Dim lTop As Long, lBottom As Long &apos; Interval in scope of binary search
+Dim lIndex As Long &apos; Index used in search
+Dim iCompare As Integer &apos; Output of _ValCompare function
+Dim lLoops As Long &apos; Count binary searches
+Dim lMaxLoops As Long &apos; Max number of loops during binary search: to avoid infinite loops if array not sorted
+Dim vFound(1) As Variant &apos; Returned array (Contains, Index)
+
+ bContains = False
+
+ If LBound(pvArray_1D) &gt; UBound(pvArray_1D) Then &apos; Empty array, do nothing
+ Else
+ &apos; Search sequentially
+ If Len(psSortOrder) = 0 Then
+ For lIndex = LBound(pvArray_1D) To UBound(pvArray_1D)
+ bContains = ( SF_Array._ValCompare(pvToFind, pvArray_1D(lIndex), pbCaseSensitive) = 0 )
+ If bContains Then Exit For
+ Next lIndex
+ Else
+ &apos; Binary search
+ If psSortOrder = &quot;ASC&quot; Then
+ lTop = UBound(pvArray_1D)
+ lBottom = lBound(pvArray_1D)
+ Else
+ lBottom = UBound(pvArray_1D)
+ lTop = lBound(pvArray_1D)
+ End If
+ lLoops = 0
+ lMaxLoops = CLng((Log(UBound(pvArray_1D) - LBound(pvArray_1D) + 1.0) / Log(2.0))) + 1
+ Do
+ lLoops = lLoops + 1
+ lIndex = (lTop + lBottom) / 2
+ iCompare = SF_Array._ValCompare(pvToFind, pvArray_1D(lIndex), pbCaseSensitive)
+ Select Case True
+ Case iCompare = 0 : bContains = True
+ Case iCompare &lt; 0 And psSortOrder = &quot;ASC&quot;
+ lTop = lIndex - 1
+ Case iCompare &gt; 0 And psSortOrder = &quot;DESC&quot;
+ lBottom = lIndex - 1
+ Case iCompare &gt; 0 And psSortOrder = &quot;ASC&quot;
+ lBottom = lIndex + 1
+ Case iCompare &lt; 0 And psSortOrder = &quot;DESC&quot;
+ lTop = lIndex + 1
+ End Select
+ Loop Until ( bContains ) Or ( lBottom &gt; lTop And psSortOrder = &quot;ASC&quot; ) Or (lBottom &lt; lTop And psSortOrder = &quot;DESC&quot; ) Or lLoops &gt; lMaxLoops
+ &apos; Flag first next non-matching element
+ If Not bContains Then lIndex = Iif(psSortOrder = &quot;ASC&quot;, lBottom, lTop)
+ End If
+ End If
+
+ &apos; Build output array
+ vFound(0) = bContains
+ vFound(1) = lIndex
+ _FindItem = vFound
+
+End Function &apos; ScriptForge.SF_Array._FindItem
+
+REM -----------------------------------------------------------------------------
+Private Function _HeapSort(ByRef pvArray As Variant _
+ , Optional ByVal pbAscending As Boolean _
+ , Optional ByVal pbCaseSensitive As Boolean _
+ ) As Variant
+&apos;&apos;&apos; Sort an array: items are presumed all strings, all dates or all numeric
+&apos;&apos;&apos; Null or Empty are allowed and are considered smaller than other items
+&apos;&apos;&apos; https://en.wikipedia.org/wiki/Heapsort
+&apos;&apos;&apos; http://www.vbforums.com/showthread.php?473677-VB6-Sorting-algorithms-(sort-array-sorting-arrays)&amp;p=2909250#post2909250
+&apos;&apos;&apos; HeapSort preferred to QuickSort because not recursive (this routine returns an array of indexes !!)
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; pvArray: a 1D array
+&apos;&apos;&apos; pbAscending: default = True
+&apos;&apos;&apos; pbCaseSensitive: default = False
+&apos;&apos;&apos; Returns
+&apos;&apos;&apos; An array of Longs of same dimensions as the input array listing the indexes of the sorted items
+&apos;&apos;&apos; An empty array if the sort failed
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; _HeapSort(Array(4, 2, 6, 1) returns (3, 1, 0, 2)
+
+Dim vIndexes As Variant &apos; Return value
+Dim i As Long
+Dim lMin As Long, lMax As Long &apos; Array bounds
+Dim lSwap As Long &apos; For index swaps
+
+ If IsMissing(pbAscending) Then pbAscending = True
+ If IsMissing(pbCaseSensitive) Then pbCaseSensitive = False
+ vIndexes = Array()
+ lMin = LBound(pvArray, 1)
+ lMax = UBound(pvArray, 1)
+
+ &apos; Initialize output array
+ ReDim vIndexes(lMin To lMax)
+ For i = lMin To lMax
+ vIndexes(i) = i
+ Next i
+
+ &apos; Initial heapify
+ For i = (lMax + lMin) \ 2 To lMin Step -1
+ SF_Array._HeapSort1(pvArray, vIndexes, i, lMin, lMax, pbCaseSensitive)
+ Next i
+ &apos; Next heapify
+ For i = lMax To lMin + 1 Step -1
+ &apos; Only indexes as swapped, not the array items themselves
+ lSwap = vIndexes(i)
+ vIndexes(i) = vIndexes(lMin)
+ vIndexes(lMin) = lSwap
+ SF_Array._HeapSort1(pvArray, vIndexes, lMin, lMin, i - 1, pbCaseSensitive)
+ Next i
+
+ If pbAscending Then _HeapSort = vIndexes() Else _HeapSort = SF_Array.Reverse(vIndexes())
+
+End Function &apos; ScriptForge.SF_Array._HeapSort
+
+REM -----------------------------------------------------------------------------
+Private Sub _HeapSort1(ByRef pvArray As Variant _
+ , ByRef pvIndexes As Variant _
+ , ByVal plIndex As Long _
+ , ByVal plMin As Long _
+ , ByVal plMax As Long _
+ , ByVal pbCaseSensitive As Boolean _
+ )
+&apos;&apos;&apos; Sub called by _HeapSort only
+
+ Dim lLeaf As Long
+ Dim lSwap As Long
+
+ Do
+ lLeaf = plIndex + plIndex - (plMin - 1)
+ Select Case lLeaf
+ Case Is &gt; plMax: Exit Do
+ Case Is &lt; plMax
+ If SF_Array._ValCompare(pvArray(pvIndexes(lLeaf + 1)), pvArray(pvIndexes(lLeaf)), pbCaseSensitive) &gt; 0 Then lLeaf = lLeaf + 1
+ End Select
+ If SF_Array._ValCompare(pvArray(pvIndexes(plIndex)), pvArray(pvIndexes(lLeaf)), pbCaseSensitive) &gt; 0 Then Exit Do
+ &apos; Only indexes as swapped, not the array items themselves
+ lSwap = pvIndexes(plIndex)
+ pvIndexes(plIndex) = pvIndexes(lLeaf)
+ pvIndexes(lLeaf) = lSwap
+ plIndex = lLeaf
+ Loop
+
+End Sub &apos; ScriptForge.SF_Array._HeapSort1
+
+REM -----------------------------------------------------------------------------
+Private Function _Repr(ByRef pvArray As Variant) As String
+&apos;&apos;&apos; Convert array to a readable string, typically for debugging purposes (DebugPrint ...)
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; pvArray: the array to convert, individual items may be of any type, including arrays
+&apos;&apos;&apos; Return:
+&apos;&apos;&apos; &quot;[ARRAY] (L:U[, L:U]...)&quot; if # of Dims &gt; 1
+&apos;&apos;&apos; &quot;[ARRAY] (L:U) (item1,item2, ...)&quot; if 1D array
+
+Dim iDims As Integer &apos; Number of dimensions of the array
+Dim sArray As String &apos; Return value
+Dim i As Long
+Const cstArrayEmpty = &quot;[ARRAY] ()&quot;
+Const cstArray = &quot;[ARRAY]&quot;
+Const cstMaxLength = 50 &apos; Maximum length for items
+Const cstSeparator = &quot;, &quot;
+
+ _Repr = &quot;&quot;
+ iDims = SF_Array.CountDims(pvArray)
+
+ Select Case iDims
+ Case -1 : Exit Function &apos; Not an array
+ Case 0 : sArray = cstArrayEmpty
+ Case Else
+ sArray = cstArray
+ For i = 1 To iDims
+ sArray = sArray &amp; Iif(i = 1, &quot; (&quot;, &quot;, &quot;) &amp; CStr(LBound(pvArray, i)) &amp; &quot;:&quot; &amp; CStr(UBound(pvArray, i))
+ Next i
+ sArray = sArray &amp; &quot;)&quot;
+ &apos; List individual items of 1D arrays
+ If iDims = 1 Then
+ sArray = sArray &amp; &quot; (&quot;
+ For i = LBound(pvArray) To UBound(pvArray)
+ sArray = sArray &amp; SF_Utils._Repr(pvArray(i), cstMaxLength) &amp; cstSeparator &apos; Recursive call
+ Next i
+ sArray = Left(sArray, Len(sArray) - Len(cstSeparator)) &apos; Suppress last comma
+ sArray = sArray &amp; &quot;)&quot;
+ End If
+ End Select
+
+ _Repr = sArray
+
+End Function &apos; ScriptForge.SF_Array._Repr
+
+REM -----------------------------------------------------------------------------
+Public Function _StaticType(ByRef pvArray As Variant) As Integer
+&apos;&apos;&apos; If array is static, return its type
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; pvArray: array to examine
+&apos;&apos;&apos; Return:
+&apos;&apos;&apos; array type, -1 if not identified
+&apos;&apos;&apos; All numeric types are aggregated into V_NUMERIC
+
+Dim iArrayType As Integer &apos; VarType of array
+Dim iType As Integer &apos; VarType of items
+
+ iArrayType = VarType(pvArray)
+ iType = iArrayType - V_ARRAY
+ Select Case iType
+ Case V_INTEGER, V_LONG, V_SINGLE, V_DOUBLE, V_CURRENCY, V_BIGINT, V_DECIMAL, V_BOOLEAN
+ _StaticType = V_NUMERIC
+ Case V_STRING, V_DATE
+ _StaticType = iType
+ Case Else
+ _StaticType = -1
+ End Select
+
+End Function &apos; ScriptForge.SF_Utils._StaticType
+
+REM -----------------------------------------------------------------------------
+Private Function _ValCompare(ByVal pvValue1 As Variant _
+ , pvValue2 As Variant _
+ , Optional ByVal pbCaseSensitive As Boolean _
+ ) As Integer
+&apos;&apos;&apos; Compare 2 values : equality, greater than or smaller than
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; pvValue1 and pvValue2: values to compare. pvValues must be String, Number, Date, Empty or Null
+&apos;&apos;&apos; By convention: Empty &lt; Null &lt; string, number or date
+&apos;&apos;&apos; pbCaseSensitive: ignored when not String comparison
+&apos;&apos;&apos; Return: -1 when pvValue1 &lt; pvValue2
+&apos;&apos;&apos; +1 when pvValue1 &gt; pvValue2
+&apos;&apos;&apos; 0 when pvValue1 = pvValue2
+&apos;&apos;&apos; -2 when comparison is nonsense
+
+Dim iCompare As Integer, iVarType1 As Integer, iVarType2 As Integer
+
+ If IsMissing(pbCaseSensitive) Then pbCaseSensitive = False
+ iVarType1 = SF_Utils._VarTypeExt(pvValue1)
+ iVarType2 = SF_Utils._VarTypeExt(pvValue2)
+
+ iCompare = -2
+ If iVarType1 = V_OBJECT Or iVarType1 = V_BYTE Or iVarType1 &gt;= V_ARRAY Then &apos; Nonsense
+ ElseIf iVarType2 = V_OBJECT Or iVarType2 = V_BYTE Or iVarType2 &gt;= V_ARRAY Then &apos; Nonsense
+ ElseIf iVarType1 = V_STRING And iVarType2 = V_STRING Then
+ iCompare = StrComp(pvValue1, pvValue2, Iif(pbCaseSensitive, 1, 0))
+ ElseIf iVarType1 = V_NULL Or iVarType1 = V_EMPTY Or iVarType2 = V_NULL Or iVarType2 = V_EMPTY Then
+ Select Case True
+ Case pvValue1 = pvValue2 : iCompare = 0
+ Case iVarType1 = V_NULL And iVarType2 = V_EMPTY : iCompare = +1
+ Case iVarType1 = V_EMPTY And iVarType2 = V_NULL : iCompare = -1
+ Case iVarType1 = V_NULL Or iVarType1 = V_EMPTY : iCompare = -1
+ Case iVarType2 = V_NULL Or iVarType2 = V_EMPTY : iCompare = +1
+ End Select
+ ElseIf iVarType1 = iVarType2 Then
+ Select Case True
+ Case pvValue1 &lt; pvValue2 : iCompare = -1
+ Case pvValue1 = pvValue2 : iCompare = 0
+ Case pvValue1 &gt; pvValue2 : iCompare = +1
+ End Select
+ End If
+
+ _ValCompare = iCompare
+
+End Function &apos; ScriptForge.SF_Array._ValCompare
+
+REM ================================================= END OF SCRIPTFORGE.SF_ARRAY
+</script:module> \ No newline at end of file
diff --git a/wizards/source/scriptforge/SF_Dictionary.xba b/wizards/source/scriptforge/SF_Dictionary.xba
new file mode 100644
index 000000000..22ada5148
--- /dev/null
+++ b/wizards/source/scriptforge/SF_Dictionary.xba
@@ -0,0 +1,959 @@
+<?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_Dictionary" 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 ClassModule
+
+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_Dictionary
+&apos;&apos;&apos; =============
+&apos;&apos;&apos; Class for management of dictionaries
+&apos;&apos;&apos; A dictionary is a collection of key-item pairs
+&apos;&apos;&apos; The key is a not case-sensitive string
+&apos;&apos;&apos; Items may be of any type
+&apos;&apos;&apos; Keys, items can be retrieved, counted, etc.
+&apos;&apos;&apos;
+&apos;&apos;&apos; The implementation is based on
+&apos;&apos;&apos; - one collection mapping keys and entries in the array
+&apos;&apos;&apos; - one 1-column array: key + data
+&apos;&apos;&apos;
+&apos;&apos;&apos; Why a Dictionary class beside the builtin Collection class ?
+&apos;&apos;&apos; A standard Basic collection does not support the retrieval of the keys
+&apos;&apos;&apos; Additionally it may contain only simple data (strings, numbers, ...)
+&apos;&apos;&apos;
+&apos;&apos;&apos; Service instantiation example:
+&apos;&apos;&apos; Dim myDict As Variant
+&apos;&apos;&apos; myDict = CreateScriptService(&quot;Dictionary&quot;) &apos; Once per dictionary
+&apos;&apos;&apos;
+&apos;&apos;&apos; Detailed user documentation:
+&apos;&apos;&apos; https://help.libreoffice.org/latest/en-US/text/sbasic/shared/03/sf_dictionary.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 DUPLICATEKEYERROR = &quot;DUPLICATEKEYERROR&quot; &apos; Key exists already
+Const UNKNOWNKEYERROR = &quot;UNKNOWNKEYERROR&quot; &apos; Key not found
+Const INVALIDKEYERROR = &quot;INVALIDKEYERROR&quot; &apos; Key contains only spaces
+
+REM ============================================================= PRIVATE MEMBERS
+
+&apos; Defines an entry in the MapItems array
+Type ItemMap
+ Key As String
+ Value As Variant
+End Type
+
+Private [Me] As Object
+Private [_Parent] As Object
+Private ObjectType As String &apos; Must be &quot;DICTIONARY&quot;
+Private ServiceName As String
+Private MapKeys As Variant &apos; To retain the original keys
+Private MapItems As Variant &apos; Array of ItemMaps
+Private _MapSize As Long &apos; Total number of entries in the dictionary
+Private _MapRemoved As Long &apos; Number of inactive entries in the dictionary
+
+REM ===================================================== CONSTRUCTOR/DESTRUCTOR
+
+REM -----------------------------------------------------------------------------
+Private Sub Class_Initialize()
+ Set [Me] = Nothing
+ Set [_Parent] = Nothing
+ ObjectType = &quot;DICTIONARY&quot;
+ ServiceName = &quot;ScriptForge.Dictionary&quot;
+ Set MapKeys = New Collection
+ Set MapItems = Array()
+ _MapSize = 0
+ _MapRemoved = 0
+End Sub &apos; ScriptForge.SF_Dictionary Constructor
+
+REM -----------------------------------------------------------------------------
+Private Sub Class_Terminate()
+ Call Class_Initialize()
+End Sub &apos; ScriptForge.SF_Dictionary Destructor
+
+REM -----------------------------------------------------------------------------
+Public Function Dispose() As Variant
+ RemoveAll()
+ Set Dispose = Nothing
+End Function &apos; ScriptForge.SF_Dictionary Explicit destructor
+
+REM ================================================================== PROPERTIES
+
+REM -----------------------------------------------------------------------------
+Property Get Count() As Long
+&apos;&apos;&apos; Actual number of entries in the dictionary
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; myDict.Count
+
+ Count = _PropertyGet(&quot;Count&quot;)
+
+End Property &apos; ScriptForge.SF_Dictionary.Count
+
+REM -----------------------------------------------------------------------------
+Public Function Item(Optional ByVal Key As Variant) As Variant
+&apos;&apos;&apos; Return the value of the item related to Key
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Key: the key value (string)
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; Empty if not found, otherwise the found value
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; myDict.Item(&quot;ThisKey&quot;)
+&apos;&apos;&apos; NB: defined as a function to not disrupt the Basic IDE debugger
+
+ Item = _PropertyGet(&quot;Item&quot;, Key)
+
+End Function &apos; ScriptForge.SF_Dictionary.Item
+
+REM -----------------------------------------------------------------------------
+Property Get Items() as Variant
+&apos;&apos;&apos; Return the list of Items as a 1D array
+&apos;&apos;&apos; The Items and Keys properties return their respective contents in the same order
+&apos;&apos;&apos; The order is however not necessarily identical to the creation sequence
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The array is empty if the dictionary is empty
+&apos;&apos;&apos; Examples
+&apos;&apos;&apos; a = myDict.Items
+&apos;&apos;&apos; For Each b In a ...
+
+ Items = _PropertyGet(&quot;Items&quot;)
+
+End Property &apos; ScriptForge.SF_Dictionary.Items
+
+REM -----------------------------------------------------------------------------
+Property Get Keys() as Variant
+&apos;&apos;&apos; Return the list of keys as a 1D array
+&apos;&apos;&apos; The Keys and Items properties return their respective contents in the same order
+&apos;&apos;&apos; The order is however not necessarily identical to the creation sequence
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The array is empty if the dictionary is empty
+&apos;&apos;&apos; Examples
+&apos;&apos;&apos; a = myDict.Keys
+&apos;&apos;&apos; For each b In a ...
+
+ Keys = _PropertyGet(&quot;Keys&quot;)
+
+End Property &apos; ScriptForge.SF_Dictionary.Keys
+
+REM ===================================================================== METHODS
+
+REM -----------------------------------------------------------------------------
+Public Function Add(Optional ByVal Key As Variant _
+ , Optional ByVal Item As Variant _
+ ) As Boolean
+&apos;&apos;&apos; Add a new key-item pair into the dictionary
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Key: must not yet exist in the dictionary
+&apos;&apos;&apos; Item: any value, including an array, a Basic object, a UNO object, ...
+&apos;&apos;&apos; Returns: True if successful
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; DUPLICATEKEYERROR: such a key exists already
+&apos;&apos;&apos; INVALIDKEYERROR: zero-length string or only spaces
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; myDict.Add(&quot;NewKey&quot;, NewValue)
+
+Dim oItemMap As ItemMap &apos; New entry in the MapItems array
+Const cstThisSub = &quot;Dictionary.Add&quot;
+Const cstSubArgs = &quot;Key, Item&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ Add = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(Key, &quot;Key&quot;, V_STRING) Then GoTo Catch
+ If IsArray(Item) Then
+ If Not SF_Utils._ValidateArray(Item, &quot;Item&quot;) Then GoTo Catch
+ Else
+ If Not SF_Utils._Validate(Item, &quot;Item&quot;) Then GoTo Catch
+ End If
+ End If
+ If Key = Space(Len(Key)) Then GoTo CatchInvalid
+ If Exists(Key) Then GoTo CatchDuplicate
+
+Try:
+ _MapSize = _MapSize + 1
+ MapKeys.Add(_MapSize, Key)
+ oItemMap.Key = Key
+ oItemMap.Value = Item
+ ReDim Preserve MapItems(1 To _MapSize)
+ MapItems(_MapSize) = oItemMap
+ Add = True
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchDuplicate:
+ SF_Exception.RaiseFatal(DUPLICATEKEYERROR, &quot;Key&quot;, Key)
+ GoTo Finally
+CatchInvalid:
+ SF_Exception.RaiseFatal(INVALIDKEYERROR, &quot;Key&quot;)
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Dictionary.Add
+
+REM -----------------------------------------------------------------------------
+Public Function ConvertToArray() As Variant
+&apos;&apos;&apos; Store the content of the dictionary in a 2-columns array:
+&apos;&apos;&apos; Key stored in 1st column, Item stored in 2nd
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; a zero-based 2D array(0:Count - 1, 0:1)
+&apos;&apos;&apos; an empty array if the dictionary is empty
+
+Dim vArray As Variant &apos; Return value
+Dim sKey As String &apos; Tempry key
+Dim vKeys As Variant &apos; Array of keys
+Dim lCount As Long &apos; Counter
+Const cstThisSub = &quot;Dictionary.ConvertToArray&quot;
+Const cstSubArgs = &quot;&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+
+Check:
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Try:
+ vArray = Array()
+ If Count = 0 Then
+ Else
+ ReDim vArray(0 To Count - 1, 0 To 1)
+ lCount = -1
+ vKeys = Keys
+ For Each sKey in vKeys
+ lCount = lCount + 1
+ vArray(lCount, 0) = sKey
+ vArray(lCount, 1) = Item(sKey)
+ Next sKey
+ End If
+
+Finally:
+ ConvertToArray = vArray()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Dictionary.ConvertToArray
+
+REM -----------------------------------------------------------------------------
+Public Function ConvertToJson(ByVal Optional Indent As Variant) As Variant
+&apos;&apos;&apos; Convert the content of the dictionary to a JSON string
+&apos;&apos;&apos; JSON = JavaScript Object Notation: https://en.wikipedia.org/wiki/JSON
+&apos;&apos;&apos; Limitations
+&apos;&apos;&apos; Allowed item types: String, Boolean, numbers, Null and Empty
+&apos;&apos;&apos; Arrays containing above types are allowed
+&apos;&apos;&apos; Dates are converted into strings (not within arrays)
+&apos;&apos;&apos; Other types are converted to their string representation (cfr. SF_String.Represent)
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Indent:
+&apos;&apos;&apos; If indent is a non-negative integer or string, then JSON array elements and object members will be pretty-printed with that indent level.
+&apos;&apos;&apos; An indent level &lt;= 0 will only insert newlines.
+&apos;&apos;&apos; &quot;&quot;, (the default) selects the most compact representation.
+&apos;&apos;&apos; Using a positive integer indent indents that many spaces per level.
+&apos;&apos;&apos; If indent is a string (such as Chr(9)), that string is used to indent each level.
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; the JSON string
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; myDict.Add(&quot;p0&quot;, 12.5)
+&apos;&apos;&apos; myDict.Add(&quot;p1&quot;, &quot;a string àé&quot;&quot;ê&quot;)
+&apos;&apos;&apos; myDict.Add(&quot;p2&quot;, DateSerial(2020,9,28))
+&apos;&apos;&apos; myDict.Add(&quot;p3&quot;, True)
+&apos;&apos;&apos; myDict.Add(&quot;p4&quot;, Array(1,2,3))
+&apos;&apos;&apos; MsgBox a.ConvertToJson() &apos; {&quot;p0&quot;: 12.5, &quot;p1&quot;: &quot;a string \u00e0\u00e9\&quot;\u00ea&quot;, &quot;p2&quot;: &quot;2020-09-28&quot;, &quot;p3&quot;: true, &quot;p4&quot;: [1, 2, 3]}
+
+Dim sJson As String &apos; Return value
+Dim vArray As Variant &apos; Array of property values
+Dim oPropertyValue As Object &apos; com.sun.star.beans.PropertyValue
+Dim sKey As String &apos; Tempry key
+Dim vKeys As Variant &apos; Array of keys
+Dim vItem As Variant &apos; Tempry item
+Dim iVarType As Integer &apos; Extended VarType
+Dim lCount As Long &apos; Counter
+Dim vIndent As Variant &apos; Python alias of Indent
+Const cstPyHelper = &quot;$&quot; &amp; &quot;_SF_Dictionary__ConvertToJson&quot;
+
+Const cstThisSub = &quot;Dictionary.ConvertToJson&quot;
+Const cstSubArgs = &quot;[Indent=Null]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+
+Check:
+ If IsMissing(Indent) Or IsEmpty(INDENT) Then Indent = &quot;&quot;
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(Indent, &quot;Indent&quot;, Array(V_STRING, V_NUMERIC)) Then GoTo Finally
+ End If
+ sJson = &quot;&quot;
+
+Try:
+ vArray = Array()
+ If Count = 0 Then
+ Else
+ ReDim vArray(0 To Count - 1)
+ lCount = -1
+ vKeys = Keys
+ For Each sKey in vKeys
+ &apos; Check item type
+ vItem = Item(sKey)
+ iVarType = SF_Utils._VarTypeExt(vItem)
+ Select Case iVarType
+ Case V_STRING, V_BOOLEAN, V_NUMERIC, V_NULL, V_EMPTY
+ Case V_DATE
+ vItem = SF_Utils._CDateToIso(vItem)
+ Case &gt;= V_ARRAY
+ Case Else
+ vItem = SF_Utils._Repr(vItem)
+ End Select
+ &apos; Build in each array entry a (Name, Value) pair
+ Set oPropertyValue = SF_Utils._MakePropertyValue(sKey, vItem)
+ lCount = lCount + 1
+ Set vArray(lCount) = oPropertyValue
+ Next sKey
+ End If
+
+ &apos;Pass array to Python script for the JSON conversion
+ With ScriptForge.SF_Session
+ vIndent = Indent
+ If VarType(Indent) = V_STRING Then
+ If Len(Indent) = 0 Then vIndent = Null
+ End If
+ sJson = .ExecutePythonScript(.SCRIPTISSHARED, _SF_.PythonHelper &amp; cstPyHelper, vArray, vIndent)
+ End With
+
+Finally:
+ ConvertToJson = sJson
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Dictionary.ConvertToJson
+
+REM -----------------------------------------------------------------------------
+Public Function ConvertToPropertyValues() As Variant
+&apos;&apos;&apos; Store the content of the dictionary in an array of PropertyValues
+&apos;&apos;&apos; Key stored in Name, Item stored in Value
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; a zero-based 1D array(0:Count - 1). Each entry is a com.sun.star.beans.PropertyValue
+&apos;&apos;&apos; Name: the key in the dictionary
+&apos;&apos;&apos; Value:
+&apos;&apos;&apos; Dates are converted to UNO dates
+&apos;&apos;&apos; Empty arrays are replaced by Null
+&apos;&apos;&apos; an empty array if the dictionary is empty
+
+Dim vArray As Variant &apos; Return value
+Dim oPropertyValue As Object &apos; com.sun.star.beans.PropertyValue
+Dim sKey As String &apos; Tempry key
+Dim vKeys As Variant &apos; Array of keys
+Dim lCount As Long &apos; Counter
+Const cstThisSub = &quot;Dictionary.ConvertToPropertyValues&quot;
+Const cstSubArgs = &quot;&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+
+Check:
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Try:
+ vArray = Array()
+ If Count = 0 Then
+ Else
+ ReDim vArray(0 To Count - 1)
+ lCount = -1
+ vKeys = Keys
+ For Each sKey in vKeys
+ &apos; Build in each array entry a (Name, Value) pair
+ Set oPropertyValue = SF_Utils._MakePropertyValue(sKey, Item(sKey))
+ lCount = lCount + 1
+ Set vArray(lCount) = oPropertyValue
+ Next sKey
+ End If
+
+Finally:
+ ConvertToPropertyValues = vArray()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Dictionary.ConvertToPropertyValues
+
+REM -----------------------------------------------------------------------------
+Public Function Exists(Optional ByVal Key As Variant) As Boolean
+&apos;&apos;&apos; Determine if a key exists in the dictionary
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Key: the key value (string)
+&apos;&apos;&apos; Returns: True if key exists
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; If myDict.Exists(&quot;SomeKey&quot;) Then &apos; don&apos;t add again
+
+Dim vItem As Variant &apos; Item part in MapKeys
+Const cstThisSub = &quot;Dictionary.Exists&quot;
+Const cstSubArgs = &quot;Key&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ Exists = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(Key, &quot;Key&quot;, V_STRING) Then GoTo Catch
+ End If
+
+Try:
+ &apos; Dirty but preferred to go through whole collection
+ On Local Error GoTo NotFound
+ vItem = MapKeys(Key)
+ NotFound:
+ Exists = ( Not ( Err = 5 ) And vItem &gt; 0 )
+ On Local Error GoTo 0
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Dictionary.Exists
+
+REM -----------------------------------------------------------------------------
+Public Function GetProperty(Optional ByVal PropertyName As Variant _
+ , Optional ByVal Key As Variant _
+ ) As Variant
+&apos;&apos;&apos; Return the actual value of the given property
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; PropertyName: the name of the property as a string
+&apos;&apos;&apos; Key: mandatory if PropertyName = &quot;Item&quot;, ignored otherwise
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The actual value of the property
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; ARGUMENTERROR The property does not exist
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; myDict.GetProperty(&quot;Count&quot;)
+
+Const cstThisSub = &quot;Dictionary.GetProperty&quot;
+Const cstSubArgs = &quot;PropertyName, [Key]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ GetProperty = Null
+
+Check:
+ If IsMissing(Key) Or IsEmpty(Key) Then Key = &quot;&quot;
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
+ End If
+
+Try:
+ GetProperty = _PropertyGet(PropertyName, Key)
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Dictionary.GetProperty
+
+REM -----------------------------------------------------------------------------
+Public Function ImportFromJson(Optional ByVal InputStr As Variant _
+ , Optional ByVal Overwrite As Variant _
+ ) As Boolean
+&apos;&apos;&apos; Adds the content of a Json string into the current dictionary
+&apos;&apos;&apos; JSON = JavaScript Object Notation: https://en.wikipedia.org/wiki/JSON
+&apos;&apos;&apos; Limitations
+&apos;&apos;&apos; The JSON string may contain numbers, strings, booleans, null values and arrays containing those types
+&apos;&apos;&apos; It must not contain JSON objects, i.e. sub-dictionaries
+&apos;&apos;&apos; An attempt is made to convert strings to dates if they fit one of next patterns:
+&apos;&apos;&apos; YYYY-MM-DD, HH:MM:SS or YYYY-MM-DD HH:MM:SS
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the json string to import
+&apos;&apos;&apos; Overwrite: when True entries with same name may exist in the dictionary and their values are overwritten
+&apos;&apos;&apos; Default = False
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if successful
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; DUPLICATEKEYERROR: such a key exists already
+&apos;&apos;&apos; INVALIDKEYERROR: zero-length string or only spaces
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; Dim s As String
+&apos;&apos;&apos; s = &quot;{&apos;firstName&apos;: &apos;John&apos;,&apos;lastName&apos;: &apos;Smith&apos;,&apos;isAlive&apos;: true,&apos;age&apos;: 66, &apos;birth&apos;: &apos;1954-09-28 20:15:00&apos;&quot; _
+&apos;&apos;&apos; &amp; &quot;,&apos;address&apos;: {&apos;streetAddress&apos;: &apos;21 2nd Street&apos;,&apos;city&apos;: &apos;New York&apos;,&apos;state&apos;: &apos;NY&apos;,&apos;postalCode&apos;: &apos;10021-3100&apos;}&quot; _
+&apos;&apos;&apos; &amp; &quot;,&apos;phoneNumbers&apos;: [{&apos;type&apos;: &apos;home&apos;,&apos;number&apos;: &apos;212 555-1234&apos;},{&apos;type&apos;: &apos;office&apos;,&apos;number&apos;: &apos;646 555-4567&apos;}]&quot; _
+&apos;&apos;&apos; &amp; &quot;,&apos;children&apos;: [&apos;Q&apos;,&apos;M&apos;,&apos;G&apos;,&apos;T&apos;],&apos;spouse&apos;: null}&quot;
+&apos;&apos;&apos; s = Replace(s, &quot;&apos;&quot;, &quot;&quot;&quot;&quot;)
+&apos;&apos;&apos; myDict.ImportFromJson(s, OverWrite := True)
+&apos;&apos;&apos; &apos; The (sub)-dictionaries &quot;address&quot; and &quot;phoneNumbers(0) and (1) are reduced to Empty
+
+Dim bImport As Boolean &apos; Return value
+Dim vArray As Variant &apos; JSON string converted to array
+Dim vArrayEntry As Variant &apos; A single entry in vArray
+Dim vKey As Variant &apos; Tempry key
+Dim vItem As Variant &apos; Tempry item
+Dim bExists As Boolean &apos; True when an entry exists
+Dim dDate As Date &apos; String converted to Date
+Const cstPyHelper = &quot;$&quot; &amp; &quot;_SF_Dictionary__ImportFromJson&quot;
+
+Const cstThisSub = &quot;Dictionary.ImportFromJson&quot;
+Const cstSubArgs = &quot;InputStr, [Overwrite=False]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bImport = False
+
+Check:
+ If IsMissing(Overwrite) Or IsEmpty(Overwrite) Then Overwrite = False
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(Overwrite, &quot;Overwrite&quot;, V_BOOLEAN) Then GoYo Finally
+ End If
+
+Try:
+ With ScriptForge.SF_Session
+ vArray = .ExecutePythonScript(.SCRIPTISSHARED, _SF_.PythonHelper &amp; cstPyHelper, InputStr)
+ End With
+ If Not IsArray(vArray) Then GoTo Finally &apos; Conversion error or nothing to do
+
+ &apos; vArray = Array of subarrays = 2D DataArray (cfr. Calc)
+ For Each vArrayEntry In vArray
+ vKey = vArrayEntry(0)
+ If VarType(vKey) = V_STRING Then &apos; Else skip
+ vItem = vArrayEntry(1)
+ If Overwrite Then bExists = Exists(vKey) Else bExists = False
+ &apos; When the item matches a date pattern, convert it to a date
+ If VarType(vItem) = V_STRING Then
+ dDate = SF_Utils._CStrToDate(vItem)
+ If dDate &gt; -1 Then vItem = dDate
+ End If
+ If bExists Then
+ ReplaceItem(vKey, vItem)
+ Else
+ Add(vKey, vItem) &apos; Key controls are done in Add
+ End If
+ End If
+ Next vArrayEntry
+
+ bImport = True
+
+Finally:
+ ImportFromJson = bImport
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Dictionary.ImportFromJson
+
+REM -----------------------------------------------------------------------------
+Public Function ImportFromPropertyValues(Optional ByVal PropertyValues As Variant _
+ , Optional ByVal Overwrite As Variant _
+ ) As Boolean
+&apos;&apos;&apos; Adds the content of an array of PropertyValues into the current dictionary
+&apos;&apos;&apos; Names contain Keys, Values contain Items
+&apos;&apos;&apos; UNO dates are replaced by Basic dates
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; PropertyValues: a zero-based 1D array. Each entry is a com.sun.star.beans.PropertyValue
+&apos;&apos;&apos; Overwrite: when True entries with same name may exist in the dictionary and their values are overwritten
+&apos;&apos;&apos; Default = False
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if successful
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; DUPLICATEKEYERROR: such a key exists already
+&apos;&apos;&apos; INVALIDKEYERROR: zero-length string or only spaces
+
+Dim bImport As Boolean &apos; Return value
+Dim oPropertyValue As Object &apos; com.sun.star.beans.PropertyValue
+Dim vItem As Variant &apos; Tempry item
+Dim sObjectType As String &apos; UNO object type of dates
+Dim bExists As Boolean &apos; True when an entry exists
+Const cstThisSub = &quot;Dictionary.ImportFromPropertyValues&quot;
+Const cstSubArgs = &quot;PropertyValues, [Overwrite=False]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bImport = False
+
+Check:
+ If IsMissing(Overwrite) Or IsEmpty(Overwrite) Then Overwrite = False
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If IsArray(PropertyValues) Then
+ If Not SF_Utils._ValidateArray(PropertyValues, &quot;PropertyValues&quot;, 1, V_OBJECT, True) Then GoTo Finally
+ Else
+ If Not SF_Utils._Validate(PropertyValues, &quot;PropertyValues&quot;, V_OBJECT) Then GoTo Finally
+ End If
+ If Not SF_Utils._Validate(Overwrite, &quot;Overwrite&quot;, V_BOOLEAN) Then GoYo Finally
+ End If
+
+Try:
+ If Not IsArray(PropertyValues) Then PropertyValues = Array(PropertyValues)
+ With oPropertyValue
+ For Each oPropertyValue In PropertyValues
+ If Overwrite Then bExists = Exists(.Name) Else bExists = False
+ If SF_Session.UnoObjectType(oPropertyValue) = &quot;com.sun.star.beans.PropertyValue&quot; Then
+ If IsUnoStruct(.Value) Then
+ sObjectType = SF_Session.UnoObjectType(.Value)
+ Select Case sObjectType
+ Case &quot;com.sun.star.util.DateTime&quot; : vItem = CDateFromUnoDateTime(.Value)
+ Case &quot;com.sun.star.util.Date&quot; : vItem = CDateFromUnoDate(.Value)
+ Case &quot;com.sun.star.util.Time&quot; : vItem = CDateFromUnoTime(.Value)
+ Case Else : vItem = .Value
+ End Select
+ Else
+ vItem = .Value
+ End If
+ If bExists Then
+ ReplaceItem(.Name, vItem)
+ Else
+ Add(.Name, vItem) &apos; Key controls are done in Add
+ End If
+ End If
+ Next oPropertyValue
+ End With
+ bImport = True
+
+Finally:
+ ImportFromPropertyValues = bImport
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Dictionary.ImportFromPropertyValues
+
+REM -----------------------------------------------------------------------------
+Public Function Methods() As Variant
+&apos;&apos;&apos; Return the list or methods of the Dictionary class as an array
+
+ Methods = Array( _
+ &quot;Add&quot; _
+ , &quot;ConvertToArray&quot; _
+ , &quot;ConvertToJson&quot; _
+ , &quot;ConvertToPropertyValues&quot; _
+ , &quot;Exists&quot; _
+ , &quot;ImportFromJson&quot; _
+ , &quot;ImportFromPropertyValues&quot; _
+ , &quot;Remove&quot; _
+ , &quot;RemoveAll&quot; _
+ , &quot;ReplaceItem&quot; _
+ , &quot;ReplaceKey&quot; _
+ )
+
+End Function &apos; ScriptForge.SF_Dictionary.Methods
+
+REM -----------------------------------------------------------------------------
+Public Function Properties() As Variant
+&apos;&apos;&apos; Return the list or properties of the Dictionary class as an array
+
+ Properties = Array( _
+ &quot;Count&quot; _
+ , &quot;Item&quot; _
+ , &quot;Items&quot; _
+ , &quot;Keys&quot; _
+ )
+
+End Function &apos; ScriptForge.SF_Dictionary.Properties
+
+REM -----------------------------------------------------------------------------
+Public Function Remove(Optional ByVal Key As Variant) As Boolean
+&apos;&apos;&apos; Remove an existing dictionary entry based on its key
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Key: must exist in the dictionary
+&apos;&apos;&apos; Returns: True if successful
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; UNKNOWNKEYERROR: the key does not exist
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; myDict.Remove(&quot;OldKey&quot;)
+
+Dim lIndex As Long &apos; To remove entry in the MapItems array
+Const cstThisSub = &quot;Dictionary.Remove&quot;
+Const cstSubArgs = &quot;Key&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ Remove = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(Key, &quot;Key&quot;, V_STRING) Then GoTo Catch
+ End If
+ If Not Exists(Key) Then GoTo CatchUnknown
+
+Try:
+ lIndex = MapKeys.Item(Key)
+ MapKeys.Remove(Key)
+ Erase MapItems(lIndex) &apos; Is now Empty
+ _MapRemoved = _MapRemoved + 1
+ Remove = True
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchUnknown:
+ SF_Exception.RaiseFatal(UNKNOWNKEYERROR, &quot;Key&quot;, Key)
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Dictionary.Remove
+
+REM -----------------------------------------------------------------------------
+Public Function RemoveAll() As Boolean
+&apos;&apos;&apos; Remove all the entries from the dictionary
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Returns: True if successful
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; myDict.RemoveAll()
+
+Dim vKeys As Variant &apos; Array of keys
+Dim sColl As String &apos; A collection key in MapKeys
+Const cstThisSub = &quot;Dictionary.RemoveAll&quot;
+Const cstSubArgs = &quot;&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ RemoveAll = False
+
+Check:
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Try:
+ vKeys = Keys
+ For Each sColl In vKeys
+ MapKeys.Remove(sColl)
+ Next sColl
+ Erase MapKeys
+ Erase MapItems
+ &apos; Make dictionary ready to receive new entries
+ Call Class_Initialize()
+ RemoveAll = True
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Dictionary.RemoveAll
+
+REM -----------------------------------------------------------------------------
+Public Function ReplaceItem(Optional ByVal Key As Variant _
+ , Optional ByVal Value As Variant _
+ ) As Boolean
+&apos;&apos;&apos; Replace the item value
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Key: must exist in the dictionary
+&apos;&apos;&apos; Returns: True if successful
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; UNKNOWNKEYERROR: the old key does not exist
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; myDict.ReplaceItem(&quot;Key&quot;, NewValue)
+
+Dim oItemMap As ItemMap &apos; Content to update in the MapItems array
+Dim lIndex As Long &apos; Entry in the MapItems array
+Const cstThisSub = &quot;Dictionary.ReplaceItem&quot;
+Const cstSubArgs = &quot;Key, Value&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ ReplaceItem = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(Key, &quot;Key&quot;, V_STRING) Then GoTo Catch
+ If IsArray(Value) Then
+ If Not SF_Utils._ValidateArray(Value, &quot;Value&quot;) Then GoTo Catch
+ Else
+ If Not SF_Utils._Validate(Value, &quot;Value&quot;) Then GoTo Catch
+ End If
+ End If
+ If Not Exists(Key) Then GoTo CatchUnknown
+
+Try:
+ &apos; Find entry in MapItems and update it with the new value
+ lIndex = MapKeys.Item(Key)
+ oItemMap = MapItems(lIndex)
+ oItemMap.Value = Value
+ ReplaceItem = True
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchUnknown:
+ SF_Exception.RaiseFatal(UNKNOWNKEYERROR, &quot;Key&quot;, Key)
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Dictionary.ReplaceItem
+
+REM -----------------------------------------------------------------------------
+Public Function ReplaceKey(Optional ByVal Key As Variant _
+ , Optional ByVal Value As Variant _
+ ) As Boolean
+&apos;&apos;&apos; Replace existing key
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Key: must exist in the dictionary
+&apos;&apos;&apos; Value: must not exist in the dictionary
+&apos;&apos;&apos; Returns: True if successful
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; UNKNOWNKEYERROR: the old key does not exist
+&apos;&apos;&apos; DUPLICATEKEYERROR: the new key exists
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; myDict.ReplaceKey(&quot;OldKey&quot;, &quot;NewKey&quot;)
+
+Dim oItemMap As ItemMap &apos; Content to update in the MapItems array
+Dim lIndex As Long &apos; Entry in the MapItems array
+Const cstThisSub = &quot;Dictionary.ReplaceKey&quot;
+Const cstSubArgs = &quot;Key, Value&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ ReplaceKey = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(Key, &quot;Key&quot;, V_STRING) Then GoTo Catch
+ If Not SF_Utils._Validate(Value, &quot;Value&quot;, V_STRING) Then GoTo Catch
+ End If
+ If Not Exists(Key) Then GoTo CatchUnknown
+ If Value = Space(Len(Value)) Then GoTo CatchInvalid
+ If Exists(Value) Then GoTo CatchDuplicate
+
+Try:
+ &apos; Remove the Key entry and create a new one in MapKeys
+ With MapKeys
+ lIndex = .Item(Key)
+ .Remove(Key)
+ .Add(lIndex, Value)
+ End With
+ oItemMap = MapItems(lIndex)
+ oItemMap.Key = Value
+ ReplaceKey = True
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchUnknown:
+ SF_Exception.RaiseFatal(UNKNOWNKEYERROR, &quot;Key&quot;, Key)
+ GoTo Finally
+CatchDuplicate:
+ SF_Exception.RaiseFatal(DUPLICATEKEYERROR, &quot;Value&quot;, Value)
+ GoTo Finally
+CatchInvalid:
+ SF_Exception.RaiseFatal(INVALIDKEYERROR, &quot;Key&quot;)
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Dictionary.ReplaceKey
+
+REM -----------------------------------------------------------------------------
+Public Function SetProperty(Optional ByVal PropertyName As Variant _
+ , Optional ByRef Value As Variant _
+ ) As Boolean
+&apos;&apos;&apos; Set a new value to the given property
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; PropertyName: the name of the property as a string
+&apos;&apos;&apos; Value: its new value
+&apos;&apos;&apos; Exceptions
+&apos;&apos;&apos; ARGUMENTERROR The property does not exist
+
+Const cstThisSub = &quot;Dictionary.SetProperty&quot;
+Const cstSubArgs = &quot;PropertyName, Value&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ SetProperty = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
+ End If
+
+Try:
+ Select Case UCase(PropertyName)
+ Case Else
+ End Select
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Dictionary.SetProperty
+
+REM =========================================================== PRIVATE FUNCTIONS
+
+REM -----------------------------------------------------------------------------
+Private Function _PropertyGet(Optional ByVal psProperty As String _
+ , Optional pvKey As Variant _
+ )
+&apos;&apos;&apos; Return the named property
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; psProperty: the name of the property
+&apos;&apos;&apos; pvKey: the key to retrieve, numeric or string
+
+Dim vItemMap As Variant &apos; Entry in the MapItems array
+Dim vArray As Variant &apos; To get Keys or Values
+Dim i As Long
+Dim cstThisSub As String
+Dim cstSubArgs As String
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+
+ cstThisSub = &quot;SF_Dictionary.get&quot; &amp; psProperty
+ If IsMissing(pvKey) Then cstSubArgs = &quot;&quot; Else cstSubArgs = &quot;[Key]&quot;
+
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+ Select Case UCase(psProperty)
+ Case UCase(&quot;Count&quot;)
+ _PropertyGet = _MapSize - _MapRemoved
+ Case UCase(&quot;Item&quot;)
+ If Not SF_Utils._Validate(pvKey, &quot;Key&quot;, V_STRING) Then GoTo Catch
+ If Exists(pvKey) Then _PropertyGet = MapItems(MapKeys(pvKey)).Value Else _PropertyGet = Empty
+ Case UCase(&quot;Keys&quot;), UCase(&quot;Items&quot;)
+ vArray = Array()
+ If _MapSize - _MapRemoved - 1 &gt;= 0 Then
+ ReDim vArray(0 To (_MapSize - _MapRemoved - 1))
+ i = -1
+ For each vItemMap In MapItems()
+ If Not IsEmpty(vItemMap) Then
+ i = i + 1
+ If UCase(psProperty) = &quot;KEYS&quot; Then vArray(i) = vItemMap.Key Else vArray(i) = vItemMap.Value
+ End If
+ Next vItemMap
+ End If
+ _PropertyGet = vArray
+ End Select
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Dictionary._PropertyGet
+
+REM -----------------------------------------------------------------------------
+Private Function _Repr() As String
+&apos;&apos;&apos; Convert the Dictionary instance to a readable string, typically for debugging purposes (DebugPrint ...)
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Return:
+&apos;&apos;&apos; &quot;[Dictionary] (key1:value1, key2:value2, ...)
+
+Dim sDict As String &apos; Return value
+Dim vKeys As Variant &apos; Array of keys
+Dim sKey As String &apos; Tempry key
+Dim vItem As Variant &apos; Tempry item
+Const cstDictEmpty = &quot;[Dictionary] ()&quot;
+Const cstDict = &quot;[Dictionary]&quot;
+Const cstMaxLength = 50 &apos; Maximum length for items
+Const cstSeparator = &quot;, &quot;
+
+ _Repr = &quot;&quot;
+
+ If Count = 0 Then
+ sDict = cstDictEmpty
+ Else
+ sDict = cstDict &amp; &quot; (&quot;
+ vKeys = Keys
+ For Each sKey in vKeys
+ vItem = Item(sKey)
+ sDict = sDict &amp; sKey &amp; &quot;:&quot; &amp; SF_Utils._Repr(vItem, cstMaxLength) &amp; cstSeparator
+ Next sKey
+ sDict = Left(sDict, Len(sDict) - Len(cstSeparator)) &amp; &quot;)&quot; &apos; Suppress last comma
+ End If
+
+ _Repr = sDict
+
+End Function &apos; ScriptForge.SF_Dictionary._Repr
+
+REM ============================================ END OF SCRIPTFORGE.SF_DICTIONARY
+</script:module> \ No newline at end of file
diff --git a/wizards/source/scriptforge/SF_Exception.xba b/wizards/source/scriptforge/SF_Exception.xba
new file mode 100644
index 000000000..11e97b02b
--- /dev/null
+++ b/wizards/source/scriptforge/SF_Exception.xba
@@ -0,0 +1,1381 @@
+<?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_Exception" 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; Exception (aka SF_Exception)
+&apos;&apos;&apos; =========
+&apos;&apos;&apos; Generic singleton class for Basic code debugging and error handling
+&apos;&apos;&apos;
+&apos;&apos;&apos; Errors may be generated by
+&apos;&apos;&apos; the Basic run-time error detection
+&apos;&apos;&apos; in the ScriptForge code =&gt; RaiseAbort()
+&apos;&apos;&apos; in a user code =&gt; Raise()
+&apos;&apos;&apos; an error detection implemented
+&apos;&apos;&apos; in the ScriptForge code =&gt; RaiseFatal()
+&apos;&apos;&apos; in a user code =&gt; Raise() or RaiseWarning()
+&apos;&apos;&apos;
+&apos;&apos;&apos; When a run-time error occurs, the properties of the Exception object are filled
+&apos;&apos;&apos; with information that uniquely identifies the error and information that can be used to handle it
+&apos;&apos;&apos; The SF_Exception object is in this context similar to the VBA Err object
+&apos;&apos;&apos; See https://docs.microsoft.com/en-us/office/vba/language/reference/user-interface-help/err-object
+&apos;&apos;&apos; The Number property identifies the error: it can be a numeric value or a string
+&apos;&apos;&apos; Numeric values up to 2000 are considered Basic run-time errors
+&apos;&apos;&apos;
+&apos;&apos;&apos; The &quot;console&quot; logs events, actual variable values, errors, ... It is an easy mean
+&apos;&apos;&apos; to debug Basic programs especially when the IDE is not usable, f.i. in Calc user defined functions
+&apos;&apos;&apos; or during control events processing
+&apos;&apos;&apos; =&gt; DebugPrint()
+&apos;&apos;&apos;
+&apos;&apos;&apos; The usual behaviour of the application when an error occurs is:
+&apos;&apos;&apos; 1. Log the error in the console
+&apos;&apos;&apos; 2, Inform the user about the error with either a standard or a customized message
+&apos;&apos;&apos; 3. Optionally, stop the execution of the current macro
+&apos;&apos;&apos;
+&apos;&apos;&apos; Detailed user documentation:
+&apos;&apos;&apos; https://help.libreoffice.org/latest/en-US/text/sbasic/shared/03/sf_exception.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
+
+&apos; SF_Utils
+Const MISSINGARGERROR = &quot;MISSINGARGERROR&quot;
+Const ARGUMENTERROR = &quot;ARGUMENTERROR&quot;
+Const ARRAYERROR = &quot;ARRAYERROR&quot;
+Const FILEERROR = &quot;FILEERROR&quot;
+
+&apos; SF_Array
+Const ARRAYSEQUENCEERROR = &quot;ARRAYSEQUENCEERROR&quot;
+Const ARRAYINSERTERROR = &quot;ARRAYINSERTERROR&quot;
+Const ARRAYINDEX1ERROR = &quot;ARRAYINDEX1ERROR&quot;
+Const ARRAYINDEX2ERROR = &quot;ARRAYINDEX2ERROR&quot;
+Const CSVPARSINGERROR = &quot;CSVPARSINGERROR&quot;
+Const CSVOVERFLOWWARNING = &quot;CSVOVERFLOWWARNING&quot;
+
+&apos; SF_Dictionary
+Const DUPLICATEKEYERROR = &quot;DUPLICATEKEYERROR&quot;
+Const UNKNOWNKEYERROR = &quot;UNKNOWNKEYERROR&quot;
+Const INVALIDKEYERROR = &quot;INVALIDKEYERROR&quot;
+
+&apos; SF_FileSystem
+Const UNKNOWNFILEERROR = &quot;UNKNOWNFILEERROR&quot;
+Const UNKNOWNFOLDERERROR = &quot;UNKNOWNFOLDERERROR&quot;
+Const NOTAFILEERROR = &quot;NOTAFILEERROR&quot;
+Const NOTAFOLDERERROR = &quot;NOTAFOLDERERROR&quot;
+Const OVERWRITEERROR = &quot;OVERWRITEERROR&quot;
+Const READONLYERROR = &quot;READONLYERROR&quot;
+Const NOFILEMATCHERROR = &quot;NOFILEMATCHFOUND&quot;
+Const FOLDERCREATIONERROR = &quot;FOLDERCREATIONERROR&quot;
+
+&apos; SF_Services
+Const UNKNOWNSERVICEERROR = &quot;UNKNOWNSERVICEERROR&quot;
+Const SERVICESNOTLOADEDERROR = &quot;SERVICESNOTLOADEDERROR&quot;
+
+&apos; SF_Session
+Const CALCFUNCERROR = &quot;CALCFUNCERROR&quot;
+Const NOSCRIPTERROR = &quot;NOSCRIPTERROR&quot;
+Const SCRIPTEXECERROR = &quot;SCRIPTEXECERROR&quot;
+Const WRONGEMAILERROR = &quot;WRONGEMAILERROR&quot;
+Const SENDMAILERROR = &quot;SENDMAILERROR&quot;
+
+&apos; SF_TextStream
+Const FILENOTOPENERROR = &quot;FILENOTOPENERROR&quot;
+Const FILEOPENMODEERROR = &quot;FILEOPENMODEERROR&quot;
+Const ENDOFFILEERROR = &quot;ENDOFFILEERROR&quot;
+
+&apos; SF_UI
+Const DOCUMENTERROR = &quot;DOCUMENTERROR&quot;
+Const DOCUMENTCREATIONERROR = &quot;DOCUMENTCREATIONERROR&quot;
+Const DOCUMENTOPENERROR = &quot;DOCUMENTOPENERROR&quot;
+Const BASEDOCUMENTOPENERROR = &quot;BASEDOCUMENTOPENERROR&quot;
+
+&apos; SF_Document
+Const DOCUMENTDEADERROR = &quot;DOCUMENTDEADERROR&quot;
+Const DOCUMENTSAVEERROR = &quot;DOCUMENTSAVEERROR&quot;
+Const DOCUMENTSAVEASERROR = &quot;DOCUMENTSAVEASERROR&quot;
+Const DOCUMENTREADONLYERROR = &quot;DOCUMENTREADONLYERROR&quot;
+Const DBCONNECTERROR = &quot;DBCONNECTERROR&quot;
+
+&apos; SF_Calc
+Const CALCADDRESSERROR = &quot;CALCADDRESSERROR&quot;
+Const DUPLICATESHEETERROR = &quot;DUPLICATESHEETERROR&quot;
+Const OFFSETADDRESSERROR = &quot;OFFSETADDRESSERROR&quot;
+Const DUPLICATECHARTERROR = &quot;DUPLICATECHARTERROR&quot;
+Const RANGEEXPORTERROR = &quot;RANGEEXPORTERROR&quot;
+
+&apos; SF_Chart
+Const CHARTEXPORTERROR = &quot;CHARTEXPORTERROR&quot;
+
+&apos; SF_Form
+Const FORMDEADERROR = &quot;FORMDEADERROR&quot;
+Const CALCFORMNOTFOUNDERROR = &quot;CALCFORMNOTFOUNDERROR&quot;
+Const WRITERFORMNOTFOUNDERROR = &quot;WRITERFORMNOTFOUNDERROR&quot;
+Const BASEFORMNOTFOUNDERROR = &quot;BASEFORMNOTFOUNDERROR&quot;
+Const SUBFORMNOTFOUNDERROR = &quot;SUBFORMNOTFOUNDERROR&quot;
+Const FORMCONTROLTYPEERROR = &quot;FORMCONTROLTYPEERROR&quot;
+
+&apos; SF_Dialog
+Const DIALOGNOTFOUNDERROR = &quot;DIALOGNOTFOUNDERROR&quot;
+Const DIALOGDEADERROR = &quot;DIALOGDEADERROR&quot;
+Const CONTROLTYPEERROR = &quot;CONTROLTYPEERROR&quot;
+Const TEXTFIELDERROR = &quot;TEXTFIELDERROR&quot;
+
+&apos; SF_Database
+Const DBREADONLYERROR = &quot;DBREADONLYERROR&quot;
+Const SQLSYNTAXERROR = &quot;SQLSYNTAXERROR&quot;
+
+&apos; Python
+Const PYTHONSHELLERROR = &quot;PYTHONSHELLERROR&quot;
+
+&apos; SF_UnitTest
+Const UNITTESTLIBRARYERROR = &quot;UNITTESTLIBRARYERROR&quot;
+Const UNITTESTMETHODERROR = &quot;UNITTESTMETHODERROR&quot;
+
+REM ============================================================= PRIVATE MEMBERS
+
+&apos; User defined errors
+Private _Number As Variant &apos; Error number/code (Integer or String)
+Private _Source As Variant &apos; Where the error occurred: a module, a Sub/Function, ...
+Private _Description As String &apos; The error message
+
+&apos; System run-time errors
+Private _SysNumber As Long &apos; Alias of Err
+Private _SysSource As Long &apos; Alias of Erl
+Private _SysDescription As String &apos; Alias of Error$
+
+REM ============================================================ MODULE CONSTANTS
+
+Const RUNTIMEERRORS = 2000 &apos; Upper limit of Basic run-time errors
+Const CONSOLENAME = &quot;ConsoleLines&quot; &apos; Name of control in the console dialog
+
+REM ===================================================== CONSTRUCTOR/DESTRUCTOR
+
+REM -----------------------------------------------------------------------------
+Public Function Dispose() As Variant
+ Set Dispose = Nothing
+End Function &apos; ScriptForge.SF_Exception Explicit destructor
+
+REM ================================================================== PROPERTIES
+
+REM -----------------------------------------------------------------------------
+Property Get Description() As Variant
+&apos;&apos;&apos; Returns the description of the last error that has occurred
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; myException.Description
+ Description = _PropertyGet(&quot;Description&quot;)
+End Property &apos; ScriptForge.SF_Exception.Description (get)
+
+REM -----------------------------------------------------------------------------
+Property Let Description(ByVal pvDescription As Variant)
+&apos;&apos;&apos; Set the description of the last error that has occurred
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; myException.Description = &quot;Not smart to divide by zero&quot;
+ _PropertySet &quot;Description&quot;, pvDescription
+End Property &apos; ScriptForge.SF_Exception.Description (let)
+
+REM -----------------------------------------------------------------------------
+Property Get Number() As Variant
+&apos;&apos;&apos; Returns the code of the last error that has occurred
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; myException.Number
+ Number = _PropertyGet(&quot;Number&quot;)
+End Property &apos; ScriptForge.SF_Exception.Number (get)
+
+REM -----------------------------------------------------------------------------
+Property Let Number(ByVal pvNumber As Variant)
+&apos;&apos;&apos; Set the code of the last error that has occurred
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; myException.Number = 11 &apos; Division by 0
+ _PropertySet &quot;Number&quot;, pvNumber
+End Property &apos; ScriptForge.SF_Exception.Number (let)
+
+REM -----------------------------------------------------------------------------
+Property Get Source() As Variant
+&apos;&apos;&apos; Returns the location of the last error that has occurred
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; myException.Source
+ Source = _PropertyGet(&quot;Source&quot;)
+End Property &apos; ScriptForge.SF_Exception.Source (get)
+
+REM -----------------------------------------------------------------------------
+Property Let Source(ByVal pvSource As Variant)
+&apos;&apos;&apos; Set the location of the last error that has occurred
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; myException.Source = 123 &apos; Line # 123. Source may also be a string
+ _PropertySet &quot;Source&quot;, pvSource
+End Property &apos; ScriptForge.SF_Exception.Source (let)
+
+REM -----------------------------------------------------------------------------
+Property Get ObjectType As String
+&apos;&apos;&apos; Only to enable object representation
+ ObjectType = &quot;SF_Exception&quot;
+End Property &apos; ScriptForge.SF_String.ObjectType
+
+REM -----------------------------------------------------------------------------
+Property Get ServiceName As String
+&apos;&apos;&apos; Internal use
+ ServiceName = &quot;ScriptForge.Exception&quot;
+End Property &apos; ScriptForge.SF_Exception.ServiceName
+
+REM ===================================================================== METHODS
+
+REM -----------------------------------------------------------------------------
+Public Sub Clear()
+&apos;&apos;&apos; Reset the current error status and clear the SF_Exception object
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; On Local Error GoTo Catch
+&apos;&apos;&apos; &apos; ...
+&apos;&apos;&apos; Catch:
+&apos;&apos;&apos; SF_Exception.Clear() &apos; Deny the error
+
+Const cstThisSub = &quot;Exception.Clear&quot;
+Const cstSubArgs = &quot;&quot;
+
+Check:
+
+Try:
+ With SF_Exception
+ ._Number = Empty
+ ._Source = Empty
+ ._Description = &quot;&quot;
+ ._SysNumber = 0
+ ._SysSource = 0
+ ._SysDescription = &quot;&quot;
+ End With
+
+Finally:
+ On Error GoTo 0
+ Exit Sub
+Catch:
+ GoTo Finally
+End Sub &apos; ScriptForge.SF_Exception.Clear
+
+REM -----------------------------------------------------------------------------
+Public Sub Console(Optional ByVal Modal As Variant, _
+ Optional ByRef _Context As Variant _
+ )
+&apos;&apos;&apos; Display the console messages in a modal or non-modal dialog
+&apos;&apos;&apos; If the dialog is already active, when non-modal, it is brought to front
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Modal: Boolean. Default = True
+&apos;&apos;&apos; _Context: From Python, the XComponentXontext (FOR INTERNAL USE ONLY)
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; SF_Exception.Console()
+
+Dim bConsoleActive As Boolean &apos; When True, dialog is active
+Dim oModalBtn As Object &apos; Modal close button
+Dim oNonModalBtn As Object &apos; Non modal close button
+Const cstThisSub = &quot;Exception.Console&quot;
+Const cstSubArgs = &quot;[Modal=True]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Finally &apos; Never interrupt processing
+
+Check:
+ If IsMissing(Modal) Or IsEmpty(Modal) Then Modal = True
+ If IsMissing(_Context) Or IsEmpty(_Context) Then _Context = Nothing
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(Modal, &quot;Modal&quot;, V_BOOLEAN) Then GoTo Finally
+ End If
+
+Try:
+ With _SF_
+ bConsoleActive = False
+ If Not IsNull(.ConsoleDialog) Then bConsoleActive = .ConsoleDialog._IsStillAlive(False) &apos; False to not raise an error
+ If bConsoleActive And Modal = False Then
+ &apos; Bring to front
+ .ConsoleDialog.Activate()
+ Else
+ &apos; Initialize dialog and fill with actual data
+ &apos; The dual modes (modal and non-modal) require to have 2 close buttons o/w only 1 is visible
+ &apos; - a usual OK button
+ &apos; - a Default button triggering the Close action
+ Set .ConsoleDialog = CreateScriptService(&quot;SFDialogs.Dialog&quot;, &quot;GlobalScope&quot;, &quot;ScriptForge&quot;, &quot;dlgConsole&quot;, _Context)
+ &apos; Setup labels and visibility
+ Set oModalBtn = .ConsoleDialog.Controls(&quot;CloseModalButton&quot;)
+ Set oNonModalBtn = .ConsoleDialog.Controls(&quot;CloseNonModalButton&quot;)
+ oModalBtn.Visible = Modal
+ oNonModalBtn.Visible = CBool(Not Modal)
+ &apos; Load console lines
+ _ConsoleRefresh()
+ .ConsoleDialog.Execute(Modal)
+ &apos; Terminate the modal dialog
+ If Modal Then
+ Set .ConsoleControl = .ConsoleControl.Dispose()
+ Set .ConsoleDialog = .ConsoleDialog.Dispose()
+ End If
+ End If
+ End With
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Sub
+End Sub &apos; ScriptForge.SF_Exception.Console
+
+REM -----------------------------------------------------------------------------
+Public Sub ConsoleClear(Optional ByVal Keep)
+&apos;&apos;&apos; Clear the console keeping an optional number of recent messages
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Keep: the number of messages to keep
+&apos;&apos;&apos; If Keep is bigger than the number of messages stored in the console,
+&apos;&apos;&apos; the console is not cleared
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; SF_Exception.ConsoleClear(5)
+
+Dim lConsole As Long &apos; UBound of ConsoleLines
+Const cstThisSub = &quot;Exception.ConsoleClear&quot;
+Const cstSubArgs = &quot;[Keep=0]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Finally &apos; Never interrupt processing
+
+Check:
+ If IsMissing(Keep) Or IsEmpty(Keep) Then Keep = 0
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(Keep, &quot;Keep&quot;, V_NUMERIC) Then GoTo Finally
+ End If
+
+Try:
+ With _SF_
+ If Keep &lt;= 0 Then
+ .ConsoleLines = Array()
+ Else
+ lConsole = UBound(.ConsoleLines)
+ If Keep &lt; lConsole + 1 Then .ConsoleLines = SF_Array.Slice(.ConsoleLines, lConsole - Keep + 1)
+ End If
+ End With
+
+ &apos; If active, the console dialog needs to be refreshed
+ _ConsoleRefresh()
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Sub
+End Sub &apos; ScriptForge.SF_Exception.ConsoleClear
+
+REM -----------------------------------------------------------------------------
+Public Function ConsoleToFile(Optional ByVal FileName As Variant) As Boolean
+&apos;&apos;&apos; Export the content of the console to a text file
+&apos;&apos;&apos; If the file exists and the console is not empty, it is overwritten without warning
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; FileName: the complete file name to export to. If it exists, is overwritten without warning
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if the file could be created
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Exception.ConsoleToFile(&quot;myFile.txt&quot;)
+
+Dim bExport As Boolean &apos; Return value
+Dim oFile As Object &apos; Output file handler
+Dim sLine As String &apos; A single line
+Const cstThisSub = &quot;Exception.ConsoleToFile&quot;
+Const cstSubArgs = &quot;FileName&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bExport = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateFile(FileName, &quot;FileName&quot;) Then GoTo Finally
+ End If
+
+Try:
+
+ If UBound(_SF_.ConsoleLines) &gt; -1 Then
+ Set oFile = SF_FileSystem.CreateTextFile(FileName, Overwrite := True)
+ If Not IsNull(oFile) Then
+ With oFile
+ For Each sLine In _SF_.ConsoleLines
+ .WriteLine(sLine)
+ Next sLine
+ .CloseFile()
+ End With
+ End If
+ bExport = True
+ End If
+
+Finally:
+ If Not IsNull(oFile) Then Set oFile = oFile.Dispose()
+ ConsoleToFile = bExport
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Exception.ConsoleToFile
+
+REM -----------------------------------------------------------------------------
+Public Sub DebugDisplay(ParamArray pvArgs() As Variant)
+&apos;&apos;&apos; Display the list of arguments in a readable form in a message box
+&apos;&apos;&apos; Arguments are separated by a LINEFEED character
+&apos;&apos;&apos; The maximum length of each individual argument = 1024 characters
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Any number of arguments of any type
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Exception.DebugDisplay(a, Array(1, 2, 3), , &quot;line1&quot; &amp; Chr(10) &amp; &quot;Line2&quot;, DateSerial(2020, 04, 09))
+
+Dim sOutputMsg As String &apos; Line to display
+Dim sOutputCon As String &apos; Line to write in console
+Dim sArgMsg As String &apos; Single argument
+Dim sArgCon As String &apos; Single argument
+Dim i As Integer
+Const cstTab = 4
+Const cstMaxLength = 1024
+Const cstThisSub = &quot;Exception.DebugDisplay&quot;
+Const cstSubArgs = &quot;Arg0, [Arg1, ...]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error Goto Finally &apos; Never interrupt processing
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+Try:
+ &apos; Build new console line
+ sOutputMsg = &quot;&quot; : sOutputCon = &quot;&quot;
+ For i = 0 To UBound(pvArgs)
+ If IsError(pvArgs(i)) Then pvArgs(i) = &quot;&quot;
+ sArgMsg = Iif(i = 0, &quot;&quot;, SF_String.sfNEWLINE) &amp; SF_Utils._Repr(pvArgs(i), cstMaxLength) &apos;Do not use SF_String.Represent()
+ sArgCon = Iif(i = 0, &quot;&quot;, SF_String.sfTAB) &amp; SF_Utils._Repr(pvArgs(i), cstMaxLength)
+ sOutputMsg = sOutputMsg &amp; sArgMsg
+ sOutputCon = sOutputCon &amp; sArgCon
+ Next i
+
+ &apos; Add to actual console
+ _SF_._AddToConsole(SF_String.ExpandTabs(sOutputCon, cstTab))
+ &apos; Display the message
+ MsgBox(sOutputMsg, MB_OK + MB_ICONINFORMATION, &quot;DebugDisplay&quot;)
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Sub
+End Sub &apos; ScriptForge.SF_Exception.DebugDisplay
+
+REM -----------------------------------------------------------------------------
+Public Sub DebugPrint(ParamArray pvArgs() As Variant)
+&apos;&apos;&apos; Print the list of arguments in a readable form in the console
+&apos;&apos;&apos; Arguments are separated by a TAB character (simulated by spaces)
+&apos;&apos;&apos; The maximum length of each individual argument = 1024 characters
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Any number of arguments of any type
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Exception.DebugPrint(a, Array(1, 2, 3), , &quot;line1&quot; &amp; Chr(10) &amp; &quot;Line2&quot;, DateSerial(2020, 04, 09))
+
+Dim sOutput As String &apos; Line to write in console
+Dim sArg As String &apos; Single argument
+Dim i As Integer
+Const cstTab = 4
+Const cstMaxLength = 1024
+Const cstThisSub = &quot;Exception.DebugPrint&quot;
+Const cstSubArgs = &quot;Arg0, [Arg1, ...]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error Goto Finally &apos; Never interrupt processing
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+Try:
+ &apos; Build new console line
+ sOutput = &quot;&quot;
+ For i = 0 To UBound(pvArgs)
+ If IsError(pvArgs(i)) Then pvArgs(i) = &quot;&quot;
+ sArg = Iif(i = 0, &quot;&quot;, SF_String.sfTAB) &amp; SF_Utils._Repr(pvArgs(i), cstMaxLength) &apos;Do not use SF_String.Represent()
+ sOutput = sOutput &amp; sArg
+ Next i
+
+ &apos; Add to actual console
+ _SF_._AddToConsole(SF_String.ExpandTabs(sOutput, cstTab))
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Sub
+End Sub &apos; ScriptForge.SF_Exception.DebugPrint
+
+REM -----------------------------------------------------------------------------
+Public Function GetProperty(Optional ByVal PropertyName As Variant) As Variant
+&apos;&apos;&apos; Return the actual value of the given property
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; PropertyName: the name of the property as a string
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The actual value of the property
+&apos;&apos;&apos; If the property does not exist, returns Null
+&apos;&apos;&apos; Exceptions
+&apos;&apos;&apos; ARGUMENTERROR The property does not exist
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; myException.GetProperty(&quot;MyProperty&quot;)
+
+Const cstThisSub = &quot;Exception.GetProperty&quot;
+Const cstSubArgs = &quot;&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ GetProperty = Null
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
+ End If
+
+Try:
+ GetProperty = _PropertyGet(PropertyName)
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Exception.GetProperty
+
+REM -----------------------------------------------------------------------------
+Public Function Methods() As Variant
+&apos;&apos;&apos; Return the list of public methods of the Exception service as an array
+
+ Methods = Array( _
+ &quot;Clear&quot; _
+ , &quot;Console&quot; _
+ , &quot;ConsoleClear&quot; _
+ , &quot;ConsoleToFile&quot; _
+ , &quot;DebugPrint&quot; _
+ , &quot;Raise&quot; _
+ , &quot;RaiseAbort&quot; _
+ , &quot;RaiseFatal&quot; _
+ , &quot;RaiseWarning&quot; _
+ )
+
+End Function &apos; ScriptForge.SF_Exception.Methods
+
+REM -----------------------------------------------------------------------------
+Public Function Properties() As Variant
+&apos;&apos;&apos; Return the list or properties of the Timer class as an array
+
+ Properties = Array( _
+ &quot;Description&quot; _
+ , &quot;Number&quot; _
+ , &quot;Source&quot; _
+ )
+
+End Function &apos; ScriptForge.SF_Exception.Properties
+
+REM -----------------------------------------------------------------------------
+Public Sub PythonPrint(ParamArray pvArgs() As Variant)
+&apos;&apos;&apos; Display the list of arguments in a readable form in the Python console
+&apos;&apos;&apos; Arguments are separated by a TAB character (simulated by spaces)
+&apos;&apos;&apos; The maximum length of each individual argument = 1024 characters
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Any number of arguments of any type
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Exception.PythonPrint(a, Array(1, 2, 3), , &quot;line1&quot; &amp; Chr(10) &amp; &quot;Line2&quot;, DateSerial(2020, 04, 09))
+
+Dim sOutput As String &apos; Line to write in console
+Dim sArg As String &apos; Single argument
+Dim i As Integer
+Const cstTab = 4
+Const cstMaxLength = 1024
+Const cstPyHelper = &quot;$&quot; &amp; &quot;_SF_Exception__PythonPrint&quot;
+Const cstThisSub = &quot;Exception.PythonPrint&quot;
+Const cstSubArgs = &quot;Arg0, [Arg1, ...]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error Goto Finally &apos; Never interrupt processing
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+Try:
+ &apos; Build new console line
+ sOutput = &quot;&quot;
+ For i = 0 To UBound(pvArgs)
+ If IsError(pvArgs(i)) Then pvArgs(i) = &quot;&quot;
+ sArg = Iif(i = 0, &quot;&quot;, SF_String.sfTAB) &amp; SF_Utils._Repr(pvArgs(i), cstMaxLength)
+ sOutput = sOutput &amp; sArg
+ Next i
+
+ &apos; Add to actual console
+ sOutput = SF_String.ExpandTabs(sOutput, cstTab)
+ _SF_._AddToConsole(sOutput)
+ &apos; Display the message in the Python shell console
+ With ScriptForge.SF_Session
+ .ExecutePythonScript(.SCRIPTISSHARED, _SF_.PythonHelper &amp; cstPyHelper, sOutput)
+ End With
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Sub
+End Sub &apos; ScriptForge.SF_Exception.PythonPrint
+
+REM -----------------------------------------------------------------------------
+Public Sub Raise(Optional ByVal Number As Variant _
+ , Optional ByVal Source As Variant _
+ , Optional ByVal Description As Variant _
+ )
+&apos;&apos;&apos; Generate a run-time error. An error message is displayed to the user and logged
+&apos;&apos;&apos; in the console. The execution is STOPPED
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Number: the error number, may be numeric or string
+&apos;&apos;&apos; If numeric and &lt;= 2000, it is considered a LibreOffice Basic run-time error (default = Err)
+&apos;&apos;&apos; Source: the line where the error occurred (default = Erl) or any string describing the location of the error
+&apos;&apos;&apos; Description: the error message to log in the console and to display to the user
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; On Local Error GoTo Catch
+&apos;&apos;&apos; &apos; ...
+&apos;&apos;&apos; Catch:
+&apos;&apos;&apos; SF_Exception.Raise() &apos; Standard behaviour
+&apos;&apos;&apos; SF_Exception.Raise(11) &apos; Force division by zero
+&apos;&apos;&apos; SF_Exception.Raise(&quot;MYAPPERROR&quot;, &quot;myFunction&quot;, &quot;Application error&quot;)
+&apos;&apos;&apos; SF_Exception.Raise(,, &quot;To divide by zero is not a good idea !&quot;)
+
+Dim sMessage As String &apos; Error message to log and to display
+Dim L10N As Object &apos; Alias to LocalizedInterface
+Const cstThisSub = &quot;Exception.Raise&quot;
+Const cstSubArgs = &quot;[Number=Err], [Source=Erl], [Description]&quot;
+
+ &apos; Save Err, Erl, .. values before any On Error ... statement
+ SF_Exception._CaptureSystemError()
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+
+Check:
+ If IsMissing(Number) Or IsEmpty(Number) Then Number = -1
+ If IsMissing(Source) Or IsEmpty(Source) Then Source = -1
+ If IsMissing(Description) Or IsEmpty(Description) Then Description = &quot;&quot;
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(Number, &quot;Number&quot;, Array(V_STRING, V_NUMERIC)) Then GoTo Finally
+ If Not SF_Utils._Validate(Source, &quot;Source&quot;, Array(V_STRING, V_NUMERIC)) Then GoTo Finally
+ If Not SF_Utils._Validate(Description, &quot;Description&quot;, V_STRING) Then GoTo Finally
+ End If
+
+Try:
+ With SF_Exception
+ If Number &gt;= 0 Then .Number = Number
+ If VarType(Source) = V_STRING Then
+ If Len(Source) &gt; 0 Then .Source = Source
+ ElseIf Source &gt;= 0 Then &apos; -1 = Default =&gt; no change
+ .Source = Source
+ End If
+ If Len(Description) &gt; 0 Then .Description = Description
+
+ &apos; Log and display
+ Set L10N = _SF_._GetLocalizedInterface()
+ sMessage = L10N.GetText(&quot;LONGERRORDESC&quot;, .Number, .Source, .Description)
+ .DebugPrint(sMessage)
+ If _SF_.DisplayEnabled Then MsgBox L10N.GetText(&quot;ERRORNUMBER&quot;, .Number) _
+ &amp; SF_String.sfNewLine &amp; L10N.GetText(&quot;ERRORLOCATION&quot;, .Source) _
+ &amp; SF_String.sfNewLine &amp; .Description _
+ , MB_OK + MB_ICONSTOP _
+ , L10N.GetText(&quot;ERRORNUMBER&quot;, .Number)
+ .Clear()
+ End With
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ If _SF_.StopWhenError Then
+ _SF_._StackReset()
+ Stop
+ End If
+ Exit Sub
+Catch:
+ GoTo Finally
+End Sub &apos; ScriptForge.SF_Exception.Raise
+
+REM -----------------------------------------------------------------------------
+Public Sub RaiseAbort(Optional ByVal Source As Variant)
+&apos;&apos;&apos; Manage a run-time error that occurred inside the ScriptForge piece of software itself.
+&apos;&apos;&apos; The event is logged.
+&apos;&apos;&apos; The execution is STOPPED
+&apos;&apos;&apos; For INTERNAL USE only
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Source: the line where the error occurred
+
+Dim sLocation As String &apos; Common header in error messages: location of error
+Dim vLocation As Variant &apos; Split array (library, module, method)
+Dim sMessage As String &apos; Error message to log and to display
+Dim L10N As Object &apos; Alias to LocalizedInterface
+Const cstTabSize = 4
+Const cstThisSub = &quot;Exception.RaiseAbort&quot;
+Const cstSubArgs = &quot;[Source=Erl]&quot;
+
+ &apos; Save Err, Erl, .. values before any On Error ... statement
+ SF_Exception._CaptureSystemError()
+ On Local Error Resume Next
+
+Check:
+ If IsMissing(Source) Or IsEmpty(Source) Then Source = &quot;&quot;
+
+Try:
+ With SF_Exception
+
+ &apos; Prepare message header
+ Set L10N = _SF_._GetLocalizedInterface()
+ If Len(_SF_.MainFunction) &gt; 0 Then &apos; MainFunction = [Library.]Module.Method
+ vLocation = Split(_SF_.MainFunction, &quot;.&quot;)
+ If UBound(vLocation) &lt; 2 Then vLocation = SF_Array.Prepend(vLocation, &quot;ScriptForge&quot;)
+ sLocation = L10N.GetText(&quot;VALIDATESOURCE&quot;, vLocation(0), vLocation(1), vLocation(2)) &amp; &quot;\n\n\n&quot;
+ Else
+ sLocation = &quot;&quot;
+ End If
+
+ &apos; Log and display
+ sMessage = L10N.GetText(&quot;LONGERRORDESC&quot;, .Number, .Source, .Description)
+ .DebugPrint(sMessage)
+ If _SF_.DisplayEnabled Then
+ sMessage = sLocation _
+ &amp; L10N.GetText(&quot;INTERNALERROR&quot;) _
+ &amp; L10N.GetText(&quot;ERRORLOCATION&quot;, Source &amp; &quot;/&quot; &amp; .Source) &amp; SF_String.sfNewLine &amp; .Description _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; L10N.GetText(&quot;STOPEXECUTION&quot;)
+ MsgBox SF_String.ExpandTabs(SF_String.Unescape(sMessage), cstTabSize) _
+ , MB_OK + MB_ICONSTOP _
+ , L10N.GetText(&quot;ERRORNUMBER&quot;, .Number)
+ End If
+
+ .Clear()
+ End With
+
+Finally:
+ _SF_._StackReset()
+ If _SF_.StopWhenError Then Stop
+ Exit Sub
+Catch:
+ GoTo Finally
+End Sub &apos; ScriptForge.SF_Exception.RaiseAbort
+
+REM -----------------------------------------------------------------------------
+Public Sub RaiseFatal(Optional ByVal ErrorCode As Variant _
+ , ParamArray pvArgs _
+ )
+&apos;&apos;&apos; Generate a run-time error caused by an anomaly in a user script detected by ScriptForge
+&apos;&apos;&apos; The message is logged in the console. The execution is STOPPED
+&apos;&apos;&apos; For INTERNAL USE only
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; ErrorCode: as a string, the unique identifier of the error
+&apos;&apos;&apos; pvArgs: the arguments to insert in the error message
+
+Dim sLocation As String &apos; Common header in error messages: location of error
+Dim sService As String &apos; Service name having detected the error
+Dim sMethod As String &apos; Method name having detected the error
+Dim vLocation As Variant &apos; Split array (library, module, method)
+Dim sMessage As String &apos; Message to log and display
+Dim L10N As Object &apos; Alias of LocalizedInterface
+Dim sAlt As String &apos; Alternative error messages
+Dim iButtons As Integer &apos; MB_OK or MB_YESNO
+Dim iMsgBox As Integer &apos; Return value of the message box
+
+Const cstTabSize = 4
+Const cstThisSub = &quot;Exception.RaiseFatal&quot;
+Const cstSubArgs = &quot;ErrorCode, [Arg0[, Arg1 ...]]&quot;
+Const cstStop = &quot;⏻&quot; &apos; Chr(9211)
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+
+Check:
+ If IsMissing(ErrorCode) Or IsEmpty(ErrorCode) Then ErrorCode = &quot;&quot;
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(ErrorCode, &quot;ErrorCode&quot;, V_STRING) Then GoTo Finally
+ End If
+
+Try:
+ Set L10N = _SF_._GetLocalizedInterface()
+ &apos; Location header common to all error messages
+ If Len(_SF_.MainFunction) &gt; 0 Then &apos; MainFunction = [Library.]Module.Method
+ vLocation = Split(_SF_.MainFunction, &quot;.&quot;)
+ If UBound(vLocation) &lt; 2 Then vLocation = SF_Array.Prepend(vLocation, &quot;ScriptForge&quot;)
+ sService = vLocation(1)
+ sMethod = vLocation(2)
+ sLocation = L10N.GetText(&quot;VALIDATESOURCE&quot;, vLocation(0), sService, sMethod) _
+ &amp; &quot;\n&quot; &amp; L10N.GetText(&quot;VALIDATEARGS&quot;, _RightCaseArgs(_SF_.MainFunctionArgs))
+ Else
+ sService = &quot;&quot;
+ sMethod = &quot;&quot;
+ sLocation = &quot;&quot;
+ End If
+
+ With L10N
+ Select Case UCase(ErrorCode)
+ Case MISSINGARGERROR &apos; SF_Utils._Validate(Name)
+ pvArgs(0) = _RightCase(pvArgs(0))
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATEERROR&quot;, pvArgs(0)) _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATEMISSING&quot;, pvArgs(0))
+ Case ARGUMENTERROR &apos; SF_Utils._Validate(Value, Name, Types, Values, Regex, Class)
+ pvArgs(1) = _RightCase(pvArgs(1))
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATEERROR&quot;, pvArgs(1)) _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATIONRULES&quot;)
+ If Len(pvArgs(2)) &gt; 0 Then sMessage = sMessage &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATETYPES&quot;, pvArgs(1), pvArgs(2))
+ If Len(pvArgs(3)) &gt; 0 Then sMessage = sMessage &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATEVALUES&quot;, pvArgs(1), pvArgs(3))
+ If Len(pvArgs(4)) &gt; 0 Then sMessage = sMessage &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATEREGEX&quot;, pvArgs(1), pvArgs(4))
+ If Len(pvArgs(5)) &gt; 0 Then sMessage = sMessage &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATECLASS&quot;, pvArgs(1), pvArgs(5))
+ sMessage = sMessage &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATEACTUAL&quot;, pvArgs(1), pvArgs(0))
+ Case ARRAYERROR &apos; SF_Utils._ValidateArray(Value, Name, Dimensions, Types, NotNull)
+ pvArgs(1) = _RightCase(pvArgs(1))
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATEERROR&quot;, pvArgs(1)) _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATIONRULES&quot;) _
+ &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATEARRAY&quot;, pvArgs(1))
+ If pvArgs(2) &gt; 0 Then sMessage = sMessage &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATEDIMS&quot;, pvArgs(1), pvArgs(2))
+ If Len(pvArgs(3)) &gt; 0 Then sMessage = sMessage &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATEALLTYPES&quot;, pvArgs(1), pvArgs(3))
+ If pvArgs(4) Then sMessage = sMessage &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATENOTNULL&quot;, pvArgs(1))
+ sMessage = sMessage &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATEACTUAL&quot;, pvArgs(1), pvArgs(0))
+ Case FILEERROR &apos; SF_Utils._ValidateFile(Value, Name, WildCards)
+ pvArgs(1) = _RightCase(pvArgs(1))
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATEERROR&quot;, pvArgs(1)) _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATIONRULES&quot;) _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATEFILE&quot;, pvArgs(1))
+ sAlt = &quot;VALIDATEFILE&quot; &amp; SF_FileSystem.FileNaming
+ sMessage = sMessage &amp; &quot;\n&quot; &amp; .GetText(sAlt, pvArgs(1))
+ If pvArgs(2) Then sMessage = sMessage &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATEWILDCARD&quot;, pvArgs(1))
+ sMessage = sMessage &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATEACTUAL&quot;, pvArgs(1), pvArgs(0))
+ Case ARRAYSEQUENCEERROR &apos; SF_Array.RangeInit(From, UpTo, ByStep)
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;ARRAYSEQUENCE&quot;, pvArgs(0), pvArgs(1), pvArgs(2))
+ Case ARRAYINSERTERROR &apos; SF_Array.AppendColumn/Row/PrependColumn/Row(VectorName, Array_2D, Vector)
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;ARRAYINSERT&quot;, pvArgs(0), pvArgs(1), pvArgs(2))
+ Case ARRAYINDEX1ERROR &apos; SF_Array.ExtractColumn/Row(IndexName, Array_2D, Index)
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;ARRAYINDEX1&quot;, pvArgs(0), pvArgs(1), pvArgs(2))
+ Case ARRAYINDEX2ERROR &apos; SF_Array.Slice(From, UpTo)
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;ARRAYINDEX2&quot;, pvArgs(0), pvArgs(1), pvArgs(2))
+ Case CSVPARSINGERROR &apos; SF_Array.ImportFromCSVFile(FileName, LineNumber, Line)
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;CSVPARSING&quot;, pvArgs(0), pvArgs(1), pvArgs(2))
+ Case DUPLICATEKEYERROR &apos; SF_Dictionary.Add/ReplaceKey(&quot;Key&quot;, Key)
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATEERROR&quot;, pvArgs(0)) _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;DUPLICATEKEY&quot;, pvArgs(0), pvArgs(1))
+ Case UNKNOWNKEYERROR &apos; SF_Dictionary.Remove/ReplaceItem/ReplaceKey(&quot;Key&quot;, Key)
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATEERROR&quot;, pvArgs(0)) _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;UNKNOWNKEY&quot;, pvArgs(0), pvArgs(1))
+ Case INVALIDKEYERROR &apos; SF_Dictionary.Add/ReplaceKey(Key)
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATEERROR&quot;, pvArgs(0)) _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;INVALIDKEY&quot;)
+ Case UNKNOWNFILEERROR &apos; SF_FileSystem.CopyFile/MoveFile/DeleteFile/CreateScriptService(&quot;L10N&quot;)(ArgName, Filename)
+ pvArgs(0) = _RightCase(pvArgs(0))
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATEERROR&quot;, pvArgs(0)) _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;UNKNOWNFILE&quot;, pvArgs(0), pvArgs(1))
+ Case UNKNOWNFOLDERERROR &apos; SF_FileSystem.CopyFolder/MoveFolder/DeleteFolder/Files/SubFolders(ArgName, Filename)
+ pvArgs(0) = _RightCase(pvArgs(0))
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATEERROR&quot;, pvArgs(0)) _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;UNKNOWNFOLDER&quot;, pvArgs(0), pvArgs(1))
+ Case NOTAFILEERROR &apos; SF_FileSystem.CopyFile/MoveFile/DeleteFile(ArgName, Filename)
+ pvArgs(0) = _RightCase(pvArgs(0))
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATEERROR&quot;, pvArgs(0)) _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;NOTAFILE&quot;, pvArgs(0), pvArgs(1))
+ Case NOTAFOLDERERROR &apos; SF_FileSystem.CopyFolder/MoveFolder/DeleteFolder/Files/SubFolders(ArgName, Filename)
+ pvArgs(0) = _RightCase(pvArgs(0))
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATEERROR&quot;, pvArgs(0)) _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;NOTAFOLDER&quot;, pvArgs(0), pvArgs(1))
+ Case OVERWRITEERROR &apos; SF_FileSystem.Copy+Move/File+Folder/CreateTextFile/OpenTextFile(ArgName, Filename)
+ pvArgs(0) = _RightCase(pvArgs(0))
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATEERROR&quot;, pvArgs(0)) _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;OVERWRITE&quot;, pvArgs(0), pvArgs(1))
+ Case READONLYERROR &apos; SF_FileSystem.Copy+Move+Delete/File+Folder(ArgName, Filename)
+ pvArgs(0) = _RightCase(pvArgs(0))
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATEERROR&quot;, pvArgs(0)) _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;READONLY&quot;, pvArgs(0), pvArgs(1))
+ Case NOFILEMATCHERROR &apos; SF_FileSystem.Copy+Move+Delete/File+Folder(ArgName, Filename)
+ pvArgs(0) = _RightCase(pvArgs(0))
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATEERROR&quot;, pvArgs(0)) _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;NOFILEMATCH&quot;, pvArgs(0), pvArgs(1))
+ Case FOLDERCREATIONERROR &apos; SF_FileSystem.CreateFolder(ArgName, Filename)
+ pvArgs(0) = _RightCase(pvArgs(0))
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATEERROR&quot;, pvArgs(0)) _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;FOLDERCREATION&quot;, pvArgs(0), pvArgs(1))
+ Case UNKNOWNSERVICEERROR &apos; SF_Services.CreateScriptService(ArgName, Value, Library, Service)
+ pvArgs(0) = _RightCase(pvArgs(0))
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATEERROR&quot;, pvArgs(0)) _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;UNKNOWNSERVICE&quot;, pvArgs(0), pvArgs(1), pvArgs(2), pvArgs(3))
+ Case SERVICESNOTLOADEDERROR &apos; SF_Services.CreateScriptService(ArgName, Value, Library)
+ pvArgs(0) = _RightCase(pvArgs(0))
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATEERROR&quot;, pvArgs(0)) _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;SERVICESNOTLOADED&quot;, pvArgs(0), pvArgs(1), pvArgs(2))
+ Case CALCFUNCERROR &apos; SF_Session.ExecuteCalcFunction(CalcFunction)
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATEERROR&quot;, _RightCase(&quot;CalcFunction&quot;)) _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;CALCFUNC&quot;, pvArgs(0))
+ Case NOSCRIPTERROR &apos; SF_Session._GetScript(Language, &quot;Scope&quot;, Scope, &quot;Script&quot;, Script)
+ pvArgs(1) = _RightCase(pvArgs(1)) : pvArgs(3) = _RightCase(pvArgs(3))
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATEERROR&quot;, _RightCase(&quot;Script&quot;)) _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;NOSCRIPT&quot;, pvArgs(0), pvArgs(1), pvArgs(2), pvArgs(3), pvArgs(4))
+ Case SCRIPTEXECERROR &apos; SF_Session.ExecuteBasicScript(&quot;Script&quot;, Script, Cause)
+ pvArgs(0) = _RightCase(pvArgs(0))
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;SCRIPTEXEC&quot;, pvArgs(0), pvArgs(1), pvArgs(2))
+ Case WRONGEMAILERROR &apos; SF_Session.SendMail(Arg, Email)
+ pvArgs(0) = _RightCase(pvArgs(0))
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATEERROR&quot;, pvArgs(0)) _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;WRONGEMAIL&quot;, pvArgs(1))
+ Case SENDMAILERROR &apos; SF_Session.SendMail()
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;SENDMAIL&quot;)
+ Case FILENOTOPENERROR &apos; SF_TextStream._IsFileOpen(FileName)
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;FILENOTOPEN&quot;, pvArgs(0))
+ Case FILEOPENMODEERROR &apos; SF_TextStream._IsFileOpen(FileName)
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;FILEOPENMODE&quot;, pvArgs(0), pvArgs(1))
+ Case ENDOFFILEERROR &apos; SF_TextStream.ReadLine/ReadAll/SkipLine(FileName)
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;ENDOFFILE&quot;, pvArgs(0))
+ Case DOCUMENTERROR &apos; SF_UI.GetDocument(ArgName, WindowName)
+ pvArgs(0) = _RightCase(pvArgs(0))
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATEERROR&quot;, pvArgs(0)) _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;DOCUMENT&quot;, pvArgs(0), pvArgs(1))
+ Case DOCUMENTCREATIONERROR &apos; SF_UI.Create(Arg1Name, DocumentType, Arg2Name, TemplateFile)
+ pvArgs(0) = _RightCase(pvArgs(0)) : pvArgs(2) = _RightCase(pvArgs(2))
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;DOCUMENTCREATION&quot;, pvArgs(0), pvArgs(1), pvArgs(2), pvArgs(3))
+ Case DOCUMENTOPENERROR &apos; SF_UI.OpenDocument(Arg1Name, FileName, Arg2Name, Password, Arg3Name, FilterName)
+ pvArgs(0) = _RightCase(pvArgs(0)) : pvArgs(2) = _RightCase(pvArgs(2)) : pvArgs(4) = _RightCase(pvArgs(4))
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;DOCUMENTOPEN&quot;, pvArgs(0), pvArgs(1), pvArgs(2), pvArgs(3), pvArgs(4), pvArgs(5))
+ Case BASEDOCUMENTOPENERROR &apos; SF_UI.OpenBaseDocument(Arg1Name, FileName, Arg2Name, RegistrationName)
+ pvArgs(0) = _RightCase(pvArgs(0)) : pvArgs(2) = _RightCase(pvArgs(2))
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;BASEDOCUMENTOPEN&quot;, pvArgs(0), pvArgs(1), pvArgs(2), pvArgs(3))
+ Case DOCUMENTDEADERROR &apos; SF_Document._IsStillAlive(FileName)
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;DOCUMENTDEAD&quot;, pvArgs(0))
+ Case DOCUMENTSAVEERROR &apos; SF_Document.Save(Arg1Name, FileName)
+ pvArgs(0) = _RightCase(pvArgs(0))
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;DOCUMENTSAVE&quot;, pvArgs(0), pvArgs(1))
+ Case DOCUMENTSAVEASERROR &apos; SF_Document.SaveAs(Arg1Name, FileName, Arg2, Overwrite, Arg3, FilterName)
+ pvArgs(0) = _RightCase(pvArgs(0)) : pvArgs(2) = _RightCase(pvArgs(2)) : pvArgs(4) = _RightCase(pvArgs(4))
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;DOCUMENTSAVEAS&quot;, pvArgs(0), pvArgs(1), pvArgs(2), pvArgs(3), pvArgs(4), pvArgs(5))
+ Case DOCUMENTREADONLYERROR &apos; SF_Document.update property(&quot;Document&quot;, FileName)
+ pvArgs(0) = _RightCase(pvArgs(0))
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;DOCUMENTREADONLY&quot;, pvArgs(0), pvArgs(1))
+ Case DBCONNECTERROR &apos; SF_Base.GetDatabase(&quot;User&quot;, User, &quot;Password&quot;, Password, FileName)
+ pvArgs(0) = _RightCase(pvArgs(0)) : pvArgs(2) = _RightCase(pvArgs(2))
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;DBCONNECT&quot;, pvArgs(0), pvArgs(1), pvArgs(2), pvArgs(3), pvArgs(4))
+ Case CALCADDRESSERROR &apos; SF_Calc._ParseAddress(Address, &quot;Range&quot;/&quot;Sheet&quot;, Scope, Document)
+ pvArgs(0) = _RightCase(pvArgs(0)) : pvArgs(2) = _RightCase(pvArgs(2))
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATEERROR&quot;, pvArgs(0)) _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;CALCADDRESS&quot; &amp; Iif(pvArgs(0) = &quot;Sheet&quot;, &quot;1&quot;, &quot;2&quot;), pvArgs(0), pvArgs(1), pvArgs(2), pvArgs(3))
+ Case DUPLICATESHEETERROR &apos; SF_Calc.InsertSheet(arg, SheetName, Document)
+ pvArgs(0) = _RightCase(pvArgs(0))
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATEERROR&quot;, pvArgs(0)) _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;DUPLICATESHEET&quot;, pvArgs(0), pvArgs(1), pvArgs(2), pvArgs(3))
+ Case OFFSETADDRESSERROR &apos; SF_Calc.RangeOffset(&quot;Range&quot;, Range, &quot;Rows&quot;, Rows, &quot;Columns&quot;, Columns, &quot;Height&quot;, Height, &quot;Width&quot;, Width, &quot;Document, Document)
+ pvArgs(0) = _RightCase(pvArgs(0)) : pvArgs(2) = _RightCase(pvArgs(2)) : pvArgs(4) = _RightCase(pvArgs(4))
+ pvArgs(6) = _RightCase(pvArgs(6)) : pvArgs(8) = _RightCase(pvArgs(8)) : pvArgs(10) = _RightCase(pvArgs(10))
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;OFFSETADDRESS&quot;, pvArgs(0), pvArgs(1), pvArgs(2), pvArgs(3), pvArgs(4) _
+ , pvArgs(5), pvArgs(6), pvArgs(7), pvArgs(8), pvArgs(9), pvArgs(10), pvArgs(11))
+ Case DUPLICATECHARTERROR &apos; SF_Calc.CreateChart(chart, ChartName, sheet, SheetName, Document, file)
+ pvArgs(0) = _RightCase(pvArgs(0)) : pvArgs(2) = _RightCase(pvArgs(2))
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATEERROR&quot;, pvArgs(0)) _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;DUPLICATECHART&quot;, pvArgs(0), pvArgs(1), pvArgs(2), pvArgs(3), pvArgs(4), pvArgs(5))
+ Case RANGEEXPORTERROR &apos; SF_Calc.ExportRangeToFile(Arg1Name, FileName, Arg2, Overwrite)
+ pvArgs(0) = _RightCase(pvArgs(0)) : pvArgs(2) = _RightCase(pvArgs(2))
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;RANGEEXPORT&quot;, pvArgs(0), pvArgs(1), pvArgs(2), pvArgs(3))
+ Case CHARTEXPORTERROR &apos; SF_Chart.ExportToFile(Arg1Name, FileName, Arg2, Overwrite)
+ pvArgs(0) = _RightCase(pvArgs(0)) : pvArgs(2) = _RightCase(pvArgs(2))
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;CHARTEXPORT&quot;, pvArgs(0), pvArgs(1), pvArgs(2), pvArgs(3))
+ Case FORMDEADERROR &apos; SF_Form._IsStillAlive(FormName, DocumentName)
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;FORMDEAD&quot;, pvArgs(0), pvArgs(1))
+ Case CALCFORMNOTFOUNDERROR &apos; SF_Calc.Forms(Index, SheetName, Document)
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;CALCFORMNOTFOUND&quot;, pvArgs(0), pvArgs(1), pvArgs(2))
+ Case WRITERFORMNOTFOUNDERROR &apos; SF_Document.Forms(Index, Document)
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;WRITERFORMNOTFOUND&quot;, pvArgs(0), pvArgs(1))
+ Case BASEFORMNOTFOUNDERROR &apos; SF_Base.Forms(Index, FormDocument, BaseDocument)
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;BASEFORMNOTFOUND&quot;, pvArgs(0), pvArgs(1), pvArgs(2))
+ Case SUBFORMNOTFOUNDERROR &apos; SF_Form.Subforms(Subform, Mainform)
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;SUBFORMNOTFOUND&quot;, pvArgs(0), pvArgs(1))
+ Case FORMCONTROLTYPEERROR &apos; SF_FormControl._SetProperty(ControlName, FormName, ControlType, Property)
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;FORMCONTROLTYPE&quot;, pvArgs(0), pvArgs(1), pvArgs(2), pvArgs(3))
+ Case DIALOGNOTFOUNDERROR &apos; SF_Dialog._NewDialog(Service, DialogName, WindowName)
+ pvArgs(0) = _RightCase(pvArgs(0)) : pvArgs(2) = _RightCase(pvArgs(2)) : pvArgs(4) = _RightCase(pvArgs(4))
+ pvArgs(6) = _RightCase(pvArgs(6))
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;DIALOGNOTFOUND&quot;, pvArgs(0), pvArgs(1), pvArgs(2), pvArgs(3), pvArgs(4) _
+ , pvArgs(5), pvArgs(6), pvArgs(7))
+ Case DIALOGDEADERROR &apos; SF_Dialog._IsStillAlive(DialogName)
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;DIALOGDEAD&quot;, pvArgs(0))
+ Case CONTROLTYPEERROR &apos; SF_DialogControl._SetProperty(ControlName, DialogName, ControlType, Property)
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;CONTROLTYPE&quot;, pvArgs(0), pvArgs(1), pvArgs(2), pvArgs(3))
+ Case TEXTFIELDERROR &apos; SF_DialogControl.WriteLine(ControlName, DialogName)
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;TEXTFIELD&quot;, pvArgs(0), pvArgs(1))
+ Case DBREADONLYERROR &apos; SF_Database.RunSql()
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;DBREADONLY&quot;, vLocation(2))
+ Case SQLSYNTAXERROR &apos; SF_Database._ExecuteSql(SQL)
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;SQLSYNTAX&quot;, pvArgs(0))
+ Case PYTHONSHELLERROR &apos; SF_Exception.PythonShell (Python only)
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;PYTHONSHELL&quot;)
+ Case UNITTESTLIBRARYERROR &apos; SFUnitTests._NewUnitTest(LibraryName)
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;UNITTESTLIBRARY&quot;, pvArgs(0))
+ Case UNITTESTMETHODERROR &apos; SFUnitTests.SF_UnitTest(Method)
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;UNITTESTMETHOD&quot;, pvArgs(0))
+ Case Else
+ End Select
+ End With
+
+ &apos; Log fatal event
+ _SF_._AddToConsole(sMessage)
+
+ &apos; Display fatal event, if relevant (default)
+ If _SF_.DisplayEnabled Then
+ If _SF_.StopWhenError Then sMessage = sMessage &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; L10N.GetText(&quot;STOPEXECUTION&quot;)
+ &apos; Do you need more help ?
+ If Len(sMethod) &gt; 0 Then
+ sMessage = sMessage &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; L10N.GetText(&quot;NEEDMOREHELP&quot;, sMethod)
+ iButtons = MB_YESNO + MB_DEFBUTTON2
+ Else
+ iButtons = MB_OK
+ End If
+ iMsgBox = MsgBox(SF_String.ExpandTabs(SF_String.Unescape(sMessage), cstTabSize) _
+ , iButtons + MB_ICONEXCLAMATION _
+ , L10N.GetText(&quot;ERRORNUMBER&quot;, ErrorCode) _
+ )
+ &apos; If more help needed ...
+ If iMsgBox = IDYES Then _OpenHelpInBrowser(sService, sMethod)
+ End If
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ _SF_._StackReset()
+ If _SF_.StopWhenError Then Stop
+ Exit Sub
+Catch:
+ GoTo Finally
+End Sub &apos; ScriptForge.SF_Exception.RaiseFatal
+
+REM -----------------------------------------------------------------------------
+Public Sub RaiseWarning(Optional ByVal Number As Variant _
+ , Optional ByVal Source As Variant _
+ , Optional ByVal Description As Variant _
+ )
+&apos;&apos;&apos; Generate a run-time error. An error message is displayed to the user and logged
+&apos;&apos;&apos; in the console. The execution is NOT STOPPED
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Number: the error number, may be numeric or string
+&apos;&apos;&apos; If numeric and &lt;= 2000, it is considered a LibreOffice Basic run-time error (default = Err)
+&apos;&apos;&apos; Source: the line where the error occurred (default = Erl) or any string describing the location of the error
+&apos;&apos;&apos; Description: the error message to log in the console and to display to the user
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if successful. Anyway, the execution continues
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; On Local Error GoTo Catch
+&apos;&apos;&apos; &apos; ...
+&apos;&apos;&apos; Catch:
+&apos;&apos;&apos; SF_Exception.RaiseWarning() &apos; Standard behaviour
+&apos;&apos;&apos; SF_Exception.RaiseWarning(11) &apos; Force division by zero
+&apos;&apos;&apos; SF_Exception.RaiseWarning(&quot;MYAPPERROR&quot;, &quot;myFunction&quot;, &quot;Application error&quot;)
+&apos;&apos;&apos; SF_Exception.RaiseWarning(,, &quot;To divide by zero is not a good idea !&quot;)
+
+Dim bStop As Boolean &apos; Alias for stop switch
+Const cstThisSub = &quot;Exception.RaiseWarning&quot;
+Const cstSubArgs = &quot;[Number=Err], [Source=Erl], [Description]&quot;
+
+ &apos; Save Err, Erl, .. values before any On Error ... statement
+ SF_Exception._CaptureSystemError()
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+
+Check:
+ If IsMissing(Number) Or IsEmpty(Number) Then Number = -1
+ If IsMissing(Source) Or IsEmpty(Source) Then Source = -1
+ If IsMissing(Description) Or IsEmpty(Description) Then Description = &quot;&quot;
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(Number, &quot;Number&quot;, Array(V_STRING, V_NUMERIC, V_EMPTY)) Then GoTo Finally
+ If Not SF_Utils._Validate(Source, &quot;Source&quot;, Array(V_STRING, V_NUMERIC, V_EMPTY)) Then GoTo Finally
+ If Not SF_Utils._Validate(Description, &quot;Description&quot;, V_STRING) Then GoTo Finally
+ End If
+
+Try:
+ bStop = _SF_.StopWhenError &apos; Store current value to reset it before leaving the Sub
+ _SF_.StopWhenError = False
+ SF_Exception.Raise(Number, Source, Description)
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ _SF_.StopWhenError = bStop
+ Exit Sub
+Catch:
+ GoTo Finally
+End Sub &apos; ScriptForge.SF_Exception.RaiseWarning
+
+REM -----------------------------------------------------------------------------
+Public Function SetProperty(Optional ByVal PropertyName As Variant _
+ , Optional ByRef Value As Variant _
+ ) As Boolean
+&apos;&apos;&apos; Set a new value to the given property
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; PropertyName: the name of the property as a string
+&apos;&apos;&apos; Value: its new value
+&apos;&apos;&apos; Exceptions
+&apos;&apos;&apos; ARGUMENTERROR The property does not exist
+
+Const cstThisSub = &quot;Exception.SetProperty&quot;
+Const cstSubArgs = &quot;PropertyName, Value&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ SetProperty = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
+ End If
+
+Try:
+ SetProperty = _PropertySet(PropertyName, Value)
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Exception.SetProperty
+
+REM =========================================================== PRIVATE FUNCTIONS
+
+REM -----------------------------------------------------------------------------
+Private Sub _CaptureSystemError()
+&apos;&apos;&apos; Store system error status in system error properties
+&apos;&apos;&apos; Called at each invocation of an error management property or method
+&apos;&apos;&apos; Reset by SF_Exception.Clear()
+
+ If Err &gt; 0 And _SysNumber = 0 Then
+ _SysNumber = Err
+ _SysSource = Erl
+ _SysDescription = Error$
+ End If
+
+End Sub &apos; ScriptForge.SF_Exception._CaptureSystemError
+
+REM -----------------------------------------------------------------------------
+Public Sub _CloseConsole(Optional ByRef poEvent As Object)
+&apos;&apos;&apos; Close the console when opened in non-modal mode
+&apos;&apos;&apos; Triggered by the CloseNonModalButton from the dlgConsole dialog
+
+ On Local Error GoTo Finally
+
+Try:
+ With _SF_
+ If Not IsNull(.ConsoleDialog) Then
+ If .ConsoleDialog._IsStillAlive(False) Then &apos; False to not raise an error
+ Set .ConsoleControl = .ConsoleControl.Dispose()
+ Set .ConsoleDialog = .ConsoleDialog.Dispose()
+ End If
+ End If
+ End With
+
+Finally:
+ Exit Sub
+End Sub &apos; ScriptForge.SF_Exception._CloseConsole
+
+REM -----------------------------------------------------------------------------
+Private Sub _ConsoleRefresh()
+&apos;&apos;&apos; Reload the content of the console in the dialog
+&apos;&apos;&apos; Needed when console first loaded or when totally or partially cleared
+
+ With _SF_
+ &apos; Do nothing if console inactive
+ If IsNull(.ConsoleDialog) Then GoTo Finally
+ If Not .ConsoleDialog._IsStillAlive(False) Then &apos; False to not generate an error when dead
+ Set .ConsoleControl = .ConsoleControl.Dispose()
+ Set .ConsoleDialog = Nothing
+ GoTo Finally
+ End If
+ &apos; Store the relevant text in the control
+ If IsNull(.ConsoleControl) Then Set .ConsoleControl = .ConsoleDialog.Controls(CONSOLENAME)
+ .ConsoleControl.Value = &quot;&quot;
+ If UBound(.ConsoleLines) &gt;= 0 Then .ConsoleControl.WriteLine(Join(.ConsoleLines, SF_String.sfNEWLINE))
+ End With
+
+Finally:
+ Exit Sub
+End Sub &apos; ScriptForge.SF_Exception._ConsoleRefresh
+
+REM -----------------------------------------------------------------------------
+Private Sub _OpenHelpInBrowser(ByVal psService As String, ByVal psMethod As String)
+&apos;&apos;&apos; Open the help page and help anchor related to the given ScriptForge service and method
+
+Dim sUrl As String &apos; URL to open
+Const cstURL = &quot;https://help.libreoffice.org/latest/en-US/text/sbasic/shared/03/sf_%1.html?&amp;DbPAR=BASIC#%2&quot;
+
+ On Local Error GoTo Finally &apos; No reason to risk abort here
+Try:
+ sUrl = SF_String.ReplaceStr(cstURL, Array(&quot;%1&quot;, &quot;%2&quot;), Array(LCase(psService), psMethod))
+ SF_Session.OpenUrlInBrowser(sUrl)
+
+Finally:
+ Exit Sub
+End Sub &apos; ScriptForge.SF_Exception._OpenHelpInBrowser
+
+REM -----------------------------------------------------------------------------
+Private Function _PropertyGet(Optional ByVal psProperty As String) As Variant
+&apos;&apos;&apos; Return the value of the named property
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; psProperty: the name of the property
+
+Dim cstThisSub As String
+Const cstSubArgs = &quot;&quot;
+
+ cstThisSub = &quot;SF_Exception.get&quot; &amp; psProperty
+
+ SF_Exception._CaptureSystemError()
+
+ Select Case psProperty
+ Case &quot;Description&quot;
+ If _Description = &quot;&quot; Then _PropertyGet = _SysDescription Else _PropertyGet = _Description
+ Case &quot;Number&quot;
+ If IsEmpty(_Number) Then _PropertyGet = _SysNumber Else _PropertyGet = _Number
+ Case &quot;Source&quot;
+ If IsEmpty(_Source) Then _PropertyGet = _SysSource Else _PropertyGet = _Source
+ Case Else
+ _PropertyGet = Null
+ End Select
+
+Finally:
+ Exit Function
+End Function &apos; ScriptForge.SF_Exception._PropertyGet
+
+REM -----------------------------------------------------------------------------
+Private Function _PropertySet(Optional ByVal psProperty As String _
+ , Optional ByVal pvValue As Variant _
+ ) As Boolean
+&apos;&apos;&apos; Set a new value to the named property
+&apos;&apos;&apos; Applicable only to user defined errors
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; psProperty: the name of the property
+&apos;&apos;&apos; pvValue: the new value
+
+Dim cstThisSub As String
+Const cstSubArgs = &quot;&quot;
+
+ cstThisSub = &quot;SF_Exception.set&quot; &amp; psProperty
+ _PropertySet = False
+
+ SF_Exception._CaptureSystemError()
+
+ &apos; Argument validation must be manual to preserve system error status
+ &apos; If wrong VarType then property set is ignored
+ Select Case psProperty
+ Case &quot;Description&quot;
+ If VarType(pvValue) = V_STRING Then _Description = pvValue
+ Case &quot;Number&quot;
+ Select Case SF_Utils._VarTypeExt(pvValue)
+ Case V_STRING
+ _Number = pvValue
+ Case V_NUMERIC
+ _Number = CLng(pvValue)
+ If _Number &lt;= RUNTIMEERRORS And Len(_Description) = 0 Then _Description = Error(_Number)
+ Case V_EMPTY
+ _Number = Empty
+ Case Else
+ End Select
+ Case &quot;Source&quot;
+ Select Case SF_Utils._VarTypeExt(pvValue)
+ Case V_STRING
+ _Source = pvValue
+ Case V_NUMERIC
+ _Source = CLng(pvValue)
+ Case Else
+ End Select
+ Case Else
+ End Select
+
+ _PropertySet = True
+
+Finally:
+ Exit Function
+End Function &apos; ScriptForge.SF_Exception._PropertySet
+
+REM -----------------------------------------------------------------------------
+Private Function _Repr() As String
+&apos;&apos;&apos; Convert the Exception instance to a readable string, typically for debugging purposes (DebugPrint ...)
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Return:
+&apos;&apos;&apos; &quot;[Exception]: A readable string&quot;
+
+ _Repr = &quot;[Exception]: &quot; &amp; _Number &amp; &quot; (&quot; &amp; _Description &amp; &quot;)&quot;
+
+End Function &apos; ScriptForge.SF_Exception._Repr
+
+REM -----------------------------------------------------------------------------
+Private Function _RightCase(psString As String) As String
+&apos;&apos;&apos; Return the input argument in lower case only when the procedure in execution
+&apos;&apos;&apos; has been triggered from a Python script
+&apos;&apos;&apos; Indeed, Python requires lower case arguments
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; psString: probably an identifier in ProperCase
+&apos;&apos;&apos; Return:
+&apos;&apos;&apos; The input argument in lower case or left unchanged depending on the execution context
+
+Try:
+ If _SF_.TriggeredByPython Then _RightCase = LCase(psString) Else _RightCase = psString
+
+Finally:
+ Exit Function
+End Function &apos; ScriptForge.SF_Exception._RightCase
+
+REM -----------------------------------------------------------------------------
+Private Function _RightCaseArgs(psString As String) As String
+&apos;&apos;&apos; Return the input argument unchanged when the execution context is Basic
+&apos;&apos;&apos; When it is Python, the argument names are lowercased.
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; psString: one of the cstSubArgs strings located in each official method
+&apos;&apos;&apos; Return:
+&apos;&apos;&apos; The input string in which the argument names are put in lower case when called from Python scripts
+
+Dim sSubArgs As String &apos; Return value
+Dim vArgs As Variant &apos; Input string split on the comma character
+Dim sSingleArg As String &apos; Single vArgs item
+Dim vSingleArgs As Variant &apos; vSingleArg split on equal sign
+Dim i As Integer
+
+Const cstComma = &quot;,&quot;
+Const cstEqual = &quot;=&quot;
+
+Try:
+ If Len(psString) = 0 Then
+ sSubArgs = &quot;&quot;
+ ElseIf _SF_.TriggeredByPython Then
+ vArgs = SF_String.SplitNotQuoted(psString, cstComma, QuoteChar := &quot;&quot;&quot;&quot;)
+ For i = 0 To UBound(vArgs)
+ sSingleArg = vArgs(i)
+ vSingleArgs = Split(sSingleArg, cstEqual)
+ vSingleArgs(0) = LCase(vSingleArgs(0))
+ vArgs(i) = join(vSingleArgs, cstEqual)
+ Next i
+ sSubArgs = Join(vArgs, cstComma)
+ Else
+ sSubArgs = psString
+ End If
+
+Finally:
+ _RightCaseArgs = sSubArgs
+ Exit Function
+End Function &apos; ScriptForge.SF_Exception._RightCaseArgs
+
+REM ============================================ END OF SCRIPTFORGE.SF_EXCEPTION
+</script:module> \ No newline at end of file
diff --git a/wizards/source/scriptforge/SF_FileSystem.xba b/wizards/source/scriptforge/SF_FileSystem.xba
new file mode 100644
index 000000000..39ea4888e
--- /dev/null
+++ b/wizards/source/scriptforge/SF_FileSystem.xba
@@ -0,0 +1,2128 @@
+<?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_FileSystem" 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_FileSystem
+&apos;&apos;&apos; =============
+&apos;&apos;&apos; Class implementing the file system service
+&apos;&apos;&apos; for common file and folder handling routines
+&apos;&apos;&apos; Including copy and move of files and folders, with or without wildcards
+&apos;&apos;&apos; The design choices are largely inspired by
+&apos;&apos;&apos; https://docs.microsoft.com/en-us/office/vba/language/reference/user-interface-help/filesystemobject-object
+&apos;&apos;&apos; The File and Folder classes have been found redundant with the current class and have not been implemented
+&apos;&apos;&apos; The implementation is mainly based on the XSimpleFileAccess UNO interface
+&apos;&apos;&apos; https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1ucb_1_1XSimpleFileAccess.html
+&apos;&apos;&apos;
+&apos;&apos;&apos; Subclasses:
+&apos;&apos;&apos; SF_TextStream
+&apos;&apos;&apos;
+&apos;&apos;&apos; Definitions:
+&apos;&apos;&apos; File and folder names may be expressed either in the (preferable because portable) URL form
+&apos;&apos;&apos; or in the more usual operating system notation (e.g. C:\... for Windows)
+&apos;&apos;&apos; The notation, both for arguments and for returned values
+&apos;&apos;&apos; is determined by the FileNaming property: either &quot;URL&quot; (default) or &quot;SYS&quot;
+&apos;&apos;&apos;
+&apos;&apos;&apos; FileName: the full name of the file including the path without any ending path separator
+&apos;&apos;&apos; FolderName: the full name of the folder including the path and the ending path separator
+&apos;&apos;&apos; Name: the last component of the File- or FolderName including its extension
+&apos;&apos;&apos; BaseName: the last component of the File- or FolderName without its extension
+&apos;&apos;&apos; NamePattern: any of the above names containing wildcards in its last component
+&apos;&apos;&apos; Admitted wildcards are: the &quot;?&quot; represents any single character
+&apos;&apos;&apos; the &quot;*&quot; represents zero, one, or multiple characters
+&apos;&apos;&apos;
+&apos;&apos;&apos; Service invocation example:
+&apos;&apos;&apos; Dim FSO As Variant
+&apos;&apos;&apos; Set FSO = CreateScriptService(&quot;FileSystem&quot;)
+&apos;&apos;&apos;
+&apos;&apos;&apos; Detailed user documentation:
+&apos;&apos;&apos; https://help.libreoffice.org/latest/en-US/text/sbasic/shared/03/sf_filesystem.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 UNKNOWNFILEERROR = &quot;UNKNOWNFILEERROR&quot; &apos; Source file does not exist
+Const UNKNOWNFOLDERERROR = &quot;UNKNOWNFOLDERERROR&quot; &apos; Source folder or Destination folder does not exist
+Const NOTAFILEERROR = &quot;NOTAFILEERROR&quot; &apos; Destination is a folder, not a file
+Const NOTAFOLDERERROR = &quot;NOTAFOLDERERROR&quot; &apos; Destination is a file, not a folder
+Const OVERWRITEERROR = &quot;OVERWRITEERROR&quot; &apos; Destination can not be overwritten
+Const READONLYERROR = &quot;READONLYERROR&quot; &apos; Destination has its read-only attribute set
+Const NOFILEMATCHERROR = &quot;NOFILEMATCHFOUND&quot; &apos; No file matches Source containing wildcards
+Const FOLDERCREATIONERROR = &quot;FOLDERCREATIONERROR&quot; &apos; FolderName is an existing folder or file
+
+REM ============================================================ MODULE CONSTANTS
+
+&apos;&apos;&apos; TextStream open modes
+Const cstForReading = 1
+Const cstForWriting = 2
+Const cstForAppending = 8
+
+REM ===================================================== CONSTRUCTOR/DESTRUCTOR
+
+REM -----------------------------------------------------------------------------
+Public Function Dispose() As Variant
+ Set Dispose = Nothing
+End Function &apos; ScriptForge.SF_FileSystem Explicit destructor
+
+REM ================================================================== PROPERTIES
+
+REM -----------------------------------------------------------------------------
+Property Get ConfigFolder() As String
+&apos;&apos;&apos; Return the configuration folder of LibreOffice
+
+Const cstThisSub = &quot;FileSystem.getConfigFolder&quot;
+
+ SF_Utils._EnterFunction(cstThisSub)
+ ConfigFolder = SF_FileSystem._GetConfigFolder(&quot;user&quot;)
+ SF_Utils._ExitFunction(cstThisSub)
+
+End Property &apos; ScriptForge.SF_FileSystem.ConfigFolder
+
+REM -----------------------------------------------------------------------------
+Property Get ExtensionsFolder() As String
+&apos;&apos;&apos; Return the folder containing the extensions installed for the current user
+
+Dim oMacro As Object &apos; /singletons/com.sun.star.util.theMacroExpander
+Const cstThisSub = &quot;FileSystem.getExtensionsFolder&quot;
+
+ SF_Utils._EnterFunction(cstThisSub)
+ Set oMacro = SF_Utils._GetUNOService(&quot;MacroExpander&quot;)
+ ExtensionsFolder = SF_FileSystem._ConvertFromUrl(oMacro.ExpandMacros(&quot;$UNO_USER_PACKAGES_CACHE&quot;) &amp; &quot;/&quot;)
+ SF_Utils._ExitFunction(cstThisSub)
+
+End Property &apos; ScriptForge.SF_FileSystem.ExtensionsFolder
+
+REM -----------------------------------------------------------------------------
+Property Get FileNaming() As Variant
+&apos;&apos;&apos; Return the current files and folder notation, either &quot;ANY&quot;, &quot;URL&quot; or &quot;SYS&quot;
+&apos;&apos;&apos; &quot;ANY&quot;: methods receive either URL or native file names, but always return URL file names
+&apos;&apos;&apos; &quot;URL&quot;: methods expect URL arguments and return URL strings (when relevant)
+&apos;&apos;&apos; &quot;SYS&quot;: idem but operating system notation
+
+Const cstThisSub = &quot;FileSystem.getFileNaming&quot;
+ SF_Utils._EnterFunction(cstThisSub)
+ FileNaming = _SF_.FileSystemNaming
+ SF_Utils._ExitFunction(cstThisSub)
+
+End Property &apos; ScriptForge.SF_FileSystem.FileNaming (get)
+
+REM -----------------------------------------------------------------------------
+Property Let FileNaming(ByVal pvNotation As Variant)
+&apos;&apos;&apos; Set the files and folders notation: &quot;ANY&quot;, &quot;URL&quot; or &quot;SYS&quot;
+
+Const cstThisSub = &quot;FileSystem.setFileNaming&quot;
+ SF_Utils._EnterFunction(cstThisSub)
+ If VarType(pvNotation) = V_STRING Then
+ Select Case UCase(pvNotation)
+ Case &quot;ANY&quot;, &quot;URL&quot;, &quot;SYS&quot; : _SF_.FileSystemNaming = UCase(pvNotation)
+ Case Else &apos; Unchanged
+ End Select
+ End If
+ SF_Utils._ExitFunction(cstThisSub)
+
+End Property &apos; ScriptForge.SF_FileSystem.FileNaming (let)
+
+REM -----------------------------------------------------------------------------
+Property Get ForAppending As Integer
+&apos;&apos;&apos; Convenient constant (see documentation)
+ ForAppending = cstForAppending
+End Property &apos; ScriptForge.SF_FileSystem.ForAppending
+
+REM -----------------------------------------------------------------------------
+Property Get ForReading As Integer
+&apos;&apos;&apos; Convenient constant (see documentation)
+ ForReading = cstForReading
+End Property &apos; ScriptForge.SF_FileSystem.ForReading
+
+REM -----------------------------------------------------------------------------
+Property Get ForWriting As Integer
+&apos;&apos;&apos; Convenient constant (see documentation)
+ ForWriting = cstForWriting
+End Property &apos; ScriptForge.SF_FileSystem.ForWriting
+
+REM -----------------------------------------------------------------------------
+Property Get HomeFolder() As String
+&apos;&apos;&apos; Return the user home folder
+
+Const cstThisSub = &quot;FileSystem.getHomeFolder&quot;
+
+ SF_Utils._EnterFunction(cstThisSub)
+ HomeFolder = SF_FileSystem._GetConfigFolder(&quot;home&quot;)
+ SF_Utils._ExitFunction(cstThisSub)
+
+End Property &apos; ScriptForge.SF_FileSystem.HomeFolder
+
+REM -----------------------------------------------------------------------------
+Property Get InstallFolder() As String
+&apos;&apos;&apos; Return the installation folder of LibreOffice
+
+Const cstThisSub = &quot;FileSystem.getInstallFolder&quot;
+
+ SF_Utils._EnterFunction(cstThisSub)
+ InstallFolder = SF_FileSystem._GetConfigFolder(&quot;inst&quot;)
+ SF_Utils._ExitFunction(cstThisSub)
+
+End Property &apos; ScriptForge.SF_FileSystem.InstallFolder
+
+REM -----------------------------------------------------------------------------
+Property Get ObjectType As String
+&apos;&apos;&apos; Only to enable object representation
+ ObjectType = &quot;SF_FileSystem&quot;
+End Property &apos; ScriptForge.SF_FileSystem.ObjectType
+
+REM -----------------------------------------------------------------------------
+Property Get ServiceName As String
+&apos;&apos;&apos; Internal use
+ ServiceName = &quot;ScriptForge.FileSystem&quot;
+End Property &apos; ScriptForge.SF_FileSystem.ServiceName
+
+REM -----------------------------------------------------------------------------
+Property Get TemplatesFolder() As String
+&apos;&apos;&apos; Return the folder defined in the LibreOffice paths options as intended for templates files
+
+Dim sPath As String &apos; Template property of com.sun.star.util.PathSettings
+Const cstThisSub = &quot;FileSystem.getTemplatesFolder&quot;
+
+ SF_Utils._EnterFunction(cstThisSub)
+ sPath = SF_Utils._GetUNOService(&quot;PathSettings&quot;).Template
+ TemplatesFolder = SF_FileSystem._ConvertFromUrl(Split(sPath, &quot;;&quot;)(0) &amp; &quot;/&quot;)
+ SF_Utils._ExitFunction(cstThisSub)
+
+End Property &apos; ScriptForge.SF_FileSystem.TemplatesFolder
+
+REM -----------------------------------------------------------------------------
+Property Get TemporaryFolder() As String
+&apos;&apos;&apos; Return the folder defined in the LibreOffice paths options as intended for temporary files
+
+Const cstThisSub = &quot;FileSystem.getTemporaryFolder&quot;
+
+ SF_Utils._EnterFunction(cstThisSub)
+ TemporaryFolder = SF_FileSystem._GetConfigFolder(&quot;temp&quot;)
+ SF_Utils._ExitFunction(cstThisSub)
+
+End Property &apos; ScriptForge.SF_FileSystem.TemporaryFolder
+
+REM -----------------------------------------------------------------------------
+Property Get UserTemplatesFolder() As String
+&apos;&apos;&apos; Return the folder defined in the LibreOffice paths options as intended for User templates files
+
+Dim sPath As String &apos; Template_writable property of com.sun.star.util.PathSettings
+Const cstThisSub = &quot;FileSystem.getUserTemplatesFolder&quot;
+
+ SF_Utils._EnterFunction(cstThisSub)
+ sPath = SF_Utils._GetUNOService(&quot;PathSettings&quot;).Template_writable
+ UserTemplatesFolder = SF_FileSystem._ConvertFromUrl(sPath &amp; &quot;/&quot;)
+ SF_Utils._ExitFunction(cstThisSub)
+
+End Property &apos; ScriptForge.SF_FileSystem.UserTemplatesFolder
+
+REM ===================================================================== METHODS
+
+REM -----------------------------------------------------------------------------
+Public Function BuildPath(Optional ByVal FolderName As Variant _
+ , Optional ByVal Name As Variant _
+ ) As String
+&apos;&apos;&apos; Combines a folder path and the name of a file and returns the combination with a valid path separator
+&apos;&apos;&apos; Inserts an additional path separator between the foldername and the name, only if necessary
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; FolderName: Path with which Name is combined. Path need not specify an existing folder
+&apos;&apos;&apos; Name: To be appended to the existing path.
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The path concatenated with the file name after insertion of a path separator, if necessary
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; Dim a As String
+&apos;&apos;&apos; FSO.FileNaming = &quot;SYS&quot;
+&apos;&apos;&apos; a = FSO.BuildPath(&quot;C:\Windows&quot;, &quot;Notepad.exe&quot;) returns C:\Windows\Notepad.exe
+
+Dim sBuild As String &apos; Return value
+Dim sFile As String &apos; Alias for Name
+Const cstFileProtocol = &quot;file:///&quot;
+Const cstThisSub = &quot;FileSystem.BuildPath&quot;
+Const cstSubArgs = &quot;FolderName, Name&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sBuild = &quot;&quot;
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateFile(FolderName, &quot;FolderName&quot;) Then GoTo Finally
+ If Not SF_Utils._Validate(Name, &quot;Name&quot;, V_STRING) Then GoTo Finally
+ End If
+ FolderName = SF_FileSystem._ConvertToUrl(FolderName)
+
+Try:
+ &apos; Add separator if necessary. FolderName is now in URL notation
+ If Len(FolderName) &gt; 0 Then
+ If Right(FolderName, 1) &lt;&gt; &quot;/&quot; Then sBuild = FolderName &amp; &quot;/&quot; Else sBuild = FolderName
+ Else
+ sBuild = cstFileProtocol
+ End If
+ &apos; Encode the file name
+ sFile = ConvertToUrl(Name)
+ &apos; Some file names produce http://file.name.suffix/
+ If Left(sFile, 7) = &quot;http://&quot; Then sFile = cstFileProtocol &amp; Mid(sFile, 8, Len(sFile) - 8)
+ &apos; Combine both parts
+ If Left(sFile, Len(cstFileProtocol)) = cstFileProtocol Then sBuild = sBuild &amp; Mid(sFile, Len(cstFileProtocol) + 1) Else sBuild = sBuild &amp; sFile
+
+Finally:
+ BuildPath = SF_FileSystem._ConvertFromUrl(sBuild)
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_FileSystem.BuildPath
+
+REM -----------------------------------------------------------------------------
+Public Function CompareFiles(Optional ByVal FileName1 As Variant _
+ , Optional ByVal FileName2 As Variant _
+ , Optional ByVal CompareContents As Variant _
+ )
+&apos;&apos;&apos; Compare 2 files and return True if they seem identical
+&apos;&apos;&apos; The comparison may be based on the file attributes, like modification time,
+&apos;&apos;&apos; or on their contents.
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; FileName1: The 1st file to compare
+&apos;&apos;&apos; FileName2: The 2nd file to compare
+&apos;&apos;&apos; CompareContents: When True, the contents of the files are compared. Default = False
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True when the files seem identical
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; UNKNOWNFILEERROR One of the files does not exist
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; FSO.FileNaming = &quot;SYS&quot;
+&apos;&apos;&apos; MsgBox FSO.CompareFiles(&quot;C:\myFile1.txt&quot;, &quot;C:\myFile2.txt&quot;, CompareContents := True)
+
+Dim bCompare As Boolean &apos; Return value
+Dim sFile As String &apos; Alias of FileName1 and 2
+Dim iFile As Integer &apos; 1 or 2
+Const cstPyHelper = &quot;$&quot; &amp; &quot;_SF_FileSystem__CompareFiles&quot;
+
+Const cstThisSub = &quot;FileSystem.CompareFiles&quot;
+Const cstSubArgs = &quot;FileName1, FileName2, [CompareContents=False]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bCompare = False
+
+Check:
+ If IsMissing(CompareContents) Or IsEmpty(CompareContents) Then CompareContents = False
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateFile(FileName1, &quot;FileName1&quot;, False) Then GoTo Finally
+ If Not SF_Utils._ValidateFile(FileName2, &quot;FileName2&quot;, False) Then GoTo Finally
+ If Not SF_Utils._Validate(CompareContents, &quot;CompareContents&quot;, V_BOOLEAN) Then GoTo Finally
+ End If
+ &apos; Do the files exist ? Otherwise raise error
+ sFile = FileName1 : iFile = 1
+ If Not SF_FileSystem.FileExists(sFile) Then GoTo CatchNotExists
+ sFile = FileName2 : iFile = 2
+ If Not SF_FileSystem.FileExists(sFile) Then GoTo CatchNotExists
+
+Try:
+ With ScriptForge.SF_Session
+ bCompare = .ExecutePythonScript(.SCRIPTISSHARED, _SF_.PythonHelper &amp; cstPyHelper _
+ , _ConvertFromUrl(FileName1) _
+ , _ConvertFromUrl(FileName2) _
+ , CompareContents)
+ End With
+
+Finally:
+ CompareFiles = bCompare
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchNotExists:
+ SF_Exception.RaiseFatal(UNKNOWNFILEERROR, &quot;FileName&quot; &amp; iFile, sFile)
+ GoTo Finally
+End Function &apos; ScriptForge.SF_FileSystem.CompareFiles
+
+REM -----------------------------------------------------------------------------
+Public Function CopyFile(Optional ByVal Source As Variant _
+ , Optional ByVal Destination As Variant _
+ , Optional ByVal Overwrite As Variant _
+ ) As Boolean
+&apos;&apos;&apos; Copies one or more files from one location to another
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Source: FileName or NamePattern which can include wildcard characters, for one or more files to be copied
+&apos;&apos;&apos; Destination: FileName where the single Source file is to be copied
+&apos;&apos;&apos; or FolderName where the multiple files from Source are to be copied
+&apos;&apos;&apos; If FolderName does not exist, it is created
+&apos;&apos;&apos; Anyway, wildcard characters are not allowed in Destination
+&apos;&apos;&apos; Overwrite: If True (default), files may be overwritten
+&apos;&apos;&apos; CopyFile will fail if Destination has the read-only attribute set, regardless of the value of Overwrite.
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if at least one file has been copied
+&apos;&apos;&apos; False if an error occurred
+&apos;&apos;&apos; An error also occurs if a source using wildcard characters doesn&apos;t match any files.
+&apos;&apos;&apos; The method stops on the first error it encounters
+&apos;&apos;&apos; No attempt is made to roll back or undo any changes made before an error occurs
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; UNKNOWNFILEERROR Source does not exist
+&apos;&apos;&apos; UNKNOWNFOLDERERROR Source folder or Destination folder does not exist
+&apos;&apos;&apos; NOFILEMATCHERROR No file matches Source containing wildcards
+&apos;&apos;&apos; NOTAFOLDERERROR Destination is a file, not a folder
+&apos;&apos;&apos; NOTAFILEERROR Destination is a folder, not a file
+&apos;&apos;&apos; OVERWRITEERROR Destination can not be overwritten
+&apos;&apos;&apos; READONLYERROR Destination has its read-only attribute set
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; FSO.FileNaming = &quot;SYS&quot;
+&apos;&apos;&apos; FSO.CopyFile(&quot;C:\Windows\*.*&quot;, &quot;C:\Temp\&quot;, Overwrite := False) &apos; Only files are copied, subfolders are not
+
+Dim bCopy As Boolean &apos; Return value
+
+Const cstThisSub = &quot;FileSystem.CopyFile&quot;
+Const cstSubArgs = &quot;Source, Destination, [Overwrite=True]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bCopy = False
+
+Check:
+ If IsMissing(Overwrite) Or IsEmpty(Overwrite) Then Overwrite = True
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateFile(Source, &quot;Source&quot;, True) Then GoTo Finally
+ If Not SF_Utils._ValidateFile(Destination, &quot;Destination&quot;, False) Then GoTo Finally
+ If Not SF_Utils._Validate(Overwrite, &quot;Overwrite&quot;, V_BOOLEAN) Then GoTo Finally
+ End If
+
+Try:
+ bCopy = SF_FileSystem._CopyMove(&quot;CopyFile&quot;, Source, Destination, Overwrite)
+
+Finally:
+ CopyFile = bCopy
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_FileSystem.CopyFile
+
+REM -----------------------------------------------------------------------------
+Public Function CopyFolder(Optional ByVal Source As Variant _
+ , Optional ByVal Destination As Variant _
+ , Optional ByVal Overwrite As Variant _
+ ) As Boolean
+&apos;&apos;&apos; Copies one or more folders from one location to another
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Source: FolderName or NamePattern which can include wildcard characters, for one or more folders to be copied
+&apos;&apos;&apos; Destination: FolderName where the single Source folder is to be copied
+&apos;&apos;&apos; or FolderName where the multiple folders from Source are to be copied
+&apos;&apos;&apos; If FolderName does not exist, it is created
+&apos;&apos;&apos; Anyway, wildcard characters are not allowed in Destination
+&apos;&apos;&apos; Overwrite: If True (default), folders and their content may be overwritten
+&apos;&apos;&apos; CopyFile will fail if Destination has the read-only attribute set, regardless of the value of Overwrite.
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if at least one folder has been copied
+&apos;&apos;&apos; False if an error occurred
+&apos;&apos;&apos; An error also occurs if a source using wildcard characters doesn&apos;t match any folders.
+&apos;&apos;&apos; The method stops on the first error it encounters
+&apos;&apos;&apos; No attempt is made to roll back or undo any changes made before an error occurs
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; UNKNOWNFILEERROR Source does not exist
+&apos;&apos;&apos; UNKNOWNFOLDERERROR Source folder or Destination folder does not exist
+&apos;&apos;&apos; NOFILEMATCHERROR No file matches Source containing wildcards
+&apos;&apos;&apos; NOTAFOLDERERROR Destination is a file, not a folder
+&apos;&apos;&apos; OVERWRITEERROR Destination can not be overwritten
+&apos;&apos;&apos; READONLYERROR Destination has its read-only attribute set
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; FSO.FileNaming = &quot;SYS&quot;
+&apos;&apos;&apos; FSO.CopyFolder(&quot;C:\Windows\*&quot;, &quot;C:\Temp\&quot;, Overwrite := False)
+
+Dim bCopy As Boolean &apos; Return value
+
+Const cstThisSub = &quot;FileSystem.CopyFolder&quot;
+Const cstSubArgs = &quot;Source, Destination, [Overwrite=True]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bCopy = False
+
+Check:
+ If IsMissing(Overwrite) Or IsEmpty(Overwrite) Then Overwrite = True
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateFile(Source, &quot;Source&quot;, True) Then GoTo Finally
+ If Not SF_Utils._ValidateFile(Destination, &quot;Destination&quot;, False) Then GoTo Finally
+ If Not SF_Utils._Validate(Overwrite, &quot;Overwrite&quot;, V_BOOLEAN) Then GoTo Finally
+ End If
+
+Try:
+ bCopy = SF_FileSystem._CopyMove(&quot;CopyFolder&quot;, Source, Destination, Overwrite)
+
+Finally:
+ CopyFolder = bCopy
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_FileSystem.CopyFolder
+
+REM -----------------------------------------------------------------------------
+Public Function CreateFolder(Optional ByVal FolderName As Variant) As Boolean
+&apos;&apos;&apos; Return True if the given folder name could be created successfully
+&apos;&apos;&apos; The parent folder does not need to exist beforehand
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; FolderName: a string representing the folder to create. It must not exist
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if FolderName is a valid folder name, does not exist and creation was successful
+&apos;&apos;&apos; False otherwise including when FolderName is a file
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; FOLDERCREATIONERROR FolderName is an existing folder or file
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; FSO.FileNaming = &quot;SYS&quot;
+&apos;&apos;&apos; FSO.CreateFolder(&quot;C:\NewFolder\&quot;)
+
+Dim bCreate As Boolean &apos; Return value
+Dim oSfa As Object &apos; com.sun.star.ucb.SimpleFileAccess
+
+Const cstThisSub = &quot;FileSystem.CreateFolder&quot;
+Const cstSubArgs = &quot;FolderName&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bCreate = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateFile(FolderName, &quot;FolderName&quot;) Then GoTo Finally
+ End If
+
+Try:
+ Set oSfa = SF_Utils._GetUnoService(&quot;FileAccess&quot;)
+ If SF_FileSystem.FolderExists(FolderName) Then GoTo CatchExists
+ If SF_FileSystem.FileExists(FolderName) Then GoTo CatchExists
+ oSfa.createFolder(SF_FileSystem._ConvertToUrl(FolderName))
+ bCreate = True
+
+Finally:
+ CreateFolder = bCreate
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchExists:
+ SF_Exception.RaiseFatal(FOLDERCREATIONERROR, &quot;FolderName&quot;, FolderName)
+ GoTo Finally
+End Function &apos; ScriptForge.SF_FileSystem.CreateFolder
+
+REM -----------------------------------------------------------------------------
+Public Function CreateTextFile(Optional ByVal FileName As Variant _
+ , Optional ByVal Overwrite As Variant _
+ , Optional ByVal Encoding As Variant _
+ ) As Object
+&apos;&apos;&apos; Creates a specified file and returns a TextStream object that can be used to write to the file
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; FileName: Identifies the file to create
+&apos;&apos;&apos; Overwrite: Boolean value that indicates if an existing file can be overwritten (default = True)
+&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 does not implement all existing sets
+&apos;&apos;&apos; Default = UTF-8
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; An instance of the SF_TextStream class representing the opened file or a Null object if an error occurred
+&apos;&apos;&apos; It doesn&apos;t check either if the given encoding is implemented in LibreOffice
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; OVERWRITEERROR File exists, creation impossible
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; Dim myFile As Object
+&apos;&apos;&apos; FSO.FileNaming = &quot;SYS&quot;
+&apos;&apos;&apos; Set myFile = FSO.CreateTextFile(&quot;C:\Temp\ThisFile.txt&quot;, Overwrite := True)
+
+Dim oTextStream As Object &apos; Return value
+Const cstThisSub = &quot;FileSystem.CreateTextFile&quot;
+Const cstSubArgs = &quot;FileName, [Overwrite=True], [Encoding=&quot;&quot;UTF-8&quot;&quot;]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ Set oTextStream = Nothing
+
+Check:
+ If IsMissing(Overwrite) Or IsEmpty(Overwrite) Then Overwrite = True
+ If IsMissing(Encoding) Or IsEmpty(Encoding) Then Encoding = &quot;UTF-8&quot;
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateFile(FileName, &quot;FileName&quot;) Then GoTo Finally
+ If Not SF_Utils._Validate(Overwrite, &quot;Overwrite&quot;, V_BOOLEAN) Then GoTo Finally
+ If Not SF_Utils._Validate(Encoding, &quot;Encoding&quot;, V_STRING) Then GoTo Finally
+ End If
+
+ With SF_FileSystem
+ If .FileExists(FileName) Then
+ If Overwrite Then .DeleteFile(FileName) Else GoTo CatchOverWrite
+ End If
+
+Try:
+ Set oTextStream = .OpenTextFile(FileName, .ForWriting, Create := True, Encoding := Encoding)
+ End With
+
+Finally:
+ Set CreateTextFile = oTextStream
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchOverWrite:
+ SF_Exception.RaiseFatal(OVERWRITEERROR, &quot;FileName&quot;, FileName)
+ GoTo Finally
+End Function &apos; ScriptForge.SF_FileSystem.CreateTextFile
+
+REM -----------------------------------------------------------------------------
+Public Function DeleteFile(Optional ByVal FileName As Variant) As Boolean
+&apos;&apos;&apos; Deletes one or more files
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; FileName: FileName or NamePattern which can include wildcard characters, for one or more files to be deleted
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if at least one file has been deleted
+&apos;&apos;&apos; False if an error occurred
+&apos;&apos;&apos; An error also occurs if a FileName using wildcard characters doesn&apos;t match any files.
+&apos;&apos;&apos; The method stops on the first error it encounters
+&apos;&apos;&apos; No attempt is made to roll back or undo any changes made before an error occurs
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; UNKNOWNFILEERROR FileName does not exist
+&apos;&apos;&apos; NOFILEMATCHERROR No file matches FileName containing wildcards
+&apos;&apos;&apos; NOTAFILEERROR Argument is a folder, not a file
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; FSO.FileNaming = &quot;SYS&quot;
+&apos;&apos;&apos; FSO.DeleteFile(&quot;C:\Temp\*.*&quot;) &apos; Only files are deleted, subfolders are not
+
+Dim bDelete As Boolean &apos; Return value
+
+Const cstThisSub = &quot;FileSystem.DeleteFile&quot;
+Const cstSubArgs = &quot;FileName&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bDelete = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateFile(FileName, &quot;FileName&quot;, True) Then GoTo Finally
+ End If
+
+Try:
+ bDelete = SF_FileSystem._Delete(&quot;DeleteFile&quot;, FileName)
+
+Finally:
+ DeleteFile = bDelete
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_FileSystem.DeleteFile
+
+REM -----------------------------------------------------------------------------
+Public Function DeleteFolder(Optional ByVal FolderName As Variant) As Boolean
+&apos;&apos;&apos; Deletes one or more Folders
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; FolderName: FolderName or NamePattern which can include wildcard characters, for one or more Folders to be deleted
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if at least one folder has been deleted
+&apos;&apos;&apos; False if an error occurred
+&apos;&apos;&apos; An error also occurs if a FolderName using wildcard characters doesn&apos;t match any folders.
+&apos;&apos;&apos; The method stops on the first error it encounters
+&apos;&apos;&apos; No attempt is made to roll back or undo any changes made before an error occurs
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; UNKNOWNFOLDERERROR FolderName does not exist
+&apos;&apos;&apos; NOFILEMATCHERROR No folder matches FolderName containing wildcards
+&apos;&apos;&apos; NOTAFOLDERERROR Argument is a file, not a folder
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; FSO.FileNaming = &quot;SYS&quot;
+&apos;&apos;&apos; FSO.DeleteFolder(&quot;C:\Temp\*&quot;) &apos; Only folders are deleted, files in the parent folder are not
+
+Dim bDelete As Boolean &apos; Return value
+
+Const cstThisSub = &quot;FileSystem.DeleteFolder&quot;
+Const cstSubArgs = &quot;FolderName&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bDelete = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateFile(FolderName, &quot;FolderName&quot;, True) Then GoTo Finally
+ End If
+
+Try:
+ bDelete = SF_FileSystem._Delete(&quot;DeleteFolder&quot;, FolderName)
+
+Finally:
+ DeleteFolder = bDelete
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_FileSystem.DeleteFolder
+
+REM -----------------------------------------------------------------------------
+Public Function ExtensionFolder(Optional ByVal Extension As Variant) As String
+&apos;&apos;&apos; Return the folder where the given extension is installed. The argument must
+&apos;&apos;&apos; be in the list of extensions provided by the SF_Platform.Extensions property
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Extension: a valid extension name
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The requested folder using the FileNaming notation
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; MsgBox FSO.ExtensionFolder(&quot;apso.python.script.organizer&quot;)
+
+Dim sFolder As String &apos; Return value
+Static vExtensions As Variant &apos; Cached list of existing extension names
+Dim oPackage As Object &apos; /singletons/com.sun.star.deployment.PackageInformationProvider
+Const cstThisSub = &quot;FileSystem.ExtensionFolder&quot;
+Const cstSubArgs = &quot;Extension&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sFolder = &quot;&quot;
+
+Check:
+ If IsEmpty(vExtensions) Then vExtensions = SF_Platform.Extensions
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(Extension, &quot;Extension&quot;, V_STRING, vExtensions) Then GoTo Finally
+ End If
+
+Try:
+ &apos; Search an individual folder
+ Set oPackage = SF_Utils._GetUnoService(&quot;PackageInformationProvider&quot;)
+ sFolder = oPackage.getPackageLocation(Extension)
+
+Finally:
+ ExtensionFolder = SF_FileSystem._ConvertFromUrl(sFolder)
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_FileSystem.ExtensionFolder
+
+REM -----------------------------------------------------------------------------
+Public Function FileExists(Optional ByVal FileName As Variant) As Boolean
+&apos;&apos;&apos; Return True if the given file exists
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; FileName: a string representing a file
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if FileName is a valid File name and it exists
+&apos;&apos;&apos; False otherwise including when FileName is a folder
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; FSO.FileNaming = &quot;SYS&quot;
+&apos;&apos;&apos; If FSO.FileExists(&quot;C:\Notepad.exe&quot;) Then ...
+
+Dim bExists As Boolean &apos; Return value
+Dim oSfa As Object &apos; com.sun.star.ucb.SimpleFileAccess
+
+Const cstThisSub = &quot;FileSystem.FileExists&quot;
+Const cstSubArgs = &quot;FileName&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bExists = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateFile(FileName, &quot;FileName&quot;) Then GoTo Finally
+ End If
+ FileName = SF_FileSystem._ConvertToUrl(FileName)
+
+Try:
+ Set oSfa = SF_Utils._GetUnoService(&quot;FileAccess&quot;)
+ bExists = oSfa.exists(FileName) And Not oSfa.isFolder(FileName)
+
+Finally:
+ FileExists = bExists
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_FileSystem.FileExists
+
+REM -----------------------------------------------------------------------------
+Public Function Files(Optional ByVal FolderName As Variant _
+ , Optional ByVal Filter As Variant _
+ ) As Variant
+&apos;&apos;&apos; Return an array of the FileNames stored in the given folder. The folder must exist
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; FolderName: the folder to explore
+&apos;&apos;&apos; Filter: contains wildcards (&quot;?&quot; and &quot;*&quot;) to limit the list to the relevant files (default = &quot;&quot;)
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; An array of strings, each entry is the FileName of an existing file
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; UNKNOWNFOLDERERROR Folder does not exist
+&apos;&apos;&apos; NOTAFOLDERERROR FolderName is a file, not a folder
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; Dim a As Variant
+&apos;&apos;&apos; FSO.FileNaming = &quot;SYS&quot;
+&apos;&apos;&apos; a = FSO.Files(&quot;C:\Windows\&quot;)
+
+Dim vFiles As Variant &apos; Return value
+Dim oSfa As Object &apos; com.sun.star.ucb.SimpleFileAccess
+Dim sFolderName As String &apos; URL lias for FolderName
+Dim sFile As String &apos; Single file
+Dim i As Long
+
+Const cstThisSub = &quot;FileSystem.Files&quot;
+Const cstSubArgs = &quot;FolderName, [Filter=&quot;&quot;&quot;&quot;]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vFiles = Array()
+
+Check:
+ If IsMissing(Filter) Or IsEmpty(Filter) Then Filter = &quot;&quot;
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateFile(FolderName, &quot;FolderName&quot;) Then GoTo Finally
+ If Not SF_Utils._Validate(Filter, &quot;Filter&quot;, V_STRING) Then GoTo Finally
+ End If
+ sFolderName = SF_FileSystem._ConvertToUrl(FolderName)
+ If SF_FileSystem.FileExists(FolderName) Then GoTo CatchFile &apos; Must not be a file
+ If Not SF_FileSystem.FolderExists(FolderName) Then GoTo CatchFolder &apos; Folder must exist
+
+Try:
+ &apos; Get files
+ Set oSfa = SF_Utils._GetUnoService(&quot;FileAccess&quot;)
+ vFiles = oSfa.getFolderContents(sFolderName, False)
+ &apos; Adjust notations
+ For i = 0 To UBound(vFiles)
+ sFile = SF_FileSystem._ConvertFromUrl(vFiles(i))
+ vFiles(i) = sFile
+ Next i
+ &apos; Reduce list to those passing the filter
+ If Len(Filter) &gt; 0 Then
+ For i = 0 To UBound(vFiles)
+ sFile = SF_FileSystem.GetName(vFiles(i))
+ If Not SF_String.IsLike(sFile, Filter) Then vFiles(i) = &quot;&quot;
+ Next i
+ vFiles = Sf_Array.TrimArray(vFiles)
+ End If
+
+Finally:
+ Files = vFiles
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchFile:
+ SF_Exception.RaiseFatal(NOTAFOLDERERROR, &quot;FolderName&quot;, FolderName)
+ GoTo Finally
+CatchFolder:
+ SF_Exception.RaiseFatal(UNKNOWNFOLDERERROR, &quot;FolderName&quot;, FolderName)
+ GoTo Finally
+End Function &apos; ScriptForge.SF_FileSystem.Files
+
+REM -----------------------------------------------------------------------------
+Public Function FolderExists(Optional ByVal FolderName As Variant) As Boolean
+&apos;&apos;&apos; Return True if the given folder name exists
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; FolderName: a string representing a folder
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if FolderName is a valid folder name and it exists
+&apos;&apos;&apos; False otherwise including when FolderName is a file
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; FSO.FileNaming = &quot;SYS&quot;
+&apos;&apos;&apos; If FSO.FolderExists(&quot;C:\&quot;) Then ...
+
+Dim bExists As Boolean &apos; Return value
+Dim oSfa As Object &apos; com.sun.star.ucb.SimpleFileAccess
+
+Const cstThisSub = &quot;FileSystem.FolderExists&quot;
+Const cstSubArgs = &quot;FolderName&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bExists = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateFile(FolderName, &quot;FolderName&quot;) Then GoTo Finally
+ End If
+ FolderName = SF_FileSystem._ConvertToUrl(FolderName)
+
+Try:
+ Set oSfa = SF_Utils._GetUnoService(&quot;FileAccess&quot;)
+ bExists = oSfa.isFolder(FolderName)
+
+Finally:
+ FolderExists = bExists
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_FileSystem.FolderExists
+
+REM -----------------------------------------------------------------------------
+Public Function GetBaseName(Optional ByVal FileName As Variant) As String
+&apos;&apos;&apos; Returns the BaseName part of the last component of a File- or FolderName, without its extension
+&apos;&apos;&apos; The method does not check for the existence of the specified file or folder
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; FileName: Path and file name
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The BaseName of the given argument in native operating system format. May be empty
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; Dim a As String
+&apos;&apos;&apos; FSO.FileNaming = &quot;SYS&quot;
+&apos;&apos;&apos; a = FSO.GetBaseName(&quot;C:\Windows\Notepad.exe&quot;) returns Notepad
+
+Dim sBase As String &apos; Return value
+Dim sExt As String &apos; Extension
+Dim sName As String &apos; Last component of FileName
+Dim vName As Variant &apos; Array of trunks of sName
+Const cstThisSub = &quot;FileSystem.GetBaseName&quot;
+Const cstSubArgs = &quot;FileName&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sBase = &quot;&quot;
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateFile(FileName, &quot;FileName&quot;) Then GoTo Finally
+ End If
+
+Try:
+ sName = SF_FileSystem.GetName(FileName)
+ If Len(sName) &gt; 0 Then
+ If InStr(sName, &quot;.&quot;) &gt; 0 Then
+ vName = Split(sName, &quot;.&quot;)
+ sExt = vName(UBound(vName))
+ sBase = Left(sName, Len(sName) - Len(sExt) - 1)
+ Else
+ sBase = sName
+ End If
+ End If
+
+Finally:
+ GetBaseName = sBase
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_FileSystem.GetBaseName
+
+REM -----------------------------------------------------------------------------
+Public Function GetExtension(Optional ByVal FileName As Variant) As String
+&apos;&apos;&apos; Returns the extension part of a File- or FolderName, without the dot (.).
+&apos;&apos;&apos; The method does not check for the existence of the specified file or folder
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; FileName: Path and file name
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The extension without a leading dot. May be empty
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; Dim a As String
+&apos;&apos;&apos; FSO.FileNaming = &quot;SYS&quot;
+&apos;&apos;&apos; a = FSO.GetExtension(&quot;C:\Windows\Notepad.exe&quot;) returns exe
+
+Dim sExt As String &apos; Return value
+Dim sName As String &apos; Last component of FileName
+Dim vName As Variant &apos; Array of trunks of sName
+Const cstThisSub = &quot;FileSystem.GetExtension&quot;
+Const cstSubArgs = &quot;FileName&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sExt = &quot;&quot;
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateFile(FileName, &quot;FileName&quot;) Then GoTo Finally
+ End If
+
+Try:
+ sName = SF_FileSystem.GetName(FileName)
+ If Len(sName) &gt; 0 And InStr(sName, &quot;.&quot;) &gt; 0 Then
+ vName = Split(sName, &quot;.&quot;)
+ sExt = vName(UBound(vName))
+ End If
+
+Finally:
+ GetExtension = sExt
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_FileSystem.GetExtension
+
+REM -----------------------------------------------------------------------------
+Public Function GetFileLen(Optional ByVal FileName As Variant) As Currency
+&apos;&apos;&apos; Return file size in bytes with four decimals &apos;&apos;&apos;
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; FileName: a string representing a file
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; File size if FileName exists
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; UNKNOWNFILEERROR The file does not exist of is a folder
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; Print SF_FileSystem.GetFileLen(&quot;C:\pagefile.sys&quot;)
+
+Dim curSize As Currency &apos; Return value
+Const cstPyHelper = &quot;$&quot; &amp; &quot;_SF_FileSystem__GetFilelen&quot;
+Const cstThisSub = &quot;FileSystem.GetFileLen&quot;
+Const cstSubArgs = &quot;FileName&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ curSize = 0
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateFile(FileName, &quot;FileName&quot;) Then GoTo Finally
+ End If
+
+Try:
+ If SF_FileSystem.FileExists(FileName) Then
+ With ScriptForge.SF_Session
+ curSize = .ExecutePythonScript(.SCRIPTISSHARED, _SF_.PythonHelper &amp; cstPyHelper _
+ , _ConvertFromUrl(FileName))
+ End With
+ Else
+ GoTo CatchNotExists
+ End If
+
+Finally:
+ GetFileLen = curSize
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchNotExists:
+ SF_Exception.RaiseFatal(UNKNOWNFILEERROR, &quot;FileName&quot;, FileName)
+ GoTo Finally
+End Function &apos; ScriptForge.SF_FileSystem.GetFileLen
+
+REM -----------------------------------------------------------------------------
+Public Function GetFileModified(Optional ByVal FileName As Variant) As Variant
+&apos;&apos;&apos; Returns the last modified date for the given file
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; FileName: a string representing an existing file
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The modification date and time as a Basic Date
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; UNKNOWNFILEERROR The file does not exist of is a folder
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; Dim a As Date
+&apos;&apos;&apos; FSO.FileNaming = &quot;SYS&quot;
+&apos;&apos;&apos; a = FSO.GetFileModified(&quot;C:\Temp\myDoc.odt&quot;)
+
+Dim dModified As Date &apos; Return value
+Dim oModified As New com.sun.star.util.DateTime
+Dim oSfa As Object &apos; com.sun.star.ucb.SimpleFileAccess
+
+Const cstThisSub = &quot;FileSystem.GetFileModified&quot;
+Const cstSubArgs = &quot;FileName&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ dModified = 0
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateFile(FileName, &quot;FileName&quot;) Then GoTo Finally
+ End If
+
+Try:
+ Set oSfa = SF_Utils._GetUnoService(&quot;FileAccess&quot;)
+ If SF_FileSystem.FileExists(FileName) Then
+ FileName = SF_FileSystem._ConvertToUrl(FileName)
+ Set oModified = oSfa.getDateTimeModified(FileName)
+ dModified = CDateFromUnoDateTime(oModified)
+ Else
+ GoTo CatchNotExists
+ End If
+
+Finally:
+ GetFileModified = dModified
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchNotExists:
+ SF_Exception.RaiseFatal(UNKNOWNFILEERROR, &quot;FileName&quot;, FileName)
+ GoTo Finally
+End Function &apos; ScriptForge.SF_FileSystem.GetFileModified
+
+REM -----------------------------------------------------------------------------
+Public Function GetName(Optional ByVal FileName As Variant) As String
+&apos;&apos;&apos; Returns the last component of a File- or FolderName
+&apos;&apos;&apos; The method does not check for the existence of the specified file or folder
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; FileName: Path and file name
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The last component of the full file name in native operating system format
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; Dim a As String
+&apos;&apos;&apos; FSO.FileNaming = &quot;SYS&quot;
+&apos;&apos;&apos; a = FSO.GetName(&quot;C:\Windows\Notepad.exe&quot;) returns Notepad.exe
+
+Dim sName As String &apos; Return value
+Dim vFile As Variant &apos; Array of components
+Const cstThisSub = &quot;FileSystem.GetName&quot;
+Const cstSubArgs = &quot;FileName&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sName = &quot;&quot;
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateFile(FileName, &quot;FileName&quot;) Then GoTo Finally
+ End If
+ FileName = SF_FileSystem._ConvertToUrl(FileName)
+
+Try:
+ If Len(FileName) &gt; 0 Then
+ If Right(FileName, 1) = &quot;/&quot; Then FileName = Left(FileName, Len(FileName) - 1)
+ vFile = Split(FileName, &quot;/&quot;)
+ sName = ConvertFromUrl(vFile(UBound(vFile))) &apos; Always in SYS format
+ End If
+
+Finally:
+ GetName = sName
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_FileSystem.GetName
+
+REM -----------------------------------------------------------------------------
+Public Function GetParentFolderName(Optional ByVal FileName As Variant) As String
+&apos;&apos;&apos; Returns a string containing the name of the parent folder of the last component in a specified File- or FolderName
+&apos;&apos;&apos; The method does not check for the existence of the specified file or folder
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; FileName: Path and file name
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; A FolderName including its final path separator
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; Dim a As String
+&apos;&apos;&apos; FSO.FileNaming = &quot;SYS&quot;
+&apos;&apos;&apos; a = FSO.GetParentFolderName(&quot;C:\Windows\Notepad.exe&quot;) returns C:\Windows\
+
+Dim sFolder As String &apos; Return value
+Dim sName As String &apos; Last component of FileName
+Dim vFile As Variant &apos; Array of file components
+Const cstThisSub = &quot;FileSystem.GetParentFolderName&quot;
+Const cstSubArgs = &quot;FileName&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sFolder = &quot;&quot;
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateFile(FileName, &quot;FileName&quot;) Then GoTo Finally
+ End If
+ FileName = SF_FileSystem._ConvertToUrl(FileName)
+
+Try:
+ If Right(FileName, 1) = &quot;/&quot; Then FileName = Left(FileName, Len(FileName) - 1)
+ vFile = Split(FileName, &quot;/&quot;)
+ If UBound(vFile) &gt;= 0 Then vFile(UBound(vFile)) = &quot;&quot;
+ sFolder = Join(vFile, &quot;/&quot;)
+ If sFolder = &quot;&quot; Or Right(sFolder, 1) &lt;&gt; &quot;/&quot; Then sFolder = sFolder &amp; &quot;/&quot;
+
+Finally:
+ GetParentFolderName = SF_FileSystem._ConvertFromUrl(sFolder)
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_FileSystem.GetParentFolderName
+
+REM -----------------------------------------------------------------------------
+Public Function GetProperty(Optional ByVal PropertyName As Variant) As Variant
+&apos;&apos;&apos; Return the actual value of the given property
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; PropertyName: the name of the property as a string
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The actual value of the property
+&apos;&apos;&apos; Exceptions
+&apos;&apos;&apos; ARGUMENTERROR The property does not exist
+
+Const cstThisSub = &quot;FileSystem.GetProperty&quot;
+Const cstSubArgs = &quot;PropertyName&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ GetProperty = Null
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
+ End If
+
+Try:
+ Select Case UCase(PropertyName)
+ Case UCase(&quot;ConfigFolder&quot;) : GetProperty = ConfigFolder
+ Case UCase(&quot;ExtensionsFolder&quot;) : GetProperty = ExtensionsFolder
+ Case UCase(&quot;FileNaming&quot;) : GetProperty = FileNaming
+ Case UCase(&quot;HomeFolder&quot;) : GetProperty = HomeFolder
+ Case UCase(&quot;InstallFolder&quot;) : GetProperty = InstallFolder
+ Case UCase(&quot;TemplatesFolder&quot;) : GetProperty = TemplatesFolder
+ Case UCase(&quot;TemporaryFolder&quot;) : GetProperty = TemporaryFolder
+ Case UCase(&quot;UserTemplatesFolder&quot;) : GetProperty = UserTemplatesFolder
+ Case Else
+ End Select
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_FileSystem.GetProperty
+
+REM -----------------------------------------------------------------------------
+Public Function GetTempName() As String
+&apos;&apos;&apos; Returns a randomly generated temporary file name that is useful for performing
+&apos;&apos;&apos; operations that require a temporary file : the method does not create any file
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; A FileName as a String that can be used f.i. with CreateTextFile()
+&apos;&apos;&apos; The FileName does not have any suffix
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; Dim a As String
+&apos;&apos;&apos; FSO.FileNaming = &quot;SYS&quot;
+&apos;&apos;&apos; a = FSO.GetTempName() &amp; &quot;.txt&quot;
+
+Dim sFile As String &apos; Return value
+Dim sTempDir As String &apos; The path to a temporary folder
+Dim lRandom As Long &apos; Random integer
+
+Const cstThisSub = &quot;FileSystem.GetTempName&quot;
+Const cstSubArgs = &quot;&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sFile = &quot;&quot;
+
+Check:
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Try:
+ lRandom = SF_Session.ExecuteCalcFunction(&quot;RANDBETWEEN&quot;, 1, 999999)
+ sFile = SF_FileSystem.TemporaryFolder &amp; &quot;SF_&quot; &amp; Right(&quot;000000&quot; &amp; lRandom, 6)
+
+Finally:
+ GetTempName = SF_FileSystem._ConvertFromUrl(sFile)
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_FileSystem.GetTempName
+
+REM -----------------------------------------------------------------------------
+Public Function HashFile(Optional ByVal FileName As Variant _
+ , Optional ByVal Algorithm As Variant _
+ ) As String
+&apos;&apos;&apos; Return an hexadecimal string representing a checksum of the given file
+&apos;&apos;&apos; Next algorithms are supported: MD5, SHA1, SHA224, SHA256, SHA384 and SHA512
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; FileName: a string representing a file
+&apos;&apos;&apos; Algorithm: The hashing algorithm to use
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The requested checksum as a string. Hexadecimal digits are lower-cased
+&apos;&apos;&apos; A zero-length string when an error occurred
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; UNKNOWNFILEERROR The file does not exist of is a folder
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; Print SF_FileSystem.HashFile(&quot;C:\pagefile.sys&quot;, &quot;MD5&quot;)
+
+Dim sHash As String &apos; Return value
+Const cstPyHelper = &quot;$&quot; &amp; &quot;_SF_FileSystem__HashFile&quot;
+Const cstThisSub = &quot;FileSystem.HashFile&quot;
+Const cstSubArgs = &quot;FileName, Algorithm=&quot;&quot;MD5&quot;&quot;|&quot;&quot;SHA1&quot;&quot;|&quot;&quot;SHA224&quot;&quot;|&quot;&quot;SHA256&quot;&quot;|&quot;&quot;SHA384&quot;&quot;|&quot;&quot;SHA512&quot;&quot;&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sHash = &quot;&quot;
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateFile(FileName, &quot;FileName&quot;) Then GoTo Finally
+ If Not SF_Utils._Validate(Algorithm, &quot;Algorithm&quot;, V_STRING _
+ , Array(&quot;MD5&quot;, &quot;SHA1&quot;, &quot;SHA224&quot;, &quot;SHA256&quot;, &quot;SHA384&quot;, &quot;SHA512&quot;)) Then GoTo Finally
+ End If
+
+Try:
+ If SF_FileSystem.FileExists(FileName) Then
+ With ScriptForge.SF_Session
+ sHash = .ExecutePythonScript(.SCRIPTISSHARED, _SF_.PythonHelper &amp; cstPyHelper _
+ , _ConvertFromUrl(FileName), LCase(Algorithm))
+ End With
+ Else
+ GoTo CatchNotExists
+ End If
+
+Finally:
+ HashFile = sHash
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchNotExists:
+ SF_Exception.RaiseFatal(UNKNOWNFILEERROR, &quot;FileName&quot;, FileName)
+ GoTo Finally
+End Function &apos; ScriptForge.SF_FileSystem.HashFile
+
+REM -----------------------------------------------------------------------------
+Public Function Methods() As Variant
+&apos;&apos;&apos; Return the list or methods of the FileSystem service as an array
+
+ Methods = Array(&quot;BuildPath&quot; _
+ , &quot;CompareFiles&quot; _
+ , &quot;CopyFile&quot; _
+ , &quot;CopyFolder&quot; _
+ , &quot;CreateFolder&quot; _
+ , &quot;CreateTextFile&quot; _
+ , &quot;DeleteFile&quot; _
+ , &quot;DeleteFolder&quot; _
+ , &quot;ExtensionFolder&quot; _
+ , &quot;FileExists&quot; _
+ , &quot;Files&quot; _
+ , &quot;FolderExists&quot; _
+ , &quot;GetBaseName&quot; _
+ , &quot;GetExtension&quot; _
+ , &quot;GetFileLen&quot; _
+ , &quot;GetFileModified&quot; _
+ , &quot;GetName&quot; _
+ , &quot;GetParentFolderName&quot; _
+ , &quot;GetTempName&quot; _
+ , &quot;HashFile&quot; _
+ , &quot;MoveFile&quot; _
+ , &quot;MoveFolder&quot; _
+ , &quot;OpenTextFile&quot; _
+ , &quot;PickFile&quot; _
+ , &quot;PickFolder&quot; _
+ , &quot;SubFolders&quot; _
+ )
+
+End Function &apos; ScriptForge.SF_FileSystem.Methods
+
+REM -----------------------------------------------------------------------------
+Public Function MoveFile(Optional ByVal Source As Variant _
+ , Optional ByVal Destination As Variant _
+ ) As Boolean
+&apos;&apos;&apos; Moves one or more files from one location to another
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Source: FileName or NamePattern which can include wildcard characters, for one or more files to be moved
+&apos;&apos;&apos; Destination: FileName where the single Source file is to be moved
+&apos;&apos;&apos; If Source and Destination have the same parent folder MoveFile amounts to renaming the Source
+&apos;&apos;&apos; or FolderName where the multiple files from Source are to be moved
+&apos;&apos;&apos; If FolderName does not exist, it is created
+&apos;&apos;&apos; Anyway, wildcard characters are not allowed in Destination
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if at least one file has been moved
+&apos;&apos;&apos; False if an error occurred
+&apos;&apos;&apos; An error also occurs if a source using wildcard characters doesn&apos;t match any files.
+&apos;&apos;&apos; The method stops on the first error it encounters
+&apos;&apos;&apos; No attempt is made to roll back or undo any changes made before an error occurs
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; UNKNOWNFILEERROR Source does not exist
+&apos;&apos;&apos; UNKNOWNFOLDERERROR Source folder or Destination folder does not exist
+&apos;&apos;&apos; NOFILEMATCHERROR No file matches Source containing wildcards
+&apos;&apos;&apos; NOTAFOLDERERROR Destination is a file, not a folder
+&apos;&apos;&apos; NOTAFILEERROR Destination is a folder, not a file
+&apos;&apos;&apos; OVERWRITEERROR Destination can not be overwritten
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; FSO.FileNaming = &quot;SYS&quot;
+&apos;&apos;&apos; FSO.MoveFile(&quot;C:\Temp1\*.*&quot;, &quot;C:\Temp2\&quot;) &apos; Only files are moved, subfolders are not
+
+Dim bMove As Boolean &apos; Return value
+
+Const cstThisSub = &quot;FileSystem.MoveFile&quot;
+Const cstSubArgs = &quot;Source, Destination&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bMove = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateFile(Source, &quot;Source&quot;, True) Then GoTo Finally
+ If Not SF_Utils._ValidateFile(Destination, &quot;Destination&quot;, False) Then GoTo Finally
+ End If
+
+Try:
+ bMove = SF_FileSystem._CopyMove(&quot;MoveFile&quot;, Source, Destination, False)
+
+Finally:
+ MoveFile = bMove
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_FileSystem.MoveFile
+
+REM -----------------------------------------------------------------------------
+Public Function MoveFolder(Optional ByVal Source As Variant _
+ , Optional ByVal Destination As Variant _
+ ) As Boolean
+&apos;&apos;&apos; Moves one or more folders from one location to another
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Source: FolderName or NamePattern which can include wildcard characters, for one or more folders to be moved
+&apos;&apos;&apos; Destination: FolderName where the single Source folder is to be moved
+&apos;&apos;&apos; FolderName must not exist
+&apos;&apos;&apos; or FolderName where the multiple folders from Source are to be moved
+&apos;&apos;&apos; If FolderName does not exist, it is created
+&apos;&apos;&apos; Anyway, wildcard characters are not allowed in Destination
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if at least one folder has been moved
+&apos;&apos;&apos; False if an error occurred
+&apos;&apos;&apos; An error also occurs if a source using wildcard characters doesn&apos;t match any folders.
+&apos;&apos;&apos; The method stops on the first error it encounters
+&apos;&apos;&apos; No attempt is made to roll back or undo any changes made before an error occurs
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; UNKNOWNFILEERROR Source does not exist
+&apos;&apos;&apos; UNKNOWNFOLDERERROR Source folder or Destination folder does not exist
+&apos;&apos;&apos; NOFILEMATCHERROR No file matches Source containing wildcards
+&apos;&apos;&apos; NOTAFOLDERERROR Destination is a file, not a folder
+&apos;&apos;&apos; OVERWRITEERROR Destination can not be overwritten
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; FSO.FileNaming = &quot;SYS&quot;
+&apos;&apos;&apos; FSO.MoveFolder(&quot;C:\Temp1\*&quot;, &quot;C:\Temp2\&quot;)
+
+Dim bMove As Boolean &apos; Return value
+
+Const cstThisSub = &quot;FileSystem.MoveFolder&quot;
+Const cstSubArgs = &quot;Source, Destination&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bMove = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateFile(Source, &quot;Source&quot;, True) Then GoTo Finally
+ If Not SF_Utils._ValidateFile(Destination, &quot;Destination&quot;, False) Then GoTo Finally
+ End If
+
+Try:
+ bMove = SF_FileSystem._CopyMove(&quot;MoveFolder&quot;, Source, Destination, False)
+
+Finally:
+ MoveFolder = bMove
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_FileSystem.MoveFolder
+
+REM -----------------------------------------------------------------------------
+Public Function OpenTextFile(Optional ByVal FileName As Variant _
+ , Optional ByVal IOMode As Variant _
+ , Optional ByVal Create As Variant _
+ , Optional ByVal Encoding As Variant _
+ ) As Object
+&apos;&apos;&apos; Opens a specified file and returns a TextStream object that can be used to read from, write to, or append to the file
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; FileName: Identifies the file to open
+&apos;&apos;&apos; IOMode: Indicates input/output mode. Can be one of three constants: ForReading, ForWriting, or ForAppending
+&apos;&apos;&apos; Create: Boolean value that indicates whether a new file can be created if the specified filename doesn&apos;t exist.
+&apos;&apos;&apos; The value is True if a new file and its parent folders may be created; False if they aren&apos;t created (default)
+&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 does not implement all existing sets
+&apos;&apos;&apos; Default = UTF-8
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; An instance of the SF_TextStream class representing the opened file or a Null object if an error occurred
+&apos;&apos;&apos; The method does not check if the file is really a text file
+&apos;&apos;&apos; It doesn&apos;t check either if the given encoding is implemented in LibreOffice nor if it is the right one
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; UNKNOWNFILEERROR File does not exist
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; Dim myFile As Object
+&apos;&apos;&apos; FSO.FileNaming = &quot;SYS&quot;
+&apos;&apos;&apos; Set myFile = FSO.OpenTextFile(&quot;C:\Temp\ThisFile.txt&quot;, FSO.ForReading)
+&apos;&apos;&apos; If Not IsNull(myFile) Then &apos; ... Go ahead with reading text lines
+
+Dim oTextStream As Object &apos; Return value
+Dim bExists As Boolean &apos; File to open does exist
+Const cstThisSub = &quot;FileSystem.OpenTextFile&quot;
+Const cstSubArgs = &quot;FileName, [IOMode=1], [Create=False], [Encoding=&quot;&quot;UTF-8&quot;&quot;]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ Set oTextStream = Nothing
+
+Check:
+ With SF_FileSystem
+ If IsMissing(IOMode) Or IsEmpty(IOMode) Then IOMode = ForReading
+ If IsMissing(Create) Or IsEmpty(Create) Then Create = False
+ If IsMissing(Encoding) Or IsEmpty(Encoding) Then Encoding = &quot;UTF-8&quot;
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateFile(FileName, &quot;FileName&quot;) Then GoTo Finally
+ If Not SF_Utils._Validate(IOMode, &quot;IOMode&quot;, V_NUMERIC _
+ , Array(ForReading, ForWriting, ForAppending)) _
+ Then GoTo Finally
+ If Not SF_Utils._Validate(Create, &quot;Create&quot;, V_BOOLEAN) Then GoTo Finally
+ If Not SF_Utils._Validate(Encoding, &quot;Encoding&quot;, V_STRING) Then GoTo Finally
+ End If
+
+ bExists = .FileExists(FileName)
+ Select Case IOMode
+ Case ForReading : If Not bExists Then GoTo CatchNotExists
+ Case Else : If Not bExists And Not Create Then GoTo CatchNotExists
+ End Select
+
+ If IOMode = ForAppending And Not bExists Then IOMode = ForWriting
+ End With
+
+Try:
+ &apos; Create and initialize TextStream class instance
+ Set oTextStream = New SF_TextStream
+ With oTextStream
+ .[Me] = oTextStream
+ .[_Parent] = SF_FileSystem
+ ._FileName = SF_FileSystem._ConvertToUrl(FileName)
+ ._IOMode = IOMode
+ ._Encoding = Encoding
+ ._FileExists = bExists
+ ._Initialize()
+ End With
+
+Finally:
+ Set OpenTextFile = oTextStream
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchNotExists:
+ SF_Exception.RaiseFatal(UNKNOWNFILEERROR, &quot;FileName&quot;, FileName)
+ GoTo Finally
+End Function &apos; ScriptForge.SF_FileSystem.OpenTextFile
+
+REM -----------------------------------------------------------------------------
+Public Function PickFile(Optional ByVal DefaultFile As Variant _
+ , Optional ByVal Mode As Variant _
+ , Optional ByVal Filter As Variant _
+ ) As String
+&apos;&apos;&apos; Returns the file selected with a FilePicker dialog box
+&apos;&apos;&apos; The mode, OPEN or SAVE, and the filter may be preset
+&apos;&apos;&apos; If mode = SAVE and the picked file exists, a warning message will be displayed
+&apos;&apos;&apos; Modified from Andrew Pitonyak&apos;s Base Macro Programming §10.4
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; DefaultFile: Folder part: the FolderName from which to start. Default = the last selected folder
+&apos;&apos;&apos; File part: the default file to open or save
+&apos;&apos;&apos; Mode: &quot;OPEN&quot; (input file) or &quot;SAVE&quot; (output file)
+&apos;&apos;&apos; Filter: by default only files having the given suffix will be displayed. Default = all suffixes
+&apos;&apos;&apos; The filter combo box will contain the given SuffixFilter (if not &quot;*&quot;) and &quot;*.*&quot;
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The selected FileName in URL format or &quot;&quot; if the dialog was cancelled
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; FSO.FileNaming = &quot;SYS&quot;
+&apos;&apos;&apos; FSO.PickFile(&quot;C:\&quot;, &quot;OPEN&quot;, &quot;txt&quot;) &apos; Only *.txt files are displayed
+
+Dim oFileDialog As Object &apos; com.sun.star.ui.dialogs.FilePicker
+Dim oFileAccess As object &apos; com.sun.star.ucb.SimpleFileAccess
+Dim oPath As Object &apos; com.sun.star.util.PathSettings
+Dim iAccept As Integer &apos; Result of dialog execution
+Dim sInitPath As String &apos; Current working directory
+Dim sBaseFile As String
+Dim iMode As Integer &apos; Numeric alias for SelectMode
+Dim sFile As String &apos; Return value
+
+Const cstThisSub = &quot;FileSystem.PickFile&quot;
+Const cstSubArgs = &quot;[DefaultFile=&quot;&quot;&quot;&quot;], [Mode=&quot;&quot;OPEN&quot;&quot;|&quot;&quot;SAVE&quot;&quot;],[Filter=&quot;&quot;&quot;&quot;]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sFile = &quot;&quot;
+
+Check:
+ If IsMissing(DefaultFile) Or IsEmpty(DefaultFile) Then DefaultFile = &quot;&quot;
+ If IsMissing(Mode) Or IsEmpty(Mode) Then Mode = &quot;OPEN&quot;
+ If IsMissing(Filter) Or IsEmpty(Filter) Then Filter = &quot;&quot;
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateFile(DefaultFile, &quot;DefaultFile&quot;, , True) Then GoTo Finally
+ If Not SF_Utils._Validate(Mode, &quot;Mode&quot;, V_STRING, Array(&quot;OPEN&quot;, &quot;SAVE&quot;)) Then GoTo Finally
+ If Not SF_Utils._Validate(Filter, &quot;Filter&quot;, V_STRING) Then GoTo Finally
+ End If
+ DefaultFile = SF_FileSystem._ConvertToUrl(DefaultFile)
+
+Try:
+ &apos; Derive numeric equivalent of the Mode argument: https://api.libreoffice.org/docs/idl/ref/TemplateDescription_8idl.html
+ With com.sun.star.ui.dialogs.TemplateDescription
+ If Mode = &quot;OPEN&quot; Then iMode = .FILEOPEN_SIMPLE Else iMode = .FILESAVE_AUTOEXTENSION
+ End With
+
+ &apos; Activate the filepicker dialog
+ Set oFileDialog = SF_Utils._GetUNOService(&quot;FilePicker&quot;)
+ With oFileDialog
+ .Initialize(Array(iMode))
+
+ &apos; Set filters
+ If Len(Filter) &gt; 0 Then .appendFilter(&quot;*.&quot; &amp; Filter, &quot;*.&quot; &amp; Filter) &apos; Twice: required by API
+ .appendFilter(&quot;*.*&quot;, &quot;*.*&quot;)
+ If Len(Filter) &gt; 0 Then .setCurrentFilter(&quot;*.&quot; &amp; Filter) Else .setCurrentFilter(&quot;*.*&quot;)
+
+ &apos; Set initial folder
+ If Len(DefaultFile) = 0 Then &apos; TODO: SF_Session.WorkingFolder
+ Set oPath = SF_Utils._GetUNOService(&quot;PathSettings&quot;)
+ sInitPath = oPath.Work &apos; Probably My Documents
+ Else
+ sInitPath = SF_FileSystem._ParseUrl(ConvertToUrl(DefaultFile)).Path
+ End If
+
+ &apos; Set default values
+ Set oFileAccess = SF_Utils._GetUNOService(&quot;FileAccess&quot;)
+ If oFileAccess.exists(sInitPath) Then .SetDisplayDirectory(sInitPath)
+ sBaseFile = SF_FileSystem.GetName(DefaultFile)
+ .setDefaultName(sBaseFile)
+
+ &apos; Get selected file
+ iAccept = .Execute()
+ If iAccept = com.sun.star.ui.dialogs.ExecutableDialogResults.OK Then sFile = .getSelectedFiles()(0)
+ End With
+
+Finally:
+ PickFile = SF_FileSystem._ConvertFromUrl(sFile)
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_FileSystem.PickFile
+
+REM -----------------------------------------------------------------------------
+Public Function PickFolder(Optional ByVal DefaultFolder As Variant _
+ , Optional ByVal FreeText As Variant _
+ ) As String
+&apos;&apos;&apos; Display a FolderPicker dialog box
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; DefaultFolder: the FolderName from which to start. Default = the last selected folder
+&apos;&apos;&apos; FreeText: text to display in the dialog. Default = &quot;&quot;
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The selected FolderName in URL or operating system format
+&apos;&apos;&apos; The zero-length string if the dialog was cancelled
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; FSO.FileNaming = &quot;SYS&quot;
+&apos;&apos;&apos; FSO.PickFolder(&quot;C:\&quot;, &quot;Choose a folder or press Cancel&quot;)
+
+Dim oFolderDialog As Object &apos; com.sun.star.ui.dialogs.FolderPicker
+Dim iAccept As Integer &apos; Value returned by the dialog (OK, Cancel, ..)
+Dim sFolder As String &apos; Return value &apos;
+
+Const cstThisSub = &quot;FileSystem.PickFolder&quot;
+Const cstSubArgs = &quot;[DefaultFolder=&quot;&quot;&quot;&quot;], [FreeText=&quot;&quot;&quot;&quot;]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sFolder = &quot;&quot;
+
+Check:
+ If IsMissing(DefaultFolder) Or IsEmpty(DefaultFolder) Then DefaultFolder = &quot;&quot;
+ If IsMissing(FreeText) Or IsEmpty(FreeText) Then FreeText = &quot;&quot;
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateFile(DefaultFolder, &quot;DefaultFolder&quot;, , True) Then GoTo Finally
+ If Not SF_Utils._Validate(FreeText, &quot;FreeText&quot;, V_STRING) Then GoTo Finally
+ End If
+ DefaultFolder = SF_FileSystem._ConvertToUrl(DefaultFolder)
+
+Try:
+ Set oFolderDialog = SF_Utils._GetUNOService(&quot;FolderPicker&quot;)
+ If Not IsNull(oFolderDialog) Then
+ With oFolderDialog
+ If Len(DefaultFolder) &gt; 0 Then .DisplayDirectory = ConvertToUrl(DefaultFolder)
+ .Description = FreeText
+ iAccept = .Execute()
+ &apos; https://api.libreoffice.org/docs/idl/ref/ExecutableDialogResults_8idl.html
+ If iAccept = com.sun.star.ui.dialogs.ExecutableDialogResults.OK Then
+ .DisplayDirectory = .Directory &apos; Set the next default initial folder to the selected one
+ sFolder = .Directory &amp; &quot;/&quot;
+ End If
+ End With
+ End If
+
+Finally:
+ PickFolder = SF_FileSystem._ConvertFromUrl(sFolder)
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_FileSystem.PickFolder
+
+REM -----------------------------------------------------------------------------
+Public Function Properties() As Variant
+&apos;&apos;&apos; Return the list or properties of the FileSystem module as an array
+
+ Properties = Array( _
+ &quot;ConfigFolder&quot; _
+ , &quot;ExtensionsFolder&quot; _
+ , &quot;FileNaming&quot; _
+ , &quot;HomeFolder&quot; _
+ , &quot;InstallFolder&quot; _
+ , &quot;TemplatesFolder&quot; _
+ , &quot;TemporaryFolder&quot; _
+ , &quot;UserTemplatesFolder&quot; _
+ )
+
+End Function &apos; ScriptForge.SF_FileSystem.Properties
+
+REM -----------------------------------------------------------------------------
+Public Function SetProperty(Optional ByVal PropertyName As Variant _
+ , Optional ByRef Value As Variant _
+ ) As Boolean
+&apos;&apos;&apos; Set a new value to the given property
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; PropertyName: the name of the property as a string
+&apos;&apos;&apos; Value: its new value
+&apos;&apos;&apos; Exceptions
+&apos;&apos;&apos; ARGUMENTERROR The property does not exist
+
+Const cstThisSub = &quot;FileSystem.SetProperty&quot;
+Const cstSubArgs = &quot;PropertyName, Value&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ SetProperty = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
+ End If
+
+Try:
+ Select Case UCase(PropertyName)
+ Case UCase(&quot;FileNaming&quot;) : FileNaming = Value
+ Case Else
+ End Select
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_FileSystem.SetProperty
+
+REM -----------------------------------------------------------------------------
+Public Function SubFolders(Optional ByVal FolderName As Variant _
+ , Optional ByVal Filter As Variant _
+ ) As Variant
+&apos;&apos;&apos; Return an array of the FolderNames stored in the given folder. The folder must exist
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; FolderName: the folder to explore
+&apos;&apos;&apos; Filter: contains wildcards (&quot;?&quot; and &quot;*&quot;) to limit the list to the relevant folders (default = &quot;&quot;)
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; An array of strings, each entry is the FolderName of an existing folder
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; UNKNOWNFOLDERERROR Folder does not exist
+&apos;&apos;&apos; NOTAFOLDERERROR FolderName is a file, not a folder
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; Dim a As Variant
+&apos;&apos;&apos; FSO.FileNaming = &quot;SYS&quot;
+&apos;&apos;&apos; a = FSO.SubFolders(&quot;C:\Windows\&quot;)
+
+Dim vSubFolders As Variant &apos; Return value
+Dim oSfa As Object &apos; com.sun.star.ucb.SimpleFileAccess
+Dim sFolderName As String &apos; URL lias for FolderName
+Dim sFolder As String &apos; Single folder
+Dim i As Long
+
+Const cstThisSub = &quot;FileSystem.SubFolders&quot;
+Const cstSubArgs = &quot;FolderName, [Filter=&quot;&quot;&quot;&quot;]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vSubFolders = Array()
+
+Check:
+ If IsMissing(Filter) Or IsEmpty(Filter) Then Filter = &quot;&quot;
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateFile(FolderName, &quot;FolderName&quot;) Then GoTo Finally
+ If Not SF_Utils._Validate(Filter, &quot;Filter&quot;, V_STRING) Then GoTo Finally
+ End If
+ sFolderName = SF_FileSystem._ConvertToUrl(FolderName)
+ If SF_FileSystem.FileExists(FolderName) Then GoTo CatchFile &apos; Must not be a file
+ If Not SF_FileSystem.FolderExists(FolderName) Then GoTo CatchFolder &apos; Folder must exist
+
+Try:
+ &apos; Get SubFolders
+ Set oSfa = SF_Utils._GetUnoService(&quot;FileAccess&quot;)
+ vSubFolders = oSfa.getFolderContents(sFolderName, True)
+ &apos; List includes files; remove them or adjust notations of folders
+ For i = 0 To UBound(vSubFolders)
+ sFolder = SF_FileSystem._ConvertFromUrl(vSubFolders(i) &amp; &quot;/&quot;)
+ If SF_FileSystem.FileExists(sFolder) Then vSubFolders(i) = &quot;&quot; Else vSubFolders(i) = sFolder
+ &apos; Reduce list to those passing the filter
+ If Len(Filter) &gt; 0 And Len(vSubFolders(i)) &gt; 0 Then
+ sFolder = SF_FileSystem.GetName(vSubFolders(i))
+ If Not SF_String.IsLike(sFolder, Filter) Then vSubFolders(i) = &quot;&quot;
+ End If
+ Next i
+ vSubFolders = SF_Array.TrimArray(vSubFolders)
+
+Finally:
+ SubFolders = vSubFolders
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchFile:
+ SF_Exception.RaiseFatal(NOTAFOLDERERROR, &quot;FolderName&quot;, FolderName)
+ GoTo Finally
+CatchFolder:
+ SF_Exception.RaiseFatal(UNKNOWNFOLDERERROR, &quot;FolderName&quot;, FolderName)
+ GoTo Finally
+End Function &apos; ScriptForge.SF_FileSystem.SubFolders
+
+REM =========================================================== PRIVATE FUNCTIONS
+
+REM -----------------------------------------------------------------------------
+Private Function _ConvertFromUrl(psFile) As String
+&apos;&apos;&apos; Execute the builtin ConvertFromUrl function only when relevant
+&apos;&apos;&apos; i.e. when FileNaming (how arguments and return values are provided) = &quot;SYS&quot;
+&apos;&apos;&apos; Called at the bottom of methods returning file names
+&apos;&apos;&apos; Remark: psFile might contain wildcards
+
+Const cstQuestion = &quot;$QUESTION$&quot;, cstStar = &quot;$STAR$&quot; &apos; Special tokens to replace wildcards
+
+ If SF_FileSystem.FileNaming = &quot;SYS&quot; Then
+ _ConvertFromUrl = Replace(Replace( _
+ ConvertFromUrl(Replace(Replace(psFile, &quot;?&quot;, cstQuestion), &quot;*&quot;, cstStar)) _
+ , cstQuestion, &quot;?&quot;), cstStar, &quot;*&quot;)
+ Else
+ _ConvertFromUrl = psFile
+ End If
+
+End Function &apos; ScriptForge.FileSystem._ConvertFromUrl
+
+REM -----------------------------------------------------------------------------
+Private Function _ConvertToUrl(psFile) As String
+&apos;&apos;&apos; Execute the builtin ConvertToUrl function only when relevant
+&apos;&apos;&apos; i.e. when FileNaming (how arguments and return values are provided) = &quot;SYS&quot;
+&apos;&apos;&apos; Called at the top of methods receiving file names as arguments
+&apos;&apos;&apos; Remark: psFile might contain wildcards
+
+ If SF_FileSystem.FileNaming = &quot;URL&quot; Then
+ _ConvertToUrl = psFile
+ Else
+ &apos; ConvertToUrl encodes &quot;?&quot;
+ _ConvertToUrl = Replace(ConvertToUrl(psFile), &quot;%3F&quot;, &quot;?&quot;)
+ End If
+
+End Function &apos; ScriptForge.FileSystem._ConvertToUrl
+
+REM -----------------------------------------------------------------------------
+Private Function _CopyMove(psMethod As String _
+ , psSource As String _
+ , psDestination As String _
+ , pbOverWrite As Boolean _
+ ) As Boolean
+&apos;&apos;&apos; Checks the arguments and executes the given method
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; psMethod: CopyFile/CopyFolder or MoveFile/MoveFolder
+&apos;&apos;&apos; psSource: Either File/FolderName
+&apos;&apos;&apos; or NamePattern which can include wildcard characters, for one or more files/folders to be copied
+&apos;&apos;&apos; psDestination: FileName or FolderName for copy/move of a single file/folder
+&apos;&apos;&apos; Otherwise a destination FolderName. If it does not exist, it is created
+&apos;&apos;&apos; pbOverWrite: If True, files/folders may be overwritten
+&apos;&apos;&apos; Must be False for Move operations
+&apos;&apos;&apos; Next checks are done:
+&apos;&apos;&apos; With wildcards (multiple files/folders):
+&apos;&apos;&apos; - Parent folder of source must exist
+&apos;&apos;&apos; - Destination must not be a file
+&apos;&apos;&apos; - Parent folder of Destination must exist
+&apos;&apos;&apos; - If the Destination folder does not exist a new folder is created,
+&apos;&apos;&apos; - At least one file matches the wildcards expression
+&apos;&apos;&apos; - Destination files/folder must not exist if pbOverWrite = False
+&apos;&apos;&apos; - Destination files/folders must not have the read-only attribute set
+&apos;&apos;&apos; - Destination files must not be folders, destination folders must not be files
+&apos;&apos;&apos; Without wildcards (single file/folder):
+&apos;&apos;&apos; - Source file/folder must exist and be a file/folder
+&apos;&apos;&apos; - Parent folder of Destination must exist
+&apos;&apos;&apos; - Destination must not be an existing folder/file
+&apos;&apos;&apos; - Destination file/folder must not exist if pbOverWrite = False
+&apos;&apos;&apos; - Destination file must not have the read-only attribute set
+
+Dim bCopyMove As Boolean &apos; Return value
+Dim bCopy As Boolean &apos; True if Copy, False if Move
+Dim bFile As Boolean &apos; True if File, False if Folder
+Dim oSfa As Object &apos; com.sun.star.ucb.SimpleFileAccess
+Dim bWildCards As Boolean &apos; True if wildcards found in Source
+Dim bCreateFolder As Boolean &apos; True when the destination folder should be created
+Dim bDestExists As Boolean &apos; True if destination exists
+Dim sSourceUrl As String &apos; Alias for Source
+Dim sDestinationUrl As String &apos; Alias for Destination
+Dim sDestinationFile As String &apos; Destination FileName
+Dim sParentFolder As String &apos; Parent folder of Source
+Dim vFiles As Variant &apos; Array of candidates for copy/move
+Dim sFile As String &apos; Single file/folder
+Dim sName As String &apos; Name (last component) of file
+Dim i As Long
+
+ &apos; Error handling left to calling routine
+ bCopyMove = False
+ bCopy = ( Left(psMethod, 4) = &quot;Copy&quot; )
+ bFile = ( Right(psMethod, 4) = &quot;File&quot; )
+ bWildCards = ( InStr(psSource, &quot;*&quot;) + InStr(psSource, &quot;?&quot;) + InStr(psSource, &quot;%3F&quot;) &gt; 0 ) &apos;ConvertToUrl() converts sometimes &quot;?&quot; to &quot;%3F&quot;
+ bDestExists = False
+
+ With SF_FileSystem
+
+Check:
+ If bWildCards Then
+ sParentFolder = .GetParentFolderName(psSource)
+ If Not .FolderExists(sParentFolder) Then GoTo CatchNoMatch
+ If .FileExists(psDestination) Then GoTo CatchFileNotFolder
+ If Not .FolderExists(.GetParentFolderName(psDestination)) Then GoTo CatchDestFolderNotExists
+ bCreateFolder = Not .FolderExists(psDestination)
+ Else
+ Select Case bFile
+ Case True &apos; File
+ If Not .FileExists(psSource) Then GoTo CatchFileNotExists
+ If Not .FolderExists(.GetParentFolderName(psDestination)) Then GoTo CatchSourceFolderNotExists
+ If .FolderExists(psDestination) Then GoTo CatchFolderNotFile
+ bDestExists = .FileExists(psDestination)
+ If pbOverWrite = False And bDestExists = True Then GoTo CatchDestinationExists
+ bCreateFolder = False
+ Case False &apos; Folder
+ If Not .FolderExists(psSource) Then GoTo CatchSourceFolderNotExists
+ If Not .FolderExists(.GetParentFolderName(psDestination)) Then GoTo CatchDestFolderNotExists
+ If .FileExists(psDestination) Then GoTo CatchFileNotFolder
+ bDestExists = .FolderExists(psDestination)
+ If pbOverWrite = False And bDestExists Then GoTo CatchDestinationExists
+ bCreateFolder = Not bDestExists
+ End Select
+ End If
+
+Try:
+ Set oSfa = SF_Utils._GetUnoService(&quot;FileAccess&quot;)
+ If bWildCards Then
+ If bFile Then vFiles = .Files(sParentFolder, .GetName(psSource)) Else vFiles = .SubFolders(sParentFolder, .GetName(psSource))
+ If UBound(vFiles) &lt; 0 Then GoTo CatchNoMatch
+ &apos; Go through the candidates
+ If bCreateFolder Then .CreateFolder(psDestination)
+ For i = 0 To UBound(vFiles)
+ sFile = vFiles(i)
+ sDestinationFile = .BuildPath(psDestination, .GetName(sFile))
+ If bFile Then bDestExists = .FileExists(sDestinationFile) Else bDestExists = .FolderExists(sDestinationFile)
+ If pbOverWrite = False Then
+ If bDestExists Then GoTo CatchDestinationExists
+ If .FolderExists(sDestinationFile) Then GoTo CatchDestinationExists
+ End If
+ sSourceUrl = ._ConvertToUrl(sFile)
+ sDestinationUrl = ._ConvertToUrl(sDestinationFile)
+ If bDestExists Then
+ If oSfa.isReadOnly(sDestinationUrl) Then GoTo CatchDestinationReadOnly
+ End If
+ Select Case bCopy
+ Case True : oSfa.copy(sSourceUrl, sDestinationUrl)
+ Case False : oSfa.move(sSourceUrl, sDestinationUrl)
+ End Select
+ Next i
+ Else
+ sSourceUrl = ._ConvertToUrl(psSource)
+ sDestinationUrl = ._ConvertToUrl(psDestination)
+ If bDestExists Then
+ If oSfa.isReadOnly(sDestinationUrl) Then GoTo CatchDestinationReadOnly
+ End If
+ If bCreateFolder Then .CreateFolder(psDestination)
+ Select Case bCopy
+ Case True : oSfa.copy(sSourceUrl, sDestinationUrl)
+ Case False : oSfa.move(sSourceUrl, sDestinationUrl)
+ End Select
+ End If
+
+ End With
+
+ bCopyMove = True
+
+Finally:
+ _CopyMove = bCopyMove
+ Exit Function
+CatchFileNotExists:
+ SF_Exception.RaiseFatal(UNKNOWNFILEERROR, &quot;Source&quot;, psSource)
+ GoTo Finally
+CatchSourceFolderNotExists:
+ SF_Exception.RaiseFatal(UNKNOWNFOLDERERROR, &quot;Source&quot;, psSource)
+ GoTo Finally
+CatchDestFolderNotExists:
+ SF_Exception.RaiseFatal(UNKNOWNFOLDERERROR, &quot;Destination&quot;, psDestination)
+ GoTo Finally
+CatchFolderNotFile:
+ SF_Exception.RaiseFatal(NOTAFILEERROR, &quot;Destination&quot;, psDestination)
+ GoTo Finally
+CatchDestinationExists:
+ SF_Exception.RaiseFatal(OVERWRITEERROR, &quot;Destination&quot;, psDestination)
+ GoTo Finally
+CatchNoMatch:
+ SF_Exception.RaiseFatal(NOFILEMATCHERROR, &quot;Source&quot;, psSource)
+ GoTo Finally
+CatchFileNotFolder:
+ SF_Exception.RaiseFatal(NOTAFOLDERERROR, &quot;Destination&quot;, psDestination)
+ GoTo Finally
+CatchDestinationReadOnly:
+ SF_Exception.RaiseFatal(READONLYERROR, &quot;Destination&quot;, Iif(bWildCards, sDestinationFile, psDestination))
+ GoTo Finally
+End Function &apos; ScriptForge.SF_FileSystem._CopyMove
+
+REM -----------------------------------------------------------------------------
+Public Function _CountTextLines(ByVal psFileName As String _
+ , Optional ByVal pbIncludeBlanks As Boolean _
+ ) As Long
+&apos;&apos;&apos; Convenient function to count the number of lines in a textfile
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; psFileName: the file in FileNaming notation
+&apos;&apos;&apos; pbIncludeBlanks: if True (default), zero-length lines are included
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The number of lines, f.i. to ease array sizing. -1 if file reading error
+
+Dim lLines As Long &apos; Return value
+Dim oFile As Object &apos; File handler
+Dim sLine As String &apos; The last line read
+
+Try:
+ lLines = 0
+ If IsMissing(pbIncludeBlanks) Then pbIncludeBlanks = True
+ Set oFile = SF_FileSystem.OpenTextFile(psFileName, ForReading)
+ With oFile
+ If Not IsNull(oFile) Then
+ Do While Not .AtEndOfStream
+ sLine = .ReadLine()
+ lLines = lLines + Iif(Len(sLine) &gt; 0 Or pbIncludeBlanks, 1, 0)
+ Loop
+ End If
+ .CloseFile()
+ Set oFile = .Dispose()
+ End With
+
+Finally:
+ _CountTextLines = lLines
+ Exit Function
+End Function &apos; ScriptForge.SF_FileSystem._CountTextLines
+
+REM -----------------------------------------------------------------------------
+Private Function _Delete(psMethod As String _
+ , psFile As String _
+ ) As Boolean
+&apos;&apos;&apos; Checks the argument and executes the given psMethod
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; psMethod: CopyFile/CopyFolder or MoveFile/MoveFolder
+&apos;&apos;&apos; psFile: Either File/FolderName
+&apos;&apos;&apos; or NamePattern which can include wildcard characters, for one or more files/folders to be deleted
+&apos;&apos;&apos; Next checks are done:
+&apos;&apos;&apos; With wildcards (multiple files/folders):
+&apos;&apos;&apos; - Parent folder of File must exist
+&apos;&apos;&apos; - At least one file matches the wildcards expression
+&apos;&apos;&apos; - Files or folders to delete must not have the read-only attribute set
+&apos;&apos;&apos; Without wildcards (single file/folder):
+&apos;&apos;&apos; - File/folder must exist and be a file/folder
+&apos;&apos;&apos; - A file or folder to delete must not have the read-only attribute set
+
+Dim bDelete As Boolean &apos; Return value
+Dim bFile As Boolean &apos; True if File, False if Folder
+Dim oSfa As Object &apos; com.sun.star.ucb.SimpleFileAccess
+Dim bWildCards As Boolean &apos; True if wildcards found in File
+Dim sFileUrl As String &apos; Alias for File
+Dim sParentFolder As String &apos; Parent folder of File
+Dim vFiles As Variant &apos; Array of candidates for deletion
+Dim sFile As String &apos; Single file/folder
+Dim sName As String &apos; Name (last component) of file
+Dim i As Long
+
+ &apos; Error handling left to calling routine
+ bDelete = False
+ bFile = ( Right(psMethod, 4) = &quot;File&quot; )
+ bWildCards = ( InStr(psFile, &quot;*&quot;) + InStr(psFile, &quot;?&quot;) + InStr(psFile, &quot;%3F&quot;) &gt; 0 ) &apos;ConvertToUrl() converts sometimes &quot;?&quot; to &quot;%3F&quot;
+
+ With SF_FileSystem
+
+Check:
+ If bWildCards Then
+ sParentFolder = .GetParentFolderName(psFile)
+ If Not .FolderExists(sParentFolder) Then GoTo CatchNoMatch
+ Else
+ Select Case bFile
+ Case True &apos; File
+ If .FolderExists(psFile) Then GoTo CatchFolderNotFile
+ If Not .FileExists(psFile) Then GoTo CatchFileNotExists
+ Case False &apos; Folder
+ If .FileExists(psFile) Then GoTo CatchFileNotFolder
+ If Not .FolderExists(psFile) Then GoTo CatchFolderNotExists
+ End Select
+ End If
+
+Try:
+ Set oSfa = SF_Utils._GetUnoService(&quot;FileAccess&quot;)
+ If bWildCards Then
+ If bFile Then vFiles = .Files(sParentFolder) Else vFiles = .SubFolders(sParentFolder)
+ &apos; Select candidates
+ For i = 0 To UBound(vFiles)
+ If Not SF_String.IsLike(.GetName(vFiles(i)), .GetName(psFile)) Then vFiles(i) = &quot;&quot;
+ Next i
+ vFiles = SF_Array.TrimArray(vFiles)
+ If UBound(vFiles) &lt; 0 Then GoTo CatchNoMatch
+ &apos; Go through the candidates
+ For i = 0 To UBound(vFiles)
+ sFile = vFiles(i)
+ sFileUrl = ._ConvertToUrl(sFile)
+ If oSfa.isReadOnly(sFileUrl) Then GoTo CatchReadOnly
+ oSfa.kill(sFileUrl)
+ Next i
+ Else
+ sFileUrl = ._ConvertToUrl(psFile)
+ If oSfa.isReadOnly(sFileUrl) Then GoTo CatchReadOnly
+ oSfa.kill(sFileUrl)
+ End If
+
+ End With
+
+ bDelete = True
+
+Finally:
+ _Delete = bDelete
+ Exit Function
+CatchFolderNotExists:
+ SF_Exception.RaiseFatal(UNKNOWNFOLDERERROR, &quot;FolderName&quot;, psFile)
+ GoTo Finally
+CatchFileNotExists:
+ SF_Exception.RaiseFatal(UNKNOWNFILEERROR, &quot;FileName&quot;, psFile)
+ GoTo Finally
+CatchFolderNotFile:
+ SF_Exception.RaiseFatal(NOTAFILEERROR, &quot;FileName&quot;, psFile)
+ GoTo Finally
+CatchNoMatch:
+ SF_Exception.RaiseFatal(NOFILEMATCHERROR, Iif(bFile, &quot;FileName&quot;, &quot;FolderName&quot;), psFile)
+ GoTo Finally
+CatchFileNotFolder:
+ SF_Exception.RaiseFatal(NOTAFOLDERERROR, &quot;FolderName&quot;, psFile)
+ GoTo Finally
+CatchReadOnly:
+ SF_Exception.RaiseFatal(READONLYERROR, Iif(bFile, &quot;FileName&quot;, &quot;FolderName&quot;), Iif(bWildCards, sFile, psFile))
+ GoTo Finally
+End Function &apos; ScriptForge.SF_FileSystem._Delete
+
+REM -----------------------------------------------------------------------------
+Private Function _GetConfigFolder(ByVal psFolder As String) As String
+&apos;&apos;&apos; Returns one of next configuration folders: see https://api.libreoffice.org/docs/idl/ref/servicecom_1_1sun_1_1star_1_1util_1_1PathSubstitution.html
+&apos;&apos;&apos; inst =&gt; Installation path of LibreOffice
+&apos;&apos;&apos; prog =&gt; Program path of LibreOffice
+&apos;&apos;&apos; user =&gt; The user installation/config directory
+&apos;&apos;&apos; work =&gt; The work directory of the user. Under Windows this would be the &quot;MyDocuments&quot; subdirectory. Under Unix this would be the home-directory
+&apos;&apos;&apos; home =&gt; The home directory of the user. Under Unix this would be the home- directory.
+&apos;&apos;&apos; Under Windows this would be the CSIDL_PERSONAL directory, for example &quot;Documents and Settings\&lt;username&gt;\Documents&quot;
+&apos;&apos;&apos; temp =&gt; The current temporary directory
+
+Dim oSubst As Object &apos; com.sun.star.util.PathSubstitution
+Dim sConfig As String &apos; Return value
+
+ sConfig = &quot;&quot;
+ Set oSubst = SF_Utils._GetUNOService(&quot;PathSubstitution&quot;)
+ If Not IsNull(oSubst) Then sConfig = oSubst.getSubstituteVariableValue(&quot;$(&quot; &amp; psFolder &amp; &quot;)&quot;) &amp; &quot;/&quot;
+
+ _GetConfigFolder = SF_FileSystem._ConvertFromUrl(sConfig)
+
+End Function &apos; ScriptForge.FileSystem._GetConfigFolder
+
+REM -----------------------------------------------------------------------------
+Public Function _ParseUrl(psUrl As String) As Object
+&apos;&apos;&apos; Returns a com.sun.star.util.URL structure based on the argument
+
+Dim oParse As Object &apos; com.sun.star.util.URLTransformer
+Dim bParsed As Boolean &apos; True if parsing is successful
+Dim oUrl As New com.sun.star.util.URL &apos; Return value
+
+ oUrl.Complete = psUrl
+ Set oParse = SF_Utils._GetUNOService(&quot;URLTransformer&quot;)
+ bParsed = oParse.parseStrict(oUrl, &quot;&quot;)
+ If bParsed Then oUrl.Path = ConvertToUrl(oUrl.Path)
+
+ Set _ParseUrl = oUrl
+
+End Function &apos; ScriptForge.SF_FileSystem._ParseUrl
+
+REM -----------------------------------------------------------------------------
+Public Function _SFInstallFolder() As String
+&apos;&apos;&apos; Returns the installation folder of the ScriptForge library
+&apos;&apos;&apos; Either:
+&apos;&apos;&apos; - The library is present in [My Macros &amp; Dialogs]
+&apos;&apos;&apos; ($config)/basic/ScriptForge
+&apos;&apos;&apos; - The library is present in [LibreOffice Macros &amp; Dialogs]
+&apos;&apos;&apos; ($install)/share/basic/ScriptForge
+
+Dim sFolder As String &apos; Folder
+
+ _SFInstallFolder = &quot;&quot;
+
+ sFolder = BuildPath(ConfigFolder, &quot;basic/ScriptForge&quot;)
+ If Not FolderExists(sFolder) Then
+ sFolder = BuildPath(InstallFolder, &quot;share/basic/ScriptForge&quot;)
+ If Not FolderExists(sFolder) Then Exit Function
+ End If
+
+ _SFInstallFolder = _ConvertFromUrl(sFolder)
+
+End Function &apos; ScriptForge.SF_FileSystem._SFInstallFolder
+
+REM ============================================ END OF SCRIPTFORGE.SF_FileSystem
+</script:module> \ No newline at end of file
diff --git a/wizards/source/scriptforge/SF_L10N.xba b/wizards/source/scriptforge/SF_L10N.xba
new file mode 100644
index 000000000..6bc6b236f
--- /dev/null
+++ b/wizards/source/scriptforge/SF_L10N.xba
@@ -0,0 +1,825 @@
+<?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_L10N" 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 ClassModule
+&apos;Option Private Module
+
+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; L10N (aka SF_L10N)
+&apos;&apos;&apos; ====
+&apos;&apos;&apos; Implementation of a Basic class for providing a number of services
+&apos;&apos;&apos; related to the translation of user interfaces into a huge number of languages
+&apos;&apos;&apos; with a minimal impact on the program code itself
+&apos;&apos;&apos;
+&apos;&apos;&apos; The design choices of this module are based on so-called PO-files
+&apos;&apos;&apos; PO-files (portable object files) have long been promoted in the free software industry
+&apos;&apos;&apos; as a mean of providing multilingual UIs. This is accomplished through the use of human-readable
+&apos;&apos;&apos; text files with a well defined structure that specifies, for any given language,
+&apos;&apos;&apos; the source language string and the localized string
+&apos;&apos;&apos;
+&apos;&apos;&apos; To read more about the PO format and its ecosystem of associated toolsets:
+&apos;&apos;&apos; https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html#PO-Files
+&apos;&apos;&apos; and, IMHO, a very good tutorial:
+&apos;&apos;&apos; http://pology.nedohodnik.net/doc/user/en_US/ch-about.html
+&apos;&apos;&apos;
+&apos;&apos;&apos; The main advantage of the PO format is the complete dissociation between the two
+&apos;&apos;&apos; very different profiles, i.e. the programmer and the translator(s).
+&apos;&apos;&apos; Being independent text files, one per language to support, the programmer may give away
+&apos;&apos;&apos; pristine PO template files (known as POT-files) for a translator to process.
+&apos;&apos;&apos;
+&apos;&apos;&apos; This class implements mainly 4 mechanisms:
+&apos;&apos;&apos; 1. AddText: for the programmer to build a set of words or sentences
+&apos;&apos;&apos; meant for being translated later
+&apos;&apos;&apos; 2. AddTextsFromDialog: to automatically execute AddText() on each fixed text of a dialog
+&apos;&apos;&apos; 3. ExportToPOTFile: All the above texts are exported into a pristine POT-file
+&apos;&apos;&apos; 4. GetText: At runtime get the text in the user language
+&apos;&apos;&apos; Note that the first two are optional: POT and PO-files may be built with a simple text editor
+&apos;&apos;&apos;
+&apos;&apos;&apos; Several instances of the L10N class may coexist
+&apos; The constraint however is that each instance should find its PO-files
+&apos;&apos;&apos; in a separate directory
+&apos;&apos;&apos; PO-files must be named with the targeted locale: f.i. &quot;en-US.po&quot; or &quot;fr-BE.po&quot;
+&apos;&apos;&apos;
+&apos;&apos;&apos; Service invocation syntax
+&apos;&apos;&apos; CreateScriptService(&quot;L10N&quot;[, FolderName[, Locale]])
+&apos;&apos;&apos; FolderName: the folder containing the PO-files (in SF_FileSystem.FileNaming notation)
+&apos;&apos;&apos; Locale: in the form la-CO (language-COUNTRY)
+&apos;&apos;&apos; Encoding: The character set that should be used (default = UTF-8)
+&apos;&apos;&apos; Use one of the Names listed in https://www.iana.org/assignments/character-sets/character-sets.xhtml
+&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; Service invocation examples:
+&apos;&apos;&apos; Dim myPO As Variant
+&apos;&apos;&apos; myPO = CreateScriptService(&quot;L10N&quot;) &apos; AddText, AddTextsFromDialog and ExportToPOTFile are allowed
+&apos;&apos;&apos; myPO = CreateScriptService(&quot;L10N&quot;, &quot;C:\myPOFiles\&quot;, &quot;fr-BE&quot;)
+&apos;&apos;&apos; &apos;All functionalities are available
+&apos;&apos;&apos;
+&apos;&apos;&apos; Detailed user documentation:
+&apos;&apos;&apos; https://help.libreoffice.org/latest/en-US/text/sbasic/shared/03/sf_l10n.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 =============================================================== PRIVATE TYPES
+
+&apos;&apos;&apos; The recognized elements of an entry in a PO file are (other elements are ignored) :
+&apos;&apos;&apos; #. Extracted comments (given by the programmer to the translator)
+&apos;&apos;&apos; #, flag (the kde-format flag when the string contains tokens)
+&apos;&apos;&apos; msgctxt Context (to store an acronym associated with the message, this is a distortion of the norm)
+&apos;&apos;&apos; msgid untranslated-string
+&apos;&apos;&apos; msgstr translated-string
+&apos;&apos;&apos; NB: plural forms are not supported
+
+Type POEntry
+ Comment As String
+ Flag As String
+ Context As String
+ MsgId As String
+ MsgStr As String
+End Type
+
+REM ================================================================== EXCEPTIONS
+
+Const DUPLICATEKEYERROR = &quot;DUPLICATEKEYERROR&quot;
+
+REM ============================================================= PRIVATE MEMBERS
+
+Private [Me] As Object
+Private [_Parent] As Object
+Private ObjectType As String &apos; Must be &quot;L10N&quot;
+Private ServiceName As String
+Private _POFolder As String &apos; PO files container
+Private _Locale As String &apos; la-CO
+Private _POFile As String &apos; PO file in URL format
+Private _Encoding As String &apos; Used to open the PO file, default = UTF-8
+Private _Dictionary As Object &apos; SF_Dictionary
+
+REM ===================================================== CONSTRUCTOR/DESTRUCTOR
+
+REM -----------------------------------------------------------------------------
+Private Sub Class_Initialize()
+ Set [Me] = Nothing
+ Set [_Parent] = Nothing
+ ObjectType = &quot;L10N&quot;
+ ServiceName = &quot;ScriptForge.L10N&quot;
+ _POFolder = &quot;&quot;
+ _Locale = &quot;&quot;
+ _POFile = &quot;&quot;
+ Set _Dictionary = Nothing
+End Sub &apos; ScriptForge.SF_L10N Constructor
+
+REM -----------------------------------------------------------------------------
+Private Sub Class_Terminate()
+
+ If Not IsNull(_Dictionary) Then Set _Dictionary = _Dictionary.Dispose()
+ Call Class_Initialize()
+End Sub &apos; ScriptForge.SF_L10N Destructor
+
+REM -----------------------------------------------------------------------------
+Public Function Dispose() As Variant
+ Call Class_Terminate()
+ Set Dispose = Nothing
+End Function &apos; ScriptForge.SF_L10N Explicit Destructor
+
+REM ================================================================== PROPERTIES
+
+REM -----------------------------------------------------------------------------
+Property Get Folder() As String
+&apos;&apos;&apos; Returns the FolderName containing the PO-files expressed as given by the current FileNaming
+&apos;&apos;&apos; property of the SF_FileSystem service. Default = URL format
+&apos;&apos;&apos; May be empty
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; myPO.Folder
+
+ Folder = _PropertyGet(&quot;Folder&quot;)
+
+End Property &apos; ScriptForge.SF_L10N.Folder
+
+REM -----------------------------------------------------------------------------
+Property Get Languages() As Variant
+&apos;&apos;&apos; Returns a zero-based array listing all the BaseNames of the PO-files found in Folder,
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; myPO.Languages
+
+ Languages = _PropertyGet(&quot;Languages&quot;)
+
+End Property &apos; ScriptForge.SF_L10N.Languages
+
+REM -----------------------------------------------------------------------------
+Property Get Locale() As String
+&apos;&apos;&apos; Returns the currently active language-COUNTRY combination. May be empty
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; myPO.Locale
+
+ Locale = _PropertyGet(&quot;Locale&quot;)
+
+End Property &apos; ScriptForge.SF_L10N.Locale
+
+REM ===================================================================== METHODS
+
+REM -----------------------------------------------------------------------------
+Public Function AddText(Optional ByVal Context As Variant _
+ , Optional ByVal MsgId As Variant _
+ , Optional ByVal Comment As Variant _
+ , Optional ByVal MsgStr As Variant _
+ ) As Boolean
+&apos;&apos;&apos; Add a new entry in the list of localizable text strings
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Context: when not empty, the key to retrieve the translated string via GetText. Default = &quot;&quot;
+&apos;&apos;&apos; MsgId: the untranslated string, i.e. the text appearing in the program code. Must not be empty
+&apos;&apos;&apos; The key to retrieve the translated string via GetText when Context is empty
+&apos;&apos;&apos; May contain placeholders (%1 ... %9) for dynamic arguments to be inserted in the text at run-time
+&apos;&apos;&apos; If the string spans multiple lines, insert escape sequences (\n) where relevant
+&apos;&apos;&apos; Comment: the so-called &quot;extracted-comments&quot; intended to inform/help translators
+&apos;&apos;&apos; If the string spans multiple lines, insert escape sequences (\n) where relevant
+&apos;&apos;&apos; MsgStr: (internal use only) the translated string
+&apos;&apos;&apos; If the string spans multiple lines, insert escape sequences (\n) where relevant
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if successful
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; DUPLICATEKEYERROR: such a key exists already
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; myPO.AddText(, &quot;This is a text to be included in a POT file&quot;)
+
+Dim bAdd As Boolean &apos; Output buffer
+Dim sKey As String &apos; The key part of the new entry in the dictionary
+Dim vItem As POEntry &apos; The item part of the new entry in the dictionary
+Const cstPipe = &quot;|&quot; &apos; Pipe forbidden in MsgId&apos;s
+Const cstThisSub = &quot;L10N.AddText&quot;
+Const cstSubArgs = &quot;[Context=&quot;&quot;&quot;&quot;], MsgId, [Comment=&quot;&quot;&quot;&quot;]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bAdd = False
+
+Check:
+ If IsMissing(Context) Or IsMissing(Context) Then Context = &quot;&quot;
+ If IsMissing(Comment) Or IsMissing(Comment) Then Comment = &quot;&quot;
+ If IsMissing(MsgStr) Or IsMissing(MsgStr) Then MsgStr = &quot;&quot;
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(Context, &quot;Context&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(MsgId, &quot;MsgId&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(Comment, &quot;Comment&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(MsgStr, &quot;MsgStr&quot;, V_STRING) Then GoTo Finally
+ End If
+ If Len(MsgId) = 0 Then GoTo Finally
+
+Try:
+ If Len(Context) &gt; 0 Then sKey = Context Else sKey = MsgId
+ If _Dictionary.Exists(sKey) Then GoTo CatchDuplicate
+
+ With vItem
+ .Comment = Comment
+ If InStr(MsgId, &quot;%&quot;) &gt; 0 Then .Flag = &quot;kde-format&quot; Else .Flag = &quot;&quot;
+ .Context = Replace(Context, cstPipe, &quot; &quot;)
+ .MsgId = Replace(MsgId, cstPipe, &quot; &quot;)
+ .MsgStr = MsgStr
+ End With
+ _Dictionary.Add(sKey, vItem)
+ bAdd = True
+
+Finally:
+ AddText = bAdd
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchDuplicate:
+ SF_Exception.RaiseFatal(DUPLICATEKEYERROR, Iif(Len(Context) &gt; 0, &quot;Context&quot;, &quot;MsgId&quot;), sKey)
+ GoTo Finally
+End Function &apos; ScriptForge.SF_L10N.AddText
+
+REM -----------------------------------------------------------------------------
+Public Function AddTextsFromDialog(Optional ByRef Dialog As Variant) As Boolean
+&apos;&apos;&apos; Add all fixed text strings of a dialog to the list of localizable text strings
+&apos;&apos;&apos; Added texts are:
+&apos;&apos;&apos; - the title of the dialog
+&apos;&apos;&apos; - the caption associated with next control types: Button, CheckBox, FixedLine, FixedText, GroupBox and RadioButton
+&apos;&apos;&apos; - the content of list- and comboboxes
+&apos;&apos;&apos; - the tip- or helptext displayed when the mouse is hovering the control
+&apos;&apos;&apos; The current method has method SFDialogs.SF_Dialog.GetTextsFromL10N as counterpart
+&apos;&apos;&apos; The targeted dialog must not be open when the current method is run
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Dialog: a SFDialogs.Dialog service instance
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True when successful
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; Dim myDialog As Object
+&apos;&apos;&apos; Set myDialog = CreateScriptService(&quot;SFDialogs.Dialog&quot;, &quot;GlobalScope&quot;, &quot;XrayTool&quot;, &quot;DlgXray&quot;)
+&apos;&apos;&apos; myPO.AddTextsFromDialog(myDialog)
+
+Dim bAdd As Boolean &apos; Return value
+Dim vControls As Variant &apos; Array of control names
+Dim sControl As String &apos; A single control name
+Dim oControl As Object &apos; SFDialogs.DialogControl
+Dim sText As String &apos; The text to insert in the dictionary
+Dim sDialogComment As String &apos; The prefix in the comment to insert in the dictionary for the dialog
+Dim sControlComment As String &apos; The prefix in the comment to insert in the dictionary for a control
+Dim vSource As Variant &apos; RowSource property of dialog control as an array
+Dim i As Long
+
+Const cstThisSub = &quot;L10N.AddTextsFromDialog&quot;
+Const cstSubArgs = &quot;Dialog&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bAdd = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(Dialog, &quot;Dialog&quot;, V_OBJECT, , , &quot;DIALOG&quot;) Then GoTo Finally
+ End If
+
+Try:
+ With Dialog
+ &apos; Store the title of the dialog
+ sDialogComment = &quot;Dialog =&gt; &quot; &amp; ._Container &amp; &quot; : &quot; &amp; ._Library &amp; &quot; : &quot; &amp; ._Name &amp; &quot; : &quot;
+ stext = .Caption
+ If Len(sText) &gt; 0 Then
+ If Not _ReplaceText(&quot;&quot;, sText, sDialogComment &amp; &quot;Caption&quot;) Then GoTo Catch
+ End If
+ &apos; Scan all controls
+ vControls = .Controls()
+ For Each sControl In vControls
+ Set oControl = .Controls(sControl)
+ sControlComment = sDialogComment &amp; sControl &amp; &quot;.&quot;
+ With oControl
+ &apos; Extract fixed texts
+ sText = .Caption
+ If Len(sText) &gt; 0 Then
+ If Not _ReplaceText(&quot;&quot;, sText, sControlComment &amp; &quot;Caption&quot;) Then GoTo Catch
+ End If
+ vSource = .RowSource &apos; List and comboboxes only
+ If IsArray(vSource) Then
+ For i = 0 To UBound(vSource)
+ If Len(vSource(i)) &gt; 0 Then
+ If Not _ReplaceText(&quot;&quot;, vSource(i), sControlComment &amp; &quot;RowSource[&quot; &amp; i &amp; &quot;]&quot;) Then GoTo Catch
+ End If
+ Next i
+ End If
+ sText = .TipText
+ If Len(sText) &gt; 0 Then
+ If Not _ReplaceText(&quot;&quot;, sText, sControlComment &amp; &quot;TipText&quot;) Then GoTo Catch
+ End If
+ End With
+ Next sControl
+ End With
+
+ bAdd = True
+
+Finally:
+ AddTextsFromDialog = bAdd
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_L10N.AddTextsFromDialog
+
+REM -----------------------------------------------------------------------------
+Public Function ExportToPOTFile(Optional ByVal FileName As Variant _
+ , Optional ByVal Header As Variant _
+ , Optional ByVal Encoding As Variant _
+ ) As Boolean
+&apos;&apos;&apos; Export a set of untranslated strings as a POT file
+&apos;&apos;&apos; The set of strings has been built either by a succession of AddText() methods
+&apos;&apos;&apos; or by a successful invocation of the L10N service with the FolderName argument
+&apos;&apos;&apos; The generated file should pass successfully the &quot;msgfmt --check &apos;the pofile&apos;&quot; GNU command
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; FileName: the complete file name to export to. If it exists, is overwritten without warning
+&apos;&apos;&apos; Header: Comments that will appear on top of the generated file. Do not include any leading &quot;#&quot;
+&apos;&apos;&apos; If the string spans multiple lines, insert escape sequences (\n) where relevant
+&apos;&apos;&apos; A standard header will be added anyway
+&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; Returns:
+&apos;&apos;&apos; True if successful
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; myPO.ExportToPOTFile(&quot;myFile.pot&quot;, Header := &quot;Top comment\nSecond line of top comment&quot;)
+
+Dim bExport As Boolean &apos; Return value
+Dim oFile As Object &apos; Generated file handler
+Dim vLines As Variant &apos; Wrapped lines
+Dim sLine As String &apos; A single line
+Dim vItems As Variant &apos; Array of dictionary items
+Dim vItem As Variant &apos; POEntry type
+Const cstSharp = &quot;# &quot;, cstSharpDot = &quot;#. &quot;, cstFlag = &quot;#, kde-format&quot;
+Const cstTabSize = 4
+Const cstWrap = 70
+Const cstThisSub = &quot;L10N.ExportToPOTFile&quot;
+Const cstSubArgs = &quot;FileName, [Header=&quot;&quot;&quot;&quot;], [Encoding=&quot;&quot;UTF-8&quot;&quot;&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bExport = False
+
+Check:
+ If IsMissing(Header) Or IsEmpty(Header) Then Header = &quot;&quot;
+ If IsMissing(Encoding) Or IsEmpty(Encoding) Then Encoding = &quot;UTF-8&quot;
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateFile(FileName, &quot;FileName&quot;) Then GoTo Finally
+ If Not SF_Utils._Validate(Header, &quot;Header&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(Encoding, &quot;Encoding&quot;, V_STRING) Then GoTo Finally
+ End If
+
+Try:
+ Set oFile = SF_FileSystem.CreateTextFile(FileName, Overwrite := True, Encoding := Encoding)
+ If Not IsNull(oFile) Then
+ With oFile
+ &apos; Standard header
+ .WriteLine(cstSharp)
+ .WriteLine(cstSharp &amp; &quot;This pristine POT file has been generated by LibreOffice/ScriptForge&quot;)
+ .WriteLine(cstSharp &amp; &quot;Full documentation is available on https://help.libreoffice.org/&quot;)
+ &apos; User header
+ If Len(Header) &gt; 0 Then
+ .WriteLine(cstSharp)
+ vLines = SF_String.Wrap(Header, cstWrap, cstTabSize)
+ For Each sLine In vLines
+ .WriteLine(cstSharp &amp; Replace(sLine, SF_String.sfLF, &quot;&quot;))
+ Next sLine
+ End If
+ &apos; Standard header
+ .WriteLine(cstSharp)
+ .WriteLine(&quot;msgid &quot;&quot;&quot;&quot;&quot;)
+ .WriteLine(&quot;msgstr &quot;&quot;&quot;&quot;&quot;)
+ .WriteLine(SF_String.Quote(&quot;Project-Id-Version: PACKAGE VERSION\n&quot;))
+ .WriteLine(SF_String.Quote(&quot;Report-Msgid-Bugs-To: &quot; _
+ &amp; &quot;https://bugs.libreoffice.org/enter_bug.cgi?product=LibreOffice&amp;bug_status=UNCONFIRMED&amp;component=UI\n&quot;))
+ .WriteLine(SF_String.Quote(&quot;POT-Creation-Date: &quot; &amp; SF_STring.Represent(Now()) &amp; &quot;\n&quot;))
+ .WriteLine(SF_String.Quote(&quot;PO-Revision-Date: YYYY-MM-DD HH:MM:SS\n&quot;))
+ .WriteLine(SF_String.Quote(&quot;Last-Translator: FULL NAME &lt;EMAIL@ADDRESS&gt;\n&quot;))
+ .WriteLine(SF_String.Quote(&quot;Language-Team: LANGUAGE &lt;EMAIL@ADDRESS&gt;\n&quot;))
+ .WriteLine(SF_String.Quote(&quot;Language: en_US\n&quot;))
+ .WriteLine(SF_String.Quote(&quot;MIME-Version: 1.0\n&quot;))
+ .WriteLine(SF_String.Quote(&quot;Content-Type: text/plain; charset=&quot; &amp; Encoding &amp; &quot;\n&quot;))
+ .WriteLine(SF_String.Quote(&quot;Content-Transfer-Encoding: 8bit\n&quot;))
+ .WriteLine(SF_String.Quote(&quot;Plural-Forms: nplurals=2; plural=n &gt; 1;\n&quot;))
+ .WriteLine(SF_String.Quote(&quot;X-Generator: LibreOffice - ScriptForge\n&quot;))
+ .WriteLine(SF_String.Quote(&quot;X-Accelerator-Marker: ~\n&quot;))
+ &apos; Individual translatable strings
+ vItems = _Dictionary.Items()
+ For Each vItem in vItems
+ .WriteBlankLines(1)
+ &apos; Comments
+ vLines = Split(vItem.Comment, &quot;\n&quot;)
+ For Each sLine In vLines
+ .WriteLine(cstSharpDot &amp; SF_String.ExpandTabs(SF_String.Unescape(sLine), cstTabSize))
+ Next sLine
+ &apos; Flag
+ If InStr(vItem.MsgId, &quot;%&quot;) &gt; 0 Then .WriteLine(cstFlag)
+ &apos; Context
+ If Len(vItem.Context) &gt; 0 Then
+ .WriteLine(&quot;msgctxt &quot; &amp; SF_String.Quote(vItem.Context))
+ End If
+ &apos; MsgId
+ vLines = SF_String.Wrap(vItem.MsgId, cstWrap, cstTabSize)
+ If UBound(vLines) = 0 Then
+ .WriteLine(&quot;msgid &quot; &amp; SF_String.Quote(SF_String.Escape(vLines(0))))
+ Else
+ .WriteLine(&quot;msgid &quot;&quot;&quot;&quot;&quot;)
+ For Each sLine in vLines
+ .WriteLine(SF_String.Quote(SF_String.Escape(sLine)))
+ Next sLine
+ End If
+ &apos; MsgStr
+ .WriteLine(&quot;msgstr &quot;&quot;&quot;&quot;&quot;)
+ Next vItem
+ .CloseFile()
+ End With
+ End If
+ bExport = True
+
+Finally:
+ If Not IsNull(oFile) Then Set oFile = oFile.Dispose()
+ ExportToPOTFile = bExport
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_L10N.ExportToPOTFile
+
+REM -----------------------------------------------------------------------------
+Public Function GetProperty(Optional ByVal PropertyName As Variant) As Variant
+&apos;&apos;&apos; Return the actual value of the given property
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; PropertyName: the name of the property as a string
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The actual value of the property
+&apos;&apos;&apos; If the property does not exist, returns Null
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; ARGUMENTERROR The property does not exist
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; myL10N.GetProperty(&quot;MyProperty&quot;)
+
+Const cstThisSub = &quot;L10N.GetProperty&quot;
+Const cstSubArgs = &quot;&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ GetProperty = Null
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
+ End If
+
+Try:
+ GetProperty = _PropertyGet(PropertyName)
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_L10N.GetProperty
+
+REM -----------------------------------------------------------------------------
+Public Function GetText(Optional ByVal MsgId As Variant _
+ , ParamArray pvArgs As Variant _
+ ) As String
+&apos;&apos;&apos; Get the translated string corresponding with the given argument
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; MsgId: the identifier of the string or the untranslated string
+&apos;&apos;&apos; Either - the untranslated text (MsgId)
+&apos;&apos;&apos; - the reference to the untranslated text (Context)
+&apos;&apos;&apos; - both (Context|MsgId) : the pipe character is essential
+&apos;&apos;&apos; pvArgs(): a list of arguments present as %1, %2, ... in the (un)translated string)
+&apos;&apos;&apos; to be substituted in the returned string
+&apos;&apos;&apos; Any type is admitted but only strings, numbers or dates are relevant
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The translated string
+&apos;&apos;&apos; If not found the MsgId string or the Context string
+&apos;&apos;&apos; Anyway the substitution is done
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; myPO.GetText(&quot;This is a text to be included in a POT file&quot;)
+&apos;&apos;&apos; &apos; Ceci est un text à inclure dans un fichier POT
+
+Dim sText As String &apos; Output buffer
+Dim sContext As String &apos; Context part of argument
+Dim sMsgId As String &apos; MsgId part of argument
+Dim vItem As POEntry &apos; Entry in the dictionary
+Dim vMsgId As Variant &apos; MsgId split on pipe
+Dim sKey As String &apos; Key of dictionary
+Dim sPercent As String &apos; %1, %2, ... placeholders
+Dim i As Long
+Const cstPipe = &quot;|&quot;
+Const cstThisSub = &quot;L10N.GetText&quot;
+Const cstSubArgs = &quot;MsgId, [Arg0, Arg1, ...]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sText = &quot;&quot;
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(MsgId, &quot;MsgId&quot;, V_STRING) Then GoTo Finally
+ End If
+ If Len(Trim(MsgId)) = 0 Then GoTo Finally
+ sText = MsgId
+
+Try:
+ &apos; Find and load entry from dictionary
+ If Left(MsgId, 1) = cstPipe then MsgId = Mid(MsgId, 2)
+ vMsgId = Split(MsgId, cstPipe)
+ sKey = vMsgId(0)
+ If Not _Dictionary.Exists(sKey) Then &apos; Not found
+ If UBound(vMsgId) = 0 Then sText = vMsgId(0) Else sText = Mid(MsgId, InStr(MsgId, cstPipe) + 1)
+ Else
+ vItem = _Dictionary.Item(sKey)
+ If Len(vItem.MsgStr) &gt; 0 Then sText = vItem.MsgStr Else sText = vItem.MsgId
+ End If
+
+ &apos; Substitute %i placeholders
+ For i = UBound(pvArgs) To 0 Step -1 &apos; Go downwards to not have a limit in number of args
+ sPercent = &quot;%&quot; &amp; (i + 1)
+ sText = Replace(sText, sPercent, SF_String.Represent(pvArgs(i)))
+ Next i
+
+Finally:
+ GetText = sText
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_L10N.GetText
+
+REM - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Public Function _(Optional ByVal MsgId As Variant _
+ , ParamArray pvArgs As Variant _
+ ) As String
+&apos;&apos;&apos; Get the translated string corresponding with the given argument
+&apos;&apos;&apos; Alias of GetText() - See above
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; myPO._(&quot;This is a text to be included in a POT file&quot;)
+&apos;&apos;&apos; &apos; Ceci est un text à inclure dans un fichier POT
+
+Dim sText As String &apos; Output buffer
+Dim sPercent As String &apos; %1, %2, ... placeholders
+Dim i As Long
+Const cstPipe = &quot;|&quot;
+Const cstThisSub = &quot;L10N._&quot;
+Const cstSubArgs = &quot;MsgId, [Arg0, Arg1, ...]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sText = &quot;&quot;
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(MsgId, &quot;MsgId&quot;, V_STRING) Then GoTo Finally
+ End If
+ If Len(Trim(MsgId)) = 0 Then GoTo Finally
+
+Try:
+ &apos; Find and load entry from dictionary
+ sText = GetText(MsgId)
+
+ &apos; Substitute %i placeholders - done here, not in GetText(), because # of arguments is undefined
+ For i = 0 To UBound(pvArgs)
+ sPercent = &quot;%&quot; &amp; (i + 1)
+ sText = Replace(sText, sPercent, SF_String.Represent(pvArgs(i)))
+ Next i
+
+Finally:
+ _ = sText
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_L10N._
+
+REM -----------------------------------------------------------------------------
+Public Function Methods() As Variant
+&apos;&apos;&apos; Return the list of public methods of the L10N service as an array
+
+ Methods = Array( _
+ &quot;AddText&quot; _
+ , &quot;ExportToPOTFile&quot; _
+ , &quot;GetText&quot; _
+ , &quot;AddTextsFromDialog&quot; _
+ , &quot;_&quot; _
+ )
+
+End Function &apos; ScriptForge.SF_L10N.Methods
+
+REM -----------------------------------------------------------------------------
+Public Function Properties() As Variant
+&apos;&apos;&apos; Return the list or properties of the Timer class as an array
+
+ Properties = Array( _
+ &quot;Folder&quot; _
+ , &quot;Languages&quot; _
+ , &quot;Locale&quot; _
+ )
+
+End Function &apos; ScriptForge.SF_L10N.Properties
+
+REM -----------------------------------------------------------------------------
+Public Function SetProperty(Optional ByVal PropertyName As Variant _
+ , Optional ByRef Value As Variant _
+ ) As Boolean
+&apos;&apos;&apos; Set a new value to the given property
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; PropertyName: the name of the property as a string
+&apos;&apos;&apos; Value: its new value
+&apos;&apos;&apos; Exceptions
+&apos;&apos;&apos; ARGUMENTERROR The property does not exist
+
+Const cstThisSub = &quot;L10N.SetProperty&quot;
+Const cstSubArgs = &quot;PropertyName, Value&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ SetProperty = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
+ End If
+
+Try:
+ Select Case UCase(PropertyName)
+ Case Else
+ End Select
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_L10N.SetProperty
+
+REM =========================================================== PRIVATE FUNCTIONS
+
+REM -----------------------------------------------------------------------------
+Public Sub _Initialize(ByVal psPOFile As String _
+ , ByVal Encoding As String _
+ )
+&apos;&apos;&apos; Completes initialization of the current instance requested from CreateScriptService()
+&apos;&apos;&apos; Load the POFile in the dictionary, otherwise leave the dictionary empty
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; psPOFile: the file to load the translated strings from
+&apos;&apos;&apos; Encoding: The character set that should be used. Default = UTF-8
+
+Dim oFile As Object &apos; PO file handler
+Dim sContext As String &apos; Collected context string
+Dim sMsgId As String &apos; Collected untranslated string
+Dim sComment As String &apos; Collected comment string
+Dim sMsgStr As String &apos; Collected translated string
+Dim sLine As String &apos; Last line read
+Dim iContinue As Integer &apos; 0 = None, 1 = MsgId, 2 = MsgStr
+Const cstMsgId = 1, cstMsgStr = 2
+
+Try:
+ &apos; Initialize dictionary anyway
+ Set _Dictionary = SF_Services.CreateScriptService(&quot;Dictionary&quot;)
+ Set _Dictionary.[_Parent] = [Me]
+
+ &apos; Load PO file
+ If Len(psPOFile) &gt; 0 Then
+ With SF_FileSystem
+ _POFolder = ._ConvertToUrl(.GetParentFolderName(psPOFile))
+ _Locale = .GetBaseName(psPOFile)
+ _POFile = ._ConvertToUrl(psPOFile)
+ End With
+ &apos; Load PO file
+ Set oFile = SF_FileSystem.OpenTextFile(psPOFile, IOMode := SF_FileSystem.ForReading, Encoding := Encoding)
+ If Not IsNull(oFile) Then
+ With oFile
+ &apos; The PO file is presumed valid =&gt; syntax check is not very strict
+ sContext = &quot;&quot; : sMsgId = &quot;&quot; : sComment = &quot;&quot; : sMsgStr = &quot;&quot;
+ Do While Not .AtEndOfStream
+ sLine = Trim(.ReadLine())
+ &apos; Trivial examination of line header
+ Select Case True
+ Case sLine = &quot;&quot;
+ If Len(sMsgId) &gt; 0 Then AddText(sContext, sMsgId, sComment, sMsgStr)
+ sContext = &quot;&quot; : sMsgId = &quot;&quot; : sComment = &quot;&quot; : sMsgStr = &quot;&quot;
+ iContinue = 0
+ Case Left(sLine, 3) = &quot;#. &quot;
+ sComment = sComment &amp; Iif(Len(sComment) &gt; 0, &quot;\n&quot;, &quot;&quot;) &amp; Trim(Mid(sLine, 4))
+ iContinue = 0
+ Case Left(sLine, 8) = &quot;msgctxt &quot;
+ sContext = SF_String.Unquote(Trim(Mid(sLine, 9)))
+ iContinue = 0
+ Case Left(sLine, 6) = &quot;msgid &quot;
+ sMsgId = SF_String.Unquote(Trim(Mid(sLine, 7)))
+ iContinue = cstMsgId
+ Case Left(sLine, 7) = &quot;msgstr &quot;
+ sMsgStr = sMsgStr &amp; SF_String.Unquote(Trim(Mid(sLine, 8)))
+ iContinue = cstMsgStr
+ Case Left(sLine, 1) = &quot;&quot;&quot;&quot;
+ If iContinue = cstMsgId Then
+ sMsgId = sMsgId &amp; SF_String.Unquote(sLine)
+ ElseIf iContinue = cstMsgStr Then
+ sMsgStr = sMsgStr &amp; SF_String.Unquote(sLine)
+ Else
+ iContinue = 0
+ End If
+ Case Else &apos; Skip line
+ iContinue = 0
+ End Select
+ Loop
+ &apos; Be sure to store the last entry
+ If Len(sMsgId) &gt; 0 Then AddText(sContext, sMsgId, sComment, sMsgStr)
+ .CloseFile()
+ Set oFile = .Dispose()
+ End With
+ End If
+ Else
+ _POFolder = &quot;&quot;
+ _Locale = &quot;&quot;
+ _POFile = &quot;&quot;
+ End If
+
+Finally:
+ Exit Sub
+End Sub &apos; ScriptForge.SF_L10N._Initialize
+
+REM -----------------------------------------------------------------------------
+Private Function _PropertyGet(Optional ByVal psProperty As String)
+&apos;&apos;&apos; Return the value of the named property
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; psProperty: the name of the property
+
+Dim vFiles As Variant &apos; Array of PO-files
+Dim i As Long
+Dim cstThisSub As String
+Dim cstSubArgs As String
+
+ cstThisSub = &quot;SF_L10N.get&quot; &amp; psProperty
+ cstSubArgs = &quot;&quot;
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+ With SF_FileSystem
+ Select Case psProperty
+ Case &quot;Folder&quot;
+ If Len(_POFolder) &gt; 0 Then _PropertyGet = ._ConvertFromUrl(_POFolder) Else _PropertyGet = &quot;&quot;
+ Case &quot;Languages&quot;
+ If Len(_POFolder) &gt; 0 Then
+ vFiles = .Files(._ConvertFromUrl(_POFolder), &quot;*.po&quot;)
+ For i = 0 To UBound(vFiles)
+ vFiles(i) = SF_FileSystem.GetBaseName(vFiles(i))
+ Next i
+ Else
+ vFiles = Array()
+ End If
+ _PropertyGet = vFiles
+ Case &quot;Locale&quot;
+ _PropertyGet = _Locale
+ Case Else
+ _PropertyGet = Null
+ End Select
+ End With
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+End Function &apos; ScriptForge.SF_L10N._PropertyGet
+
+REM -----------------------------------------------------------------------------
+Private Function _ReplaceText(ByVal psContext As String _
+ , ByVal psMsgId As String _
+ , ByVal psComment As String _
+ ) As Boolean
+&apos;&apos;&apos; When the entry in the dictionary does not yet exist, equivalent to AddText
+&apos;&apos;&apos; When it exists already, extend the existing comment with the psComment argument
+&apos;&apos;&apos; Used from AddTextsFromDialog to manage identical strings without raising errors,
+&apos;&apos;&apos; e.g. when multiple dialogs have the same &quot;Close&quot; button
+
+Dim bAdd As Boolean &apos; Return value
+Dim sKey As String &apos; The key part of an entry in the dictionary
+Dim vItem As POEntry &apos; The item part of the new entry in the dictionary
+
+Try:
+ bAdd = False
+ If Len(psContext) &gt; 0 Then sKey = psContext Else sKey = psMsgId
+ If _Dictionary.Exists(sKey) Then
+ &apos; Load the entry, adapt comment and rewrite
+ vItem = _Dictionary.Item(sKey)
+ If Len(vItem.Comment) = 0 Then vItem.Comment = psComment Else vItem.Comment = vItem.Comment &amp; &quot;\n&quot; &amp; psComment
+ bAdd = _Dictionary.ReplaceItem(sKey, vItem)
+ Else
+ &apos; Add a new entry as usual
+ bAdd = AddText(psContext, psMsgId, psComment)
+ End If
+
+Finally:
+ _ReplaceText = bAdd
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_L10N._ReplaceText
+
+REM -----------------------------------------------------------------------------
+Private Function _Repr() As String
+&apos;&apos;&apos; Convert the L10N instance to a readable string, typically for debugging purposes (DebugPrint ...)
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Return:
+&apos;&apos;&apos; &quot;[L10N]: PO file&quot;
+
+ _Repr = &quot;[L10N]: &quot; &amp; _POFile
+
+End Function &apos; ScriptForge.SF_L10N._Repr
+
+REM ============================================ END OF SCRIPTFORGE.SF_L10N
+</script:module> \ No newline at end of file
diff --git a/wizards/source/scriptforge/SF_Platform.xba b/wizards/source/scriptforge/SF_Platform.xba
new file mode 100644
index 000000000..8403866ff
--- /dev/null
+++ b/wizards/source/scriptforge/SF_Platform.xba
@@ -0,0 +1,451 @@
+<?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_Platform" 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_Platform
+&apos;&apos;&apos; ===========
+&apos;&apos;&apos; Singleton class implementing the &quot;ScriptForge.Platform&quot; service
+&apos;&apos;&apos; Implemented as a usual Basic module
+&apos;&apos;&apos;
+&apos;&apos;&apos; A collection of properties about the execution environment:
+&apos;&apos;&apos; - HW platform
+&apos;&apos;&apos; - Operating System
+&apos;&apos;&apos; - current user
+&apos;&apos;&apos; - LibreOffice version
+&apos;&apos;&apos;
+&apos;&apos;&apos; Service invocation example:
+&apos;&apos;&apos; Dim platform As Variant
+&apos;&apos;&apos; platform = CreateScriptService(&quot;Platform&quot;)
+&apos;&apos;&apos;
+&apos;&apos;&apos; Detailed user documentation:
+&apos;&apos;&apos; https://help.libreoffice.org/latest/en-US/text/sbasic/shared/03/sf_platform.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
+
+REM ============================================================ MODULE CONSTANTS
+
+REM ===================================================== CONSTRUCTOR/DESTRUCTOR
+
+REM -----------------------------------------------------------------------------
+Public Function Dispose() As Variant
+ Set Dispose = Nothing
+End Function &apos; ScriptForge.SF_Array Explicit destructor
+
+REM ================================================================== PROPERTIES
+
+REM -----------------------------------------------------------------------------
+Property Get Architecture() As String
+&apos;&apos;&apos; Returns the actual bit architecture
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; MsgBox platform.Architecture &apos; 64bit
+ Architecture = _PropertyGet(&quot;Architecture&quot;)
+End Property &apos; ScriptForge.SF_Platform.Architecture (get)
+
+REM -----------------------------------------------------------------------------
+Property Get ComputerName() As String
+&apos;&apos;&apos; Returns the computer&apos;s network name
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; MsgBox platform.ComputerName
+ ComputerName = _PropertyGet(&quot;ComputerName&quot;)
+End Property &apos; ScriptForge.SF_Platform.ComputerName (get)
+
+REM -----------------------------------------------------------------------------
+Property Get CPUCount() As Integer
+&apos;&apos;&apos; Returns the number of Central Processor Units
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; MsgBox platform.CPUCount &apos; 4
+ CPUCount = _PropertyGet(&quot;CPUCount&quot;)
+End Property &apos; ScriptForge.SF_Platform.CPUCount (get)
+
+REM -----------------------------------------------------------------------------
+Property Get CurrentUser() As String
+&apos;&apos;&apos; Returns the name of logged in user
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; MsgBox platform.CurrentUser
+ CurrentUser = _PropertyGet(&quot;CurrentUser&quot;)
+End Property &apos; ScriptForge.SF_Platform.CurrentUser (get)
+
+REM -----------------------------------------------------------------------------
+Property Get Extensions() As Variant
+&apos;&apos;&apos; Returns the list of availableeExtensions as an unsorted array of unique strings
+&apos;&apos;&apos; To get the list sorted, use SF_Array.Sort()
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; myExtensionsList = platform.Extensions
+ Extensions = _PropertyGet(&quot;Extensions&quot;)
+End Property &apos; ScriptForge.SF_Platform.Extensions (get)
+
+REM -----------------------------------------------------------------------------
+Property Get FilterNames() As Variant
+&apos;&apos;&apos; Returns the list of available document import and export filter names as an unsorted array of unique strings
+&apos;&apos;&apos; To get the list sorted, use SF_Array.Sort()
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; myFilterNamesList = platform.FilterNames
+ FilterNames = _PropertyGet(&quot;FilterNames&quot;)
+End Property &apos; ScriptForge.SF_Platform.FilterNames (get)
+
+REM -----------------------------------------------------------------------------
+Property Get Fonts() As Variant
+&apos;&apos;&apos; Returns the list of available fonts as an unsorted array of unique strings
+&apos;&apos;&apos; To get the list sorted, use SF_Array.Sort()
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; myFontsList = platform.Fonts
+ Fonts = _PropertyGet(&quot;Fonts&quot;)
+End Property &apos; ScriptForge.SF_Platform.Fonts (get)
+
+REM -----------------------------------------------------------------------------
+Property Get FormatLocale() As String
+&apos;&apos;&apos; Returns the locale used for number and date formats, combining language-COUNTRY (la-CO)
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; MsgBox platform.FormatLocale
+ FormatLocale = _PropertyGet(&quot;FormatLocale&quot;)
+End Property &apos; ScriptForge.SF_Platform.FormatLocale (get)
+
+REM -----------------------------------------------------------------------------
+Property Get Locale() As String
+&apos;&apos;&apos; Returns the locale of the operating system, combining language-COUNTRY (la-CO)
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; MsgBox platform.Locale
+ Locale = _PropertyGet(&quot;Locale&quot;)
+End Property &apos; ScriptForge.SF_Platform.Locale (get)
+
+REM -----------------------------------------------------------------------------
+Property Get Machine() As String
+&apos;&apos;&apos; Returns the machine type like &apos;i386&apos; or &apos;x86_64&apos;
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; MsgBox platform.Machine
+ Machine = _PropertyGet(&quot;Machine&quot;)
+End Property &apos; ScriptForge.SF_Platform.Machine (get)
+
+REM -----------------------------------------------------------------------------
+Property Get ObjectType As String
+&apos;&apos;&apos; Only to enable object representation
+ ObjectType = &quot;SF_Platform&quot;
+End Property &apos; ScriptForge.SF_Platform.ObjectType
+
+REM -----------------------------------------------------------------------------
+Property Get OfficeLocale() As String
+&apos;&apos;&apos; Returns the locale of the user interface, combining language-COUNTRY (la-CO)
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; MsgBox platform.OfficeLocale
+ OfficeLocale = _PropertyGet(&quot;OfficeLocale&quot;)
+End Property &apos; ScriptForge.SF_Platform.OfficeLocale (get)
+
+REM -----------------------------------------------------------------------------
+Property Get OfficeVersion() As String
+&apos;&apos;&apos; Returns the office software version in the form &apos;LibreOffice w.x.y.z (The Document Foundation)&apos;
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; MsgBox platform.OfficeVersion
+ OfficeVersion = _PropertyGet(&quot;OfficeVersion&quot;)
+End Property &apos; ScriptForge.SF_Platform.OfficeVersion (get)
+
+REM -----------------------------------------------------------------------------
+Property Get OSName() As String
+&apos;&apos;&apos; Returns the name of the operating system like &apos;Linux&apos; or &apos;Windows&apos;
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; MsgBox platform.OSName
+ OSName = _PropertyGet(&quot;OSName&quot;)
+End Property &apos; ScriptForge.SF_Platform.OSName (get)
+
+REM -----------------------------------------------------------------------------
+Property Get OSPlatform() As String
+&apos;&apos;&apos; Returns a single string identifying the underlying platform with as much useful and human-readable information as possible
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; MsgBox platform.OSPlatform &apos; Linux-4.15.0-117-generic-x86_64-with-Ubuntu-18.04-bionic
+ OSPlatform = _PropertyGet(&quot;OSPlatform&quot;)
+End Property &apos; ScriptForge.SF_Platform.OSPlatform (get)
+
+REM -----------------------------------------------------------------------------
+Property Get OSRelease() As String
+&apos;&apos;&apos; Returns the operating system&apos;s release
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; MsgBox platform.OSRelease &apos; 4.15.0-117-generic
+ OSRelease = _PropertyGet(&quot;OSRelease&quot;)
+End Property &apos; ScriptForge.SF_Platform.OSRelease (get)
+
+REM -----------------------------------------------------------------------------
+Property Get OSVersion() As String
+&apos;&apos;&apos; Returns the name of the operating system build or version
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; MsgBox platform.OSVersion &apos; #118-Ubuntu SMP Fri Sep 4 20:02:41 UTC 2020
+ OSVersion = _PropertyGet(&quot;OSVersion&quot;)
+End Property &apos; ScriptForge.SF_Platform.OSVersion (get)
+
+REM -----------------------------------------------------------------------------
+Property Get Printers() As Variant
+&apos;&apos;&apos; Returns the list of available printers type as a zero-based array
+&apos;&apos;&apos; The default printer is put in the 1st position in the list (index = 0)
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; MsgBox join(platform.Printers, &quot;,&quot;)
+ Printers = _PropertyGet(&quot;Printers&quot;)
+End Property &apos; ScriptForge.SF_Platform.Printers (get)
+
+REM -----------------------------------------------------------------------------
+Property Get Processor() As String
+&apos;&apos;&apos; Returns the (real) processor name, e.g. &apos;amdk6&apos;. Might return the same value as Machine
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; MsgBox platform.Processor
+ Processor = _PropertyGet(&quot;Processor&quot;)
+End Property &apos; ScriptForge.SF_Platform.Processor (get)
+
+REM -----------------------------------------------------------------------------
+Property Get PythonVersion() As String
+&apos;&apos;&apos; Returns the Python version as string &apos;Python major.minor.patchlevel&apos;
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; MsgBox platform.PythonVersion &apos; Python 3.7.7
+ PythonVersion = _PropertyGet(&quot;PythonVersion&quot;)
+End Property &apos; ScriptForge.SF_Platform.PythonVersion (get)
+
+REM -----------------------------------------------------------------------------
+Property Get ServiceName As String
+&apos;&apos;&apos; Internal use
+ ServiceName = &quot;ScriptForge.Platform&quot;
+End Property &apos; ScriptForge.SF_Platform.ServiceName
+
+REM -----------------------------------------------------------------------------
+Property Get SystemLocale() As String
+&apos;&apos;&apos; Returns the locale of the operating system, combining language-COUNTRY (la-CO)
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; MsgBox platform.SystemLocale
+ SystemLocale = _PropertyGet(&quot;SystemLocale&quot;)
+End Property &apos; ScriptForge.SF_Platform.SystemLocale (get)
+
+REM ===================================================================== METHODS
+
+REM -----------------------------------------------------------------------------
+Public Function GetProperty(Optional ByVal PropertyName As Variant) As Variant
+&apos;&apos;&apos; Return the actual value of the given property
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; PropertyName: the name of the property as a string
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The actual value of the property
+&apos;&apos;&apos; If the property does not exist, returns Null
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; ARGUMENTERROR The property does not exist
+
+Const cstThisSub = &quot;Platform.GetProperty&quot;
+Const cstSubArgs = &quot;&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ GetProperty = Null
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
+ End If
+
+Try:
+ GetProperty = _PropertyGet(PropertyName)
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Platform.GetProperty
+
+REM -----------------------------------------------------------------------------
+Public Function Methods() As Variant
+&apos;&apos;&apos; Return the list of public methods of the Model service as an array
+
+ Methods = Array( _
+ )
+
+End Function &apos; ScriptForge.SF_Platform.Methods
+
+REM -----------------------------------------------------------------------------
+Public Function Properties() As Variant
+&apos;&apos;&apos; Return the list or properties of the Platform class as an array
+
+ Properties = Array( _
+ &quot;Architecture&quot; _
+ , &quot;ComputerName&quot; _
+ , &quot;CPUCount&quot; _
+ , &quot;CurrentUser&quot; _
+ , &quot;Extensions&quot; _
+ , &quot;FilterNames&quot; _
+ , &quot;Fonts&quot; _
+ , &quot;FormatLocale&quot; _
+ , &quot;Locale&quot; _
+ , &quot;Machine&quot; _
+ , &quot;OfficeLocale&quot; _
+ , &quot;OfficeVersion&quot; _
+ , &quot;OSName&quot; _
+ , &quot;OSPlatform&quot; _
+ , &quot;OSRelease&quot; _
+ , &quot;OSVersion&quot; _
+ , &quot;Printers&quot; _
+ , &quot;Processor&quot; _
+ , &quot;PythonVersion&quot; _
+ , &quot;SystemLocale&quot; _
+ )
+
+End Function &apos; ScriptForge.SF_Platform.Properties
+
+REM =========================================================== PRIVATE FUNCTIONS
+
+REM -----------------------------------------------------------------------------
+Public Function _GetPrinters() as Variant
+&apos;&apos;&apos; Returns the list of available printers.
+&apos;&apos;&apos; The default printer is put in the 1st position (index = 0)
+
+Dim oPrinterServer As Object &apos; com.sun.star.awt.PrinterServer
+Dim vPrinters As Variant &apos; Array of printer names
+Dim sDefaultPrinter As String &apos; The default printer
+Dim lDefault As Long &apos; Initial position of the default printer in the list
+
+ On Local Error GoTo Catch &apos; Prevent any error
+ vPrinters = Array()
+
+Try:
+ &apos; Find printers
+ Set oPrinterServer = SF_Utils._GetUNOService(&quot;PrinterServer&quot;)
+ With oPrinterServer
+ vPrinters = .getPrinterNames()
+ sDefaultPrinter = .getDefaultPrinterName()
+ End With
+
+ &apos; Put the default printer on top of the list
+ If Len(sDefaultPrinter) &gt; 0 Then
+ lDefault = SF_Array.IndexOf(vPrinters, sDefaultPrinter, CaseSensitive := True)
+ If lDefault &gt; 0 Then &apos; Invert 2 printers
+ vPrinters(lDefault) = vPrinters(0)
+ vPrinters(0) = sDefaultPrinter
+ End If
+ End If
+
+Finally:
+ _GetPrinters() = vPrinters()
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Platform._GetPrinters
+
+REM -----------------------------------------------------------------------------
+Public Function _GetProductName() as String
+&apos;&apos;&apos; Returns Office product and version numbers found in configuration registry
+&apos;&apos;&apos; Derived from the Tools library
+
+Dim oProdNameAccess as Object &apos; configmgr.RootAccess
+Dim sProdName as String
+Dim sVersion as String
+Dim sVendor As String
+
+ On Local Error GoTo Catch &apos; Prevent any error
+ _GetProductName = &quot;&quot;
+
+Try:
+ Set oProdNameAccess = SF_Utils._GetRegistryKeyContent(&quot;org.openoffice.Setup/Product&quot;)
+
+ sProdName = oProdNameAccess.ooName
+ sVersion = oProdNameAccess.ooSetupVersionAboutBox
+ sVendor = oProdNameAccess.ooVendor
+
+ _GetProductName = sProdName &amp; &quot; &quot; &amp; sVersion &amp; &quot; (&quot; &amp; sVendor &amp; &quot;)&quot;
+
+Finally:
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Platform._GetProductName
+
+REM -----------------------------------------------------------------------------
+Private Function _PropertyGet(Optional ByVal psProperty As String) As Variant
+&apos;&apos;&apos; Return the value of the named property
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; psProperty: the name of the property
+
+Dim sOSName As String &apos; Operating system
+Dim oLocale As Object &apos; com.sun.star.lang.Locale
+Dim oPrinterServer As Object &apos; com.sun.star.awt.PrinterServer
+Dim oToolkit As Object &apos; com.sun.star.awt.Toolkit
+Dim oDevice As Object &apos; com.sun.star.awt.XDevice
+Dim oFilterFactory As Object &apos; com.sun.star.document.FilterFactory
+Dim oFontDescriptors As Variant &apos; Array of com.sun.star.awt.FontDescriptor
+Dim sFonts As String &apos; Comma-separated list of fonts
+Dim sFont As String &apos; A single font name
+Dim vExtensionsList As Variant &apos; Array of extension descriptors
+Dim sExtensions As String &apos; Comma separated list of extensions
+Dim sExtension As String &apos; A single extension name
+Dim i As Long
+
+Const cstPyHelper = &quot;$&quot; &amp; &quot;_SF_Platform&quot;
+Dim cstThisSub As String
+Const cstSubArgs = &quot;&quot;
+
+ cstThisSub = &quot;Platform.get&quot; &amp; psProperty
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+ Select Case psProperty
+ Case &quot;Architecture&quot;, &quot;ComputerName&quot;, &quot;CPUCount&quot;, &quot;CurrentUser&quot;, &quot;Machine&quot; _
+ , &quot;OSPlatform&quot;, &quot;OSRelease&quot;, &quot;OSVersion&quot;, &quot;Processor&quot;, &quot;PythonVersion&quot;
+ With ScriptForge.SF_Session
+ _PropertyGet = .ExecutePythonScript(.SCRIPTISSHARED, _SF_.PythonHelper &amp; cstPyHelper, psProperty)
+ End With
+ Case &quot;Extensions&quot;
+ Set vExtensionsList = SF_Utils._GetUnoService(&quot;PackageInformationProvider&quot;).ExtensionList
+ sExtensions = &quot;&quot;
+ For i = 0 To UBound(vExtensionsList)
+ sExtensions = sExtensions &amp; &quot;,&quot; &amp; vExtensionsList(i)(0)
+ Next i
+ If Len(sExtensions) &gt; 0 Then _PropertyGet = Split(Mid(sExtensions, 2), &quot;,&quot;) Else _PropertyGet = Array()
+ Case &quot;FilterNames&quot;
+ Set oFilterFactory = SF_Utils._GetUNOService(&quot;FilterFactory&quot;)
+ _PropertyGet = oFilterFactory.getElementNames()
+ Case &quot;Fonts&quot;
+ Set oToolkit = SF_Utils._GetUnoService(&quot;Toolkit&quot;)
+ Set oDevice = oToolkit.createScreenCompatibleDevice(0, 0)
+ oFontDescriptors = oDevice.FontDescriptors()
+ sFonts = &quot;,&quot;
+ &apos; Select only not yet registered fonts
+ For i = 0 To UBound(oFontDescriptors)
+ sFont = oFontDescriptors(i).Name
+ If InStr(1, sFonts, &quot;,&quot; &amp; sFont &amp; &quot;,&quot;, 0) = 0 Then sFonts = sFonts &amp; sFont &amp; &quot;,&quot; &apos; Case-sensitive comparison
+ Next i
+ &apos; Remove leading and trailing commas
+ If Len(sFonts) &gt; 1 Then _PropertyGet = Split(Mid(sFonts, 2, Len(sFonts) - 2), &quot;,&quot;) Else _PropertyGet = Array()
+ Case &quot;FormatLocale&quot;
+ Set oLocale = SF_Utils._GetUNOService(&quot;FormatLocale&quot;)
+ _PropertyGet = oLocale.Language &amp; &quot;-&quot; &amp; oLocale.Country
+ Case &quot;OfficeLocale&quot;
+ Set oLocale = SF_Utils._GetUNOService(&quot;OfficeLocale&quot;)
+ _PropertyGet = oLocale.Language &amp; &quot;-&quot; &amp; oLocale.Country
+ Case &quot;OfficeVersion&quot;
+ _PropertyGet = _GetProductName()
+ Case &quot;OSName&quot;
+ &apos; Calc INFO function preferred to Python script to avoid ScriptForge initialization risks when Python is not installed
+ sOSName = _SF_.OSName
+ If sOSName = &quot;&quot; Then
+ sOSName = SF_Session.ExecuteCalcFunction(&quot;INFO&quot;, &quot;system&quot;)
+ Select Case sOSName
+ Case &quot;WNT&quot; : sOSName = &quot;Windows&quot;
+ Case &quot;MACOSX&quot; : sOSName = &quot;macOS&quot;
+ Case &quot;LINUX&quot; : sOSName = &quot;Linux&quot;
+ Case &quot;SOLARIS&quot; : sOSName = &quot;Solaris&quot;
+ Case Else : sOSName = SF_String.Capitalize(sOSName)
+ End Select
+ EndIf
+ _PropertyGet = sOSName
+ Case &quot;Printers&quot;
+ _PropertyGet = _GetPrinters()
+ Case &quot;SystemLocale&quot;, &quot;Locale&quot;
+ Set oLocale = SF_Utils._GetUNOService(&quot;SystemLocale&quot;)
+ _PropertyGet = oLocale.Language &amp; &quot;-&quot; &amp; oLocale.Country
+ Case Else
+ _PropertyGet = Null
+ End Select
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+End Function &apos; ScriptForge.SF_Platform._PropertyGet
+
+REM ============================================ END OF SCRIPTFORGE.SF_PLATFORM
+</script:module> \ No newline at end of file
diff --git a/wizards/source/scriptforge/SF_PythonHelper.xba b/wizards/source/scriptforge/SF_PythonHelper.xba
new file mode 100644
index 000000000..99d9f86c6
--- /dev/null
+++ b/wizards/source/scriptforge/SF_PythonHelper.xba
@@ -0,0 +1,967 @@
+<?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
+
+&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_PythonHelper (aka Basic)
+&apos;&apos;&apos; ===============
+&apos;&apos;&apos; Singleton class implementing the &quot;ScriptForge.Basic&quot; service
+&apos;&apos;&apos; Implemented as a usual Basic module
+&apos;&apos;&apos;
+&apos;&apos;&apos; The &quot;Basic&quot; service must be called ONLY from a PYTHON script
+&apos;&apos;&apos; Service invocations: Next Python code lines are equivalent:
+&apos;&apos;&apos; bas = CreateScriptService(&apos;ScriptForge.Basic&apos;)
+&apos;&apos;&apos; bas = CreateScriptService(&apos;Basic&apos;)
+&apos;&apos;&apos;
+&apos;&apos;&apos; This service proposes a collection of methods to be executed in a Python context
+&apos;&apos;&apos; to simulate the exact behaviour of the identical Basic builtin method.
+&apos;&apos;&apos; Typical example:
+&apos;&apos;&apos; bas.MsgBox(&apos;This has to be displayed in a message box&apos;)
+&apos;&apos;&apos;
+&apos;&apos;&apos; The service includes also an agnostic &quot;Python Dispatcher&quot; function.
+&apos;&apos;&apos; It dispatches Python script requests to execute Basic services to the
+&apos;&apos;&apos; appropriate properties and methods via dynamic call techniques
+&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;
+
+REM ================================================================== EXCEPTIONS
+
+REM ============================================================ MODULE CONSTANTS
+
+REM ===================================================== CONSTRUCTOR/DESTRUCTOR
+
+REM -----------------------------------------------------------------------------
+Public Function Dispose() As Variant
+ Set Dispose = Nothing
+End Function &apos; ScriptForge.SF_PythonHelper Explicit destructor
+
+REM ================================================================== PROPERTIES
+
+REM -----------------------------------------------------------------------------
+Property Get ObjectType As String
+&apos;&apos;&apos; Only to enable object representation
+ ObjectType = &quot;SF_PythonHelper&quot;
+End Property &apos; ScriptForge.SF_PythonHelper.ObjectType
+
+REM -----------------------------------------------------------------------------
+Property Get ServiceName As String
+&apos;&apos;&apos; Internal use
+ ServiceName = &quot;ScriptForge.Basic&quot;
+End Property &apos; ScriptForge.SF_PythonHelper.ServiceName
+
+REM ============================================================== PUBLIC METHODS
+
+REM -----------------------------------------------------------------------------
+Public Function PyCDate(ByVal DateArg As Variant) As Variant
+&apos;&apos;&apos; Convenient function to replicate CDate() in Python scripts
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; DateArg: a date as a string or as a double
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The converted date as a UNO DateTime structure
+&apos;&apos;&apos; If the input argument could not be recognized as a date, return the argument unchanged
+&apos;&apos;&apos; Example: (Python code)
+&apos;&apos;&apos; a = bas.CDate(&apos;2021-02-18&apos;)
+
+Dim vDate As Variant &apos; Return value
+Const cstThisSub = &quot;Basic.CDate&quot;
+Const cstSubArgs = &quot;datearg&quot;
+
+ On Local Error GoTo Catch
+ vDate = Null
+
+Check:
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Try:
+ vDate = CDate(DateArg)
+
+Finally:
+ If VarType(vDate) = V_DATE Then PyCDate = CDateToUnoDateTime(vDate) Else PyCDate = DateArg
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ On Local Error GoTo 0
+ GoTo Finally
+End Function &apos; ScriptForge.SF_PythonHelper.PyCDate
+
+REM -----------------------------------------------------------------------------
+Public Function PyConvertFromUrl(ByVal FileName As Variant) As String
+&apos;&apos;&apos; Convenient function to replicate ConvertFromUrl() in Python scripts
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; FileName: a string representing a file in URL format
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The same file name in native operating system notation
+&apos;&apos;&apos; Example: (Python code)
+&apos;&apos;&apos; a = bas.ConvertFromUrl(&apos;file:////boot.sys&apos;)
+
+Dim sFileName As String &apos; Return value
+Const cstThisSub = &quot;Basic.ConvertFromUrl&quot;
+Const cstSubArgs = &quot;filename&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sFileName = &quot;&quot;
+
+Check:
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Try:
+ sFileName = ConvertFromUrl(FileName)
+
+Finally:
+ PyConvertFromUrl = sFileName
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_PythonHelper.PyConvertFromUrl
+
+REM -----------------------------------------------------------------------------
+Public Function PyConvertToUrl(ByVal FileName As Variant) As String
+&apos;&apos;&apos; Convenient function to replicate ConvertToUrl() in Python scripts
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; FileName: a string representing a file in native operating system notation
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The same file name in URL format
+&apos;&apos;&apos; Example: (Python code)
+&apos;&apos;&apos; a = bas.ConvertToUrl(&apos;C:\boot.sys&apos;)
+
+Dim sFileName As String &apos; Return value
+Const cstThisSub = &quot;Basic.ConvertToUrl&quot;
+Const cstSubArgs = &quot;filename&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sFileName = &quot;&quot;
+
+Check:
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Try:
+ sFileName = ConvertToUrl(FileName)
+
+Finally:
+ PyConvertToUrl = sFileName
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_PythonHelper.PyConvertToUrl
+
+REM -----------------------------------------------------------------------------
+Public Function PyCreateUnoService(ByVal UnoService As Variant) As Variant
+&apos;&apos;&apos; Convenient function to replicate CreateUnoService() in Python scripts
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; UnoService: a string representing the service to create
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; A UNO object
+&apos;&apos;&apos; Example: (Python code)
+&apos;&apos;&apos; a = bas.CreateUnoService(&apos;com.sun.star.i18n.CharacterClassification&apos;)
+
+Dim vUno As Variant &apos; Return value
+Const cstThisSub = &quot;Basic.CreateUnoService&quot;
+Const cstSubArgs = &quot;unoservice&quot;
+
+ 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 &apos; ScriptForge.SF_PythonHelper.PyCreateUnoService
+
+REM -----------------------------------------------------------------------------
+Public Function PyDateAdd(ByVal Add As Variant _
+ , ByVal Count As Variant _
+ , ByVal DateArg As Variant _
+ ) As Variant
+&apos;&apos;&apos; Convenient function to replicate DateAdd() in Python scripts
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Add: The unit to add
+&apos;&apos;&apos; Count: how many times to add (might be negative)
+&apos;&apos;&apos; DateArg: a date as a com.sun.star.util.DateTime UNO structure
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The new date as a string in iso format
+&apos;&apos;&apos; Example: (Python code)
+&apos;&apos;&apos; a = bas.DateAdd(&apos;d&apos;, 1, bas.Now()) &apos; Tomorrow
+
+Dim vNewDate As Variant &apos; Return value
+Dim vDate As Date &apos; Alias of DateArg
+Const cstThisSub = &quot;Basic.DateAdd&quot;
+Const cstSubArgs = &quot;add, count, datearg&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vNewDate = &quot;&quot;
+
+Check:
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Try:
+ If VarType(DateArg) = V_OBJECT Then
+ vDate = CDateFromUnoDateTime(DateArg)
+ Else
+ vDate = SF_Utils._CStrToDate(DateArg)
+ End If
+ vNewDate = DateAdd(Add, Count, vDate)
+
+Finally:
+ If VarType(vNewDate) = V_DATE Then PyDateAdd = CDateToUnoDateTime(vNewDate) Else PyDateAdd = vNewDate
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; 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
+&apos;&apos;&apos; Convenient function to replicate DateDiff() in Python scripts
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Add: The unit of the date interval
+&apos;&apos;&apos; Date1, Date2: the two dates to be compared
+&apos;&apos;&apos; WeekStart: the starting day of a week
+&apos;&apos;&apos; YearStart: the starting week of a year
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The number of intervals expressed in Adds
+&apos;&apos;&apos; Example: (Python code)
+&apos;&apos;&apos; a = bas.DateDiff(&apos;d&apos;, bas.DateAdd(&apos;d&apos;, 1, bas.Now()), bas.Now()) &apos; -1 day
+
+Dim lDiff As Long &apos; Return value
+Dim vDate1 As Date &apos; Alias of Date1
+Dim vDate2 As Date &apos; Alias of Date2
+Const cstThisSub = &quot;Basic.DateDiff&quot;
+Const cstSubArgs = &quot;add, date1, date2, [weekstart=1], [yearstart=1]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ lDiff = 0
+
+Check:
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Try:
+ If VarType(Date1) = V_OBJECT Then
+ vDate1 = CDateFromUnoDateTime(Date1)
+ Else
+ vDate1 = SF_Utils._CStrToDate(Date1)
+ End If
+ If VarType(Date2) = V_OBJECT Then
+ vDate2 = CDateFromUnoDateTime(Date2)
+ Else
+ vDate2 = SF_Utils._CStrToDate(Date2)
+ End If
+ lDiff = DateDiff(Add, vDate1, vDate2, WeekStart, YearStart)
+
+
+Finally:
+ PyDateDiff = lDiff
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; 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
+&apos;&apos;&apos; Convenient function to replicate DatePart() in Python scripts
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Add: The unit of the date interval
+&apos;&apos;&apos; DateArg: The date from which to extract a part
+&apos;&apos;&apos; WeekStart: the starting day of a week
+&apos;&apos;&apos; YearStart: the starting week of a year
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The specified part of the date
+&apos;&apos;&apos; Example: (Python code)
+&apos;&apos;&apos; a = bas.DatePart(&apos;y&apos;, bas.Now()) &apos; day of year
+
+Dim lPart As Long &apos; Return value
+Dim vDate As Date &apos; Alias of DateArg
+Const cstThisSub = &quot;Basic.DatePart&quot;
+Const cstSubArgs = &quot;add, datearg, [weekstart=1], [yearstart=1]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ lPart = 0
+
+Check:
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Try:
+ If VarType(DateArg) = V_OBJECT Then
+ vDate = CDateFromUnoDateTime(DateArg)
+ Else
+ vDate = SF_Utils._CStrToDate(DateArg)
+ End If
+ lPart = DatePart(Add, vDate, WeekStart, YearStart)
+
+
+Finally:
+ PyDatePart = lPart
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_PythonHelper.PyDatePart
+
+REM -----------------------------------------------------------------------------
+Public Function PyDateValue(ByVal DateArg As Variant) As Variant
+&apos;&apos;&apos; Convenient function to replicate DateValue() in Python scripts
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; DateArg: a date as a string
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The converted date as a UNO DateTime structure
+&apos;&apos;&apos; Example: (Python code)
+&apos;&apos;&apos; a = bas.DateValue(&apos;2021-02-18&apos;)
+
+Dim vDate As Variant &apos; Return value
+Const cstThisSub = &quot;Basic.DateValue&quot;
+Const cstSubArgs = &quot;datearg&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vDate = &quot;&quot;
+
+Check:
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Try:
+ vDate = DateValue(DateArg)
+
+Finally:
+ If VarType(vDate) = V_DATE Then PyDateValue = CDateToUnoDateTime(vDate) Else PyDateValue = vDate
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_PythonHelper.PyDateValue
+
+REM -----------------------------------------------------------------------------
+Public Function PyFormat(ByVal Value As Variant _
+ , ByVal Pattern As Variant _
+ ) As String
+&apos;&apos;&apos; Convenient function to replicate Format() in Python scripts
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Value: a date or a number
+&apos;&apos;&apos; Pattern: the format to apply
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The formatted value
+&apos;&apos;&apos; Example: (Python code)
+&apos;&apos;&apos; MsgBox bas.Format(6328.2, &apos;##,##0.00&apos;)
+
+Dim sFormat As String &apos; Return value
+Dim vValue As Variant &apos; Alias of Value
+Const cstThisSub = &quot;Basic.Format&quot;
+Const cstSubArgs = &quot;value, pattern&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sFormat = &quot;&quot;
+
+Check:
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Try:
+ If VarType(Value) = V_OBJECT Then vValue = CDateFromUnoDateTime(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 &apos; ScriptForge.SF_PythonHelper.PyFormat
+
+REM -----------------------------------------------------------------------------
+Public Function PyGetGuiType() As Integer
+&apos;&apos;&apos; Convenient function to replicate GetGuiType() in Python scripts
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The GetGuiType value
+&apos;&apos;&apos; Example: (Python code)
+&apos;&apos;&apos; MsgBox bas.GetGuiType()
+
+Const cstThisSub = &quot;Basic.GetGuiType&quot;
+Const cstSubArgs = &quot;&quot;
+
+Check:
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Try:
+ PyGetGuiType = GetGuiType()
+
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+End Function &apos; ScriptForge.SF_PythonHelper.PyGetGuiType
+
+REM -----------------------------------------------------------------------------
+Public Function PyGetSystemTicks() As Long
+&apos;&apos;&apos; Convenient function to replicate GetSystemTicks() in Python scripts
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The GetSystemTicks value
+&apos;&apos;&apos; Example: (Python code)
+&apos;&apos;&apos; MsgBox bas.GetSystemTicks()
+
+Const cstThisSub = &quot;Basic.GetSystemTicks&quot;
+Const cstSubArgs = &quot;&quot;
+
+Check:
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Try:
+ PyGetSystemTicks = GetSystemTicks()
+
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+End Function &apos; ScriptForge.SF_PythonHelper.PyGetSystemTicks
+
+REM -----------------------------------------------------------------------------
+Public Function PyGlobalScope(ByVal Library As Variant) As Object
+&apos;&apos;&apos; Convenient function to replicate GlobalScope() in Python scripts
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Library: &quot;Basic&quot; or &quot;Dialog&quot;
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The GlobalScope value
+&apos;&apos;&apos; Example: (Python code)
+&apos;&apos;&apos; MsgBox bas.GlobalScope.BasicLibraries()
+
+Const cstThisSub = &quot;Basic.GlobalScope.BasicLibraries&quot; &apos; or DialogLibraries
+Const cstSubArgs = &quot;&quot;
+
+Check:
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Try:
+ Select Case Library
+ Case &quot;Basic&quot;
+ PyGlobalScope = GlobalScope.BasicLibraries()
+ Case &quot;Dialog&quot;
+ PyGlobalScope = GlobalScope.DialogLibraries()
+ Case Else
+ End Select
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+End Function &apos; ScriptForge.SF_PythonHelper.PyGlobalScope
+
+REM -----------------------------------------------------------------------------
+Public Function PyInputBox(ByVal Msg As Variant _
+ , ByVal Title As Variant _
+ , ByVal Default As Variant _
+ , Optional ByVal XPosTwips As Variant _
+ , Optional ByVal YPosTwips As Variant _
+ ) As String
+&apos;&apos;&apos; Convenient function to replicate InputBox() in Python scripts
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Msg: String expression displayed as the message in the dialog box
+&apos;&apos;&apos; Title: String expression displayed in the title bar of the dialog box
+&apos;&apos;&apos; Default: String expression displayed in the text box as default if no other input is given
+&apos;&apos;&apos; XPosTwips: Integer expression that specifies the horizontal position of the dialog
+&apos;&apos;&apos; YPosTwips: Integer expression that specifies the vertical position of the dialog
+&apos;&apos;&apos; If XPosTwips and YPosTwips are omitted, the dialog is centered on the screen
+&apos;&apos;&apos; The position is specified in twips.
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The entered value or &quot;&quot; if the user pressed the Cancel button
+&apos;&apos;&apos; Example: (Python code)
+&apos;&apos;&apos; a = bas.InputBox (&apos;Please enter a phrase:&apos;, &apos;Dear User&apos;)
+
+Dim sInput As String &apos; Return value
+Const cstThisSub = &quot;Basic.InputBox&quot;
+Const cstSubArgs = &quot;msg, [title=&apos;&apos;], [default=&apos;&apos;], [xpostwips], [ypostwips]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sInput = &quot;&quot;
+
+Check:
+ If IsMissing(YPosTwips) Then YPosTwips = 1
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Try:
+ If IsMissing(XPosTwips) Then
+ sInput = InputBox(Msg, Title, Default)
+ Else
+ sInput = InputBox(Msg, Title, Default, XPosTwips, YPosTwips)
+ End If
+
+Finally:
+ PyInputBox = sInput
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_PythonHelper.PyInputBox
+
+REM -----------------------------------------------------------------------------
+Public Function PyMsgBox(ByVal Text As Variant _
+ , ByVal DialogType As Variant _
+ , ByVal DialogTitle As Variant _
+ ) As Integer
+&apos;&apos;&apos; Convenient function to replicate MsgBox() in Python scripts
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Text: String expression displayed as a message in the dialog box
+&apos;&apos;&apos; DialogType: Any integer expression that defines the number and type of buttons or icons displayed
+&apos;&apos;&apos; DialogTitle: String expression displayed in the title bar of the dialog
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The pressed button
+&apos;&apos;&apos; Example: (Python code)
+&apos;&apos;&apos; a = bas.MsgBox (&apos;Please press a button:&apos;, bas.MB_EXCLAMATION, &apos;Dear User&apos;)
+
+Dim iMsg As Integer &apos; Return value
+Const cstThisSub = &quot;Basic.MsgBox&quot;
+Const cstSubArgs = &quot;text, [dialogtype=0], [dialogtitle]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ iMsg = -1
+
+Check:
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Try:
+ iMsg = MsgBox(Text, DialogType, DialogTitle)
+
+Finally:
+ PyMsgBox = iMsg
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; 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
+&apos;&apos;&apos; Called from Python only
+&apos;&apos;&apos; The method calls the method Script associated with the BasicObject class or module
+&apos;&apos;&apos; with the given arguments
+&apos;&apos;&apos; The invocation of the method can be a Property Get, Property Let or a usual call
+&apos;&apos;&apos; NB: arguments and return values must not be 2D arrays
+&apos;&apos;&apos; The implementation intends to be as AGNOSTIC as possible in terms of objects nature and methods called
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; BasicObject: a module or a class instance - May also be the reserved string: &quot;SF_Services&quot;
+&apos;&apos;&apos; CallType: one of the constants applicable to a CallByName statement + optional protocol flags
+&apos;&apos;&apos; Script: the name of the method or property
+&apos;&apos;&apos; Args: the arguments to pass to the method. Input arguments can contain symbolic constants for Null, Missing, etc.
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; A 1D array:
+&apos;&apos;&apos; [0] The returned value - scalar, object or 1D array
+&apos;&apos;&apos; [1] The VarType() of the returned value
+&apos;&apos;&apos; Null, Empty and Nothing have different vartypes but return all None to Python
+&apos;&apos;&apos; Additionally, when array:
+&apos;&apos;&apos; [2] Number of dimensions in Basic
+&apos;&apos;&apos; Additionally, when Basic object:
+&apos;&apos;&apos; [2] Module (1), Class instance (2) or UNO (3)
+&apos;&apos;&apos; [3] The object&apos;s ObjectType
+&apos;&apos;&apos; [4] The object&apos;s service name
+&apos;&apos;&apos; [5] The object&apos;s name
+&apos;&apos;&apos; When an error occurs Python receives None as a scalar. This determines the occurrence of a failure
+
+Dim vReturn As Variant &apos; The value returned by the invoked property or method
+Dim vReturnArray As Variant &apos; Return value
+Dim vBasicObject As Variant &apos; Alias of BasicObject to avoid &quot;Object reference not set&quot; error
+Dim iNbArgs As Integer &apos; Number of valid input arguments
+Dim vArg As Variant &apos; Alias for a single argument
+Dim vArgs() As Variant &apos; Alias for Args()
+Dim sScript As String &apos; Argument of ExecuteBasicScript()
+Dim vParams As Variant &apos; Array of arguments to pass to a ParamArray
+Dim sObjectType As String &apos; Alias of object.ObjectType
+Dim sServiceName As String &apos; Alias of BasicObject.ServiceName
+Dim bBasicClass As Boolean &apos; True when BasicObject is a class
+Dim sLibrary As String &apos; Library where the object belongs to
+Dim bUno As Boolean &apos; Return value is a UNO object
+Dim oObjDesc As Object &apos; _ObjectDescriptor type
+Dim iDims As Integer &apos; # of dims of vReturn
+Dim sess As Object : Set sess = ScriptForge.SF_Session
+Dim i As Long, j As Long
+
+&apos; Conventional special input or output values
+Const cstNoArgs = &quot;+++NOARGS+++&quot;, cstSymEmpty = &quot;+++EMPTY+++&quot;, cstSymNull = &quot;+++NULL+++&quot;, cstSymMissing = &quot;+++MISSING+++&quot;
+
+&apos; https://support.office.com/en-us/article/CallByName-fonction-49ce9475-c315-4f13-8d35-e98cfe98729a
+&apos; Determines the CallType
+Const vbGet = 2, vbLet = 4, vbMethod = 1, vbSet = 8
+&apos; Protocol flags
+Const cstDateArg = 64 &apos; May contain a date argument
+Const cstDateRet = 128 &apos; Return value can be a date
+Const cstUno = 256 &apos; Return value can be a UNO object
+Const cstArgArray = 512 &apos; Any argument can be a 2D array
+Const cstRetArray = 1024 &apos; Return value can be an array
+Const cstObject = 2048 &apos; 1st argument is a Basic object when numeric
+Const cstHardCode = 4096 &apos; Method must not be executed with CallByName()
+&apos; 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
+
+ &apos; Ignore Null basic objects (Null = Null or Nothing)
+ If IsNull(BasicObject) Or IsEmpty(BasicObject) Then GoTo Catch
+
+ &apos; Reinterpret arguments one by one into vArgs, convert UNO date/times and conventional NoArgs/Empty/Null/Missing values
+ iNbArgs = -1
+ vArgs = Array()
+
+ If UBound(Args) &gt;= 0 Then
+ For i = 0 To UBound(Args)
+ vArg = Args(i)
+ &apos; Are there arguments ?
+ If i = 0 And VarType(vArg) = V_STRING Then
+ If vArg = cstNoArgs Then Exit For
+ End If
+ &apos; Is 1st argument a reference to a Basic object ?
+ If i = 0 And (( CallType And cstObject ) = cstObject) And SF_Utils._VarTypeExt(vArg) = V_NUMERIC Then
+ If vArg &lt; 0 Or Not IsArray(_SF_.PythonStorage) Then GoTo Catch
+ If vArg &gt; UBound(_SF_.PythonStorage) Then GoTo Catch
+ vArg = _SF_.PythonStorage(vArg)
+ &apos; Is argument a symbolic constant for Null, Empty, ... , or a date?
+ ElseIf VarType(vArg) = V_STRING Then
+ If Len(vArg) = 0 Then
+ ElseIf vArg = cstSymEmpty Then
+ vArg = Empty
+ ElseIf vArg = cstSymNull Then
+ vArg = Null
+ ElseIf vArg = cstSymMissing Then
+ Exit For &apos; Next arguments must be missing also
+ End If
+ ElseIf VarType(vArg) = V_OBJECT Then
+ If ( CallType And cstDateArg ) = cstDateArg Then vArg = CDateFromUnoDateTime(vArg)
+ End If
+ iNbArgs = iNbArgs + 1
+
+ ReDim Preserve vArgs(iNbArgs)
+ vArgs(iNbArgs) = vArg
+ Next i
+ End If
+
+Try:
+ &apos; Dispatching strategy: based on next constraints
+ &apos; (1) Bug https://bugs.documentfoundation.org/show_bug.cgi?id=138155
+ &apos; The CallByName function fails when returning an array
+ &apos; (2) Python has tuples and tuple of tuples, not 2D arrays
+ &apos; (3) Passing 2D arrays through a script provider always transform it into a sequence of sequences
+ &apos; (4) The CallByName function takes exclusive control on the targeted object up to its exit
+ &apos; 1. Methods in usual modules are called by ExecuteBasicScript() except if they use a ParamArray
+ &apos; 2. Properties in any service are got and set with obj.GetProperty/SetProperty(...)
+ &apos; 3. Methods in class modules are invoked with CallByName
+ &apos; 4. Methods in class modules using a 2D array or returning arrays, or methods using ParamArray,
+&apos;&apos;&apos; are hardcoded as exceptions or are not implemented
+ &apos; 5. Due to constraint (4), a predefined list of method calls must be hardcoded to avoid blocking use of CallByName
+ &apos; The concerned methods are flagged with cstHardCode
+
+ With _SF_
+ &apos; Initialize Python persistent storage at 1st call
+ If IsEmpty(.PythonStorage) Then ._InitPythonStorage()
+ &apos; Reset any error
+ ._Stackreset()
+ &apos; Set Python trigger to manage signatures in error messages
+ .TriggeredByPython = True
+ End With
+
+ Select case VarType(BasicObject)
+ Case V_STRING
+ &apos; Special entry for CreateScriptService()
+ vBasicObject = BasicObject
+ If vBasicObject = &quot;SF_Services&quot; 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) &lt;&gt; &quot;SF_&quot; )
+ End If
+
+ &apos; Implement dispatching strategy
+ Case V_INTEGER
+ If BasicObject &lt; 0 Or Not IsArray(_SF_.PythonStorage) Then GoTo Catch
+ If BasicObject &gt; UBound(_SF_.PythonStorage) Then GoTo Catch
+ vBasicObject = _SF_.PythonStorage(BasicObject)
+ sObjectType = vBasicObject.ObjectType
+ sServiceName = vBasicObject.ServiceName
+
+ &apos; Basic modules have type = &quot;SF_*&quot;
+ bBasicClass = ( Left(sObjectType, 3) &lt;&gt; &quot;SF_&quot; )
+ sLibrary = Split(sServiceName, &quot;.&quot;)(0)
+
+ &apos; Methods in standard modules returning/passing a date are hardcoded as exceptions
+ If Not bBasicClass And ((CallType And vbMethod) = vbMethod) _
+ And (((CallType And cstDateRet) = cstDateRet) Or ((CallType And cstDateArg) = cstDateArg)) Then
+ Select Case sServiceName
+ Case &quot;ScriptForge.FileSystem&quot;
+ If Script = &quot;GetFileModified&quot; Then vReturn = SF_FileSystem.GetFileModified(vArgs(0))
+ Case &quot;ScriptForge.Region&quot;
+ Select Case Script
+ Case &quot;DSTOffset&quot; : vReturn = SF_Region.DSTOffset(vArgs(0), vArgs(1), vArgs(2))
+ Case &quot;LocalDateTime&quot; : vReturn = SF_Region.LocalDateTime(vArgs(0), vArgs(1), vArgs(2))
+ Case &quot;UTCDateTime&quot; : vReturn = SF_Region.UTCDateTime(vArgs(0), vArgs(1), vArgs(2))
+ Case &quot;UTCNow&quot; : vReturn = SF_Region.UTCNow(vArgs(0), vArgs(1))
+ Case Else
+ End Select
+ End Select
+
+ &apos; Methods in usual modules using a 2D array or returning arrays are hardcoded as exceptions
+ ElseIf Not bBasicClass And _
+ (((CallType And vbMethod) + (CallType And cstArgArray)) = vbMethod + cstArgArray Or _
+ ((CallType And vbMethod) + (CallType And cstRetArray)) = vbMethod + cstRetArray) Then
+ &apos; Not service related
+ If Script = &quot;Methods&quot; Then
+ vReturn = vBasicObject.Methods()
+ ElseIf Script = &quot;Properties&quot; Then
+ vReturn = vBasicObject.Properties()
+ Else
+ Select Case sServiceName
+ Case &quot;ScriptForge.Array&quot;
+ If Script = &quot;ImportFromCSVFile&quot; Then vReturn = SF_Array.ImportFromCSVFile(vArgs(0), vArgs(1), vArgs(2), True)
+ End Select
+ End If
+
+ &apos; Methods in usual modules are called by ExecuteBasicScript() except if they use a ParamArray
+ ElseIf Not bBasicClass And (CallType And vbMethod) = vbMethod Then
+ sScript = sLibrary &amp; &quot;.&quot; &amp; sObjectType &amp; &quot;.&quot; &amp; Script
+ &apos; Force validation in targeted function, not in ExecuteBasicScript()
+ _SF_.StackLevel = -1
+ 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
+ _SF_.StackLevel = 0
+
+ &apos; Properties in any service are got and set with obj.GetProperty/SetProperty(...)
+ ElseIf (CallType And vbGet) = vbGet Then &apos; In some cases (Calc ...) GetProperty may have an argument
+ If UBound(vArgs) &lt; 0 Then vReturn = vBasicObject.GetProperty(Script) Else vReturn = vBasicObject.GetProperty(Script, vArgs(0))
+ ElseIf (CallType And vbLet) = vbLet Then
+ vReturn = vBasicObject.SetProperty(Script, vArgs(0))
+
+ &apos; Methods in class modules using a 2D array or returning arrays are hardcoded as exceptions. Bug #138155
+ ElseIf ((CallType And vbMethod) + (CallType And cstArgArray)) = vbMethod + cstArgArray Or _
+ ((CallType And vbMethod) + (CallType And cstRetArray)) = vbMethod + cstRetArray Then
+ If Script = &quot;Methods&quot; Then
+ vReturn = vBasicObject.Methods()
+ ElseIf Script = &quot;Properties&quot; Then
+ vReturn = vBasicObject.Properties()
+ Else
+ Select Case sServiceName
+ Case &quot;SFDatabases.Database&quot;
+ If Script = &quot;GetRows&quot; Then vReturn = vBasicObject.GetRows(vArgs(0), vArgs(1), vArgs(2), vArgs(3))
+ Case &quot;SFDialogs.Dialog&quot;
+ If Script = &quot;Controls&quot; Then vReturn = vBasicObject.Controls(vArgs(0))
+ Case &quot;SFDialogs.DialogControl&quot;
+ If Script = &quot;SetTableData&quot; Then vReturn = vBasicObject.SetTableData(vArgs(0), vArgs(1), vArgs(2))
+ Case &quot;SFDocuments.Document&quot;
+ If Script = &quot;Forms&quot; Then vReturn = vBasicObject.Forms(vArgs(0))
+ Case &quot;SFDocuments.Base&quot;
+ Select Case Script
+ Case &quot;FormDocuments&quot; : vReturn = vBasicObject.FormDocuments()
+ Case &quot;Forms&quot; : vReturn = vBasicObject.Forms(vArgs(0), vArgs(1))
+ End Select
+ Case &quot;SFDocuments.Calc&quot;
+ Select Case Script
+ Case &quot;Charts&quot; : vReturn = vBasicObject.Charts(vArgs(0), vArgs(1))
+ Case &quot;Forms&quot; : vReturn = vBasicObject.Forms(vArgs(0), vArgs(1))
+ Case &quot;GetFormula&quot; : vReturn = vBasicObject.GetFormula(vArgs(0))
+ Case &quot;GetValue&quot; : vReturn = vBasicObject.GetValue(vArgs(0))
+ Case &quot;SetArray&quot; : vReturn = vBasicObject.SetArray(vArgs(0), vArgs(1))
+ Case &quot;SetFormula&quot; : vReturn = vBasicObject.SetFormula(vArgs(0), vArgs(1))
+ Case &quot;SetValue&quot; : vReturn = vBasicObject.SetValue(vArgs(0), vArgs(1))
+ End Select
+ Case &quot;SFDocuments.Form&quot;
+ Select Case Script
+ Case &quot;Controls&quot; : vReturn = vBasicObject.Controls(vArgs(0))
+ Case &quot;Subforms&quot; : vReturn = vBasicObject.Subforms(vArgs(0))
+ End Select
+ Case &quot;SFDocuments.FormControl&quot;
+ If Script = &quot;Controls&quot; Then vReturn = vBasicObject.Controls(vArgs(0))
+ End Select
+ End If
+
+ &apos; Methods in class modules may better not be executed with CallByName()
+ ElseIf bBasicClass And ((CallType And vbMethod) + (CallType And cstHardCode)) = vbMethod + cstHardCode Then
+ Select Case sServiceName
+ Case &quot;SFDialogs.Dialog&quot;
+ Select Case Script
+ Case &quot;Activate&quot; : vReturn = vBasicObject.Activate()
+ Case &quot;Center&quot;
+ If UBound(vArgs) &lt; 0 Then vReturn = vBasicObject.Center() Else vReturn = vBasicObject.Center(vArgs(0))
+ Case &quot;EndExecute&quot; : vReturn = vBasicObject.EndExecute(vArgs(0))
+ Case &quot;Execute&quot; : vReturn = vBasicObject.Execute(vArgs(0))
+ Case &quot;Resize&quot; : vReturn = vBasicObject.Resize(vArgs(0), vArgs(1), vArgs(2), vArgs(3))
+ End Select
+ End Select
+
+ &apos; Methods in class modules are invoked with CallByName
+ ElseIf bBasicClass And ((CallType And vbMethod) = vbMethod) Then
+ Select Case UBound(vArgs)
+ &apos; Dirty alternatives to process usual and ParamArray cases
+ &apos; But, up to ... how many ?
+ &apos; - The OFFSETADDRESSERROR has 12 arguments
+ &apos; - The &quot;.uno:DataSort&quot; command may have 14 property name-value pairs
+ Case -1 : vReturn = CallByName(vBasicObject, Script, vbMethod)
+ 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))
+ Case 8 : vReturn = CallByName(vBasicObject, Script, vbMethod, vArgs(0), vArgs(1), vArgs(2), vArgs(3), vArgs(4), vArgs(5), vArgs(6), vArgs(7) _
+ , vArgs(8))
+ Case 9 : vReturn = CallByName(vBasicObject, Script, vbMethod, vArgs(0), vArgs(1), vArgs(2), vArgs(3), vArgs(4), vArgs(5), vArgs(6), vArgs(7) _
+ , vArgs(8), vArgs(9))
+ Case 10 : vReturn = CallByName(vBasicObject, Script, vbMethod, vArgs(0), vArgs(1), vArgs(2), vArgs(3), vArgs(4), vArgs(5), vArgs(6), vArgs(7) _
+ , vArgs(8), vArgs(9), vArgs(10))
+ Case 11 : vReturn = CallByName(vBasicObject, Script, vbMethod, vArgs(0), vArgs(1), vArgs(2), vArgs(3), vArgs(4), vArgs(5), vArgs(6), vArgs(7) _
+ , vArgs(8), vArgs(9), vArgs(10), vArgs(11))
+ Case 12, 13 : vReturn = CallByName(vBasicObject, Script, vbMethod, vArgs(0), vArgs(1), vArgs(2), vArgs(3), vArgs(4), vArgs(5), vArgs(6), vArgs(7) _
+ , vArgs(8), vArgs(9), vArgs(10), vArgs(11), vArgs(12))
+ Case 14, 15 : vReturn = CallByName(vBasicObject, Script, vbMethod, vArgs(0), vArgs(1), vArgs(2), vArgs(3), vArgs(4), vArgs(5), vArgs(6), vArgs(7) _
+ , vArgs(8), vArgs(9), vArgs(10), vArgs(11), vArgs(12), vArgs(13), vArgs(14))
+ Case 16, 17 : vReturn = CallByName(vBasicObject, Script, vbMethod, vArgs(0), vArgs(1), vArgs(2), vArgs(3), vArgs(4), vArgs(5), vArgs(6), vArgs(7) _
+ , vArgs(8), vArgs(9), vArgs(10), vArgs(11), vArgs(12), vArgs(13), vArgs(14), vArgs(15), vArgs(16))
+ Case 18, 19 : vReturn = CallByName(vBasicObject, Script, vbMethod, vArgs(0), vArgs(1), vArgs(2), vArgs(3), vArgs(4), vArgs(5), vArgs(6), vArgs(7) _
+ , vArgs(8), vArgs(9), vArgs(10), vArgs(11), vArgs(12), vArgs(13), vArgs(14), vArgs(15), vArgs(16), vArgs(17), vArgs(18))
+ Case 20, 21 : vReturn = CallByName(vBasicObject, Script, vbMethod, vArgs(0), vArgs(1), vArgs(2), vArgs(3), vArgs(4), vArgs(5), vArgs(6), vArgs(7) _
+ , vArgs(8), vArgs(9), vArgs(10), vArgs(11), vArgs(12), vArgs(13), vArgs(14), vArgs(15), vArgs(16), vArgs(17), vArgs(18) _
+ , vArgs(19), vArgs(20))
+ Case 22, 23 : vReturn = CallByName(vBasicObject, Script, vbMethod, vArgs(0), vArgs(1), vArgs(2), vArgs(3), vArgs(4), vArgs(5), vArgs(6), vArgs(7) _
+ , vArgs(8), vArgs(9), vArgs(10), vArgs(11), vArgs(12), vArgs(13), vArgs(14), vArgs(15), vArgs(16), vArgs(17), vArgs(18) _
+ , vArgs(19), vArgs(20), vArgs(21), vArgs(22))
+ Case 24, 25 : vReturn = CallByName(vBasicObject, Script, vbMethod, vArgs(0), vArgs(1), vArgs(2), vArgs(3), vArgs(4), vArgs(5), vArgs(6), vArgs(7) _
+ , vArgs(8), vArgs(9), vArgs(10), vArgs(11), vArgs(12), vArgs(13), vArgs(14), vArgs(15), vArgs(16), vArgs(17), vArgs(18) _
+ , vArgs(19), vArgs(20), vArgs(21), vArgs(22), vArgs(23), vArgs(24))
+ Case 26, 27 : vReturn = CallByName(vBasicObject, Script, vbMethod, vArgs(0), vArgs(1), vArgs(2), vArgs(3), vArgs(4), vArgs(5), vArgs(6), vArgs(7) _
+ , vArgs(8), vArgs(9), vArgs(10), vArgs(11), vArgs(12), vArgs(13), vArgs(14), vArgs(15), vArgs(16), vArgs(17), vArgs(18) _
+ , vArgs(19), vArgs(20), vArgs(21), vArgs(22), vArgs(23), vArgs(24), vArgs(25), vArgs(26))
+ Case &gt;= 28 : vReturn = CallByName(vBasicObject, Script, vbMethod, vArgs(0), vArgs(1), vArgs(2), vArgs(3), vArgs(4), vArgs(5), vArgs(6), vArgs(7) _
+ , vArgs(8), vArgs(9), vArgs(10), vArgs(11), vArgs(12), vArgs(13), vArgs(14), vArgs(15), vArgs(16), vArgs(17), vArgs(18) _
+ , vArgs(19), vArgs(20), vArgs(21), vArgs(22), vArgs(23), vArgs(24), vArgs(25), vArgs(26), vArgs(27), vArgs(28))
+ End Select
+ End If
+
+ &apos; Post processing
+ If Script = &quot;Dispose&quot; Then
+ &apos; Special case: Dispose() must update the cache for class objects created in Python scripts
+ Set _SF_.PythonStorage(BasicObject) = Nothing
+ End If
+ Case Else
+ End Select
+
+ &apos; Format the returned array
+ vReturnArray = Array()
+ &apos; Distinguish: Basic object
+ &apos; UNO object
+ &apos; Array
+ &apos; Scalar
+ If IsArray(vReturn) Then
+ ReDim vReturnArray(0 To 2)
+ iDims = SF_Array.CountDims(vReturn)
+ &apos; Replace dates by UNO format
+ If iDims = 1 Then
+ For i = LBound(vReturn) To UBound(vReturn)
+ If VarType(vReturn(i)) = V_DATE Then vReturn(i) = CDateToUnoDateTime(vReturn(i))
+ Next i
+ ElseIf iDims = 2 Then
+ For i = LBound(vReturn, 1) To UBound(vReturn, 1)
+ For j = LBound(vReturn, 2) To UBound(vReturn, 2)
+ If VarType(vReturn(i, j)) = V_DATE Then vReturn(i, j) = CDateToUnoDateTime(vReturn(i, j))
+ Next j
+ Next i
+ End If
+ vReturnArray(0) = vReturn &apos; 2D arrays are flattened by the script provider when returning to Python
+ vReturnArray(1) = VarType(vReturn)
+ vReturnArray(2) = iDims
+ ElseIf VarType(vReturn) = V_OBJECT And Not IsNull(vReturn) Then
+ &apos; Uno or not Uno ?
+ bUno = False
+ If (CallType And cstUno) = cstUno Then &apos; UNO considered only when pre-announced in CallType
+ Set oObjDesc = SF_Utils._VarTypeObj(vReturn)
+ bUno = ( oObjDesc.iVarType = V_UNOOBJECT )
+ 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
+ vReturnArray(5) = &quot;&quot;
+ If vReturn.ObjectType &lt;&gt; &quot;SF_CalcReference&quot; Then &apos; Calc references are implemented as a Type ... End Type data structure
+ If SF_Array.Contains(vReturn.Properties(), &quot;Name&quot;, SortOrder := &quot;ASC&quot;) Then vReturnArray(5) = vReturn.Name
+ End If
+ End If
+ Else &apos; Scalar or Nothing
+ ReDim vReturnArray(0 To 1)
+ If VarType(vReturn) = V_DATE Then vReturnArray(0) = CDateToUnoDateTime(vReturn) Else vReturnArray(0) = vReturn
+ vReturnArray(1) = VarType(vReturn)
+ End If
+
+ _PythonDispatcher = vReturnArray
+
+Finally:
+ _SF_.TriggeredByPython = False &apos; Reset normal state
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_PythonHelper._PythonDispatcher
+
+REM -----------------------------------------------------------------------------
+Private Function _Repr() As String
+&apos;&apos;&apos; Convert the Basic instance to a readable string, typically for debugging purposes (DebugPrint ...)
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Return:
+&apos;&apos;&apos; &quot;[PythonHelper]&quot;
+
+ _Repr = &quot;[PythonHelper]&quot;
+
+End Function &apos; 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_Region.xba b/wizards/source/scriptforge/SF_Region.xba
new file mode 100644
index 000000000..d3eacfae0
--- /dev/null
+++ b/wizards/source/scriptforge/SF_Region.xba
@@ -0,0 +1,861 @@
+<?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_Region" 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_Region
+&apos;&apos;&apos; =========
+&apos;&apos;&apos; Singleton class implementing the &quot;ScriptForge.Region&quot; service
+&apos;&apos;&apos; Implemented as a usual Basic module
+&apos;&apos;&apos;
+&apos;&apos;&apos; A collection of functions about languages, countries and timezones
+&apos;&apos;&apos; - Locales
+&apos;&apos;&apos; - Currencies
+&apos;&apos;&apos; - Numbers and dates formatting
+&apos;&apos;&apos; - Calendars
+&apos;&apos;&apos; - Timezones conversions
+&apos;&apos;&apos; - Numbers transformed to text
+&apos;&apos;&apos;
+&apos;&apos;&apos; Definitions:
+&apos;&apos;&apos; Locale or Region
+&apos;&apos;&apos; A combination of a language (2 or 3 lower case characters) and a country (2 upper case characters)
+&apos;&apos;&apos; Most properties and methods require a locale as argument.
+&apos;&apos;&apos; Some of them accept either the complete locale or only the language or country parts.
+&apos;&apos;&apos; When absent, the considered locale is the locale used in the LibreOffice user interface.
+&apos;&apos;&apos; (see the SF_Platform.OfficeLocale property)
+&apos;&apos;&apos; Timezone
+&apos;&apos;&apos; Specified as &quot;Region/City&quot; name like &quot;Europe/Berlin&quot;, or a custom time zone ID such as &quot;UTC&quot; or &quot;GMT-8:00&quot;.
+&apos;&apos;&apos; The time offset between the timezone and the Greenwich Meridian Time (GMT) is expressed in minutes.
+&apos;&apos;&apos; The Daylight Saving Time (DST) is an additional offset.
+&apos;&apos;&apos; Both offsets can be positive or negative.
+&apos;&apos;&apos; More info on
+&apos;&apos;&apos; https://timezonedb.com/time-zones
+&apos;&apos;&apos; https://en.wikipedia.org/wiki/Time_zone
+&apos;&apos;&apos;
+&apos;&apos;&apos; Service invocation example:
+&apos;&apos;&apos; Dim regio As Object
+&apos;&apos;&apos; Set regio = CreateScriptService(&quot;Region&quot;)
+&apos;&apos;&apos;
+&apos;&apos;&apos; Detailed user documentation:
+&apos;&apos;&apos; https://help.libreoffice.org/latest/en-US/text/sbasic/shared/03/sf_region.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
+
+REM ============================================================= PRIVATE MEMBERS
+
+Private UserLocale As String &apos; platform.OfficeLocale
+
+&apos; Reference tables
+Private LocaleData As Variant &apos; com.sun.star.i18n.LocaleData
+Private LocaleNames As Variant &apos; Array of all available &quot;la-CO&quot; strings
+
+Private UserIndex As Integer &apos; Index of UserLocale in reference tables
+
+REM ============================================================ MODULE CONSTANTS
+
+REM ===================================================== CONSTRUCTOR/DESTRUCTOR
+
+REM -----------------------------------------------------------------------------
+Public Function Dispose() As Variant
+ Set Dispose = Nothing
+End Function &apos; ScriptForge.SF_Region Explicit destructor
+
+REM ================================================================== PROPERTIES
+
+REM -----------------------------------------------------------------------------
+Property Get Country(Optional ByVal Region As Variant) As String
+&apos;&apos;&apos; Returns the english country name applicable in the given region.
+&apos;&apos;&apos; The region expressed either as a
+&apos;&apos;&apos; - locale combining language-COUNTRY (la-CO)
+&apos;&apos;&apos; - country only (CO)
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; MsgBox Regio.Country(&quot;IT&quot;) &apos; Italy
+ Country = _PropertyGet(&quot;Country&quot;, Region)
+End Property &apos; ScriptForge.SF_Region.Country (get)
+
+REM -----------------------------------------------------------------------------
+Property Get Currency(Optional ByVal Region As Variant) As String
+&apos;&apos;&apos; Returns the currency applicable in the given region.
+&apos;&apos;&apos; The region is expressed either as a
+&apos;&apos;&apos; - locale combining language-COUNTRY (la-CO)
+&apos;&apos;&apos; - country only (CO)
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; MsgBox Regio.Currency(&quot;IT&quot;) &apos; EUR
+ Currency = _PropertyGet(&quot;Currency&quot;, Region)
+End Property &apos; ScriptForge.SF_Region.Currency (get)
+
+REM -----------------------------------------------------------------------------
+Public Function DatePatterns(Optional ByVal Region As Variant) As Variant &apos; Function better than Property when return value is an array
+&apos;&apos;&apos; Returns list of date acceptance patterns for the given region.
+&apos;&apos;&apos; Patterns with input combinations that are accepted as incomplete date input, such as M/D or D.M
+&apos;&apos;&apos; The region is expressed as a locale combining language-COUNTRY (la-CO)
+&apos;&apos;&apos; The list is zero-based.
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; MsgBox Join(Regio.DatePatterns(&quot;it-IT&quot;), &quot;,&quot;) &apos; D/M/Y,D/M
+ DatePatterns = _PropertyGet(&quot;DatePatterns&quot;, Region)
+End Function &apos; ScriptForge.SF_Region.DatePatterns (get)
+
+REM -----------------------------------------------------------------------------
+Property Get DateSeparator(Optional ByVal Region As Variant) As String
+&apos;&apos;&apos; Returns the separator used in dates applicable in the given region.
+&apos;&apos;&apos; The region is expressed as a locale combining language-COUNTRY (la-CO)
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; MsgBox Regio.DateSeparator(&quot;it-IT&quot;) &apos; /
+ DateSeparator = _PropertyGet(&quot;DateSeparator&quot;, Region)
+End Property &apos; ScriptForge.SF_Region.DateSeparator (get)
+
+REM -----------------------------------------------------------------------------
+Public Function DayAbbrevNames(Optional ByVal Region As Variant) As Variant &apos; Function better than Property when return value is an array
+&apos;&apos;&apos; Returns list of abbreviated names of weekdays applicable in the given region.
+&apos;&apos;&apos; The region expressed as a
+&apos;&apos;&apos; - locale combining language-COUNTRY (la-CO)
+&apos;&apos;&apos; - language only (la)
+&apos;&apos;&apos; The list is zero-based. The 1st in the list [0] is the Monday.
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; MsgBox Join(Regio.DayAbbrevNames(&quot;it-IT&quot;), &quot;,&quot;) &apos; lun,mar,mer,gio,ven,sab,dom
+ DayAbbrevNames = _PropertyGet(&quot;DayAbbrevNames&quot;, Region)
+End Function &apos; ScriptForge.SF_Region.DayAbbrevNames (get)
+
+REM -----------------------------------------------------------------------------
+Public Function DayNames(Optional ByVal Region As Variant) As Variant &apos; Function better than Property when return value is an array
+&apos;&apos;&apos; Returns list of names of weekdays applicable in the given region.
+&apos;&apos;&apos; The region expressed as a
+&apos;&apos;&apos; - locale combining language-COUNTRY (la-CO)
+&apos;&apos;&apos; - language only (la)
+&apos;&apos;&apos; The list is zero-based. The 1st in the list [0] is the Monday.
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; MsgBox Join(Regio.DayNames(&quot;it-IT&quot;), &quot;,&quot;) &apos; lunedì,martedì,mercoledì,giovedì,venerdì,sabato,domenica
+ DayNames = _PropertyGet(&quot;DayNames&quot;, Region)
+End Function &apos; ScriptForge.SF_Region.DayNames (get)
+
+REM -----------------------------------------------------------------------------
+Public Function DayNarrowNames(Optional ByVal Region As Variant) As Variant &apos; Function better than Property when return value is an array
+&apos;&apos;&apos; Returns list of initials of weekdays applicable in the given region.
+&apos;&apos;&apos; The region expressed as a
+&apos;&apos;&apos; - locale combining language-COUNTRY (la-CO)
+&apos;&apos;&apos; - language only (la)
+&apos;&apos;&apos; The list is zero-based. The 1st in the list [0] is the Monday.
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; MsgBox Join(Regio.DayNarrowNames(&quot;it-IT&quot;), &quot;,&quot;) &apos; l,m,m,g,v,s,d
+ DayNarrowNames = _PropertyGet(&quot;DayNarrowNames&quot;, Region)
+End Function &apos; ScriptForge.SF_Region.DayNarrowNames (get)
+
+REM -----------------------------------------------------------------------------
+Property Get DecimalPoint(Optional ByVal Region As Variant) As String
+&apos;&apos;&apos; Returns the decimal separator used in numbers applicable in the given region.
+&apos;&apos;&apos; The region is expressed as a locale combining language-COUNTRY (la-CO)
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; MsgBox Regio.DecimalPoint(&quot;it-IT&quot;) &apos; .
+ DecimalPoint = _PropertyGet(&quot;DecimalPoint&quot;, Region)
+End Property &apos; ScriptForge.SF_Region.DecimalPoint (get)
+
+REM -----------------------------------------------------------------------------
+Property Get Language(Optional ByVal Region As Variant) As String
+&apos;&apos;&apos; Returns the english Language name applicable in the given region.
+&apos;&apos;&apos; The region expressed as a
+&apos;&apos;&apos; - locale combining language-COUNTRY (la-CO)
+&apos;&apos;&apos; - language only (la)
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; MsgBox Regio.Language(&quot;it-IT&quot;) &apos; Italian
+ Language = _PropertyGet(&quot;Language&quot;, Region)
+End Property &apos; ScriptForge.SF_Region.Language (get)
+
+REM -----------------------------------------------------------------------------
+Property Get ListSeparator(Optional ByVal Region As Variant) As String
+&apos;&apos;&apos; Returns the separator used in lists applicable in the given region.
+&apos;&apos;&apos; The region is expressed as a locale combining language-COUNTRY (la-CO)
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; MsgBox Regio.ListSeparator(&quot;it-IT&quot;) &apos; ;
+ ListSeparator = _PropertyGet(&quot;ListSeparator&quot;, Region)
+End Property &apos; ScriptForge.SF_Region.ListSeparator (get)
+
+REM -----------------------------------------------------------------------------
+Public Function MonthAbbrevNames(Optional ByVal Region As Variant) As Variant &apos; Function better than Property when return value is an array
+&apos;&apos;&apos; Returns list of abbreviated names of months applicable in the given region.
+&apos;&apos;&apos; The region expressed as a
+&apos;&apos;&apos; - locale combining language-COUNTRY (la-CO)
+&apos;&apos;&apos; - language only (la)
+&apos;&apos;&apos; The list is zero-based.
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; MsgBox Join(Regio.MonthAbbrevNames(&quot;it-IT&quot;), &quot;,&quot;) &apos; gen,feb,mar,apr,mag,giu,lug,ago,set,ott,nov,dic
+ MonthAbbrevNames = _PropertyGet(&quot;MonthAbbrevNames&quot;, Region)
+End Function &apos; ScriptForge.SF_Region.MonthAbbrevNames (get)
+
+REM -----------------------------------------------------------------------------
+Public Function MonthNames(Optional ByVal Region As Variant) As Variant &apos; Function better than Property when return value is an array
+&apos;&apos;&apos; Returns list of names of months applicable in the given region.
+&apos;&apos;&apos; The region expressed as a
+&apos;&apos;&apos; - locale combining language-COUNTRY (la-CO)
+&apos;&apos;&apos; - language only (la)
+&apos;&apos;&apos; The list is zero-based.
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; MsgBox Join(Regio.MonthNames(&quot;it-IT&quot;), &quot;,&quot;) &apos; gennaio,febbraio,marzo,aprile,maggio,giugno,luglio,agosto,settembre,ottobre,novembre,dicembre
+ MonthNames = _PropertyGet(&quot;MonthNames&quot;, Region)
+End Function &apos; ScriptForge.SF_Region.MonthNames (get)
+
+REM -----------------------------------------------------------------------------
+Public Function MonthNarrowNames(Optional ByVal Region As Variant) As Variant &apos; Function better than Property when return value is an array
+&apos;&apos;&apos; Returns list of initials of months applicable in the given region.
+&apos;&apos;&apos; The region expressed as a
+&apos;&apos;&apos; - locale combining language-COUNTRY (la-CO)
+&apos;&apos;&apos; - language only (la)
+&apos;&apos;&apos; The list is zero-based.
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; MsgBox Join(Regio.MonthNarrowNames(&quot;it-IT&quot;), &quot;,&quot;) &apos; g,f,m,a,m,g,l,a,s,o,n,d
+ MonthNarrowNames = _PropertyGet(&quot;MonthNarrowNames&quot;, Region)
+End Function &apos; ScriptForge.SF_Region.MonthNarrowNames (get)
+
+REM -----------------------------------------------------------------------------
+Property Get ObjectType As String
+&apos;&apos;&apos; Only to enable object representation
+ ObjectType = &quot;SF_Region&quot;
+End Property &apos; ScriptForge.SF_Region.ObjectType
+
+REM -----------------------------------------------------------------------------
+Property Get ServiceName As String
+&apos;&apos;&apos; Internal use
+ ServiceName = &quot;ScriptForge.Region&quot;
+End Property &apos; ScriptForge.SF_Region.ServiceName
+
+REM -----------------------------------------------------------------------------
+Property Get ThousandSeparator(Optional ByVal Region As Variant) As String
+&apos;&apos;&apos; Returns the thousands separator used in numbers applicable in the given region.
+&apos;&apos;&apos; The region is expressed as a locale combining language-COUNTRY (la-CO)
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; MsgBox Regio.ThousandSeparator(&quot;it-IT&quot;) &apos; .
+ ThousandSeparator = _PropertyGet(&quot;ThousandSeparator&quot;, Region)
+End Property &apos; ScriptForge.SF_Region.ThousandSeparator (get)
+
+REM -----------------------------------------------------------------------------
+Property Get TimeSeparator(Optional ByVal Region As Variant) As String
+&apos;&apos;&apos; Returns the separator used to format times applicable in the given region.
+&apos;&apos;&apos; The region is expressed as a locale combining language-COUNTRY (la-CO)
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; MsgBox Regio.TimeSeparator(&quot;it-IT&quot;) &apos; :
+ TimeSeparator = _PropertyGet(&quot;TimeSeparator&quot;, Region)
+End Property &apos; ScriptForge.SF_Region.TimeSeparator (get)
+
+REM ===================================================================== METHODS
+
+REM -----------------------------------------------------------------------------
+Public Function DSTOffset(Optional ByVal LocalDateTime As Variant _
+ , Optional ByVal TimeZone As Variant _
+ , Optional ByVal Locale As Variant _
+ ) As Integer
+&apos;&apos;&apos; Computes the additional offset due to daylight saving (&quot;summer time&quot;)
+&apos;&apos;&apos; Args
+&apos;&apos;&apos; LocalDateTime: local date and time as a Date. DST offset varies during the year.
+&apos;&apos;&apos; TimeZone: specified as &quot;Region/City&quot; name like &quot;Europe/Berlin&quot;, or a custom time zone ID such as &quot;UTC&quot; or &quot;GMT-8:00&quot;
+&apos;&apos;&apos; Locale: expressed as a locale combining language-COUNTRY (la-CO), or COUNTRY alone (CO)
+&apos;&apos;&apos; Return:
+&apos;&apos;&apos; The offset in minutes
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; regio.DSTOffset(DateSerial(2022, 8, 20) + TimeSerial(16, 58, 17), &quot;Europe/Brussels&quot;, &quot;fr-BE&quot;) &apos; 60
+
+Dim iDSTOffset As Integer &apos; Return value
+Dim oLocale As Object &apos; com.sun.star.lang.Locale
+Dim oCalendarImpl As Object &apos; com.sun.star.i18n.CalendarImpl
+Const cstThisSub = &quot;Region.DSTOffset&quot;
+Const cstSubArgs = &quot;LocalDateTime, TimeZone, [Locale=&quot;&quot;&quot;&quot;]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ iDSTOffset = 0
+
+Check:
+ If IsMissing(Locale) Or IsEmpty(Locale) Then Locale = &quot;&quot;
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(LocalDateTime, &quot;LocalDateTime&quot;, V_DATE) Then GoTo Finally
+ If Not SF_Utils._Validate(TimeZone, &quot;TimeZone&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(Locale, &quot;Locale&quot;, V_STRING) Then GoTo Finally
+ End If
+
+ Set oLocale = SF_Region._GetLocale(Locale, pbCountry := True)
+ If IsNull(oLocale) Then GoTo Finally
+
+Try:
+ Set oCalendarImpl = SF_Utils._GetUNOService(&quot;CalendarImpl&quot;)
+ With oCalendarImpl
+ .loadDefaultCalendarTZ(oLocale, TimeZone)
+ .setLocalDateTime(LocaldateTime)
+ iDSTOffset = .getValue(com.sun.star.i18n.CalendarFieldIndex.DST_OFFSET)
+ End With
+
+Finally:
+ DSTOffset = iDSTOffset
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Region.DSTOffset
+
+REM -----------------------------------------------------------------------------
+Public Function GetProperty(Optional ByVal PropertyName As Variant _
+ , Optional Region As Variant _
+ ) As Variant
+&apos;&apos;&apos; Return the actual value of the given property
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; PropertyName: the name of the property as a string
+&apos;&apos;&apos; Region: the language-COUNTRY combination (la-CO) or the country (CO- or the language (la)
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The actual value of the property
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; ARGUMENTERROR The property does not exist
+
+Const cstThisSub = &quot;Region.GetProperty&quot;
+Const cstSubArgs = &quot;&quot;
+
+ If ScriptForge.SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ GetProperty = Null
+
+Check:
+ If IsMissing(Region) Or IsEmpty(Region) Then Region = &quot;&quot;
+ If ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not ScriptForge.SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
+ If Not ScriptForge.SF_Utils._Validate(Region, &quot;Region&quot;, V_STRING) Then GoTo Catch
+ End If
+
+Try:
+ If Len(Region) = 0 Then
+ GetProperty = _PropertyGet(PropertyName)
+ Else
+ GetProperty = _PropertyGet(PropertyName, Region)
+ End If
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Region.GetProperty
+
+REM -----------------------------------------------------------------------------
+Public Function LocalDateTime(Optional ByVal UTCDateTime As Variant _
+ , Optional ByVal TimeZone As Variant _
+ , Optional ByVal Locale As Variant _
+ ) As Date
+&apos;&apos;&apos; Computes the local date and time from a UTC date and time
+&apos;&apos;&apos; Args
+&apos;&apos;&apos; UTCDateTime: the universal date and time to be converted to local time
+&apos;&apos;&apos; TimeZone: specified as &quot;Region/City&quot; name like &quot;Europe/Berlin&quot;, or a custom time zone ID such as &quot;UTC&quot; or &quot;GMT-8:00&quot;
+&apos;&apos;&apos; Locale: expressed as a locale combining language-COUNTRY (la-CO), or COUNTRY alone (CO)
+&apos;&apos;&apos; Return:
+&apos;&apos;&apos; The local time converted from the corresponding UTC date and time as a Date
+&apos;&apos;&apos; If the returned value is before 1900, it is likely that the Locale is not recognized
+&apos;&apos;&apos; If the returned value matches the local time, it is likely that the timezone is not recognized
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; regio.LocalDateTime(DateSerial(2022, 3, 20) + TimeSerial(16, 58, 17), &quot;Europe/Brussels&quot;, &quot;fr-BE&quot;)
+&apos;&apos;&apos; &apos; 2022-03-20 17:58:17
+
+Dim dLocalDateTime As Double &apos; Return value
+Dim oLocale As Object &apos; com.sun.star.lang.Locale
+Dim oCalendarImpl As Object &apos; com.sun.star.i18n.CalendarImpl
+Const cstThisSub = &quot;Region.LocalDateTime&quot;
+Const cstSubArgs = &quot;UTCDateTime, TimeZone, [Locale=&quot;&quot;&quot;&quot;]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ dLocalDateTime = -1
+
+Check:
+ If IsMissing(Locale) Or IsEmpty(Locale) Then Locale = &quot;&quot;
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(LocalDateTime, &quot;LocalDateTime&quot;, V_DATE) Then GoTo Finally
+ If Not SF_Utils._Validate(TimeZone, &quot;TimeZone&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(Locale, &quot;Locale&quot;, V_STRING) Then GoTo Finally
+ End If
+
+ Set oLocale = SF_Region._GetLocale(Locale, pbCountry := True)
+ If IsNull(oLocale) Then GoTo Finally
+
+Try:
+ Set oCalendarImpl = SF_Utils._GetUNOService(&quot;CalendarImpl&quot;)
+ With oCalendarImpl
+ .loadDefaultCalendarTZ(oLocale, TimeZone)
+ .setDateTime(UTCDateTime)
+ dLocalDateTime = .getLocalDateTime()
+ End With
+
+Finally:
+ LocalDateTime = CDate(dLocalDateTime)
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Region.LocalDateTime
+
+REM -----------------------------------------------------------------------------
+Public Function Methods() As Variant
+&apos;&apos;&apos; Return the list of public methods of the Region class as an array
+
+ Methods = Array( _
+ &quot;DSTOffset&quot; _
+ , &quot;LocalDateTime&quot; _
+ , &quot;Number2Text&quot; _
+ , &quot;TimeZoneOffset&quot; _
+ , &quot;UTCDateTime&quot; _
+ , &quot;UTCNow&quot; _
+ )
+
+End Function &apos; ScriptForge.SF_Region.Methods
+
+REM -----------------------------------------------------------------------------
+Public Function Number2Text(Optional ByVal Number As Variant _
+ , Optional ByVal Locale As Variant _
+ ) As String
+&apos;&apos;&apos; Convert numbers and money amounts in many languages into words
+&apos;&apos;&apos; Args
+&apos;&apos;&apos; Number: the number to spell out
+&apos;&apos;&apos; Accepted types: strings or numeric values (integer or real numbers)
+&apos;&apos;&apos; When a string, a variety of prefixes is supported
+&apos;&apos;&apos; The string &quot;help&quot; provides helpful tips about allowed prefixes by language
+&apos;&apos;&apos; Example for french
+&apos;&apos;&apos; un, deux, trois
+&apos;&apos;&apos; feminine: une, deux, trois
+&apos;&apos;&apos; masculine: un, deux, trois
+&apos;&apos;&apos; ordinal: premier, deuxième, troisième
+&apos;&apos;&apos; ordinal-feminine: première, deuxième, troisième
+&apos;&apos;&apos; ordinal-masculine: premier, deuxième, troisième
+&apos;&apos;&apos; informal: onze-cents, douze-cents, treize-cents
+&apos;&apos;&apos; Numbers may be prefixed by ISO currency codes (EUR, USD, ...)
+&apos;&apos;&apos; Locale: expressed as a locale combining language-COUNTRY (la-CO), or language alone (la)
+&apos;&apos;&apos; The list of supported languages can be found on
+&apos;&apos;&apos; https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1linguistic2_1_1XNumberText.html
+&apos;&apos;&apos; Return:
+&apos;&apos;&apos; The number or amount transformed in words
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; regio.Number2Text(&quot;help&quot;, &quot;fr&quot;) &apos; See above
+&apos;&apos;&apos; regio.Number2Text(&quot;79,93&quot;, &quot;fr-BE&quot;) &apos; septante-neuf virgule nonante-trois
+&apos;&apos;&apos; regio.Number2Text(Pi(), &quot;pt-BR&quot;) &apos; três vírgula um quatro um cinco nove dois seis cinco três cinco oito nove sete nove
+&apos;&apos;&apos; regio.Number2Text(&quot;EUR 1234.56&quot;, &quot;it&quot;) &apos; milleduecentotrentaquattro euro cinquantasei centesimi
+
+Dim sNumber2Text As String &apos; Return value
+Dim oLocale As Object &apos; com.sun.star.lang.Locale
+Dim oNumber2Text As Object &apos; com.sun.star.linguistic2.NumberText
+Const cstThisSub = &quot;Region.Number2Text&quot;
+Const cstSubArgs = &quot;Number, [Locale=&quot;&quot;&quot;&quot;]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sNumber2Text = &quot;&quot;
+
+Check:
+ If IsMissing(Locale) Or IsEmpty(Locale) Then Locale = &quot;&quot;
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(Number, &quot;Number&quot;, Array(V_STRING, V_NUMERIC)) Then GoTo Finally
+ If Not SF_Utils._Validate(Locale, &quot;Locale&quot;, V_STRING) Then GoTo Finally
+ End If
+
+ Set oLocale = SF_Region._GetLocale(Locale, pbLanguage := True)
+ If IsNull(oLocale) Then GoTo Finally
+
+Try:
+ Set oNumber2Text = SF_Utils._GetUNOService(&quot;Number2Text&quot;)
+ sNumber2Text = oNumber2Text.getNumberText(Number, oLocale)
+
+Finally:
+ Number2Text = sNumber2Text
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Region.Number2Text
+
+REM -----------------------------------------------------------------------------
+Public Function Properties() As Variant
+&apos;&apos;&apos; Return the list or properties of the Region class as an array
+
+ Properties = Array( _
+ &quot;Country&quot; _
+ , &quot;Currency&quot; _
+ , &quot;DatePatterns&quot; _
+ , &quot;DateSeparator&quot; _
+ , &quot;DayAbbrevNames&quot; _
+ , &quot;DayNames&quot; _
+ , &quot;DayNarrowNames&quot; _
+ , &quot;DecimalPoint&quot; _
+ , &quot;Language&quot; _
+ , &quot;ListSeparator&quot; _
+ , &quot;MonthAbbrevNames&quot; _
+ , &quot;MonthNames&quot; _
+ , &quot;MonthNarrowNames&quot; _
+ , &quot;ThousandSeparator&quot; _
+ , &quot;TimeSeparator&quot; _
+ )
+
+End Function &apos; ScriptForge.SF_Region.Properties
+
+REM -----------------------------------------------------------------------------
+Public Function TimeZoneOffset(Optional ByVal TimeZone As Variant _
+ , Optional ByVal Locale As Variant _
+ ) As Integer
+&apos;&apos;&apos; Computes the offset between GMT and the given timezone and locale
+&apos;&apos;&apos; Args
+&apos;&apos;&apos; TimeZone: specified as &quot;Region/City&quot; name like &quot;Europe/Berlin&quot;, or a custom time zone ID such as &quot;UTC&quot; or &quot;GMT-8:00&quot;
+&apos;&apos;&apos; Locale: expressed as a locale combining language-COUNTRY (la-CO), or COUNTRY alone (CO)
+&apos;&apos;&apos; Return:
+&apos;&apos;&apos; The offset in minutes
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; regio.TimeZoneOffset(&quot;Europe/Brussels&quot;, &quot;fr-BE&quot;) &apos; 60
+
+Dim iTimeZoneOffset As Integer &apos; Return value
+Dim oLocale As Object &apos; com.sun.star.lang.Locale
+Dim oCalendarImpl As Object &apos; com.sun.star.i18n.CalendarImpl
+Const cstThisSub = &quot;Region.TimeZoneOffset&quot;
+Const cstSubArgs = &quot;TimeZone, [Locale=&quot;&quot;&quot;&quot;]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ iTimeZoneOffset = 0
+
+Check:
+ If IsMissing(Locale) Or IsEmpty(Locale) Then Locale = &quot;&quot;
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(TimeZone, &quot;TimeZone&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(Locale, &quot;Locale&quot;, V_STRING) Then GoTo Finally
+ End If
+
+ Set oLocale = SF_Region._GetLocale(Locale, pbCountry := True)
+ If IsNull(oLocale) Then GoTo Finally
+
+Try:
+ Set oCalendarImpl = SF_Utils._GetUNOService(&quot;CalendarImpl&quot;)
+ With oCalendarImpl
+ .loadDefaultCalendarTZ(oLocale, TimeZone)
+ iTimeZoneOffset = .getValue(com.sun.star.i18n.CalendarFieldIndex.ZONE_OFFSET)
+ End With
+
+Finally:
+ TimeZoneOffset = iTimeZoneOffset
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Region.TimeZoneOffset
+
+REM -----------------------------------------------------------------------------
+Public Function UTCDateTime(Optional ByVal LocalDateTime As Variant _
+ , Optional ByVal TimeZone As Variant _
+ , Optional ByVal Locale As Variant _
+ ) As Date
+&apos;&apos;&apos; Computes the UTC date and time of a given local date and time
+&apos;&apos;&apos; Args
+&apos;&apos;&apos; LocalDateTime: the date and time measured in a given timezone
+&apos;&apos;&apos; TimeZone: specified as &quot;Region/City&quot; name like &quot;Europe/Berlin&quot;, or a custom time zone ID such as &quot;UTC&quot; or &quot;GMT-8:00&quot;
+&apos;&apos;&apos; Locale: expressed as a locale combining language-COUNTRY (la-CO), or COUNTRY alone (CO)
+&apos;&apos;&apos; Return:
+&apos;&apos;&apos; The local time converted to the corresponding UTC date and time as a Date
+&apos;&apos;&apos; If the returned value is before 1900, it is likely that the Locale is not recognized
+&apos;&apos;&apos; If the returned value matches the local time, it is likely that the the timezone is not recognized
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; regio.UTCDateTime(DateSerial(2022, 3, 20) + TimeSerial(17, 58, 17), &quot;Europe/Brussels&quot;, &quot;fr-BE&quot;)
+&apos;&apos;&apos; &apos; 2022-03-20 16:58:17
+
+Dim dUTCDateTime As Double &apos; Return value
+Dim oLocale As Object &apos; com.sun.star.lang.Locale
+Dim oCalendarImpl As Object &apos; com.sun.star.i18n.CalendarImpl
+Const cstThisSub = &quot;Region.UTCDateTime&quot;
+Const cstSubArgs = &quot;LocalDateTime, TimeZone, [Locale=&quot;&quot;&quot;&quot;]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ dUTCDateTime = -1
+
+Check:
+ If IsMissing(Locale) Or IsEmpty(Locale) Then Locale = &quot;&quot;
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(LocalDateTime, &quot;LocalDateTime&quot;, V_DATE) Then GoTo Finally
+ If Not SF_Utils._Validate(TimeZone, &quot;TimeZone&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(Locale, &quot;Locale&quot;, V_STRING) Then GoTo Finally
+ End If
+
+ Set oLocale = SF_Region._GetLocale(Locale, pbCountry := True)
+ If IsNull(oLocale) Then GoTo Finally
+
+Try:
+ Set oCalendarImpl = SF_Utils._GetUNOService(&quot;CalendarImpl&quot;)
+ With oCalendarImpl
+ .loadDefaultCalendarTZ(oLocale, TimeZone)
+ .setLocalDateTime(LocalDateTime)
+ dUTCDateTime = .getDateTime()
+ End With
+
+Finally:
+ UTCDateTime = CDate(dUTCDateTime)
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Region.UTCDateTime
+
+REM -----------------------------------------------------------------------------
+Public Function UTCNow(Optional ByVal TimeZone As Variant _
+ , Optional ByVal Locale As Variant _
+ ) As Date
+&apos;&apos;&apos; Computes the actual UTC date and time
+&apos;&apos;&apos; Args
+&apos;&apos;&apos; TimeZone: specified as &quot;Region/City&quot; name like &quot;Europe/Berlin&quot;, or a custom time zone ID such as &quot;UTC&quot; or &quot;GMT-8:00&quot;
+&apos;&apos;&apos; Locale: expressed as a locale combining language-COUNTRY (la-CO), or COUNTRY alone (CO)
+&apos;&apos;&apos; Return:
+&apos;&apos;&apos; The actual UTC date and time as a Date
+&apos;&apos;&apos; If the returned value is before 1900, it is likely that the Locale is not recognized
+&apos;&apos;&apos; If the returned value matches the local time, it is likely that the the timezone is not recognized
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; regio.UTCNow(&quot;Europe/Brussels&quot;, &quot;fr-BE&quot;) &apos; 2022-03-20 16:58:17
+
+Dim dUTCNow As Double &apos; Return value
+Dim oLocale As Object &apos; com.sun.star.lang.Locale
+Dim oCalendarImpl As Object &apos; com.sun.star.i18n.CalendarImpl
+Const cstThisSub = &quot;Region.UTCNow&quot;
+Const cstSubArgs = &quot;TimeZone, [Locale=&quot;&quot;&quot;&quot;]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ dUTCNow = -1
+
+Check:
+ If IsMissing(Locale) Or IsEmpty(Locale) Then Locale = &quot;&quot;
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(TimeZone, &quot;TimeZone&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(Locale, &quot;Locale&quot;, V_STRING) Then GoTo Finally
+ End If
+
+ Set oLocale = SF_Region._GetLocale(Locale, pbCountry := True)
+ If IsNull(oLocale) Then GoTo Finally
+
+Try:
+ Set oCalendarImpl = SF_Utils._GetUNOService(&quot;CalendarImpl&quot;)
+ With oCalendarImpl
+ .loadDefaultCalendarTZ(oLocale, TimeZone)
+ .setLocalDateTime(Now())
+ dUTCNow = .getDateTime()
+ End With
+
+Finally:
+ UTCNow = CDate(dUTCNow)
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Region.UTCNow
+
+REM =========================================================== PRIVATE FUNCTIONS
+
+REM -----------------------------------------------------------------------------
+Private Function _GetLocale(ByVal psLocale As String _
+ , Optional ByVal pbCountry As Variant _
+ , Optional ByVal pbLanguage As Variant _
+ ) As Object
+&apos;&apos;&apos; Convert a locale given as a string to a com.sun.star.lang.Locale object
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; psLocale: the input string, as &quot;la-CO&quot;, &quot;la&quot; or &quot;CO&quot;
+&apos;&apos;&apos; pbCountry: True when &quot;CO&quot; only is admitted
+&apos;&apos;&apos; pbLanguage: True when &quot;la&quot; only is admitted
+&apos;&apos;&apos; At most one out of pbLanguage or pbCountry may be True
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; com.sun.star.lang.Locale
+
+Dim sLocale As String &apos; &quot;la-CO&quot;
+Dim iLocale As Integer &apos; Index in reference tables
+Dim oLocale As Object &apos; Return value com.sun.star.lang.Locale
+Dim i As Integer
+
+ If IsMissing(pbCountry) Or IsEmpty(pbCountry) Then pbCountry = False
+ If IsMissing(pbLanguage) Or IsEmpty(pbLanguage) Then pbLanguage = False
+
+ _LoadAllLocales() &apos; Initialize locale reference tables
+
+Check:
+ &apos; The argument may be a language &quot;la&quot;, a country &quot;CO&quot; or a Locale &quot;la-CO&quot;
+ &apos; Scan the reference tables to find a valid locale as a com.sun.star.lang.Locale
+ Set oLocale = Nothing : sLocale = &quot;&quot; : iLocale = -1
+ If Len(psLocale) = 0 Then &apos; Default value is the office com.sun.star.i18n.Locale
+ sLocale = UserLocale
+ iLocale = UserIndex
+ ElseIf InStr(psLocale, &quot;-&quot;) = 0 Then &apos; Language only or country only
+ Select Case True
+ Case pbLanguage
+ &apos; Find any locale having the argument as language
+ For i = 0 To UBound(LocaleNames)
+ &apos; A language is presumed 2 or 3 characters long
+ If Split(LocaleNames(i), &quot;-&quot;)(0) = LCase(psLocale) Then
+ sLocale = LocaleNames(i)
+ iLocale = i
+ Exit For
+ End If
+ Next i
+ Case pbCountry
+ &apos; Find any locale having the argument as country
+ For i = 0 To UBound(LocaleNames)
+ &apos; A country is presumed exactly 2 characters long
+ If Right(LocaleNames(i), 2) = UCase(psLocale) Then
+ sLocale = LocaleNames(i)
+ iLocale = i
+ Exit For
+ End If
+ Next i
+ Case Else
+ End Select
+ Else &apos; A full locale is given
+ iLocale = SF_Array.IndexOf(LocaleNames, psLocale, CaseSensitive := False)
+ If iLocale &gt;= 0 Then sLocale = LocaleNames(iLocale)
+ End If
+
+Try:
+ &apos; Build error message when relevant
+ If iLocale &lt; 0 Then
+ If Not SF_Utils._Validate(psLocale, &quot;Locale&quot;, V_STRING, LocaleNames) Then GoTo Finally
+ Else
+ Set oLocale = CreateUnoStruct(&quot;com.sun.star.lang.Locale&quot;)
+ oLocale.Language = Split(sLocale, &quot;-&quot;)(0) &apos; A language is 2 or 3 characters long
+ oLocale.Country = Right(sLocale, 2)
+ End If
+
+Finally:
+ Set _GetLocale = oLocale
+ Exit Function
+End Function &apos; ScriptForge.SF_Region._GetLocale
+
+REM -----------------------------------------------------------------------------
+Private Sub _LoadAllLocales()
+&apos;&apos;&apos; Initialize the LocaleNames array = the list of all available locales in the LibreOffice installation
+
+Dim oOffice As Object &apos; com.sun.star.lang.Locale
+Dim vLocales As Variant &apos; Array of com.sun.star.lang.Locale
+Dim iTop As Integer &apos; Upper bound of LocaleNames
+Dim i As Integer
+
+Try:
+ &apos; Office locale
+ If Len(UserLocale) = 0 Then
+ Set oOffice = SF_Utils._GetUNOService(&quot;OfficeLocale&quot;)
+ UserLocale = oOffice.Language &amp; &quot;-&quot; &amp; oOffice.Country
+ End If
+
+ &apos; LocaleData, localeNames and UserIndex
+ If IsEmpty(LocaleData) Or IsNull(LocaleData) Or Not IsArray(LocaleNames) Then
+ LocaleData = SF_Utils._GetUNOService(&quot;LocaleData&quot;)
+ vLocales = LocaleData.getAllInstalledLocaleNames()
+ LocaleNames = Array()
+ iTop = UBound(vLocales)
+ ReDim LocaleNames(0 To iTop)
+ For i = 0 To iTop
+ LocaleNames(i) = vLocales(i).Language &amp; &quot;-&quot; &amp; vLocales(i).Country
+ If LocaleNames(i) = UserLocale Then UserIndex = i
+ Next i
+ End If
+
+End Sub &apos; ScriptForge.SF_Region._LoadAllLocales
+
+REM -----------------------------------------------------------------------------
+Private Function _PropertyGet(Optional ByVal psProperty As String _
+ , Optional ByVal pvLocale As Variant) As Variant
+&apos;&apos;&apos; Return the value of the named property
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; psProperty: the name of the property
+&apos;&apos;&apos; pvLocale: a locale in the form language-COUNTRY (la-CO) or language only, or country only
+&apos;&apos;&apos; When language or country only, any locale matching either the language or the country is selected
+
+Dim oLocale As Object &apos; com.sun.star.lang.Locale
+Dim vCurrencies As Variant &apos; Array of com.sun.star.i18n.Currency
+Dim oCurrency As Object &apos; com.sun.star.i18n.Currency
+Dim oLanguageCountryInfo As Object &apos; com.sun.star.i18n.LanguageCountryInfo
+Dim oLocaleDataItem2 As Object &apos; com.sun.star.i18n.LocaleDataItem2
+Dim oCalendarImpl As Object &apos; com.sun.star.i18n.CalendarImpl
+Dim oCalItem As Object &apos; com.sun.star.i18n.CalendarItem2
+Dim vCalItems() As Variant &apos; Array of days/months
+Dim i As Integer, j As Integer
+
+Dim cstThisSub As String
+Const cstSubArgs = &quot;&quot;
+
+ cstThisSub = &quot;Region.Get&quot; &amp; psProperty
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Check:
+ If IsMissing(pvLocale) Or IsEmpty(pvLocale) Then pvLocale = &quot;&quot;
+ If Not SF_Utils._Validate(pvLocale, &quot;Locale&quot;, V_STRING) Then GoTo Finally
+
+ Select Case psProperty
+ Case &quot;Currency&quot;, &quot;Country&quot;
+ Set oLocale = SF_Region._GetLocale(pvLocale, pbCountry := True) &apos; Country only is admitted
+ Case &quot;Language&quot;, &quot;DayNames&quot;, &quot;DayAbbrevNames&quot;, &quot;DayNarrowNames&quot; _
+ , &quot;MonthNames&quot;, &quot;MonthAbbrevNames&quot;, &quot;MonthNarrowNames&quot;
+ Set oLocale = SF_Region._GetLocale(pvLocale, pbLanguage := True) &apos; Language only is admitted
+ Case Else
+ Set oLocale = SF_Region._GetLocale(pvLocale)
+ End Select
+ If IsNull(oLocale) Then GoTo Finally
+
+Try:
+ Select Case psProperty
+ Case &quot;Country&quot;, &quot;Language&quot;
+ Set oLanguageCountryInfo = LocaleData.getLanguageCountryInfo(oLocale)
+ With oLanguageCountryInfo
+ If psProperty = &quot;Country&quot; Then _PropertyGet = .CountryDefaultName Else _PropertyGet = .LanguageDefaultName
+ End With
+ Case &quot;Currency&quot;
+ vCurrencies = LocaleData.getAllCurrencies(oLocale)
+ _PropertyGet = &quot;&quot;
+ For Each oCurrency In vCurrencies
+ If oCurrency.Default Then
+ _PropertyGet = oCurrency.BankSymbol
+ Exit For
+ End If
+ Next oCurrency
+ Case &quot;DatePatterns&quot;
+ _PropertyGet = LocaleData.getDateAcceptancePatterns(oLocale)
+ Case &quot;DateSeparator&quot;, &quot;DecimalPoint&quot;, &quot;ListSeparator&quot;, &quot;ThousandSeparator&quot;, &quot;TimeSeparator&quot;
+ Set oLocaleDataItem2 = LocaleData.getLocaleItem2(oLocale)
+ With oLocaleDataItem2
+ Select Case psProperty
+ Case &quot;DateSeparator&quot; : _PropertyGet = .dateSeparator
+ Case &quot;DecimalPoint&quot; : _PropertyGet = .decimalSeparator
+ Case &quot;ListSeparator&quot; : _PropertyGet = .listSeparator
+ Case &quot;ThousandSeparator&quot; : _PropertyGet = .thousandSeparator
+ Case &quot;TimeSeparator&quot; : _PropertyGet = .timeSeparator
+ End Select
+ End With
+ Case &quot;DayAbbrevNames&quot;, &quot;DayNames&quot;, &quot;DayNarrowNames&quot;
+ Set oCalendarImpl = SF_Utils._GetUNOService(&quot;CalendarImpl&quot;)
+ With oCalendarImpl
+ .loadDefaultCalendar(oLocale)
+ vCalItems = Array() : ReDim vCalItems(0 To 6)
+ For i = 0 To UBound(.Days2)
+ Set oCalItem = .Days2(i)
+ j = Iif(i = 0, 6, i - 1)
+ Select Case psProperty
+ Case &quot;DayNames&quot; : vCalItems(j) = oCalItem.FullName
+ Case &quot;DayAbbrevNames&quot; : vCalItems(j) = oCalItem.AbbrevName
+ Case &quot;DayNarrowNames&quot; : vCalItems(j) = oCalItem.NarrowName
+ End Select
+ Next i
+ _PropertyGet = vCalItems
+ End With
+ Case &quot;MonthAbbrevNames&quot;, &quot;MonthNames&quot;, &quot;MonthNarrowNames&quot;
+ Set oCalendarImpl = SF_Utils._GetUNOService(&quot;CalendarImpl&quot;)
+ With oCalendarImpl
+ .loadDefaultCalendar(oLocale)
+ vCalItems = Array() : ReDim vCalItems(0 To 11)
+ For i = 0 To UBound(.Months2)
+ Set oCalItem = .Months2(i)
+ Select Case psProperty
+ Case &quot;MonthNames&quot; : vCalItems(i) = oCalItem.FullName
+ Case &quot;MonthAbbrevNames&quot; : vCalItems(i) = oCalItem.AbbrevName
+ Case &quot;MonthNarrowNames&quot; : vCalItems(i) = oCalItem.NarrowName
+ End Select
+ Next i
+ _PropertyGet = vCalItems
+ End With
+ Case Else
+ _PropertyGet = &quot;&quot;
+ End Select
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+End Function &apos; ScriptForge.SF_Region._PropertyGet
+
+REM ================================================ END OF SCRIPTFORGE.SF_REGION
+</script:module> \ No newline at end of file
diff --git a/wizards/source/scriptforge/SF_Root.xba b/wizards/source/scriptforge/SF_Root.xba
new file mode 100644
index 000000000..4db0efb42
--- /dev/null
+++ b/wizards/source/scriptforge/SF_Root.xba
@@ -0,0 +1,1070 @@
+<?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_Root" 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 ClassModule
+Option Private Module
+
+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_Root
+&apos;&apos;&apos; =======
+&apos;&apos;&apos; FOR INTERNAL USE ONLY
+&apos;&apos;&apos; Singleton class holding all persistent variables shared
+&apos;&apos;&apos; by all the modules of the ScriptForge library
+&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 ============================================================= PRIVATE MEMBERS
+
+&apos; Internals
+Private [Me] As Object
+Private [_Parent] As Object
+Private ObjectType As String &apos; Must be &quot;ROOT&quot;
+Private MainFunction As String &apos; Name of method or property called by user script
+Private MainFunctionArgs As String &apos; Syntax of method called by user script
+Private StackLevel As Integer &apos; Depth of calls between internal methods
+
+&apos; Error management
+Private ErrorHandler As Boolean &apos; True = error handling active, False = internal debugging
+Private ConsoleLines() As Variant &apos; Array of messages displayable in console
+Private ConsoleDialog As Object &apos; SFDialogs.Dialog object
+Private ConsoleControl As Object &apos; SFDialogs.DialogControl object
+Private DisplayEnabled As Boolean &apos; When True, display of console or error messages is allowed
+Private StopWhenError As Boolean &apos; When True, process stops after error &gt; &quot;WARNING&quot;
+Private TriggeredByPython As Boolean &apos; When True, the actual user script is a Python script
+Private DebugMode As Boolean &apos; When True, log enter/exit each official Sub
+
+&apos; Progress and status bars
+Private ProgressBarDialog As Object &apos; SFDialogs.Dialog object
+Private ProgressBarText As Object &apos; SFDialogs.DialogControl object
+Private ProgressBarBar As Object &apos; SFDialogs.DialogControl object
+Private Statusbar As Object
+
+&apos; Services management
+Private ServicesList As Variant &apos; Dictionary of provided services
+
+&apos; Usual UNO services
+Private FunctionAccess As Object &apos; com.sun.star.sheet.FunctionAccess
+Private PathSettings As Object &apos; com.sun.star.util.PathSettings
+Private PathSubstitution As Object &apos; com.sun.star.util.PathSubstitution
+Private ScriptProvider As Object &apos; com.sun.star.script.provider.MasterScriptProviderFactory
+Private SystemShellExecute As Object &apos; com.sun.star.system.SystemShellExecute
+Private CoreReflection As Object &apos; com.sun.star.reflection.CoreReflection
+Private DispatchHelper As Object &apos; com.sun.star.frame.DispatchHelper
+Private TextSearch As Object &apos; com.sun.star.util.TextSearch
+Private SearchOptions As Object &apos; com.sun.star.util.SearchOptions
+Private SystemLocale As Object &apos; com.sun.star.lang.Locale
+Private OfficeLocale As Object &apos; com.sun.star.lang.Locale
+Private FormatLocale As Object &apos; com.sun.star.lang.Locale
+Private LocaleData As Object &apos; com.sun.star.i18n.LocaleData
+Private CalendarImpl As Object &apos; com.sun.star.i18n.CalendarImpl
+Private Number2Text As Object &apos; com.sun.star.linguistic2.NumberText
+Private PrinterServer As Object &apos; com.sun.star.awt.PrinterServer
+Private CharacterClass As Object &apos; com.sun.star.i18n.CharacterClassification
+Private FileAccess As Object &apos; com.sun.star.ucb.SimpleFileAccess
+Private FilterFactory As Object &apos; com.sun.star.document.FilterFactory
+Private FolderPicker As Object &apos; com.sun.star.ui.dialogs.FolderPicker
+Private FilePicker As Object &apos; com.sun.star.ui.dialogs.FilePicker
+Private URLTransformer As Object &apos; com.sun.star.util.URLTransformer
+Private Introspection As Object &apos; com.sun.star.beans.Introspection
+Private BrowseNodeFactory As Object &apos; com.sun.star.script.browse.BrowseNodeFactory
+Private DatabaseContext As Object &apos; com.sun.star.sdb.DatabaseContext
+Private ConfigurationProvider _
+ As Object &apos; com.sun.star.configuration.ConfigurationProvider
+Private PackageProvider As Object &apos; com.sun.star.comp.deployment.PackageInformationProvider
+Private MailService As Object &apos; com.sun.star.system.SimpleCommandMail or com.sun.star.system.SimpleSystemMail
+Private GraphicExportFilter As Object &apos; com.sun.star.drawing.GraphicExportFilter
+Private Toolkit As Object &apos; com.sun.star.awt.Toolkit
+
+&apos; Specific persistent services objects or properties
+Private FileSystemNaming As String &apos; If &quot;SYS&quot;, file and folder naming is based on operating system notation
+Private PythonHelper As String &apos; File name of Python helper functions (stored in $(inst)/share/Scripts/python)
+Private PythonHelper2 As String &apos; Alternate Python helper file name for test purposes
+Private LocalizedInterface As Object &apos; ScriptForge own L10N service
+Private OSName As String &apos; WIN, LINUX, MACOS
+Private SFDialogs As Variant &apos; Persistent storage for the SFDialogs library
+Private SFForms As Variant &apos; Persistent storage for the SF_Form class in the SFDocuments library
+Private PythonStorage As Variant &apos; Persistent storage for the objects created and processed in Python
+Private PythonPermanent As Long &apos; Number of permanent entries in PythonStorage containing standard module objects
+
+REM ====================================================== CONSTRUCTOR/DESTRUCTOR
+
+REM -----------------------------------------------------------------------------
+Private Sub Class_Initialize()
+ Set [Me] = Nothing
+ Set [_Parent] = Nothing
+ ObjectType = &quot;ROOT&quot;
+ MainFunction = &quot;&quot;
+ MainFunctionArgs = &quot;&quot;
+ StackLevel = 0
+ ErrorHandler = True
+ ConsoleLines = Array()
+ Set ConsoleDialog = Nothing
+ Set ConsoleControl = Nothing
+ DisplayEnabled = True
+ StopWhenError = True
+ TriggeredByPython = False
+ DebugMode = False
+ Set ProgressBarDialog = Nothing
+ Set ProgressBarText = Nothing
+ Set progressBarBar = Nothing
+ Set Statusbar = Nothing
+ ServicesList = Empty
+ Set FunctionAccess = Nothing
+ Set PathSettings = Nothing
+ Set PathSubstitution = Nothing
+ Set ScriptProvider = Nothing
+ Set SystemShellExecute = Nothing
+ Set CoreReflection = Nothing
+ Set DispatchHelper = Nothing
+ Set TextSearch = Nothing
+ Set SearchOptions = Nothing
+ Set SystemLocale = Nothing
+ Set OfficeLocale = Nothing
+ Set FormatLocale = Nothing
+ Set LocaleData = Nothing
+ Set CalendarImpl = Nothing
+ Set Number2Text = Nothing
+ Set PrinterServer = Nothing
+ Set CharacterClass = Nothing
+ Set FileAccess = Nothing
+ Set FilterFactory = Nothing
+ Set FolderPicker = Nothing
+ Set FilePicker = Nothing
+ Set URLTransformer = Nothing
+ Set Introspection = Nothing
+ FileSystemNaming = &quot;ANY&quot;
+ PythonHelper = &quot;ScriptForgeHelper.py&quot;
+ PythonHelper2 = &quot;&quot;
+ Set LocalizedInterface = Nothing
+ Set BrowseNodeFactory = Nothing
+ Set DatabaseContext = Nothing
+ Set ConfigurationProvider = Nothing
+ Set PackageProvider = Nothing
+ Set MailService = Nothing
+ Set GraphicExportFilter = Nothing
+ Set Toolkit = Nothing
+ OSName = &quot;&quot;
+ SFDialogs = Empty
+ SFForms = Empty
+ PythonStorage = Empty
+ PythonPermanent = -1
+End Sub &apos; ScriptForge.SF_Root Constructor
+
+REM -----------------------------------------------------------------------------
+Private Sub Class_Terminate()
+ Call Class_Initialize()
+End Sub &apos; ScriptForge.SF_Root Destructor
+
+REM -----------------------------------------------------------------------------
+Public Function Dispose() As Variant
+ Call Class_Terminate()
+ Set Dispose = Nothing
+End Function &apos; ScriptForge.SF_Root Explicit destructor
+
+REM =========================================================== PRIVATE FUNCTIONS
+
+REM -----------------------------------------------------------------------------
+Public Sub _AddToConsole(ByVal psLine As String)
+&apos;&apos;&apos; Add a new line to the console
+&apos;&apos;&apos; TAB characters are expanded before the insertion of the line
+&apos;&apos;&apos; NB: Array redimensioning of a member of an object must be done in the class module
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; psLine: the line to add
+
+Dim lConsole As Long &apos; UBound of ConsoleLines
+Dim sLine As String &apos; Alias of psLine
+
+ &apos; Resize ConsoleLines
+ lConsole = UBound(ConsoleLines)
+ If lConsole &lt; 0 Then
+ ReDim ConsoleLines(0)
+ Else
+ ReDim Preserve ConsoleLines(0 To lConsole + 1)
+ End If
+
+ &apos; Add a timestamp to the line and insert it (without date)
+ sLine = Mid(SF_Utils._Repr(Now()), 12) &amp; &quot; -&gt; &quot; &amp; psLine
+ ConsoleLines(lConsole + 1) = sLine
+
+ &apos; Add the new line to the actual (probably non-modal) console, if active
+ If Not IsNull(ConsoleDialog) Then
+ If ConsoleDialog._IsStillAlive(False) Then &apos; False to not raise an error
+ If IsNull(ConsoleControl) Then Set ConsoleControl = ConsoleDialog.Controls(SF_Exception.CONSOLENAME) &apos; Should not happen ...
+ ConsoleControl.WriteLine(sLine)
+ End If
+ End If
+
+End Sub &apos; ScriptForge.SF_Root._AddToConsole
+
+REM -----------------------------------------------------------------------------
+Public Function _AddToPythonStorage(ByRef poObject As Object) As Long
+&apos;&apos;&apos; Insert a newly created object in the Python persistent storage
+&apos;&apos;&apos; and return the index of the used entry
+&apos;&apos;&apos; The persistent storage is a simple array of objects
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; poObject: the object to insert
+
+Dim lIndex As Long &apos; Return value
+Dim lSize As Long &apos; UBound of the persistent storage
+Dim i As Long
+
+Check:
+ lIndex = -1
+ If IsNull(poObject) Then Exit Function
+ On Local Error GoTo Finally
+ lSize = UBound(PythonStorage)
+
+Try:
+ &apos; Can an empty entry be reused ?
+ For i = PythonPermanent + 1 To lSize
+ If IsNull(PythonStorage(i)) Then
+ lIndex = i
+ Exit For
+ End If
+ Next i
+
+ &apos; Resize Python storage if no empty space
+ If lIndex &lt; 0 Then
+ lSize = lSize + 1
+ ReDim Preserve PythonStorage(0 To lSize)
+ lIndex = lSize
+ End If
+
+ &apos; Insert new object
+ Set PythonStorage(lIndex) = poObject
+
+Finally:
+ _AddToPythonStorage = lIndex
+ Exit Function
+End Function &apos; ScriptForge.SF_Root._AddToPythonStorage
+
+REM ------------------------------------------------------------------------------
+Public Function _GetLocalizedInterface() As Object
+&apos;&apos;&apos; Returns the LN object instance related to the ScriptForge internal localization
+&apos;&apos;&apos; If not yet done, load it from the shipped po files
+&apos;&apos;&apos; Makes that the localized user interface is loaded only when needed
+
+Try:
+ If IsNull(LocalizedInterface) Then _LoadLocalizedInterface()
+
+Finally:
+ Set _GetLocalizedInterface = LocalizedInterface
+ Exit Function
+End Function &apos; ScriptForge.SF_Root._GetLocalizedInterface
+
+REM -----------------------------------------------------------------------------
+Public Sub _InitPythonStorage()
+&apos;&apos;&apos; Make PythonStorage an array
+&apos;&apos;&apos; In prevision to an abundant use of those objects in Python, hardcode to optimize the performance and memory :
+&apos;&apos;&apos; Initialize the first entries with the standard module objects located in the ScriptForge library
+
+Try:
+ If Not IsArray(PythonStorage) Then
+ PythonPermanent = 8
+ PythonStorage = Array()
+ ReDim PythonStorage(0 To PythonPermanent)
+ &apos; Initialize each entry
+ PythonStorage(0) = ScriptForge.SF_Array
+ PythonStorage(1) = ScriptForge.SF_Exception
+ PythonStorage(2) = ScriptForge.SF_FileSystem
+ PythonStorage(3) = ScriptForge.SF_Platform
+ PythonStorage(4) = ScriptForge.SF_Region
+ PythonStorage(5) = ScriptForge.SF_Services
+ PythonStorage(6) = ScriptForge.SF_Session
+ PythonStorage(7) = ScriptForge.SF_String
+ PythonStorage(8) = ScriptForge.SF_UI
+ End If
+
+Finally:
+ Exit Sub
+End Sub &apos; ScriptForge.SF_Root._InitPythonStorage
+
+REM -----------------------------------------------------------------------------
+Public Sub _LoadLocalizedInterface(Optional ByVal psMode As String)
+&apos;&apos;&apos; Build the user interface in a persistent L10N object
+&apos;&apos;&apos; Executed - only once - at first request of a label inside the LocalizedInterface dictionary
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; psMode: ADDTEXT =&gt; the (english) labels are loaded from code below
+&apos;&apos;&apos; POFILE =&gt; the localized labels are loaded from a PO file
+&apos;&apos;&apos; the name of the file is &quot;la.po&quot; where la = language part of locale
+&apos;&apos;&apos; (fallback to ADDTEXT mode if file does not exist)
+
+Dim sInstallFolder As String &apos; ScriptForge installation directory
+Dim sPOFolder As String &apos; Folder containing the PO files
+Dim sPOFile As String &apos; PO File to load
+Dim sLocale As String &apos; Locale
+
+ If ErrorHandler Then On Local Error GoTo Catch
+
+Try:
+ &apos;TODO: Modify default value
+ If IsMissing(psMode) Then psMode = &quot;POFILE&quot;
+
+ If psMode = &quot;POFILE&quot; Then &apos; Use this mode in production
+ &apos; Build the po file name
+ With SF_FileSystem
+ sInstallFolder = ._SFInstallFolder() &apos; ScriptForge installation folder
+ sLocale = SF_Utils._GetUNOService(&quot;OfficeLocale&quot;).Language
+ sPOFolder = .BuildPath(sInstallFolder, &quot;po&quot;)
+ sPOFile = .BuildPath(sPOFolder, sLocale &amp; &quot;.po&quot;)
+ If sLocale = &quot;en&quot; Then &apos; LocalizedInterface loaded by code i.o. read from po file
+ psMode = &quot;ADDTEXT&quot;
+ ElseIf Not .FileExists(sPOFile) Then &apos; File not found =&gt; load texts from code below
+ psMode = &quot;ADDTEXT&quot;
+ Else
+ Set LocalizedInterface = CreateScriptService(&quot;L10N&quot;, sPOFolder, sLocale)
+ End If
+ End With
+ End If
+
+ If psMode = &quot;ADDTEXT&quot; Then &apos; Use this mode in development to prepare a new POT file
+ Set LocalizedInterface = CreateScriptService(&quot;L10N&quot;)
+ With LocalizedInterface
+ &apos; SF_Exception.Raise
+ .AddText( Context := &quot;ERRORNUMBER&quot; _
+ , MsgId := &quot;Error %1&quot; _
+ , Comment := &quot;Title in error message box\n&quot; _
+ &amp; &quot;%1: an error number&quot; _
+ )
+ .AddText( Context := &quot;ERRORLOCATION&quot; _
+ , MsgId := &quot;Location : %1&quot; _
+ , Comment := &quot;Error message box\n&quot; _
+ &amp; &quot;%1: a line number&quot; _
+ )
+ .AddText( Context := &quot;LONGERRORDESC&quot; _
+ , MsgId := &quot;Error %1 - Location = %2 - Description = %3&quot; _
+ , Comment := &quot;Logfile record&quot; _
+ )
+ .AddText( Context := &quot;STOPEXECUTION&quot; _
+ , MsgId := &quot;THE EXECUTION IS CANCELLED.&quot; _
+ , Comment := &quot;Any blocking error message&quot; _
+ )
+ .AddText( Context := &quot;NEEDMOREHELP&quot; _
+ , MsgId := &quot;Do you want to receive more information about the &apos;%1&apos; method ?&quot; _
+ , Comment := &quot;Any blocking error message\n&quot; _
+ &amp; &quot;%1: a method name&quot; _
+ )
+ &apos; SF_Exception.RaiseAbort
+ .AddText( Context := &quot;INTERNALERROR&quot; _
+ , MsgId := &quot;The ScriptForge library has crashed. The reason is unknown.\n&quot; _
+ &amp; &quot;Maybe a bug that could be reported on\n&quot; _
+ &amp; &quot;\thttps://bugs.documentfoundation.org/\n\n&quot; _
+ &amp; &quot;More details : \n\n&quot; _
+ , Comment := &quot;SF_Exception.RaiseAbort error message&quot; _
+ )
+ &apos; SF_Utils._Validate
+ .AddText( Context := &quot;VALIDATESOURCE&quot; _
+ , MsgId := &quot;Library : \t%1\nService : \t%2\nMethod : \t%3&quot; _
+ , Comment := &quot;SF_Utils._Validate error message\n&quot; _
+ &amp; &quot;%1: probably ScriptForge\n&quot; _
+ &amp; &quot;%2: service or module name\n&quot; _
+ &amp; &quot;%3: property or method name where the error occurred&quot; _
+ )
+ .AddText( Context := &quot;VALIDATEARGS&quot; _
+ , MsgId := &quot;Arguments: %1&quot; _
+ , Comment := &quot;SF_Utils._Validate error message\n&quot; _
+ &amp; &quot;%1: list of arguments of the method&quot; _
+ )
+ .AddText( Context := &quot;VALIDATEERROR&quot; _
+ , MsgId := &quot;A serious error has been detected in your code on argument : « %1 ».&quot; _
+ , Comment := &quot;SF_Utils._Validate error message\n&quot; _
+ &amp; &quot;%1: Wrong argument name&quot; _
+ )
+ .AddText( Context := &quot;VALIDATIONRULES&quot; _
+ , MsgId := &quot;\tValidation rules :&quot;, Comment := &quot;SF_Utils.Validate error message&quot; _
+ )
+ .AddText( Context := &quot;VALIDATETYPES&quot; _
+ , MsgId := &quot;\t\t« %1 » must have next type (or one of next types) : %2&quot; _
+ , Comment := &quot;SF_Utils._Validate error message\n&quot; _
+ &amp; &quot;%1: Wrong argument name\n&quot; _
+ &amp; &quot;%2: Comma separated list of allowed types&quot; _
+ )
+ .AddText( Context := &quot;VALIDATEVALUES&quot; _
+ , MsgId := &quot;\t\t« %1 » must contain one of next values : %2&quot; _
+ , Comment := &quot;SF_Utils._Validate error message\n&quot; _
+ &amp; &quot;%1: Wrong argument name\n&quot; _
+ &amp; &quot;%2: Comma separated list of allowed values&quot; _
+ )
+ .AddText( Context := &quot;VALIDATEREGEX&quot; _
+ , MsgId := &quot;\t\t« %1 » must match next regular expression : %2&quot; _
+ , Comment := &quot;SF_Utils._Validate error message\n&quot; _
+ &amp; &quot;%1: Wrong argument name\n&quot; _
+ &amp; &quot;%2: A regular expression&quot; _
+ )
+ .AddText( Context := &quot;VALIDATECLASS&quot; _
+ , MsgId := &quot;\t\t« %1 » must be a Basic object of class : %2&quot; _
+ , Comment := &quot;SF_Utils._Validate error message\n&quot; _
+ &amp; &quot;%1: Wrong argument name\n&quot; _
+ &amp; &quot;%2: The name of a Basic class&quot; _
+ )
+ .AddText( Context := &quot;VALIDATEACTUAL&quot; _
+ , MsgId := &quot;The actual value of « %1 » is : &apos;%2&apos;&quot; _
+ , Comment := &quot;SF_Utils._Validate error message\n&quot; _
+ &amp; &quot;%1: Wrong argument name\n&quot; _
+ &amp; &quot;%2: The value of the argument as a string&quot; _
+ )
+ .AddText( Context := &quot;VALIDATEMISSING&quot; _
+ , MsgId := &quot;The « %1 » argument is mandatory, yet it is missing.&quot; _
+ , Comment := &quot;SF_Utils._Validate error message\n&quot; _
+ &amp; &quot;%1: Wrong argument name&quot; _
+ )
+ &apos; SF_Utils._ValidateArray
+ .AddText( Context := &quot;VALIDATEARRAY&quot; _
+ , MsgId := &quot;\t\t« %1 » must be an array.&quot; _
+ , Comment := &quot;SF_Utils._ValidateArray error message\n&quot; _
+ &amp; &quot;%1: Wrong argument name&quot; _
+ )
+ .AddText( Context := &quot;VALIDATEDIMS&quot; _
+ , MsgId := &quot;\t\t« %1 » must have exactly %2 dimension(s).&quot; _
+ , Comment := &quot;SF_Utils._ValidateArray error message\n&quot; _
+ &amp; &quot;%1: Wrong argument name\n&quot; _
+ &amp; &quot;%2: Number of dimensions of the array&quot; _
+ )
+ .AddText( Context := &quot;VALIDATEALLTYPES&quot; _
+ , MsgId := &quot;\t\t« %1 » must have all elements of the same type : %2&quot; _
+ , Comment := &quot;SF_Utils._ValidateArray error message\n&quot; _
+ &amp; &quot;%1: Wrong argument name\n&quot; _
+ &amp; &quot;%2: Either one single type or &apos;String, Date, Numeric&apos;&quot; _
+ )
+ .AddText( Context := &quot;VALIDATENOTNULL&quot; _
+ , MsgId := &quot;\t\t« %1 » must not contain any NULL or EMPTY elements.&quot; _
+ , Comment := &quot;SF_Utils._ValidateArray error message\n&quot; _
+ &amp; &quot;%1: Wrong argument name\n&quot; _
+ &amp; &quot;NULL and EMPTY should not be translated&quot; _
+ )
+ &apos; SF_Utils._ValidateFile
+ .AddText( Context := &quot;VALIDATEFILE&quot; _
+ , MsgId := &quot;\t\t« %1 » must be of type String.&quot; _
+ , Comment := &quot;SF_Utils._ValidateFile error message\n&quot; _
+ &amp; &quot;%1: Wrong argument name\n&quot; _
+ &amp; &quot;&apos;String&apos; should not be translated&quot; _
+ )
+ .AddText( Context := &quot;VALIDATEFILESYS&quot; _
+ , MsgId := &quot;\t\t« %1 » must be a valid file or folder name expressed in the operating system native notation.&quot; _
+ , Comment := &quot;SF_Utils._ValidateFile error message\n&quot; _
+ &amp; &quot;%1: Wrong argument name&quot; _
+ )
+ .AddText( Context := &quot;VALIDATEFILEURL&quot; _
+ , MsgId := &quot;\t\t« %1 » must be a valid file or folder name expressed in the portable URL notation.&quot; _
+ , Comment := &quot;SF_Utils._ValidateFile error message\n&quot; _
+ &amp; &quot;%1: Wrong argument name\n&quot; _
+ &amp; &quot;&apos;URL&apos; should not be translated&quot; _
+ )
+ .AddText( Context := &quot;VALIDATEFILEANY&quot; _
+ , MsgId := &quot;\t\t« %1 » must be a valid file or folder name.&quot; _
+ , Comment := &quot;SF_Utils._ValidateFile error message\n&quot; _
+ &amp; &quot;%1: Wrong argument name&quot; _
+ )
+ .AddText( Context := &quot;VALIDATEWILDCARD&quot; _
+ , MsgId := &quot;\t\t« %1 » may contain one or more wildcard characters (?, *) in its last path component only.&quot; _
+ , Comment := &quot;SF_Utils._ValidateFile error message\n&quot; _
+ &amp; &quot;%1: Wrong argument name\n&quot; _
+ &amp; &quot;&apos;(?, *)&apos; is to be left as is&quot; _
+ )
+ &apos; SF_Array.RangeInit
+ .AddText( Context := &quot;ARRAYSEQUENCE&quot; _
+ , MsgId := &quot;The respective values of &apos;From&apos;, &apos;UpTo&apos; and &apos;ByStep&apos; are incoherent.\n\n&quot; _
+ &amp; &quot;\t« From » = %1\n&quot; _
+ &amp; &quot;\t« UpTo » = %2\n&quot; _
+ &amp; &quot;\t« ByStep » = %3&quot; _
+ , Comment := &quot;SF_Array.RangeInit error message\n&quot; _
+ &amp; &quot;%1, %2, %3: Numeric values\n&quot; _
+ &amp; &quot;&apos;From&apos;, &apos;UpTo&apos;, &apos;ByStep&apos; should not be translated&quot; _
+ )
+ &apos; SF_Array.AppendColumn, AppendRow, PrependColumn, PrependRow
+ .AddText( Context := &quot;ARRAYINSERT&quot; _
+ , MsgId := &quot;The array and the vector to insert have incompatible sizes.\n\n&quot; _
+ &amp; &quot;\t« Array_2D » = %2\n&quot; _
+ &amp; &quot;\t« %1 » = %3&quot; _
+ , Comment := &quot;SF_Array.AppendColumn (...) error message\n&quot; _
+ &amp; &quot;%1: &apos;Column&apos; or &apos;Row&apos; of a matrix\n&quot; _
+ &amp; &quot;%2, %3: array contents\n&quot; _
+ &amp; &quot;&apos;Array_2D&apos; should not be translated&quot; _
+ )
+ &apos; SF_Array.ExtractColumn, ExtractRow
+ .AddText( Context := &quot;ARRAYINDEX1&quot; _
+ , MsgId := &quot;The given index does not fit within the bounds of the array.\n\n&quot; _
+ &amp; &quot;\t« Array_2D » = %2\n&quot; _
+ &amp; &quot;\t« %1 » = %3&quot; _
+ , Comment := &quot;SF_Array.ExtractColumn (...) error message\n&quot; _
+ &amp; &quot;%1: &apos;Column&apos; or &apos;Row&apos; of a matrix\n&quot; _
+ &amp; &quot;%2, %3: array contents\n&quot; _
+ &amp; &quot;&apos;Array_2D&apos; should not be translated&quot; _
+ )
+ &apos; SF_Array.ExtractColumn, ExtractRow
+ .AddText( Context := &quot;ARRAYINDEX2&quot; _
+ , MsgId := &quot;The given slice limits do not fit within the bounds of the array.\n\n&quot; _
+ &amp; &quot;\t« Array_1D » = %1\n&quot; _
+ &amp; &quot;\t« From » = %2\n&quot; _
+ &amp; &quot;\t« UpTo » = %3&quot; _
+ , Comment := &quot;SF_Array.ExtractColumn (...) error message\n&quot; _
+ &amp; &quot;%1: &apos;Column&apos; or &apos;Row&apos; of a matrix\n&quot; _
+ &amp; &quot;%2, %3: array contents\n&quot; _
+ &amp; &quot;&apos;Array_1D&apos;, &apos;From&apos; and &apos;UpTo&apos; should not be translated&quot; _
+ )
+ &apos; SF_Array.ImportFromCSVFile
+ .AddText( Context := &quot;CSVPARSING&quot; _
+ , MsgId := &quot;The given file could not be parsed as a valid CSV file.\n\n&quot; _
+ &amp; &quot;\t« File name » = %1\n&quot; _
+ &amp; &quot;\tLine number = %2\n&quot; _
+ &amp; &quot;\tContent = %3&quot; _
+ , Comment := &quot;SF_Array.ImportFromCSVFile error message\n&quot; _
+ &amp; &quot;%1: a file name\n&quot; _
+ &amp; &quot;%2: numeric\n&quot; _
+ &amp; &quot;%3: a long string&quot; _
+ )
+ &apos; SF_Dictionary.Add/ReplaceKey
+ .AddText( Context := &quot;DUPLICATEKEY&quot; _
+ , MsgId := &quot;The insertion of a new key &quot; _
+ &amp; &quot;into a dictionary failed because the key already exists.\n&quot; _
+ &amp; &quot;Note that the comparison between keys is NOT case-sensitive.\n\n&quot; _
+ &amp; &quot;« %1 » = %2&quot; _
+ , Comment := &quot;SF_Dictionary Add/ReplaceKey error message\n&quot; _
+ &amp; &quot;%1: An identifier&quot; _
+ &amp; &quot;%2: a (potentially long) string&quot; _
+ )
+ &apos; SF_Dictionary.Remove/ReplaceKey/ReplaceItem
+ .AddText( Context := &quot;UNKNOWNKEY&quot; _
+ , MsgId := &quot;The requested key does not exist in the dictionary.\n\n&quot; _
+ &amp; &quot;« %1 » = %2&quot; _
+ , Comment := &quot;SF_Dictionary Remove/ReplaceKey/ReplaceItem error message\n&quot; _
+ &amp; &quot;%1: An identifier&quot; _
+ &amp; &quot;%2: a (potentially long) string&quot; _
+ )
+ &apos; SF_Dictionary.Add/ReplaceKey
+ .AddText( Context := &quot;INVALIDKEY&quot; _
+ , MsgId := &quot;The insertion or the update of an entry &quot; _
+ &amp; &quot;into a dictionary failed because the given key contains only spaces.&quot; _
+ , Comment := &quot;SF_Dictionary Add/ReplaceKey error message\n&quot; _
+ )
+ &apos; SF_FileSystem.CopyFile/MoveFile/DeleteFile/CreateScriptService(&quot;L10N&quot;)
+ .AddText( Context := &quot;UNKNOWNFILE&quot; _
+ , MsgId := &quot;The given file could not be found on your system.\n\n&quot; _
+ &amp; &quot;« %1 » = %2&quot; _
+ , Comment := &quot;SF_FileSystem copy/move/delete error message\n&quot; _
+ &amp; &quot;%1: An identifier\n&quot; _
+ &amp; &quot;%2: A file name&quot; _
+ )
+ &apos; SF_FileSystem.CopyFolder/MoveFolder/DeleteFolder/Files/SubFolders
+ .AddText( Context := &quot;UNKNOWNFOLDER&quot; _
+ , MsgId := &quot;The given folder could not be found on your system.\n\n&quot; _
+ &amp; &quot;« %1 » = %2&quot; _
+ , Comment := &quot;SF_FileSystem copy/move/delete error message\n&quot; _
+ &amp; &quot;%1: An identifier\n&quot; _
+ &amp; &quot;%2: A folder name&quot; _
+ )
+ &apos; SF_FileSystem.CopyFile/MoveFolder/DeleteFile
+ .AddText( Context := &quot;NOTAFILE&quot; _
+ , MsgId := &quot;« %1 » contains the name of an existing folder, not that of a file.\n\n&quot; _
+ &amp; &quot;« %1 » = %2&quot; _
+ , Comment := &quot;SF_FileSystem copy/move/delete error message\n&quot; _
+ &amp; &quot;%1: An identifier\n&quot; _
+ &amp; &quot;%2: A file name&quot; _
+ )
+ &apos; SF_FileSystem.CopyFolder/MoveFolder/DeleteFolder/Files/SubFolders
+ .AddText( Context := &quot;NOTAFOLDER&quot; _
+ , MsgId := &quot;« %1 » contains the name of an existing file, not that of a folder.\n\n&quot; _
+ &amp; &quot;« %1 » = %2&quot; _
+ , Comment := &quot;SF_FileSystem copy/move/delete error message\n&quot; _
+ &amp; &quot;%1: An identifier\n&quot; _
+ &amp; &quot;%2: A folder name&quot; _
+ )
+ &apos; SF_FileSystem.Copy+Move/File+Folder/CreateTextFile/OpenTextFile
+ .AddText( Context := &quot;OVERWRITE&quot; _
+ , MsgId := &quot;You tried to create a new file which already exists. Overwriting it has been rejected.\n\n&quot; _
+ &amp; &quot;« %1 » = %2&quot; _
+ , Comment := &quot;SF_FileSystem copy/move/... error message\n&quot; _
+ &amp; &quot;%1: An identifier\n&quot; _
+ &amp; &quot;%2: A file name&quot; _
+ )
+ &apos; SF_FileSystem.Copy+Move+Delete/File+Folder
+ .AddText( Context := &quot;READONLY&quot; _
+ , MsgId := &quot;Copying or moving a file to a destination which has its read-only attribute set, or deleting such a file or folder is forbidden.\n\n&quot; _
+ &amp; &quot;« %1 » = %2&quot; _
+ , Comment := &quot;SF_FileSystem copy/move/delete error message\n&quot; _
+ &amp; &quot;%1: An identifier\n&quot; _
+ &amp; &quot;%2: A file name&quot; _
+ )
+ &apos; SF_FileSystem.Copy+Move+Delete/File+Folder
+ .AddText( Context := &quot;NOFILEMATCH&quot; _
+ , MsgId := &quot;When « %1 » contains wildcards. at least one file or folder must match the given filter. Otherwise the operation is rejected.\n\n&quot; _
+ &amp; &quot;« %1 » = %2&quot; _
+ , Comment := &quot;SF_FileSystem copy/move/delete error message\n&quot; _
+ &amp; &quot;%1: An identifier\n&quot; _
+ &amp; &quot;%2: A file or folder name with wildcards&quot; _
+ )
+ &apos; SF_FileSystem.CreateFolder
+ .AddText( Context := &quot;FOLDERCREATION&quot; _
+ , MsgId := &quot;« %1 » contains the name of an existing file or an existing folder. The operation is rejected.\n\n&quot; _
+ &amp; &quot;« %1 » = %2&quot; _
+ , Comment := &quot;SF_FileSystem CreateFolder error message\n&quot; _
+ &amp; &quot;%1: An identifier\n&quot; _
+ &amp; &quot;%2: A file or folder name&quot; _
+ )
+ &apos; SF_Services.CreateScriptService
+ .AddText( Context := &quot;UNKNOWNSERVICE&quot; _
+ , MsgId := &quot;No service named &apos;%4&apos; has been registered for the library &apos;%3&apos;.\n\n&quot; _
+ &amp; &quot;« %1 » = %2&quot; _
+ , Comment := &quot;SF_Services.CreateScriptService error message\n&quot; _
+ &amp; &quot;%1: An identifier\n&quot; _
+ &amp; &quot;%2: A string\n&quot; _
+ &amp; &quot;%3: A Basic library name\n&quot; _
+ &amp; &quot;%4: A service (1 word) name&quot; _
+ )
+ &apos; SF_Services.CreateScriptService
+ .AddText( Context := &quot;SERVICESNOTLOADED&quot; _
+ , MsgId := &quot;The library &apos;%3&apos; and its services could not been loaded.\n&quot; _
+ &amp; &quot;The reason is unknown.\n&quot; _
+ &amp; &quot;However, checking the &apos;%3.SF_Services.RegisterScriptServices()&apos; function and its return value can be a good starting point.\n\n&quot; _
+ &amp; &quot;« %1 » = %2&quot; _
+ , Comment := &quot;SF_Services.CreateScriptService error message\n&quot; _
+ &amp; &quot;%1: An identifier\n&quot; _
+ &amp; &quot;%2: A string\n&quot; _
+ &amp; &quot;%3: A Basic library name&quot; _
+ )
+ &apos; SF_Session.ExecuteCalcFunction
+ .AddText( Context := &quot;CALCFUNC&quot; _
+ , MsgId := &quot;The Calc &apos;%1&apos; function encountered an error. Either the given function does not exist or its arguments are invalid.&quot; _
+ , Comment := &quot;SF_Session.ExecuteCalcFunction error message\n&quot; _
+ &amp; &quot;&apos;Calc&apos; should not be translated&quot; _
+ )
+ &apos; SF_Session._GetScript
+ .AddText( Context := &quot;NOSCRIPT&quot; _
+ , MsgId := &quot;The requested %1 script could not be located in the given libraries and modules.\n&quot; _
+ &amp; &quot;« %2 » = %3\n&quot; _
+ &amp; &quot;« %4 » = %5&quot; _
+ , Comment := &quot;SF_Session._GetScript error message\n&quot; _
+ &amp; &quot;%1: &apos;Basic&apos; or &apos;Python&apos;\n&quot; _
+ &amp; &quot;%2: An identifier\n&quot; _
+ &amp; &quot;%3: A string\n&quot; _
+ &amp; &quot;%4: An identifier\n&quot; _
+ &amp; &quot;%5: A string&quot; _
+ )
+ &apos; SF_Session.ExecuteBasicScript
+ .AddText( Context := &quot;SCRIPTEXEC&quot; _
+ , MsgId := &quot;An exception occurred during the execution of the Basic script.\n&quot; _
+ &amp; &quot;Cause: %3\n&quot; _
+ &amp; &quot;« %1 » = %2&quot; _
+ , Comment := &quot;SF_Session.ExecuteBasicScript error message\n&quot; _
+ &amp; &quot;%1: An identifier\n&quot; _
+ &amp; &quot;%2: A string\n&quot; _
+ &amp; &quot;%3: A (long) string&quot; _
+ )
+ &apos; SF_Session.SendMail
+ .AddText( Context := &quot;WRONGEMAIL&quot; _
+ , MsgId := &quot;One of the email addresses has been found invalid.\n&quot; _
+ &amp; &quot;Invalid mail = « %1 »&quot; _
+ , Comment := &quot;SF_Session.SendMail error message\n&quot; _
+ &amp; &quot;%1 = a mail address&quot; _
+ )
+ &apos; SF_Session.SendMail
+ .AddText( Context := &quot;SENDMAIL&quot; _
+ , MsgId := &quot;The message could not be sent due to a system error.\n&quot; _
+ &amp; &quot;A possible cause is that LibreOffice could not find any mail client.&quot; _
+ , Comment := &quot;SF_Session.SendMail error message&quot; _
+ )
+ &apos; SF_TextStream._IsFileOpen
+ .AddText( Context := &quot;FILENOTOPEN&quot; _
+ , MsgId := &quot;The requested file operation could not be executed because the file was closed previously.\n\n&quot; _
+ &amp; &quot;File name = &apos;%1&apos;&quot; _
+ , Comment := &quot;SF_TextStream._IsFileOpen error message\n&quot; _
+ &amp; &quot;%1: A file name&quot; _
+ )
+ &apos; SF_TextStream._IsFileOpen
+ .AddText( Context := &quot;FILEOPENMODE&quot; _
+ , MsgId := &quot;The requested file operation could not be executed because it is incompatible with the mode in which the file was opened.\n\n&quot; _
+ &amp; &quot;File name = &apos;%1&apos;\n&quot; _
+ &amp; &quot;Open mode = %2&quot; _
+ , Comment := &quot;SF_TextStream._IsFileOpen error message\n&quot; _
+ &amp; &quot;%1: A file name\n&quot; _
+ &amp; &quot;%2: READ, WRITE or APPEND&quot; _
+ )
+ &apos; SF_TextStream.ReadLine, ReadAll, SkipLine
+ .AddText( Context := &quot;ENDOFFILE&quot; _
+ , MsgId := &quot;The requested file read operation could not be completed because an unexpected end-of-file was encountered.\n\n&quot; _
+ &amp; &quot;File name = &apos;%1&apos;&quot; _
+ , Comment := &quot;SF_TextStream.ReadLine/ReadAll/SkipLine error message\n&quot; _
+ &amp; &quot;%1: A file name&quot; _
+ )
+ &apos; SF_UI.Document
+ .AddText( Context := &quot;DOCUMENT&quot; _
+ , MsgId := &quot;The requested document could not be found.\n\n&quot; _
+ &amp; &quot;%1 = &apos;%2&apos;&quot; _
+ , Comment := &quot;SF_UI.GetDocument error message\n&quot; _
+ &amp; &quot;%1: An identifier\n&quot; _
+ &amp; &quot;%2: A string&quot; _
+ )
+ &apos; SF_UI.Create
+ .AddText( Context := &quot;DOCUMENTCREATION&quot; _
+ , MsgId := &quot;The creation of a new document failed.\n&quot; _
+ &amp; &quot;Something must be wrong with some arguments.\n\n&quot; _
+ &amp; &quot;Either the document type is unknown, or no template file was given,\n&quot; _
+ &amp; &quot;or the given template file was not found on your system.\n\n&quot; _
+ &amp; &quot;%1 = &apos;%2&apos;\n&quot; _
+ &amp; &quot;%3 = &apos;%4&apos;&quot; _
+ , Comment := &quot;SF_UI.GetDocument error message\n&quot; _
+ &amp; &quot;%1: An identifier\n&quot; _
+ &amp; &quot;%2: A string\n&quot; _
+ &amp; &quot;%3: An identifier\n&quot; _
+ &amp; &quot;%4: A string&quot; _
+ )
+ &apos; SF_UI.OpenDocument
+ .AddText( Context := &quot;DOCUMENTOPEN&quot; _
+ , MsgId := &quot;The opening of the document failed.\n&quot; _
+ &amp; &quot;Something must be wrong with some arguments.\n\n&quot; _
+ &amp; &quot;Either the file does not exist, or the password is wrong, or the given filter is invalid.\n\n&quot; _
+ &amp; &quot;%1 = &apos;%2&apos;\n&quot; _
+ &amp; &quot;%3 = &apos;%4&apos;\n&quot; _
+ &amp; &quot;%5 = &apos;%6&apos;&quot; _
+ , Comment := &quot;SF_UI.OpenDocument error message\n&quot; _
+ &amp; &quot;%1: An identifier\n&quot; _
+ &amp; &quot;%2: A string\n&quot; _
+ &amp; &quot;%3: An identifier\n&quot; _
+ &amp; &quot;%4: A string\n&quot; _
+ &amp; &quot;%5: An identifier\n&quot; _
+ &amp; &quot;%6: A string&quot; _
+ )
+ &apos; SF_UI.OpenBaseDocument
+ .AddText( Context := &quot;BASEDOCUMENTOPEN&quot; _
+ , MsgId := &quot;The opening of the Base document failed.\n&quot; _
+ &amp; &quot;Something must be wrong with some arguments.\n\n&quot; _
+ &amp; &quot;Either the file does not exist, or the file is not registered under the given name.\n\n&quot; _
+ &amp; &quot;%1 = &apos;%2&apos;\n&quot; _
+ &amp; &quot;%3 = &apos;%4&apos;&quot; _
+ , Comment := &quot;SF_UI.OpenDocument error message\n&quot; _
+ &amp; &quot;%1: An identifier\n&quot; _
+ &amp; &quot;%2: A string\n&quot; _
+ &amp; &quot;%3: An identifier\n&quot; _
+ &amp; &quot;%4: A string&quot; _
+ )
+ &apos; SF_Document._IsStillAlive
+ .AddText( Context := &quot;DOCUMENTDEAD&quot; _
+ , MsgId := &quot;The requested action could not be executed because the document was closed inadvertently.\n\n&quot; _
+ &amp; &quot;The concerned document is &apos;%1&apos;&quot; _
+ , Comment := &quot;SF_Document._IsStillAlive error message\n&quot; _
+ &amp; &quot;%1: A file name&quot; _
+ )
+ &apos; SF_Document.Save
+ .AddText( Context := &quot;DOCUMENTSAVE&quot; _
+ , MsgId := &quot;The document could not be saved.\n&quot; _
+ &amp; &quot;Either the document has been opened read-only, or the destination file has a read-only attribute set, &quot; _
+ &amp; &quot;or the file where to save to is undefined.\n\n&quot; _
+ &amp; &quot;%1 = &apos;%2&apos;&quot; _
+ , Comment := &quot;SF_Document.SaveAs error message\n&quot; _
+ &amp; &quot;%1: An identifier\n&quot; _
+ &amp; &quot;%2: A file name\n&quot; _
+ )
+ &apos; SF_Document.SaveAs
+ .AddText( Context := &quot;DOCUMENTSAVEAS&quot; _
+ , MsgId := &quot;The document could not be saved.\n&quot; _
+ &amp; &quot;Either the document must not be overwritten, or the destination file has a read-only attribute set, &quot; _
+ &amp; &quot;or the given filter is invalid.\n\n&quot; _
+ &amp; &quot;%1 = &apos;%2&apos;\n&quot; _
+ &amp; &quot;%3 = %4\n&quot; _
+ &amp; &quot;%5 = &apos;%6&apos;&quot; _
+ , Comment := &quot;SF_Document.SaveAs error message\n&quot; _
+ &amp; &quot;%1: An identifier\n&quot; _
+ &amp; &quot;%2: A file name\n&quot; _
+ &amp; &quot;%3: An identifier\n&quot; _
+ &amp; &quot;%4: True or False\n&quot; _
+ &amp; &quot;%5: An identifier\n&quot; _
+ &amp; &quot;%6: A string&quot; _
+ )
+ &apos; SF_Document.any update
+ .AddText( Context := &quot;DOCUMENTREADONLY&quot; _
+ , MsgId := &quot;You tried to edit a document which is not modifiable. The document has not been changed.\n\n&quot; _
+ &amp; &quot;« %1 » = %2&quot; _
+ , Comment := &quot;SF_Document any update\n&quot; _
+ &amp; &quot;%1: An identifier\n&quot; _
+ &amp; &quot;%2: A file name&quot; _
+ )
+ &apos; SF_Base.GetDatabase
+ .AddText( Context := &quot;DBCONNECT&quot; _
+ , MsgId := &quot;The database related to the actual Base document could not be retrieved.\n&quot; _
+ &amp; &quot;Check the connection/login parameters.\n\n&quot; _
+ &amp; &quot;« %1 » = &apos;%2&apos;\n&quot; _
+ &amp; &quot;« %3 » = &apos;%4&apos;\n&quot; _
+ &amp; &quot;« Document » = %5&quot; _
+ , Comment := &quot;SF_Base GetDatabase\n&quot; _
+ &amp; &quot;%1: An identifier\n&quot; _
+ &amp; &quot;%2: A user name\n&quot; _
+ &amp; &quot;%3: An identifier\n&quot; _
+ &amp; &quot;%4: A password\n&quot; _
+ &amp; &quot;%5: A file name&quot; _
+ )
+ &apos; SF_Calc._ParseAddress (sheet)
+ .AddText( Context := &quot;CALCADDRESS1&quot; _
+ , MsgId := &quot;The given address does not correspond with a valid sheet name.\n\n&quot; _
+ &amp; &quot;« %1 » = %2\n&quot; _
+ &amp; &quot;« %3 » = %4&quot; _
+ , Comment := &quot;SF_Calc _ParseAddress (sheet)\n&quot; _
+ &amp; &quot;%1: An identifier\n&quot; _
+ &amp; &quot;%2: A string\n&quot; _
+ &amp; &quot;%3: An identifier\n&quot; _
+ &amp; &quot;%4: A file name&quot; _
+ )
+ &apos; SF_Calc._ParseAddress (range)
+ .AddText( Context := &quot;CALCADDRESS2&quot; _
+ , MsgId := &quot;The given address does not correspond with a valid range of cells.\n\n&quot; _
+ &amp; &quot;« %1 » = %2\n&quot; _
+ &amp; &quot;« %3 » = %4&quot; _
+ , Comment := &quot;SF_Calc _ParseAddress (range)\n&quot; _
+ &amp; &quot;%1: An identifier\n&quot; _
+ &amp; &quot;%2: A string\n&quot; _
+ &amp; &quot;%3: An identifier\n&quot; _
+ &amp; &quot;%4: A file name&quot; _
+ )
+ &apos; SF_Calc.InsertSheet
+ .AddText( Context := &quot;DUPLICATESHEET&quot; _
+ , MsgId := &quot;There exists already in the document a sheet with the same name.\n\n&quot; _
+ &amp; &quot;« %1 » = %2\n&quot; _
+ &amp; &quot;« %3 » = %4&quot; _
+ , Comment := &quot;SF_Calc InsertSheet\n&quot; _
+ &amp; &quot;%1: An identifier\n&quot; _
+ &amp; &quot;%2: A string\n&quot; _
+ &amp; &quot;%3: An identifier\n&quot; _
+ &amp; &quot;%4: A file name&quot; _
+ )
+ &apos; SF_Calc.Offset
+ .AddText( Context := &quot;OFFSETADDRESS&quot; _
+ , MsgId := &quot;The computed range falls beyond the sheet boundaries or is meaningless.\n\n&quot; _
+ &amp; &quot;« %1 » = %2\n&quot; _
+ &amp; &quot;« %3 » = %4\n&quot; _
+ &amp; &quot;« %5 » = %6\n&quot; _
+ &amp; &quot;« %7 » = %8\n&quot; _
+ &amp; &quot;« %9 » = %10\n&quot; _
+ &amp; &quot;« %11 » = %12&quot; _
+ , Comment := &quot;SF_Calc Offset\n&quot; _
+ &amp; &quot;%1: An identifier\n&quot; _
+ &amp; &quot;%2: A Calc reference\n&quot; _
+ &amp; &quot;%3: An identifier\n&quot; _
+ &amp; &quot;%4: A number\n&quot; _
+ &amp; &quot;%5: An identifier\n&quot; _
+ &amp; &quot;%6: A number\n&quot; _
+ &amp; &quot;%7: An identifier\n&quot; _
+ &amp; &quot;%8: A number\n&quot; _
+ &amp; &quot;%9: An identifier\n&quot; _
+ &amp; &quot;%10: A number\n&quot; _
+ &amp; &quot;%11: An identifier\n&quot; _
+ &amp; &quot;%12: A file name&quot; _
+ )
+ &apos; SF_Calc.CreateChart
+ .AddText( Context := &quot;DUPLICATECHART&quot; _
+ , MsgId := &quot;A chart with the same name exists already in the sheet.\n\n&quot; _
+ &amp; &quot;« %1 » = %2\n&quot; _
+ &amp; &quot;« %3 » = %4\n&quot; _
+ &amp; &quot;« %5 » = %6\n&quot; _
+ , Comment := &quot;SF_Calc CreateChart\n&quot; _
+ &amp; &quot;%1: An identifier\n&quot; _
+ &amp; &quot;%2: A string\n&quot; _
+ &amp; &quot;%3: An identifier\n&quot; _
+ &amp; &quot;%4: A string\n&quot; _
+ &amp; &quot;%5: An identifier\n&quot; _
+ &amp; &quot;%6: A file name&quot; _
+ )
+ &apos; SF_Calc.ExportRangeToFile
+ .AddText( Context := &quot;RANGEEXPORT&quot; _
+ , MsgId := &quot;The given range could not be exported.\n&quot; _
+ &amp; &quot;Either the destination file must not be overwritten, or it has a read-only attribute set.\n\n&quot; _
+ &amp; &quot;%1 = &apos;%2&apos;\n&quot; _
+ &amp; &quot;%3 = %4&quot; _
+ , Comment := &quot;SF_Calc.ExportRangeToFile error message\n&quot; _
+ &amp; &quot;%1: An identifier\n&quot; _
+ &amp; &quot;%2: A file name\n&quot; _
+ &amp; &quot;%3: An identifier\n&quot; _
+ &amp; &quot;%4: True or False\n&quot; _
+ )
+ &apos; SF_Chart.ExportToFile
+ .AddText( Context := &quot;CHARTEXPORT&quot; _
+ , MsgId := &quot;The chart could not be exported.\n&quot; _
+ &amp; &quot;Either the destination file must not be overwritten, or it has a read-only attribute set.\n\n&quot; _
+ &amp; &quot;%1 = &apos;%2&apos;\n&quot; _
+ &amp; &quot;%3 = %4&quot; _
+ , Comment := &quot;SF_Chart.ExportToFile error message\n&quot; _
+ &amp; &quot;%1: An identifier\n&quot; _
+ &amp; &quot;%2: A file name\n&quot; _
+ &amp; &quot;%3: An identifier\n&quot; _
+ &amp; &quot;%4: True or False\n&quot; _
+ )
+ &apos; SF_Form._IsStillAlive
+ .AddText( Context := &quot;FORMDEAD&quot; _
+ , MsgId := &quot;The requested action could not be executed because the form is not open or the document was closed inadvertently.\n\n&quot; _
+ &amp; &quot;The concerned form is &apos;%1&apos; in document &apos;%2&apos;.&quot; _
+ , Comment := &quot;SF_Dialog._IsStillAlive error message\n&quot; _
+ &amp; &quot;%1: An identifier&quot; _
+ &amp; &quot;%2: A file name&quot; _
+ )
+ &apos; SF_Calc.Forms
+ .AddText( Context := &quot;CALCFORMNOTFOUND&quot; _
+ , MsgId := &quot;The requested form could not be found in the Calc sheet. The given index is off-limits.\n\n&quot; _
+ &amp; &quot;The concerned Calc document is &apos;%3&apos;.\n\n&quot; _
+ &amp; &quot;The name of the sheet = &apos;%2&apos;\n&quot; _
+ &amp; &quot;The index = %1.&quot; _
+ , Comment := &quot;SF_Form determination\n&quot; _
+ &amp; &quot;%1: A number\n&quot; _
+ &amp; &quot;%2: A sheet name\n&quot; _
+ &amp; &quot;%3: A file name&quot; _
+ )
+ &apos; SF_Document.Forms
+ .AddText( Context := &quot;WRITERFORMNOTFOUND&quot; _
+ , MsgId := &quot;The requested form could not be found in the Writer document. The given index is off-limits.\n\n&quot; _
+ &amp; &quot;The concerned Writer document is &apos;%2&apos;.\n\n&quot; _
+ &amp; &quot;The index = %1.&quot; _
+ , Comment := &quot;SF_Form determination\n&quot; _
+ &amp; &quot;%1: A number\n&quot; _
+ &amp; &quot;%2: A file name&quot; _
+ )
+ &apos; SF_Base.Forms
+ .AddText( Context := &quot;BASEFORMNOTFOUND&quot; _
+ , MsgId := &quot;The requested form could not be found in the form document &apos;%2&apos;. The given index is off-limits.\n\n&quot; _
+ &amp; &quot;The concerned Base document is &apos;%3&apos;.\n\n&quot; _
+ &amp; &quot;The index = %1.&quot; _
+ , Comment := &quot;SF_Form determination\n&quot; _
+ &amp; &quot;%1: A number\n&quot; _
+ &amp; &quot;%2: A string\n&quot; _
+ &amp; &quot;%3: A file name&quot; _
+ )
+ &apos; SF_Form.Subforms
+ .AddText( Context := &quot;SUBFORMNOTFOUND&quot; _
+ , MsgId := &quot;The requested subform could not be found below the given main form.\n\n&quot; _
+ &amp; &quot;The main form = &apos;%2&apos;.\n&quot; _
+ &amp; &quot;The subform = &apos;%1&apos;.&quot; _
+ , Comment := &quot;SF_Form determination\n&quot; _
+ &amp; &quot;%1: A form name\n&quot; _
+ &amp; &quot;%2: A form name&quot; _
+ )
+ &apos; SF_FormControl._SetProperty
+ .AddText( Context := &quot;FORMCONTROLTYPE&quot; _
+ , MsgId := &quot;The control &apos;%1&apos; in form &apos;%2&apos; is of type &apos;%3&apos;.\n&quot; _
+ &amp; &quot;The property or method &apos;%4&apos; is not applicable on that type of form controls.&quot; _
+ , Comment := &quot;SF_FormControl property setting\n&quot; _
+ &amp; &quot;%1: An identifier\n&quot; _
+ &amp; &quot;%2: An identifier\n&quot; _
+ &amp; &quot;%3: A string\n&quot; _
+ &amp; &quot;%4: An identifier&quot; _
+ )
+ &apos; SF_Dialog._NewDialog
+ .AddText( Context := &quot;DIALOGNOTFOUND&quot; _
+ , MsgId := &quot;The requested dialog could not be located in the given container or library.\n&quot; _
+ &amp; &quot;« %1 » = %2\n&quot; _
+ &amp; &quot;« %3 » = %4\n&quot; _
+ &amp; &quot;« %5 » = %6\n&quot; _
+ &amp; &quot;« %7 » = %8&quot; _
+ , Comment := &quot;SF_Dialog creation\n&quot; _
+ &amp; &quot;%1: An identifier\n&quot; _
+ &amp; &quot;%2: A string\n&quot; _
+ &amp; &quot;%3: An identifier\n&quot; _
+ &amp; &quot;%4: A file name\n&quot; _
+ &amp; &quot;%5: An identifier\n&quot; _
+ &amp; &quot;%6: A string\n&quot; _
+ &amp; &quot;%7: An identifier\n&quot; _
+ &amp; &quot;%8: A string&quot; _
+ )
+ &apos; SF_Dialog._IsStillAlive
+ .AddText( Context := &quot;DIALOGDEAD&quot; _
+ , MsgId := &quot;The requested action could not be executed because the dialog was closed inadvertently.\n\n&quot; _
+ &amp; &quot;The concerned dialog is &apos;%1&apos;.&quot; _
+ , Comment := &quot;SF_Dialog._IsStillAlive error message\n&quot; _
+ &amp; &quot;%1: An identifier&quot; _
+ )
+ &apos; SF_DialogControl._SetProperty
+ .AddText( Context := &quot;CONTROLTYPE&quot; _
+ , MsgId := &quot;The control &apos;%1&apos; in dialog &apos;%2&apos; is of type &apos;%3&apos;.\n&quot; _
+ &amp; &quot;The property or method &apos;%4&apos; is not applicable on that type of dialog controls.&quot; _
+ , Comment := &quot;SF_DialogControl property setting\n&quot; _
+ &amp; &quot;%1: An identifier\n&quot; _
+ &amp; &quot;%2: An identifier\n&quot; _
+ &amp; &quot;%3: A string\n&quot; _
+ &amp; &quot;%4: An identifier&quot; _
+ )
+ &apos; SF_DialogControl.WriteLine
+ .AddText( Context := &quot;TEXTFIELD&quot; _
+ , MsgId := &quot;The control &apos;%1&apos; in dialog &apos;%2&apos; is not a multiline text field.\n&quot; _
+ &amp; &quot;The requested method could not be executed.&quot; _
+ , Comment := &quot;SF_DialogControl add line in textbox\n&quot; _
+ &amp; &quot;%1: An identifier\n&quot; _
+ &amp; &quot;%2: An identifier&quot; _
+ )
+ &apos; SF_Database.RunSql
+ .AddText( Context := &quot;DBREADONLY&quot; _
+ , MsgId := &quot;The database has been opened in read-only mode.\n&quot; _
+ &amp; &quot;The &apos;%1&apos; method must not be executed in this context.&quot; _
+ , Comment := &quot;SF_Database when running update SQL statement\n&quot; _
+ &amp; &quot;%1: The concerned method&quot; _
+ )
+ &apos; SF_Database._ExecuteSql
+ .AddText( Context := &quot;SQLSYNTAX&quot; _
+ , MsgId := &quot;An SQL statement could not be interpreted or executed by the database system.\n&quot; _
+ &amp; &quot;Check its syntax, table and/or field names, ...\n\n&quot; _
+ &amp; &quot;SQL Statement : « %1 »&quot; _
+ , Comment := &quot;SF_Database can&apos;t interpret SQL statement\n&quot; _
+ &amp; &quot;%1: The statement&quot; _
+ )
+ &apos; SF_Exception.PythonShell (Python only)
+ .AddText( Context := &quot;PYTHONSHELL&quot; _
+ , MsgId := &quot;The APSO extension could not be located in your LibreOffice installation.&quot; _
+ , Comment := &quot;SF_Exception.PythonShell error message&quot; _
+ &amp; &quot;APSO: to leave unchanged&quot; _
+ )
+ &apos; SFUnitTests._NewUnitTest
+ .AddText( Context := &quot;UNITTESTLIBRARY&quot; _
+ , MsgId := &quot;The requested library could not be located.\n&quot; _
+ &amp; &quot;The UnitTest service has not been initialized.\n\n&quot; _
+ &amp; &quot;Library name : « %1 »&quot; _
+ , Comment := &quot;SFUnitTest could not locate the library gven as argument\n&quot; _
+ &amp; &quot;%1: The name of the library&quot; _
+ )
+ &apos; SFUnitTests.SF_UnitTest
+ .AddText( Context := &quot;UNITTESTMETHOD&quot; _
+ , MsgId := &quot;The method &apos;%1&apos; is unexpected in the current context.\n&quot; _
+ &amp; &quot;The UnitTest service cannot proceed further with the on-going test.&quot; _
+ , Comment := &quot;SFUnitTest finds a RunTest() call in a inappropriate location\n&quot; _
+ &amp; &quot;%1: The name of a method&quot; _
+ )
+ End With
+ End If
+
+Finally:
+ Exit Sub
+Catch:
+ GoTo Finally
+End Sub &apos; ScriptForge.SF_Root._LoadLocalizedInterface
+
+REM -----------------------------------------------------------------------------
+Public Function _Repr() As String
+&apos;&apos;&apos; Convert the unique SF_Root instance to a readable string, typically for debugging purposes (DebugPrint ...)
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Return:
+&apos;&apos;&apos; &quot;[Root] (MainFunction: xxx, Console: yyy lines, ServicesList)&quot;
+
+Dim sRoot As String &apos; Return value
+Const cstRoot = &quot;[Root] (&quot;
+
+ sRoot = cstRoot &amp; &quot;MainFunction: &quot; &amp; MainFunction &amp; &quot;, Console: &quot; &amp; UBound(ConsoleLines) + 1 &amp; &quot; lines&quot; _
+ &amp; &quot;, Libraries:&quot; &amp; SF_Utils._Repr(ServicesList.Keys) _
+ &amp; &quot;)&quot;
+
+ _Repr = sRoot
+
+End Function &apos; ScriptForge.SF_Root._Repr
+
+REM -----------------------------------------------------------------------------
+Public Sub _StackReset()
+&apos;&apos;&apos; Reset private members after a fatal/abort error to leave
+&apos;&apos;&apos; a stable persistent storage after an unwanted interrupt
+
+ MainFunction = &quot;&quot;
+ MainFunctionArgs = &quot;&quot;
+ StackLevel = 0
+ TriggeredByPython = False
+
+End Sub &apos; ScriptForge.SF_Root._StackReset
+
+REM ================================================== END OF SCRIPTFORGE.SF_ROOT
+</script:module> \ No newline at end of file
diff --git a/wizards/source/scriptforge/SF_Services.xba b/wizards/source/scriptforge/SF_Services.xba
new file mode 100644
index 000000000..627dc4d2e
--- /dev/null
+++ b/wizards/source/scriptforge/SF_Services.xba
@@ -0,0 +1,639 @@
+<?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; The ScriptForge framework includes
+&apos;&apos;&apos; the current ScriptForge library
+&apos;&apos;&apos; a number of &quot;associated&quot; libraries
+&apos;&apos;&apos; any user/contributor extension wanting to fit into the framework
+&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 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 ScriptForge.Array or SF_Array
+&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;
+
+ 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;documentevent&quot;, &quot;formevent&quot;
+ sLibrary = &quot;SFDocuments&quot;
+ Case &quot;dialog&quot;, &quot;dialogevent&quot; : sLibrary = &quot;SFDialogs&quot;
+ Case &quot;database&quot; : sLibrary = &quot;SFDatabases&quot;
+ Case &quot;unittest&quot; : sLibrary = &quot;SFUnitTests&quot;
+ Case &quot;menu&quot;, &quot;popupmenu&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()
+ 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;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 vCategories As Variant &apos; &quot;user&quot; or &quot;share&quot; library categories
+Dim sCategory As String
+Dim vLanguages As Variant &apos; &quot;Basic&quot;, &quot;Python&quot;, ... programming languages
+Dim sLanguage 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; Method/properties/subs/functions
+Dim sMethod As String
+Dim oRoot As Object &apos; com.sun.star.script.browse.BrowseNodeFactory
+Dim i As Integer, j As Integer, k As Integer, l As Integer, m As Integer
+
+ _FindModuleFromMethod = &quot;&quot;
+ Set oRoot = SF_Utils._GetUNOService(&quot;BrowseNodeFactory&quot;).createView(com.sun.star.script.browse.BrowseNodeFactoryViewTypes.MACROORGANIZER)
+
+ &apos; Exploration is done via tree nodes
+ If Not IsNull(oRoot) Then
+ If oRoot.hasChildNodes() Then
+ vCategories = oRoot.getChildNodes()
+ For i = 0 To UBound(vCategories)
+ sCategory = vCategories(i).getName()
+ &apos; Consider &quot;My macros &amp; Dialogs&quot; and &quot;LibreOffice Macros &amp; Dialogs&quot; only
+ If sCategory = &quot;user&quot; Or sCategory = &quot;share&quot; Then
+ If vCategories(i).hasChildNodes() Then
+ vLanguages = vCategories(i).getChildNodes()
+ For j = 0 To UBound(vLanguages)
+ sLanguage = vLanguages(j).getName()
+ &apos; Consider Basic libraries only
+ If sLanguage = &quot;Basic&quot; Then
+ If vLanguages(j).hasChildNodes() Then
+ vLibraries = vLanguages(j).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
+ End If
+ Next j
+ End If
+ End If
+ Next i
+ End If
+ End If
+
+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() As Variant
+&apos;&apos;&apos; Create a new instance of the SF_Dictionary class
+&apos;&apos;&apos; Returns: the instance or Nothing
+
+Dim oDict As Variant
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+
+Check:
+
+Try:
+ Set oDict = New SF_Dictionary
+ Set oDict.[Me] = oDict
+
+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> \ No newline at end of file
diff --git a/wizards/source/scriptforge/SF_Session.xba b/wizards/source/scriptforge/SF_Session.xba
new file mode 100644
index 000000000..b4292f36e
--- /dev/null
+++ b/wizards/source/scriptforge/SF_Session.xba
@@ -0,0 +1,1076 @@
+<?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_Session" 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_Session
+&apos;&apos;&apos; ==========
+&apos;&apos;&apos; Singleton class implementing the &quot;ScriptForge.Session&quot; service
+&apos;&apos;&apos; Implemented as a usual Basic module
+&apos;&apos;&apos;
+&apos;&apos;&apos; Gathers diverse general-purpose properties and methods about :
+&apos;&apos;&apos; - installation/execution environment
+&apos;&apos;&apos; - UNO introspection utilities
+&apos;&apos;&apos; - clipboard management
+&apos;&apos;&apos; - invocation of external scripts or programs
+&apos;&apos;&apos;
+&apos;&apos;&apos; Service invocation example:
+&apos;&apos;&apos; Dim session As Variant
+&apos;&apos;&apos; session = CreateScriptService(&quot;Session&quot;)
+&apos;&apos;&apos;
+&apos;&apos;&apos; Detailed user documentation:
+&apos;&apos;&apos; https://help.libreoffice.org/latest/en-US/text/sbasic/shared/03/sf_session.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 CALCFUNCERROR = &quot;CALCFUNCERROR&quot; &apos; Calc function execution failed
+Const NOSCRIPTERROR = &quot;NOSCRIPTERROR&quot; &apos; Script could not be located
+Const SCRIPTEXECERROR = &quot;SCRIPTEXECERROR&quot; &apos; Exception during script execution
+Const WRONGEMAILERROR = &quot;WRONGEMAILERROR&quot; &apos; Wrong email address
+Const SENDMAILERROR = &quot;SENDMAILERROR&quot; &apos; Mail could not be sent
+Const UNKNOWNFILEERROR = &quot;UNKNOWNFILEERROR&quot; &apos; Source file does not exist
+
+REM ============================================================ MODULE CONSTANTS
+
+&apos;&apos;&apos; Script locations
+&apos;&apos;&apos; ================
+&apos;&apos;&apos; Use next constants as Scope argument when invoking next methods:
+&apos;&apos;&apos; ExecuteBasicScript()
+&apos;&apos;&apos; ExecutePythonScript()
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; session.ExecuteBasicScript(session.SCRIPTISEMBEDDED, &quot;Standard.myModule.myFunc&quot;, etc)
+
+Const cstSCRIPTISEMBEDDED = &quot;document&quot; &apos; a library of the document (BASIC + PYTHON)
+Const cstSCRIPTISAPPLICATION = &quot;application&quot; &apos; a shared library (BASIC)
+Const cstSCRIPTISPERSONAL = &quot;user&quot; &apos; a library of My Macros (PYTHON)
+Const cstSCRIPTISPERSOXT = &quot;user:uno_packages&quot; &apos; an extension for the current user (PYTHON)
+Const cstSCRIPTISSHARED = &quot;share&quot; &apos; a library of LibreOffice Macros (PYTHON)
+Const cstSCRIPTISSHAROXT = &quot;share:uno_packages&quot; &apos; an extension for all users (PYTHON)
+Const cstSCRIPTISOXT = &quot;uno_packages&quot; &apos; an extension but install params are unknown (PYTHON)
+
+&apos;&apos;&apos; To build or to parse scripting framework URI&apos;s
+Const cstScript1 = &quot;vnd.sun.star.script:&quot;
+Const cstScript2 = &quot;?language=&quot;
+Const cstScript3 = &quot;&amp;location=&quot;
+
+REM ===================================================== CONSTRUCTOR/DESTRUCTOR
+
+REM -----------------------------------------------------------------------------
+Public Function Dispose() As Variant
+ Set Dispose = Nothing
+End Function &apos; ScriptForge.SF_Array Explicit destructor
+
+REM ================================================================== PROPERTIES
+
+REM -----------------------------------------------------------------------------
+Property Get ObjectType As String
+&apos;&apos;&apos; Only to enable object representation
+ ObjectType = &quot;SF_Session&quot;
+End Property &apos; ScriptForge.SF_Session.ObjectType
+
+REM -----------------------------------------------------------------------------
+Property Get ServiceName As String
+&apos;&apos;&apos; Internal use
+ ServiceName = &quot;ScriptForge.Session&quot;
+End Property &apos; ScriptForge.SF_Array.ServiceName
+
+REM -----------------------------------------------------------------------------
+Property Get SCRIPTISAPPLICATION As String
+&apos;&apos;&apos; Convenient constants
+ SCRIPTISAPPLICATION = cstSCRIPTISAPPLICATION
+End Property &apos; ScriptForge.SF_Session.SCRIPTISAPPLICATION
+
+REM -----------------------------------------------------------------------------
+Property Get SCRIPTISEMBEDDED As String
+&apos;&apos;&apos; Convenient constants
+ SCRIPTISEMBEDDED = cstSCRIPTISEMBEDDED
+End Property &apos; ScriptForge.SF_Session.SCRIPTISEMBEDDED
+
+REM -----------------------------------------------------------------------------
+Property Get SCRIPTISOXT As String
+&apos;&apos;&apos; Convenient constants
+ SCRIPTISOXT = cstSCRIPTISOXT
+End Property &apos; ScriptForge.SF_Session.SCRIPTISOXT
+
+REM -----------------------------------------------------------------------------
+Property Get SCRIPTISPERSONAL As String
+&apos;&apos;&apos; Convenient constants
+ SCRIPTISPERSONAL = cstSCRIPTISPERSONAL
+End Property &apos; ScriptForge.SF_Session.SCRIPTISPERSONAL
+
+REM -----------------------------------------------------------------------------
+Property Get SCRIPTISPERSOXT As String
+&apos;&apos;&apos; Convenient constants
+ SCRIPTISPERSOXT = cstSCRIPTISPERSOXT
+End Property &apos; ScriptForge.SF_Session.SCRIPTISPERSOXT
+
+REM -----------------------------------------------------------------------------
+Property Get SCRIPTISSHARED As String
+&apos;&apos;&apos; Convenient constants
+ SCRIPTISSHARED = cstSCRIPTISSHARED
+End Property &apos; ScriptForge.SF_Session.SCRIPTISSHARED
+
+REM -----------------------------------------------------------------------------
+Property Get SCRIPTISSHAROXT As String
+&apos;&apos;&apos; Convenient constants
+ SCRIPTISSHAROXT = cstSCRIPTISSHAROXT
+End Property &apos; ScriptForge.SF_Session.SCRIPTISSHAROXT
+
+REM ============================================================== PUBLIC METHODS
+
+REM -----------------------------------------------------------------------------
+Public Function ExecuteBasicScript(Optional ByVal Scope As Variant _
+ , Optional ByVal Script As Variant _
+ , ParamArray pvArgs As Variant _
+ ) As Variant
+&apos;&apos;&apos; Execute the Basic script given as a string and return the value returned by the script
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Scope: &quot;Application&quot; (default) or &quot;Document&quot; (NOT case-sensitive)
+&apos;&apos;&apos; (or use one of the SCRIPTIS... public constants above)
+&apos;&apos;&apos; Script: library.module.method (Case sensitive)
+&apos;&apos;&apos; library =&gt; The library may be not loaded yet
+&apos;&apos;&apos; module =&gt; Must not be a class module
+&apos;&apos;&apos; method =&gt; Sub or Function
+&apos;&apos;&apos; Read https://wiki.documentfoundation.org/Documentation/DevGuide/Scripting_Framework#Scripting_Framework_URI_Specification
+&apos;&apos;&apos; pvArgs: the arguments of the called script
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The value returned by the call to the script
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; NOSCRIPTERROR The script could not be found
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; session.ExecuteBasicScript(, &quot;XrayTool._Main.Xray&quot;, someuno) &apos; Sub: no return expected
+
+Dim oScript As Object &apos; Script to be invoked
+Dim vReturn As Variant &apos; Returned value
+
+Const cstThisSub = &quot;Session.ExecuteBasicScript&quot;
+Const cstSubArgs = &quot;[Scope], Script, arg0[, arg1] ...&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+
+ vReturn = Empty
+
+Check:
+ If IsMissing(Scope) Or IsEmpty(Scope) Then Scope = SCRIPTISAPPLICATION
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(Scope, &quot;Scope&quot;, V_STRING _
+ , Array(SCRIPTISAPPLICATION, SCRIPTISEMBEDDED)) Then GoTo Finally
+ If Not SF_Utils._Validate(Script, &quot;Script&quot;, V_STRING) Then GoTo Finally
+ End If
+
+Try:
+ &apos; Execute script
+ Set oScript = SF_Session._GetScript(&quot;Basic&quot;, Scope, Script)
+ On Local Error GoTo CatchExec
+ If Not IsNull(oScript) Then vReturn = oScript.Invoke(pvArgs, Array(), Array())
+
+Finally:
+ ExecuteBasicScript = vReturn
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchExec:
+ SF_Exception.RaiseFatal(SCRIPTEXECERROR, &quot;Script&quot;, Script, Error$)
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Session.ExecuteBasicScript
+
+REM -----------------------------------------------------------------------------
+Public Function ExecuteCalcFunction(Optional ByVal CalcFunction As Variant _
+ , ParamArray pvArgs As Variant _
+ ) As Variant
+&apos;&apos;&apos; Execute a Calc function by its (english) name and based on the given arguments
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; CalcFunction: the english name of the function to execute
+&apos;&apos;&apos; pvArgs: the arguments of the called function
+&apos;&apos;&apos; Each argument must be either a string, a numeric value
+&apos;&apos;&apos; or an array of arrays combining those types
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The (string or numeric) value or the array of arrays returned by the call to the function
+&apos;&apos;&apos; When the arguments contain arrays, the function is executed as an array function
+&apos;&apos;&apos; Wrong arguments generate an error
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; CALCFUNCERROR &apos; Execution error in calc function
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; session.ExecuteCalcFunction(&quot;AVERAGE&quot;, 1, 5, 3, 7) returns 4
+&apos;&apos;&apos; session.ExecuteCalcFunction(&quot;ABS&quot;, Array(Array(-1,2,3),Array(4,-5,6),Array(7,8,-9)))(2)(2) returns 9
+&apos;&apos;&apos; session.ExecuteCalcFunction(&quot;LN&quot;, -3) generates an error
+
+Dim oCalc As Object &apos; Give access to the com.sun.star.sheet.FunctionAccess service
+Dim vReturn As Variant &apos; Returned value
+Const cstThisSub = &quot;Session.ExecuteCalcFunction&quot;
+Const cstSubArgs = &quot;CalcFunction, arg0[, arg1] ...&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vReturn = Empty
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(CalcFunction, &quot;CalcFunction&quot;, V_STRING) Then GoTo Finally
+ End If
+
+Try:
+ &apos; Execute function
+ Set oCalc = SF_Utils._GetUNOService(&quot;FunctionAccess&quot;)
+ &apos; Intercept calls from Python when no arguments. Example NOW()
+ If UBound(pvArgs) = 0 Then
+ If IsEmpty(pvArgs(0)) Then pvArgs = Array()
+ End If
+ On Local Error GoTo CatchCall
+ vReturn = oCalc.callFunction(UCase(CalcFunction), pvArgs())
+
+Finally:
+ ExecuteCalcFunction = vReturn
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchCall:
+ SF_Exception.RaiseFatal(CALCFUNCERROR, CalcFunction)
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Session.ExecuteCalcFunction
+
+REM -----------------------------------------------------------------------------
+Public Function ExecutePythonScript(Optional ByVal Scope As Variant _
+ , Optional ByVal Script As Variant _
+ , ParamArray pvArgs As Variant _
+ ) As Variant
+&apos;&apos;&apos; Execute the Python script given as a string and return the value returned by the script
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Scope: one of the SCRIPTIS... public constants above (default = &quot;share&quot;)
+&apos;&apos;&apos; Script: (Case sensitive)
+&apos;&apos;&apos; &quot;library/module.py$method&quot;
+&apos;&apos;&apos; or &quot;module.py$method&quot;
+&apos;&apos;&apos; or &quot;myExtension.oxt|myScript|module.py$method&quot;
+&apos;&apos;&apos; library =&gt; The library may be not loaded yet
+&apos;&apos;&apos; myScript =&gt; The directory containing the python module
+&apos;&apos;&apos; module.py =&gt; The python module
+&apos;&apos;&apos; method =&gt; The python function
+&apos;&apos;&apos; Read https://wiki.documentfoundation.org/Documentation/DevGuide/Scripting_Framework#Scripting_Framework_URI_Specification
+&apos;&apos;&apos; pvArgs: the arguments of the called script
+&apos;&apos;&apos; Date arguments are converted to iso format. However dates in arrays are not converted
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The value(s) returned by the call to the script. If &gt;1 values, enclosed in an array
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; NOSCRIPTERROR The script could not be found
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; session.ExecutePythonScript(session.SCRIPTISSHARED, &quot;Capitalise.py$getNewString&quot;, &quot;Abc&quot;) returns &quot;abc&quot;
+
+Dim oScript As Object &apos; Script to be invoked
+Dim vArg As Variant &apos; Individual argument
+Dim vReturn As Variant &apos; Returned value
+Dim i As Long
+
+Const cstThisSub = &quot;Session.ExecutePythonScript&quot;
+Const cstSubArgs = &quot;[Scope], Script, arg0[, arg1] ...&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+
+ vReturn = Empty
+
+Check:
+ If IsError(Scope) Or IsMissing(Scope) Then Scope = SCRIPTISSHARED
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(Scope, &quot;Scope&quot;, V_STRING _
+ , Array(SCRIPTISSHARED, SCRIPTISEMBEDDED, SCRIPTISPERSONAL, SCRIPTISSHAROXT, SCRIPTISPERSOXT, SCRIPTISOXT) _
+ ) Then GoTo Finally
+ If Not SF_Utils._Validate(Script, &quot;Script&quot;, V_STRING) Then GoTo Finally
+ End If
+
+Try:
+ &apos; Filter date arguments - NB: dates in arrays are not filtered
+ For i = 0 To UBound(pvArgs) &apos; pvArgs always zero-based
+ vArg = pvArgs(i)
+ If VarType(vArg) = V_DATE Then pvArgs(i) = SF_Utils._CDateToIso(vArg)
+ Next i
+
+ &apos; Intercept alternate Python helpers file when relevant
+ With _SF_
+ If SF_String.StartsWith(Script, .PythonHelper) And Len(.PythonHelper2) &gt; 0 Then
+ Scope = SCRIPTISPERSONAL
+ Script = .PythonHelper2 &amp; Mid(Script, Len(.PythonHelper) + 1)
+ End If
+ End With
+ &apos; Find script
+ Set oScript = SF_Session._GetScript(&quot;Python&quot;, Scope, Script)
+
+ &apos; Execute script
+ If Not IsNull(oScript) Then
+ vReturn = oScript.Invoke(pvArgs(), Array(), Array())
+ &apos; Remove surrounding array when single returned value
+ If IsArray(vReturn) Then
+ If UBound(vReturn) = 0 Then vReturn = vReturn(0)
+ End If
+ End If
+
+Finally:
+ ExecutePythonScript = vReturn
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Session.ExecutePythonScript
+
+REM -----------------------------------------------------------------------------
+Public Function GetPDFExportOptions() As Variant
+&apos;&apos;&apos; Return the actual values of the PDF export options
+&apos;&apos;&apos; The PDF options are described on https://wiki.openoffice.org/wiki/API/Tutorials/PDF_export
+&apos;&apos;&apos; PDF options are set at each use of the Export as ... PDF command by the user and kept
+&apos;&apos;&apos; permanently until their reset by script or by a new export
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; A ScriptForge dictionary instance listing the 40+ properties and their value
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; Dim dict As Object
+&apos;&apos;&apos; Set dict = session.GetPDFExportOptions()
+&apos;&apos;&apos; MsgBox dict.Item(&quot;Quality&quot;)
+
+Dim vDict As Variant &apos; Returned value
+Dim oConfig As Object &apos; com.sun.star.configuration.ConfigurationProvider
+Dim oNodePath As Object &apos; com.sun.star.beans.PropertyValue
+Dim oOptions As Object &apos; configmgr.RootAccess
+Dim vOptionNames As Variant &apos; Array of PDF options names
+Dim vOptionValues As Variant &apos; Array of PDF options values
+Dim i As Long
+
+Const cstThisSub = &quot;Session.GetPDFExportOptions&quot;
+Const cstSubArgs = &quot;&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ Set vDict = Nothing
+
+Check:
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Try:
+ &apos; Get the (read-only) internal PDF options
+ Set oConfig = SF_Utils._GetUNOService(&quot;ConfigurationProvider&quot;)
+ Set oNodePath = SF_Utils._MakePropertyValue(&quot;nodepath&quot;, &quot;/org.openoffice.Office.Common/Filter/PDF/Export/&quot;)
+ Set oOptions = oConfig.createInstanceWithArguments(&quot;com.sun.star.configuration.ConfigurationAccess&quot;, Array(oNodePath))
+
+ &apos; Copy the options into a ScriptForge dictionary
+ Set vDict = CreateScriptService(&quot;dictionary&quot;)
+ vOptionNames = oOptions.getElementNames()
+ vOptionValues = oOptions.getPropertyValues(vOptionNames)
+ &apos;
+ For i = 0 To UBound(vOptionNames)
+ vDict.Add(vOptionNames(i), vOptionValues(i))
+ Next i
+
+
+Finally:
+ GetPDFExportOptions = vDict
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Session.GetPDFExportOptions
+
+REM -----------------------------------------------------------------------------
+Public Function GetProperty(Optional ByVal PropertyName As Variant) As Variant
+&apos;&apos;&apos; Return the actual value of the given property
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; PropertyName: the name of the property as a string
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The actual value of the property
+&apos;&apos;&apos; Exceptions
+&apos;&apos;&apos; ARGUMENTERROR The property does not exist
+
+Const cstThisSub = &quot;Session.GetProperty&quot;
+Const cstSubArgs = &quot;PropertyName&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ GetProperty = Null
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
+ End If
+
+Try:
+ Select Case UCase(PropertyName)
+ Case Else
+ End Select
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Session.GetProperty
+
+REM -----------------------------------------------------------------------------
+Public Function HasUnoMethod(Optional ByRef UnoObject As Variant _
+ , Optional ByVal MethodName As Variant _
+ ) As Boolean
+&apos;&apos;&apos; Returns True if a UNO object contains the given method
+&apos;&apos;&apos; Code-snippet derived from XRAY
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; UnoObject: the object to identify
+&apos;&apos;&apos; MethodName: the name of the method as a string. The search is case-sensitive
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; False when the method is not found or when an argument is invalid
+
+Dim oIntrospect As Object &apos; com.sun.star.beans.Introspection
+Dim oInspect As Object &apos; com.sun.star.beans.XIntrospectionAccess
+Dim bMethod As Boolean &apos; Return value
+Const cstThisSub = &quot;Session.HasUnoMethod&quot;
+Const cstSubArgs = &quot;UnoObject, MethodName&quot;
+
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Check:
+ bMethod = False
+ If VarType(UnoObject) &lt;&gt; V_OBJECT Then GoTo Finally
+ If IsNull(UnoObject) Then GoTo Finally
+ If VarType(MethodName) &lt;&gt; V_STRING Then GoTo Finally
+ If MethodName = Space(Len(MethodName)) Then GoTo Finally
+
+Try:
+ On Local Error GoTo Catch
+ Set oIntrospect = SF_Utils._GetUNOService(&quot;Introspection&quot;)
+ Set oInspect = oIntrospect.inspect(UnoObject)
+ bMethod = oInspect.hasMethod(MethodName, com.sun.star.beans.MethodConcept.ALL)
+
+Finally:
+ HasUnoMethod = bMethod
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ On Local Error GoTo 0
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Session.HasUnoMethod
+
+REM -----------------------------------------------------------------------------
+Public Function HasUnoProperty(Optional ByRef UnoObject As Variant _
+ , Optional ByVal PropertyName As Variant _
+ ) As Boolean
+&apos;&apos;&apos; Returns True if a UNO object contains the given property
+&apos;&apos;&apos; Code-snippet derived from XRAY
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; UnoObject: the object to identify
+&apos;&apos;&apos; PropertyName: the name of the property as a string. The search is case-sensitive
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; False when the property is not found or when an argument is invalid
+
+Dim oIntrospect As Object &apos; com.sun.star.beans.Introspection
+Dim oInspect As Object &apos; com.sun.star.beans.XIntrospectionAccess
+Dim bProperty As Boolean &apos; Return value
+Const cstThisSub = &quot;Session.HasUnoProperty&quot;
+Const cstSubArgs = &quot;UnoObject, PropertyName&quot;
+
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Check:
+ bProperty = False
+ If VarType(UnoObject) &lt;&gt; V_OBJECT Then GoTo Finally
+ If IsNull(UnoObject) Then GoTo Finally
+ If VarType(PropertyName) &lt;&gt; V_STRING Then GoTo Finally
+ If PropertyName = Space(Len(PropertyName)) Then GoTo Finally
+
+Try:
+ On Local Error GoTo Catch
+ Set oIntrospect = SF_Utils._GetUNOService(&quot;Introspection&quot;)
+ Set oInspect = oIntrospect.inspect(UnoObject)
+ bProperty = oInspect.hasProperty(PropertyName, com.sun.star.beans.PropertyConcept.ALL)
+
+Finally:
+ HasUnoProperty = bProperty
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ On Local Error GoTo 0
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Session.HasUnoProperty
+
+REM -----------------------------------------------------------------------------
+Public Function Methods() As Variant
+&apos;&apos;&apos; Return the list of public methods of the Session service as an array
+
+ Methods = Array( _
+ &quot;ExecuteBasicScript&quot; _
+ , &quot;ExecuteCalcFunction&quot; _
+ , &quot;ExecutePythonScript&quot; _
+ , &quot;HasUnoMethod&quot; _
+ , &quot;HasUnoProperty&quot; _
+ , &quot;OpenURLInBrowser&quot; _
+ , &quot;RunApplication&quot; _
+ , &quot;SendMail&quot; _
+ , &quot;UnoMethods&quot; _
+ , &quot;UnoObjectType&quot; _
+ , &quot;UnoProperties&quot; _
+ , &quot;WebService&quot; _
+ )
+
+End Function &apos; ScriptForge.SF_Session.Methods
+
+REM -----------------------------------------------------------------------------
+Public Sub OpenURLInBrowser(Optional ByVal URL As Variant)
+&apos;&apos;&apos; Opens a URL in the default browser
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; URL: The URL to open in the browser
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; session.OpenURLInBrowser(&quot;https://docs.python.org/3/library/webbrowser.html&quot;)
+
+Const cstPyHelper = &quot;$&quot; &amp; &quot;_SF_Session__OpenURLInBrowser&quot;
+
+Const cstThisSub = &quot;Session.OpenURLInBrowser&quot;
+Const cstSubArgs = &quot;URL&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(URL, &quot;URL&quot;, V_STRING) Then GoTo Finally
+ End If
+
+Try:
+ ExecutePythonScript(SCRIPTISSHARED, _SF_.PythonHelper &amp; cstPyHelper, URL)
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Sub
+Catch:
+ GoTo Finally
+End Sub &apos; ScriptForge.SF_Session.OpenURLInBrowser
+
+REM -----------------------------------------------------------------------------
+Public Function Properties() As Variant
+&apos;&apos;&apos; Return the list or properties as an array
+
+ Properties = Array( _
+ )
+
+End Function &apos; ScriptForge.SF_Session.Properties
+
+REM -----------------------------------------------------------------------------
+Public Function RunApplication(Optional ByVal Command As Variant _
+ , Optional ByVal Parameters As Variant _
+ ) As Boolean
+&apos;&apos;&apos; Executes an arbitrary system command
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Command: The command to execute
+&apos;&apos;&apos; This may be an executable file or a document which is registered with an application
+&apos;&apos;&apos; so that the system knows what application to launch for that document
+&apos;&apos;&apos; Parameters: a list of space separated parameters as a single string
+&apos;&apos;&apos; The method does not validate the given parameters, but only passes them to the specified command
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if success
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; session.RunApplication(&quot;Notepad.exe&quot;)
+&apos;&apos;&apos; session.RunApplication(&quot;C:\myFolder\myDocument.odt&quot;)
+&apos;&apos;&apos; session.RunApplication(&quot;kate&quot;, &quot;/home/me/install.txt&quot;) &apos; (Linux)
+
+Dim bReturn As Boolean &apos; Returned value
+Dim oShell As Object &apos; com.sun.star.system.SystemShellExecute
+Dim sCommand As String &apos; Command as an URL
+Const cstThisSub = &quot;Session.RunApplication&quot;
+Const cstSubArgs = &quot;Command, [Parameters]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bReturn = False
+
+Check:
+ If IsMissing(Parameters) Then Parameters = &quot;&quot;
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateFile(Command, &quot;Command&quot;) Then GoTo Finally
+ If Not SF_Utils._Validate(Parameters, &quot;Parameters&quot;, V_STRING) Then GoTo Finally
+ End If
+
+Try:
+ Set oShell = SF_Utils._GetUNOService(&quot;SystemShellExecute&quot;)
+ sCommand = SF_FileSystem._ConvertToUrl(Command)
+ oShell.execute(sCommand, Parameters, com.sun.star.system.SystemShellExecuteFlags.URIS_ONLY)
+ bReturn = True
+
+Finally:
+ RunApplication = bReturn
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Session.RunApplication
+
+REM -----------------------------------------------------------------------------
+Public Sub SendMail(Optional ByVal Recipient As Variant _
+ , Optional ByRef Cc As Variant _
+ , Optional ByRef Bcc As Variant _
+ , Optional ByVal Subject As Variant _
+ , Optional ByRef Body As Variant _
+ , Optional ByVal FileNames As Variant _
+ , Optional ByVal EditMessage As Variant _
+ )
+&apos;&apos;&apos; Send a message (with or without attachments) to recipients from the user&apos;s mail client
+&apos;&apos;&apos; The message may be edited by the user before sending or, alternatively, be sent immediately
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Recipient: an email addresses (To recipient)
+&apos;&apos;&apos; Cc: a comma-delimited list of email addresses (carbon copy)
+&apos;&apos;&apos; Bcc: a comma-delimited list of email addresses (blind carbon copy)
+&apos;&apos;&apos; Subject: the header of the message
+&apos;&apos;&apos; FileNames: a comma-separated list of filenames to attach to the mail. SF_FileSystem naming conventions apply
+&apos;&apos;&apos; Body: the unformatted text of the message
+&apos;&apos;&apos; EditMessage: when True (default) the message is editable before being sent
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; UNKNOWNFILEERROR File does not exist
+&apos;&apos;&apos; WRONGEMAILERROR String not recognized as an email address
+&apos;&apos;&apos; SENDMAILERROR System error, probably no mail client
+
+Dim sEmail As String &apos; An single email address
+Dim sFile As String &apos; A single file name
+Dim sArg As String &apos; Argument name
+Dim vCc As Variant &apos; Array alias of Cc
+Dim vBcc As Variant &apos; Array alias of Bcc
+Dim vFileNames As Variant &apos; Array alias of FileNames
+Dim oMailService As Object &apos; com.sun.star.system.SimpleCommandMail or com.sun.star.system.SimpleSystemMail
+Dim oMail As Object &apos; com.sun.star.system.XSimpleMailClient
+Dim oMessage As Object &apos; com.sun.star.system.XSimpleMailMessage
+Dim lFlag As Long &apos; com.sun.star.system.SimpleMailClientFlags.XXX
+Dim ARR As Object : ARR = ScriptForge.SF_Array
+Dim i As Long
+Const cstComma = &quot;,&quot;, cstSemiColon = &quot;;&quot;
+Const cstThisSub = &quot;Session.SendMail&quot;
+Const cstSubArgs = &quot;Recipient, [Cc=&quot;&quot;&quot;&quot;], [Bcc=&quot;&quot;&quot;&quot;], [Subject=&quot;&quot;&quot;&quot;], [FileNames=&quot;&quot;&quot;&quot;], [Body=&quot;&quot;&quot;&quot;], [EditMessage=True]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+
+Check:
+ If IsMissing(Cc) Or IsEmpty(Cc) Then Cc = &quot;&quot;
+ If IsMissing(Bcc) Or IsEmpty(Bcc) Then Bcc = &quot;&quot;
+ If IsMissing(Subject) Or IsEmpty(Subject) Then Subject = &quot;&quot;
+ If IsMissing(FileNames) Or IsEmpty(FileNames) Then FileNames = &quot;&quot;
+ If IsMissing(Body) Or IsEmpty(Body) Then Body = &quot;&quot;
+ If IsMissing(EditMessage) Or IsEmpty(EditMessage) Then EditMessage = True
+
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(Cc, &quot;Recipient&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(Cc, &quot;Cc&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(Bcc, &quot;Bcc&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(Subject, &quot;Subject&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(FileNames, &quot;FileNames&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(Body, &quot;Body&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(EditMessage, &quot;EditMessage&quot;, V_BOOLEAN) Then GoTo Finally
+ End If
+
+ &apos; Check email addresses
+ sArg = &quot;Recipient&quot; : sEmail = Recipient
+ If Not SF_String.IsEmail(sEmail) Then GoTo CatchEmail
+ sArg = &quot;Cc&quot; : vCc = ARR.TrimArray(Split(Cc, cstComma))
+ For Each sEmail In vCc
+ If Not SF_String.IsEmail(sEmail) Then GoTo CatchEmail
+ Next sEmail
+ sArg = &quot;Bcc&quot; : vBcc = ARR.TrimArray(Split(Bcc, cstComma))
+ For Each sEmail In vBcc
+ If Not SF_String.IsEmail(sEmail) Then GoTo CatchEmail
+ Next sEmail
+
+ &apos; Check file existence
+ If Len(FileNames) &gt; 0 Then
+ vFileNames = ARR.TrimArray(Split(FileNames, cstComma))
+ For i = 0 To UBound(vFileNames)
+ sFile = vFileNames(i)
+ If Not SF_Utils._ValidateFile(sFile, &quot;FileNames&quot;) Then GoTo Finally
+ If Not SF_FileSystem.FileExists(sFile) Then GoTo CatchNotExists
+ vFileNames(i) = ConvertToUrl(sFile)
+ Next i
+ End If
+
+Try:
+ &apos; Initialize the mail service
+ Set oMailService = SF_Utils._GetUNOService(&quot;MailService&quot;)
+ If IsNull(oMailService) Then GoTo CatchMail
+ Set oMail = oMailService.querySimpleMailClient()
+ If IsNull(oMail) Then GoTo CatchMail
+ Set oMessage = oMail.createSimpleMailMessage()
+ If IsNull(oMessage) Then GoTo CatchMail
+
+ &apos; Feed the new mail message
+ With oMessage
+ .setRecipient(Recipient)
+ If Subject &lt;&gt; &quot;&quot; Then .setSubject(Subject)
+ If UBound(vCc) &gt;= 0 Then .setCcRecipient(vCc)
+ If UBound(vBcc) &gt;= 0 Then .setBccRecipient(vBcc)
+ .Body = Iif(Len(Body) = 0, &quot; &quot;, Body) &apos; Body must not be the empty string ??
+ .setAttachement(vFileNames)
+ End With
+ lFlag = Iif(EditMessage, com.sun.star.system.SimpleMailClientFlags.DEFAULTS, com.sun.star.system.SimpleMailClientFlags.NO_USER_INTERFACE)
+
+ &apos; Send using the mail service
+ oMail.sendSimpleMailMessage(oMessage, lFlag)
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Sub
+Catch:
+ GoTo Finally
+CatchEmail:
+ SF_Exception.RaiseFatal(WRONGEMAILERROR, sArg, sEmail)
+ GoTo Finally
+CatchNotExists:
+ SF_Exception.RaiseFatal(UNKNOWNFILEERROR, &quot;FileNames&quot;, sFile)
+ GoTo Finally
+CatchMail:
+ SF_Exception.RaiseFatal(SENDMAILERROR)
+ GoTo Finally
+End Sub &apos; ScriptForge.SF_Session.SendMail
+
+REM -----------------------------------------------------------------------------
+Public Function SetPDFExportOptions(Optional ByRef PDFOptions As Variant) As Boolean
+&apos;&apos;&apos; Modify the actual values of the PDF export options from an options dictionary
+&apos;&apos;&apos; The PDF options are described on https://wiki.openoffice.org/wiki/API/Tutorials/PDF_export
+&apos;&apos;&apos; PDF options are set at each use of the Export as ... PDF command by the user and kept
+&apos;&apos;&apos; permanently until their reset by script (like this one) or by a new export
+&apos;&apos;&apos; The changed options are applicable on any subsequent ExportToPDF user command or to any SaveAsPDF script execution
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; PDFOptions: a ScriptForge dictionary object
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True when successful
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; Dim dict As Object
+&apos;&apos;&apos; Set dict = session.GetPDFExportOptions()
+&apos;&apos;&apos; dict.ReplaceItem(&quot;Quality&quot;, 50)
+&apos;&apos;&apos; session.SetPDFExportOptions(dict)
+
+Dim bSetPDF As Boolean &apos; Returned value
+Dim oConfig As Object &apos; com.sun.star.configuration.ConfigurationProvider
+Dim oNodePath As Object &apos; com.sun.star.beans.PropertyValue
+Dim oOptions As Object &apos; configmgr.RootAccess
+Dim vOptionNames As Variant &apos; Array of PDF options names
+Dim vOptionValues As Variant &apos; Array of PDF options values
+Dim oDict As Object &apos; Alias of PDFOptions
+Dim i As Long
+
+Const cstThisSub = &quot;Session.SetPDFExportOptions&quot;
+Const cstSubArgs = &quot;PDFOptions&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bSetPDF = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(PDFOptions, &quot;PDFOptions&quot;, V_OBJECT, , , &quot;DICTIONARY&quot;) Then GoTo Finally
+ End If
+
+Try:
+ &apos; Get the (updatable) internal PDF options
+ Set oConfig = SF_Utils._GetUNOService(&quot;ConfigurationProvider&quot;)
+ Set oNodePath = SF_Utils._MakePropertyValue(&quot;nodepath&quot;, &quot;/org.openoffice.Office.Common/Filter/PDF/Export/&quot;)
+ Set oOptions = oConfig.createInstanceWithArguments(&quot;com.sun.star.configuration.ConfigurationUpdateAccess&quot;, Array(oNodePath))
+
+ &apos; Copy the options from the ScriptForge dictionary in argument to property values
+ Set oDict = PDFOptions
+ oOptions.setPropertyValues(oDict.Keys, oDict.Items)
+ oOptions.commitChanges()
+
+ bSetPDF = True
+
+Finally:
+ SetPDFExportOptions = bSetPDF
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Session.SetPDFExportOptions
+
+REM -----------------------------------------------------------------------------
+Public Function SetProperty(Optional ByVal PropertyName As Variant _
+ , Optional ByRef Value As Variant _
+ ) As Boolean
+&apos;&apos;&apos; Set a new value to the given property
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; PropertyName: the name of the property as a string
+&apos;&apos;&apos; Value: its new value
+&apos;&apos;&apos; Exceptions
+&apos;&apos;&apos; ARGUMENTERROR The property does not exist
+
+Const cstThisSub = &quot;Session.SetProperty&quot;
+Const cstSubArgs = &quot;PropertyName, Value&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ SetProperty = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
+ End If
+
+Try:
+ Select Case UCase(PropertyName)
+ Case Else
+ End Select
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Session.SetProperty
+
+REM -----------------------------------------------------------------------------
+Public Function UnoMethods(Optional ByRef UnoObject As Variant) As Variant
+&apos;&apos;&apos; Returns a list of the methods callable from an UNO object
+&apos;&apos;&apos; Code-snippet derived from XRAY
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; UnoObject: the object to identify
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; A zero-based sorted array. May be empty
+
+Dim oIntrospect As Object &apos; com.sun.star.beans.Introspection
+Dim oInspect As Object &apos; com.sun.star.beans.XIntrospectionAccess
+Dim vMethods As Variant &apos; Array of com.sun.star.reflection.XIdlMethod
+Dim vMethod As Object &apos; com.sun.star.reflection.XIdlMethod
+Dim lMax As Long &apos; UBounf of vMethods
+Dim vMethodsList As Variant &apos; Return value
+Dim i As Long
+Const cstThisSub = &quot;Session.UnoMethods&quot;
+Const cstSubArgs = &quot;UnoObject&quot;
+
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Check:
+ vMethodsList = Array()
+ If VarType(UnoObject) &lt;&gt; V_OBJECT Then GoTo Finally
+ If IsNull(UnoObject) Then GoTo Finally
+
+Try:
+ On Local Error GoTo Catch
+ Set oIntrospect = SF_Utils._GetUNOService(&quot;Introspection&quot;)
+ Set oInspect = oIntrospect.inspect(UnoObject)
+ vMethods = oInspect.getMethods(com.sun.star.beans.MethodConcept.ALL)
+
+ &apos; The names must be extracted from com.sun.star.reflection.XIdlMethod structures
+ lMax = UBound(vMethods)
+ If lMax &gt;= 0 Then
+ ReDim vMethodsList(0 To lMax)
+ For i = 0 To lMax
+ vMethodsList(i) = vMethods(i).Name
+ Next i
+ vMethodsList = SF_Array.Sort(vMethodsList, CaseSensitive := True)
+ End If
+
+Finally:
+ UnoMethods = vMethodsList
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ On Local Error GoTo 0
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Session.UnoMethods
+
+REM -----------------------------------------------------------------------------
+Public Function UnoObjectType(Optional ByRef UnoObject As Variant) As String
+&apos;&apos;&apos; Identify the UNO type of an UNO object
+&apos;&apos;&apos; Code-snippet derived from XRAY
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; UnoObject: the object to identify
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; com.sun.star. ... as a string
+&apos;&apos;&apos; a zero-length string if identification was not successful
+
+Dim oObjDesc As Object &apos; _ObjectDescriptor type
+Dim sObjectType As String &apos; Return value
+Const cstThisSub = &quot;Session.UnoObjectType&quot;
+Const cstSubArgs = &quot;UnoObject&quot;
+
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Check:
+ sObjectType = &quot;&quot;
+ If VarType(UnoObject) &lt;&gt; V_OBJECT Then GoTo Finally
+ If IsNull(UnoObject) Then GoTo Finally
+
+Try:
+ Set oObjDesc = SF_Utils._VarTypeObj(UnoObject)
+ If oObjDesc.iVarType = V_UNOOBJECT Then sObjectType = oObjDesc.sObjectType
+
+Finally:
+ UnoObjectType = sObjectType
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+End Function &apos; ScriptForge.SF_Session.UnoObjectType
+
+REM -----------------------------------------------------------------------------
+Public Function UnoProperties(Optional ByRef UnoObject As Variant) As Variant
+&apos;&apos;&apos; Returns a list of the properties of an UNO object
+&apos;&apos;&apos; Code-snippet derived from XRAY
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; UnoObject: the object to identify
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; A zero-based sorted array. May be empty
+
+Dim oIntrospect As Object &apos; com.sun.star.beans.Introspection
+Dim oInspect As Object &apos; com.sun.star.beans.XIntrospectionAccess
+Dim vProperties As Variant &apos; Array of com.sun.star.beans.Property
+Dim vProperty As Object &apos; com.sun.star.beans.Property
+Dim lMax As Long &apos; UBounf of vProperties
+Dim vPropertiesList As Variant &apos; Return value
+Dim i As Long
+Const cstThisSub = &quot;Session.UnoProperties&quot;
+Const cstSubArgs = &quot;UnoObject&quot;
+
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Check:
+ vPropertiesList = Array()
+ If VarType(UnoObject) &lt;&gt; V_OBJECT Then GoTo Finally
+ If IsNull(UnoObject) Then GoTo Finally
+
+Try:
+ On Local Error GoTo Catch
+ Set oIntrospect = SF_Utils._GetUNOService(&quot;Introspection&quot;)
+ Set oInspect = oIntrospect.inspect(UnoObject)
+ vProperties = oInspect.getProperties(com.sun.star.beans.PropertyConcept.ALL)
+
+ &apos; The names must be extracted from com.sun.star.beans.Property structures
+ lMax = UBound(vProperties)
+ If lMax &gt;= 0 Then
+ ReDim vPropertiesList(0 To lMax)
+ For i = 0 To lMax
+ vPropertiesList(i) = vProperties(i).Name
+ Next i
+ vPropertiesList = SF_Array.Sort(vPropertiesList, CaseSensitive := True)
+ End If
+
+Finally:
+ UnoProperties = vPropertiesList
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ On Local Error GoTo 0
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Session.UnoProperties
+
+REM -----------------------------------------------------------------------------
+Public Function WebService(Optional ByVal URI As Variant) As String
+&apos;&apos;&apos; Get some web content from a URI
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; URI: URI text of the web service
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The web page content of the URI
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; CALCFUNCERROR
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; session.WebService(&quot;wiki.documentfoundation.org/api.php?&quot; _
+&apos;&apos;&apos; &amp; &quot;hidebots=1&amp;days=7&amp;limit=50&amp;action=feedrecentchanges&amp;feedformat=rss&quot;)
+
+Dim sReturn As String &apos; Returned value
+Const cstThisSub = &quot;Session.WebService&quot;
+Const cstSubArgs = &quot;URI&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sReturn = &quot;&quot;
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(URI, &quot;URI&quot;, V_STRING) Then GoTo Finally
+ End If
+
+Try:
+ sReturn = SF_Session.ExecuteCalcFunction(&quot;WEBSERVICE&quot;, URI)
+
+Finally:
+ WebService = sReturn
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Session.WebService
+
+REM =========================================================== PRIVATE FUNCTIONS
+
+REM -----------------------------------------------------------------------------
+Private Function _ExecuteScript(ByVal psScript As String _
+ , Optional ByRef pvArg As Variant _
+ ) As Variant
+&apos;&apos;&apos; Execute the script expressed in the scripting framework_URI notation
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; psScript: read https://wiki.documentfoundation.org/Documentation/DevGuide/Scripting_Framework#Scripting_Framework_URI_Specification
+&apos;&apos;&apos; pvArg: the unique argument to pass to the called script.
+&apos;&apos;&apos; It is often an event object that triggered the execution of the script.
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The return value after the script execution. May be ignored for events
+
+Dim sScope As String &apos; The scope part of the script URI
+Dim sLanguage As String &apos; The language part of the script URI
+Dim sScript As String &apos; The script part of the script URI
+Dim vStrings As Variant &apos; Array of strings: (script, language, scope)
+Const cstComma = &quot;,&quot;
+
+Try:
+ If ScriptForge.SF_String.StartsWith(psScript, cstScript1) Then
+ &apos; Parse script
+ vStrings = Split( _
+ Replace( _
+ Replace(Mid(psScript, Len(cstScript1) + 1), cstScript2, cstComma) _
+ , cstScript3, cstComma) _
+ , cstComma)
+ sScript = vStrings(0) : sLanguage = vStrings(1) : sScope = vStrings(2)
+ &apos; Execute script
+ If UCase(sLanguage) = &quot;BASIC&quot; Then
+ _ExecuteScript = ExecuteBasicScript(sScope, sScript, pvArg)
+ Else &apos; Python
+ _ExecuteScript = ExecutePythonScript(sScope, sScript, pvArg)
+ End If
+ End If
+
+End Function &apos; ScriptForge.SF_Session._ExecuteScript
+
+REM -----------------------------------------------------------------------------
+Private Function _GetScript(ByVal psLanguage As String _
+ , ByVal psScope As String _
+ , ByVal psScript As String _
+ ) As Object
+&apos;&apos;&apos; Get the adequate script provider and from there the requested script
+&apos;&apos;&apos; Called by ExecuteBasicScript() and ExecutePythonScript()
+&apos;&apos;&apos; The execution of the script is done by the caller
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; psLanguage: Basic or Python
+&apos;&apos;&apos; psScope: one of the SCRIPTISxxx constants
+&apos;&apos;&apos; The SCRIPTISOXT constant is an alias for 2 cases, extension either
+&apos;&apos;&apos; installed for one user only, or for all users
+&apos;&apos;&apos; Managed here by trial and error
+&apos;&apos;&apos; psScript: Read https://wiki.documentfoundation.org/Documentation/DevGuide/Scripting_Framework#Scripting_Framework_URI_Specification
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; A com.sun.star.script.provider.XScript object
+
+Dim sScript As String &apos; The complete script string
+Dim oScriptProvider As Object &apos; Script provider singleton
+Dim oScript As Object &apos; Return value
+
+Try:
+ &apos; Build script string
+ sScript = cstScript1 &amp; psScript &amp; cstScript2 &amp; psLanguage &amp; cstScript3 &amp; LCase(psScope)
+
+ &apos; Find script
+ Set oScript = Nothing
+ &apos; Python only: installation of extension is determined by user =&gt; unknown to script author
+ If psScope = SCRIPTISOXT Then &apos; =&gt; Trial and error
+ On Local Error GoTo ForAllUsers
+ sScript = cstScript1 &amp; psScript &amp; cstScript2 &amp; psLanguage &amp; cstScript3 &amp; SCRIPTISPERSOXT
+ Set oScriptProvider = SF_Utils._GetUNOService(&quot;ScriptProvider&quot;, SCRIPTISPERSOXT)
+ Set oScript = oScriptProvider.getScript(sScript)
+ End If
+ ForAllUsers:
+ On Local Error GoTo CatchNotFound
+ If IsNull(oScript) Then
+ If psScope = SCRIPTISOXT Then psScope = SCRIPTISSHAROXT
+ Set oScriptProvider = SF_Utils._GetUNOService(&quot;ScriptProvider&quot;, psScope)
+ Set oScript = oScriptProvider.getScript(sScript)
+ End If
+
+Finally:
+ _GetScript = oScript
+ Exit Function
+CatchNotFound:
+ SF_Exception.RaiseFatal(NOSCRIPTERROR, psLanguage, &quot;Scope&quot;, psScope, &quot;Script&quot;, psScript)
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Session._GetScript
+
+REM =============================================== END OF SCRIPTFORGE.SF_SESSION
+</script:module> \ No newline at end of file
diff --git a/wizards/source/scriptforge/SF_String.xba b/wizards/source/scriptforge/SF_String.xba
new file mode 100644
index 000000000..888cf672c
--- /dev/null
+++ b/wizards/source/scriptforge/SF_String.xba
@@ -0,0 +1,2734 @@
+<?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_String" 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_String
+&apos;&apos;&apos; =========
+&apos;&apos;&apos; Singleton class implementing the &quot;ScriptForge.String&quot; service
+&apos;&apos;&apos; Implemented as a usual Basic module
+&apos;&apos;&apos; Focus on string manipulation, regular expressions, encodings and hashing algorithms
+&apos;&apos;&apos; The first argument of almost every method is the string to consider
+&apos;&apos;&apos; It is always passed by reference and left unchanged
+&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; Definitions
+&apos;&apos;&apos; Line breaks: symbolic name(Ascii number)
+&apos;&apos;&apos; LF(10), VT(12), CR(13), LF+CR, File separator(28), Group separator(29), Record separator(30),
+&apos;&apos;&apos; Next Line(133), Line separator(8232), Paragraph separator(8233)
+&apos;&apos;&apos; Whitespaces: symbolic name(Ascii number)
+&apos;&apos;&apos; Space(32), HT(9), LF(10), VT(11), FF(12), CR(13), Next Line(133), No-break space(160),
+&apos;&apos;&apos; Line separator(8232), Paragraph separator(8233)
+&apos;&apos;&apos; A quoted string:
+&apos;&apos;&apos; The quoting character must be the double quote (&quot;)
+&apos;&apos;&apos; To preserve a quoting character inside the quoted substring, use (\) or (&quot;) as escape character
+&apos;&apos;&apos; =&gt; [str\&quot;i&quot;&quot;ng] means [str&quot;i&quot;ng]
+&apos;&apos;&apos; Escape sequences: symbolic name(Ascii number) = escape sequence
+&apos;&apos;&apos; Line feed(10) = &quot;\n&quot;
+&apos;&apos;&apos; Carriage return(13) = &quot;\r&quot;
+&apos;&apos;&apos; Horizontal tab(9) = &quot;\t&quot;
+&apos;&apos;&apos; Double the backslash to ignore the sequence, e.g. &quot;\\n&quot; means &quot;\n&quot; (not &quot;\&quot; &amp; Chr(10)).
+&apos;&apos;&apos; Not printable characters:
+&apos;&apos;&apos; Defined in the Unicode character database as “Other” or “Separator”
+&apos;&apos;&apos; In particular, &quot;control&quot; characters (ascii code &lt;= 0x1F) are not printable
+&apos;&apos;&apos;
+&apos;&apos;&apos; Detailed user documentation:
+&apos;&apos;&apos; https://help.libreoffice.org/latest/en-US/text/sbasic/shared/03/sf_string.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;
+&apos;&apos;&apos; Some references:
+&apos;&apos;&apos; https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1i18n_1_1KCharacterType.html
+&apos;&apos;&apos; com.sun.star.i18n.KCharacterType.###
+&apos;&apos;&apos; https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1i18n_1_1XCharacterClassification.html
+&apos;&apos;&apos; com.sun.star.i18n.XCharacterClassification
+
+REM ============================================================ MODULE CONSTANTS
+
+&apos;&apos;&apos; Most expressions below are derived from https://www.regular-expressions.info/
+
+Const REGEXALPHA = &quot;^[A-Za-z]+$&quot; &apos; Not used
+Const REGEXALPHANUM = &quot;^[\w]+$&quot;
+Const REGEXDATEDAY = &quot;(0[1-9]|[12][0-9]|3[01])&quot;
+Const REGEXDATEMONTH = &quot;(0[1-9]|1[012])&quot;
+Const REGEXDATEYEAR = &quot;(19|20)\d\d&quot;
+Const REGEXTIMEHOUR = &quot;(0[1-9]|1[0-9]|2[0123])&quot;
+Const REGEXTIMEMIN = &quot;([0-5][0-9])&quot;
+Const REGEXTIMESEC = REGEXTIMEMIN
+Const REGEXDIGITS = &quot;^[0-9]+$&quot;
+Const REGEXEMAIL = &quot;^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$&quot;
+Const REGEXFILELINUX = &quot;^[^&lt;&gt;:;,?&quot;&quot;*|\\]+$&quot;
+Const REGEXFILEWIN = &quot;^([A-Z]|[a-z]:)?[^&lt;&gt;:;,?&quot;&quot;*|]+$&quot;
+Const REGEXHEXA = &quot;^(0X|&amp;H)?[0-9A-F]+$&quot; &apos; Includes 0xFF and &amp;HFF
+Const REGEXIPV4 = &quot;^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$&quot;
+Const REGEXNUMBER = &quot;^[-+]?(([0-9]+)?\.)?[0-9]+([eE][-+]?[0-9]+)?$&quot;
+Const REGEXURL = &quot;^(https?|ftp)://[^\s/$.?#].[^\s]*$&quot;
+Const REGEXWHITESPACES = &quot;^[\s]+$&quot;
+Const REGEXLTRIM = &quot;^[\s]+&quot;
+Const REGEXRTRIM = &quot;[\s]+$&quot;
+Const REGEXSPACES = &quot;[\s]+&quot;
+
+&apos;&apos;&apos; Accented characters substitution: https://docs.google.com/spreadsheets/d/1pJKSueZK8RkAcJFQIiKpYUamWSC1u1xVQchK7Z7BIwc/edit#gid=0
+&apos;&apos;&apos; (Many of them are in the list, but do not consider the list as closed vs. the Unicode database)
+
+Const cstCHARSWITHACCENT = &quot;ÀÁÂÃÄÅÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖÙÚÛÜÝàáâãäåçèéêëìíîïðñòóôõöùúûüýÿŠšŸŽž&quot; _
+ &amp; &quot;ĂăĐđĨĩŨũƠơƯưẠạẢảẤấẦầẨẩẪẫẬậẮắẰằẲẳẴẵẶặẸẹẺẻẼẽẾếỀềỂểỄễỆệỈỉỊịỌọỎỏỐốỒồỔổỖỗỘộỚớỜờỞởỠỡỢợỤụỦủỨứỪừỬửỮữỰựỲỳỴỵỶỷỸỹ₫&quot;
+Const cstCHARSWITHOUTACCENT = &quot;AAAAAACEEEEIIIIDNOOOOOUUUUYaaaaaaceeeeiiiidnooooouuuuyySsYZz&quot; _
+ &amp; &quot;AaDdIiUuOoUuAaAaAaAaAaAaAaAaAaAaAaAaEeEeEeEeEeEeEeEeIiIiOoOoOoOoOoOoOoOoOoOoOoOoUuUuUuUuUuUuUuYyYyYyYyd&quot;
+
+REM ===================================================== CONSTRUCTOR/DESTRUCTOR
+
+REM -----------------------------------------------------------------------------
+Public Function Dispose() As Variant
+ Set Dispose = Nothing
+End Function &apos; ScriptForge.SF_String Explicit destructor
+
+REM ================================================================== PROPERTIES
+
+REM -----------------------------------------------------------------------------
+Property Get CHARSWITHACCENT() As String
+&apos;&apos;&apos; Latin accents
+ CHARSWITHACCENT = cstCHARSWITHACCENT
+End Property &apos; ScriptForge.SF_String.CHARSWITHACCENT
+
+REM -----------------------------------------------------------------------------
+Property Get CHARSWITHOUTACCENT() As String
+&apos;&apos;&apos; Latin accents
+ CHARSWITHOUTACCENT = cstCHARSWITHOUTACCENT
+End Property &apos; ScriptForge.SF_String.CHARSWITHOUTACCENT
+
+&apos;&apos;&apos; Symbolic constants for linebreaks
+REM -----------------------------------------------------------------------------
+Property Get sfCR() As Variant
+&apos;&apos;&apos; Carriage return
+ sfCR = Chr(13)
+End Property &apos; ScriptForge.SF_String.sfCR
+
+REM -----------------------------------------------------------------------------
+Property Get sfCRLF() As Variant
+&apos;&apos;&apos; Carriage return
+ sfCRLF = Chr(13) &amp; Chr(10)
+End Property &apos; ScriptForge.SF_String.sfCRLF
+
+REM -----------------------------------------------------------------------------
+Property Get sfLF() As Variant
+&apos;&apos;&apos; Linefeed
+ sfLF = Chr(10)
+End Property &apos; ScriptForge.SF_String.sfLF
+
+REM -----------------------------------------------------------------------------
+Property Get sfNEWLINE() As Variant
+&apos;&apos;&apos; Linefeed or Carriage return + Linefeed
+ sfNEWLINE = Iif(GetGuiType() = 1, Chr(13), &quot;&quot;) &amp; Chr(10)
+End Property &apos; ScriptForge.SF_String.sfNEWLINE
+
+REM -----------------------------------------------------------------------------
+Property Get sfTAB() As Variant
+&apos;&apos;&apos; Horizontal tabulation
+ sfTAB = Chr(9)
+End Property &apos; ScriptForge.SF_String.sfTAB
+
+REM -----------------------------------------------------------------------------
+Property Get ObjectType As String
+&apos;&apos;&apos; Only to enable object representation
+ ObjectType = &quot;SF_String&quot;
+End Property &apos; ScriptForge.SF_String.ObjectType
+
+REM -----------------------------------------------------------------------------
+Property Get ServiceName As String
+&apos;&apos;&apos; Internal use
+ ServiceName = &quot;ScriptForge.String&quot;
+End Property &apos; ScriptForge.SF_String.ServiceName
+
+REM ============================================================== PUBLIC METHODS
+
+REM -----------------------------------------------------------------------------
+Public Function Capitalize(Optional ByRef InputStr As Variant) As String
+&apos;&apos;&apos; Return the input string with the 1st character of each word in title case
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The input string with the 1st character of each word in title case
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.Capitalize(&quot;this is a title for jean-pierre&quot;) returns &quot;This Is A Title For Jean-Pierre&quot;
+
+Dim sCapital As String &apos; Return value
+Dim lLength As Long &apos; Length of input string
+Dim oLocale As Object &apos; com.sun.star.lang.Locale
+Dim oChar As Object &apos; com.sun.star.i18n.CharacterClassification
+Const cstThisSub = &quot;String.Capitalize&quot;
+Const cstSubArgs = &quot;InputStr&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sCapital = &quot;&quot;
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ End If
+
+Try:
+ lLength = Len(InputStr)
+ If lLength &gt; 0 Then
+ Set oLocale = SF_Utils._GetUNOService(&quot;SystemLocale&quot;)
+ Set oChar = SF_Utils._GetUNOService(&quot;CharacterClass&quot;)
+ sCapital = oChar.toTitle(InputStr, 0, lLength * 4, oLocale) &apos; length * 4 because length is expressed in bytes
+ End If
+
+Finally:
+ Capitalize = sCapital
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.Capitalize
+
+REM -----------------------------------------------------------------------------
+Public Function Count(Optional ByRef InputStr As Variant _
+ , Optional ByVal Substring As Variant _
+ , Optional ByRef IsRegex As Variant _
+ , Optional ByVal CaseSensitive As Variant _
+ ) As Long
+&apos;&apos;&apos; Counts the number of occurrences of a substring or a regular expression within a string
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input stringto examine
+&apos;&apos;&apos; Substring: the substring to identify
+&apos;&apos;&apos; IsRegex: True if Substring is a regular expression (default = False)
+&apos;&apos;&apos; CaseSensitive: default = False
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The number of occurrences as a Long
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.Count(&quot;Lorem ipsum dolor sit amet, consectetur adipiscing elit.&quot;, &quot;\b[a-z]+\b&quot;, IsRegex := True, CaseSensitive := True)
+&apos;&apos;&apos; returns 7 (the number of words in lower case)
+&apos;&apos;&apos; SF_String.Count(&quot;Lorem ipsum dolor sit amet, consectetur adipiscing elit.&quot;, &quot;or&quot;, CaseSensitive := False)
+&apos;&apos;&apos; returns 2
+
+
+Dim lOccurrences As Long &apos; Return value
+Dim lStart As Long &apos; Start index of search
+Dim sSubstring As String &apos; Substring to replace
+Dim iCaseSensitive As Integer &apos; Integer alias for boolean CaseSensitive
+Const cstThisSub = &quot;String.Count&quot;
+Const cstSubArgs = &quot;InputStr, Substring, [IsRegex=False], [CaseSensitive=False]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ lOccurrences = 0
+
+Check:
+ If IsMissing(IsRegex) Or IsEmpty(IsRegex) Then IsRegex = False
+ If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(Substring, &quot;Substring&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(IsRegex, &quot;IsRegex&quot;, V_BOOLEAN) Then GoTo Finally
+ If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
+ End If
+
+Try:
+ iCaseSensitive = Iif(CaseSensitive, 0, 1) &apos; 1 = False ;)
+ lStart = 1
+
+ Do While lStart &gt;= 1 And lStart &lt;= Len(InputStr)
+ Select Case IsRegex
+ Case False &apos; Use InStr
+ lStart = InStr(lStart, InputStr, Substring, iCaseSensitive)
+ If lStart = 0 Then Exit Do
+ lStart = lStart + Len(Substring)
+ Case True &apos; Use FindRegex
+ sSubstring = SF_String.FindRegex(InputStr, Substring, lStart, CaseSensitive)
+ If lStart = 0 Then Exit Do
+ lStart = lStart + Len(sSubstring)
+ End Select
+ lOccurrences = lOccurrences + 1
+ Loop
+
+Finally:
+ Count = lOccurrences
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.Count
+
+REM -----------------------------------------------------------------------------
+Public Function EndsWith(Optional ByRef InputStr As Variant _
+ , Optional ByVal Substring As Variant _
+ , Optional ByVal CaseSensitive As Variant _
+ ) As Boolean
+&apos;&apos;&apos; Returns True if the last characters of InputStr are identical to Substring
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string
+&apos;&apos;&apos; Substring: the suffixing characters
+&apos;&apos;&apos; CaseSensitive: default = False
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if the comparison is satisfactory
+&apos;&apos;&apos; False if either InputStr or Substring have a length = 0
+&apos;&apos;&apos; False if Substr is longer than InputStr
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.EndsWith(&quot;abcdefg&quot;, &quot;EFG&quot;) returns True
+&apos;&apos;&apos; SF_String.EndsWith(&quot;abcdefg&quot;, &quot;EFG&quot;, CaseSensitive := True) returns False
+
+Dim bEndsWith As Boolean &apos; Return value
+Dim lSub As Long &apos; Length of SUbstring
+Const cstThisSub = &quot;String.EndsWith&quot;
+Const cstSubArgs = &quot;InputStr, Substring, [CaseSensitive=False]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bEndsWith = False
+
+Check:
+ If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(Substring, &quot;Substring&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
+ End If
+
+Try:
+ lSub = Len(Substring)
+ If Len(InputStr) &gt; 0 And lSub &gt; 0 And lSub &lt;= Len(InputStr) Then
+ bEndsWith = ( StrComp(Right(InputStr, lSub), Substring, Iif(CaseSensitive, 1, 0)) = 0 )
+ End If
+
+Finally:
+ EndsWith = bEndsWith
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.EndsWith
+
+REM -----------------------------------------------------------------------------
+Public Function Escape(Optional ByRef InputStr As Variant) As String
+&apos;&apos;&apos; Convert any hard line breaks or tabs by their escaped equivalent
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The input string after replacement of &quot;\&quot;, Chr(10), Chr(13), Chr(9)characters
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.Escape(&quot;abc&quot; &amp; Chr(10) &amp; Chr(9) &amp; &quot;def\n&quot;) returns &quot;abc\n\tdef\\n&quot;
+
+Dim sEscape As String &apos; Return value
+Const cstThisSub = &quot;String.Escape&quot;
+Const cstSubArgs = &quot;InputStr&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sEscape = &quot;&quot;
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ End If
+
+Try:
+ sEscape = SF_String.ReplaceStr( InputStr _
+ , Array(&quot;\&quot;, SF_String.sfLF, SF_String.sfCR, SF_String.sfTAB) _
+ , Array(&quot;\\&quot;, &quot;\n&quot;, &quot;\r&quot;, &quot;\t&quot;) _
+ )
+
+Finally:
+ Escape = sEscape
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.Escape
+
+REM -----------------------------------------------------------------------------
+Public Function ExpandTabs(Optional ByRef InputStr As Variant _
+ , Optional ByVal TabSize As Variant _
+ ) As String
+&apos;&apos;&apos; Return the input string with each TAB (Chr(9)) character replaced by the adequate number of spaces
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string
+&apos;&apos;&apos; TabSize: defines the TAB positions at TabSize + 1, 2 * TabSize + 1 , ... N * TabSize + 1
+&apos;&apos;&apos; Default = 8
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The input string with spaces replacing the TAB characters
+&apos;&apos;&apos; If the input string contains line breaks, the TAB positions are reset
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.ExpandTabs(&quot;abc&quot; &amp; SF_String.sfTAB &amp; SF_String.sfTAB &amp; &quot;def&quot;, 4) returns &quot;abc def&quot;
+&apos;&apos;&apos; SF_String.ExpandTabs(&quot;abc&quot; &amp; SF_String.sfTAB &amp; &quot;def&quot; &amp; SF_String.sfLF &amp; SF_String.sfTAB &amp; &quot;ghi&quot;)
+&apos;&apos;&apos; returns &quot;abc def&quot; &amp; SF_String.sfLF &amp; &quot; ghi&quot;
+
+Dim sExpanded As String &apos; Return value
+Dim lCharPosition As Long &apos; Position of current character in current line in expanded string
+Dim lSpaces As Long &apos; Spaces counter
+Dim sChar As String &apos; A single character
+Dim i As Long
+Const cstTabSize = 8
+Const cstThisSub = &quot;String.ExpandTabs&quot;
+Const cstSubArgs = &quot;InputStr, [TabSize=8]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sExpanded = &quot;&quot;
+
+Check:
+ If IsMissing(TabSize) Or IsEmpty(TabSize) Then TabSize = cstTabSize
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(TabSize, &quot;TabSize&quot;, V_NUMERIC) Then GoTo Finally
+ End If
+ If TabSize &lt;= 0 Then TabSize = cstTabSize
+
+Try:
+ lCharPosition = 0
+ If Len(InputStr) &gt; 0 Then
+ For i = 1 To Len(InputStr)
+ sChar = Mid(InputStr, i, 1)
+ Select Case sChar
+ Case SF_String.sfLF, Chr(12), SF_String.sfCR, Chr(28), Chr(29), Chr(30), Chr(133), Chr(8232), Chr(8233)
+ sExpanded = sExpanded &amp; sChar
+ lCharPosition = 0
+ Case SF_String.sfTAB
+ lSpaces = Int(lCharPosition / TabSize + 1) * TabSize - lCharPosition
+ sExpanded = sExpanded &amp; Space(lSpaces)
+ lCharPosition = lCharPosition + lSpaces
+ Case Else
+ sExpanded = sExpanded &amp; sChar
+ lCharPosition = lCharPosition + 1
+ End Select
+ Next i
+ End If
+
+Finally:
+ ExpandTabs = sExpanded
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.ExpandTabs
+
+REM -----------------------------------------------------------------------------
+Public Function FilterNotPrintable(Optional ByRef InputStr As Variant _
+ , Optional ByVal ReplacedBy As Variant _
+ ) As String
+&apos;&apos;&apos; Return the input string in which all the not printable characters are replaced by ReplacedBy
+&apos;&apos;&apos; Among others, control characters (Ascii &lt;= 1F) are not printable
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string
+&apos;&apos;&apos; ReplacedBy: zero, one or more characters replacing the found not printable characters
+&apos;&apos;&apos; Default = the zero-length string
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The input string in which all the not printable characters are replaced by ReplacedBy
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.FilterNotPrintable(&quot;àén ΣlPµ&quot; &amp; Chr(10) &amp; &quot; Русский&quot;, &quot;\n&quot;) returns &quot;àén ΣlPµ\n Русский&quot;
+
+Dim sPrintable As String &apos; Return value
+Dim bPrintable As Boolean &apos; Is a single character printable ?
+Dim lLength As Long &apos; Length of InputStr
+Dim lReplace As Long &apos; Length of ReplacedBy
+Dim oChar As Object &apos; com.sun.star.i18n.CharacterClassification
+Dim oLocale As Object &apos; com.sun.star.lang.Locale
+Dim lType As Long &apos; com.sun.star.i18n.KCharacterType
+Dim sChar As String &apos; A single character
+Dim lPRINTABLE As Long : lPRINTABLE = com.sun.star.i18n.KCharacterType.PRINTABLE
+Dim i As Long
+Const cstThisSub = &quot;String.FilterNotPrintable&quot;
+Const cstSubArgs = &quot;InputStr, [ReplacedBy=&quot;&quot;&quot;&quot;]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sPrintable = &quot;&quot;
+
+Check:
+ If IsMissing(ReplacedBy) Or IsEmpty(ReplacedBy) Then ReplacedBy = &quot;&quot;
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(ReplacedBy, &quot;ReplacedBy&quot;, V_STRING) Then GoTo Finally
+ End If
+
+Try:
+ lLength = Len(InputStr)
+ lReplace = Len(ReplacedBy)
+ If lLength &gt; 0 Then
+ Set oLocale = SF_Utils._GetUNOService(&quot;SystemLocale&quot;)
+ Set oChar = SF_Utils._GetUNOService(&quot;CharacterClass&quot;)
+ For i = 0 To lLength - 1
+ sChar = Mid(InputStr, i + 1, 1)
+ lType = oChar.getCharacterType(sChar, 0, oLocale)
+ &apos; Parenthses (), [], {} have a KCharacterType = 0
+ bPrintable = ( (lType And lPRINTABLE) = lPRINTABLE Or (lType = 0 And Asc(sChar) &lt;= 127) )
+ If Not bPrintable Then
+ If lReplace &gt; 0 Then sPrintable = sPrintable &amp; ReplacedBy
+ Else
+ sPrintable = sPrintable &amp; sChar
+ End If
+ Next i
+ End If
+
+Finally:
+ FilterNotPrintable = sPrintable
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.FilterNotPrintable
+
+REM -----------------------------------------------------------------------------
+Public Function FindRegex(Optional ByRef InputStr As Variant _
+ , Optional ByVal Regex As Variant _
+ , Optional ByRef Start As Variant _
+ , Optional ByVal CaseSensitive As Variant _
+ , Optional ByVal Forward As Variant _
+ ) As String
+&apos;&apos;&apos; Find in InputStr a substring matching a given regular expression
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string to be searched for the expression
+&apos;&apos;&apos; Regex: the regular expression
+&apos;&apos;&apos; Start (passed by reference): where to start searching from
+&apos;&apos;&apos; Should be = 1 (Forward = True) or = Len(InputStr) (Forward = False) the 1st time
+&apos;&apos;&apos; After execution points to the first character of the found substring
+&apos;&apos;&apos; CaseSensitive: default = False
+&apos;&apos;&apos; Forward: True (default) or False (backward)
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The found substring matching the regular expression
+&apos;&apos;&apos; A zero-length string if not found (Start is set to 0)
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; Dim lStart As Long : lStart = 1
+&apos;&apos;&apos; SF_String.FindRegex(&quot;abCcdefghHij&quot;, &quot;C.*H&quot;, lStart, CaseSensitive := True) returns &quot;CcdefghH&quot;
+&apos;&apos;&apos; Above statement may be reexecuted for searching the same or another pattern
+&apos;&apos;&apos; by starting from lStart + Len(matching string)
+
+Dim sOutput As String &apos; Return value
+Dim oTextSearch As Object &apos; com.sun.star.util.TextSearch
+Dim vOptions As Variant &apos; com.sun.star.util.SearchOptions
+Dim lEnd As Long &apos; Upper limit of search area
+Dim vResult As Object &apos; com.sun.star.util.SearchResult
+Const cstThisSub = &quot;String.FindRegex&quot;
+Const cstSubArgs = &quot;InputStr, Regex, [Start=1], [CaseSensitive=False], [Forward=True]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sOutput = &quot;&quot;
+
+Check:
+ If IsMissing(Start) Or IsEmpty(Start) Then Start = 1
+ If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False
+ If IsMissing(Forward) Or IsEmpty(Forward) Then Forward = True
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(Regex, &quot;Regex&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(Start, &quot;Start&quot;, V_NUMERIC) Then GoTo Finally
+ If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
+ If Not SF_Utils._Validate(Forward, &quot;Forward&quot;, V_BOOLEAN) Then GoTo Finally
+ End If
+ If Start &lt;= 0 Or Start &gt; Len(InputStr) Then GoTo Finally
+
+Try:
+ sOutput = &quot;&quot;
+ Set oTextSearch = SF_Utils._GetUNOService(&quot;TextSearch&quot;)
+ &apos; Set pattern search options
+ vOptions = SF_Utils._GetUNOService(&quot;SearchOptions&quot;)
+ With vOptions
+ .searchString = Regex
+ If CaseSensitive Then .transliterateFlags = 0 Else .transliterateFlags = com.sun.star.i18n.TransliterationModules.IGNORE_CASE
+ End With
+ &apos; Run search
+ With oTextSearch
+ .setOptions(vOptions)
+ If Forward Then
+ lEnd = Len(InputStr)
+ vResult = .searchForward(InputStr, Start - 1, lEnd)
+ Else
+ lEnd = 1
+ vResult = .searchBackward(InputStr, Start, lEnd - 1)
+ End If
+ End With
+ &apos; https://api.libreoffice.org/docs/idl/ref/structcom_1_1sun_1_1star_1_1util_1_1SearchResult.html
+ With vResult
+ If .subRegExpressions &gt;= 1 Then
+ If Forward Then
+ Start = .startOffset(0) + 1
+ lEnd = .endOffset(0) + 1
+ Else
+ Start = .endOffset(0) + 1
+ lEnd = .startOffset(0) + 1
+ End If
+ sOutput = Mid(InputStr, Start, lEnd - Start)
+ Else
+ Start = 0
+ End If
+ End With
+
+Finally:
+ FindRegex = sOutput
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.FindRegex
+
+REM -----------------------------------------------------------------------------
+Public Function GetProperty(Optional ByVal PropertyName As Variant) As Variant
+&apos;&apos;&apos; Return the actual value of the given property
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; PropertyName: the name of the property as a string
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The actual value of the property
+&apos;&apos;&apos; Exceptions
+&apos;&apos;&apos; ARGUMENTERROR The property does not exist
+
+Const cstThisSub = &quot;String.GetProperty&quot;
+Const cstSubArgs = &quot;PropertyName&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ GetProperty = Null
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
+ End If
+
+Try:
+ Select Case UCase(PropertyName)
+ Case &quot;SFCR&quot; : GetProperty = sfCR
+ Case &quot;SFCRLF&quot; : GetProperty = sfCRLF
+ Case &quot;SFLF&quot; : GetProperty = sfLF
+ Case &quot;SFNEWLINE&quot; : GetProperty = sfNEWLINE
+ Case &quot;SFTAB&quot; : GetProperty = sfTAB
+ Case Else
+ End Select
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.GetProperty
+
+REM -----------------------------------------------------------------------------
+Public Function HashStr(Optional ByVal InputStr As Variant _
+ , Optional ByVal Algorithm As Variant _
+ ) As String
+&apos;&apos;&apos; Return an hexadecimal string representing a checksum of the given input string
+&apos;&apos;&apos; Next algorithms are supported: MD5, SHA1, SHA224, SHA256, SHA384 and SHA512
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the string to be hashed
+&apos;&apos;&apos; Algorithm: The hashing algorithm to use
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The requested checksum as a string. Hexadecimal digits are lower-cased
+&apos;&apos;&apos; A zero-length string when an error occurred
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; Print SF_String.HashStr(&quot;œ∑¡™£¢∞§¶•ªº–≠œ∑´®†¥¨ˆøπ“‘åß∂ƒ©˙∆˚¬&quot;, &quot;MD5&quot;) &apos; 616eb9c513ad07cd02924b4d285b9987
+
+Dim sHash As String &apos; Return value
+Const cstPyHelper = &quot;$&quot; &amp; &quot;_SF_String__HashStr&quot;
+Const cstThisSub = &quot;String.HashStr&quot;
+Const cstSubArgs = &quot;InputStr, Algorithm=&quot;&quot;MD5&quot;&quot;|&quot;&quot;SHA1&quot;&quot;|&quot;&quot;SHA224&quot;&quot;|&quot;&quot;SHA256&quot;&quot;|&quot;&quot;SHA384&quot;&quot;|&quot;&quot;SHA512&quot;&quot;&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sHash = &quot;&quot;
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(Algorithm, &quot;Algorithm&quot;, V_STRING _
+ , Array(&quot;MD5&quot;, &quot;SHA1&quot;, &quot;SHA224&quot;, &quot;SHA256&quot;, &quot;SHA384&quot;, &quot;SHA512&quot;)) Then GoTo Finally
+ End If
+
+Try:
+ With ScriptForge.SF_Session
+ sHash = .ExecutePythonScript(.SCRIPTISSHARED, _SF_.PythonHelper &amp; cstPyHelper _
+ , InputStr, LCase(Algorithm))
+ End With
+
+Finally:
+ HashStr = sHash
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.HashStr
+
+REM -----------------------------------------------------------------------------
+Public Function HtmlEncode(Optional ByRef InputStr As Variant) As String
+&apos;&apos;&apos; &amp;-encoding of the input string (e.g. &quot;é&quot; becomes &quot;&amp;eacute;&quot; or numeric equivalent
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; the encoded string
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.HtmlEncode(&quot;&lt;a href=&quot;&quot;https://a.b.com&quot;&quot;&gt;From α to ω&lt;/a&gt;&quot;)
+&apos;&apos;&apos; returns &quot;&amp;lt;a href=&amp;quot;https://a.b.com&amp;quot;&amp;gt;From &amp;#945; to &amp;#969;&amp;lt;/a&amp;gt;&quot;
+
+Dim sEncode As String &apos; Return value
+Dim lPos As Long &apos; Position in InputStr
+Dim sChar As String &apos; A single character extracted from InputStr
+Dim i As Long
+Const cstThisSub = &quot;String.HtmlEncode&quot;
+Const cstSubArgs = &quot;InputStr&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sEncode = &quot;&quot;
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ End If
+
+Try:
+ If Len(InputStr) &gt; 0 Then
+ lPos = 1
+ sEncode = InputStr
+ Do While lPos &lt;= Len(sEncode)
+ sChar = Mid(sEncode, lPos, 1)
+ &apos; Leave as is or encode every single char
+ Select Case sChar
+ Case &quot;&quot;&quot;&quot; : sChar = &quot;&amp;quot;&quot;
+ Case &quot;&amp;&quot; : sChar = &quot;&amp;amp;&quot;
+ Case &quot;&lt;&quot; : sChar = &quot;&amp;lt;&quot;
+ Case &quot;&gt;&quot; : sChar = &quot;&amp;gt;&quot;
+ Case &quot;&apos;&quot; : sChar = &quot;&amp;apos;&quot;
+ Case &quot;:&quot;, &quot;/&quot;, &quot;?&quot;, &quot;#&quot;, &quot;[&quot;, &quot;]&quot;, &quot;@&quot; &apos; Reserved characters
+ Case SF_String.sfCR : sChar = &quot;&quot; &apos; Carriage return
+ Case SF_String.sfLF : sChar = &quot;&lt;br&gt;&quot; &apos; Line Feed
+ Case &lt; Chr(126)
+ Case &quot;€&quot; : sChar = &quot;&amp;euro;&quot;
+ Case Else : sChar = &quot;&amp;#&quot; &amp; Asc(sChar) &amp; &quot;;&quot;
+ End Select
+ If Len(sChar) = 1 Then
+ Mid(sEncode, lPos, 1) = sChar
+ Else
+ sEncode = Left(sEncode, lPos - 1) &amp; sChar &amp; Mid(sEncode, lPos + 1)
+ End If
+ lPos = lPos + Len(sChar)
+ Loop
+ End If
+
+Finally:
+ HtmlEncode = sEncode
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.HtmlEncode
+
+REM -----------------------------------------------------------------------------
+Public Function IsADate(Optional ByRef InputStr As Variant _
+ , Optional ByVal DateFormat _
+ ) As Boolean
+&apos;&apos;&apos; Return True if the string is a valid date respecting the given format
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string
+&apos;&apos;&apos; DateFormat: either YYYY-MM-DD (default), DD-MM-YYYY or MM-DD-YYYY
+&apos;&apos;&apos; The dash (-) may be replaced by a dot (.), a slash (/) or a space
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if the string contains a valid date and there is at least one character
+&apos;&apos;&apos; False otherwise or if the date format is invalid
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.IsADate(&quot;2019-12-31&quot;, &quot;YYYY-MM-DD&quot;) returns True
+
+Dim bADate As Boolean &apos; Return value
+Dim sFormat As String &apos; Alias for DateFormat
+Dim iYear As Integer &apos; Alias of year in input string
+Dim iMonth As Integer &apos; Alias of month in input string
+Dim iDay As Integer &apos; Alias of day in input string
+Dim dDate As Date &apos; Date value
+Const cstFormat = &quot;YYYY-MM-DD&quot; &apos; Default date format
+Const cstFormatRegex = &quot;(YYYY[- /.]MM[- /.]DD|MM[- /.]DD[- /.]YYYY|DD[- /.]MM[- /.]YYYY)&quot;
+ &apos; The regular expression the format must match
+Const cstThisSub = &quot;String.IsADate&quot;
+Const cstSubArgs = &quot;InputStr, [DateFormat=&quot;&quot;&quot; &amp; cstFormat &amp; &quot;&quot;&quot;]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bADate = False
+
+Check:
+ If IsMissing(DateFormat) Or IsEmpty(DateFormat) Then DateFormat = &quot;YYYY-MM-DD&quot;
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(DateFormat, &quot;DateFormat&quot;, V_STRING) Then GoTo Finally
+ End If
+ sFormat = UCase(DateFormat)
+ If Len(sFormat) &lt;&gt; Len(cstFormat)Then GoTo Finally
+ If sFormat &lt;&gt; cstFormat Then &apos; Do not check if default format
+ If Not SF_String.IsRegex(sFormat, cstFormatRegex) Then GoTo Finally
+ End If
+
+Try:
+ If Len(InputStr) = Len(DateFormat) Then
+ &apos; Extract the date components YYYY, MM, DD from the input string
+ iYear = CInt(Mid(InputStr, InStr(sFormat, &quot;YYYY&quot;), 4))
+ iMonth = CInt(Mid(InputStr, InStr(sFormat, &quot;MM&quot;), 2))
+ iDay = CInt(Mid(InputStr, InStr(sFormat, &quot;DD&quot;), 2))
+ &apos; Check the validity of the date
+ On Local Error GoTo NotADate
+ dDate = DateSerial(iYear, iMonth, iDay)
+ bADate = True &apos; Statement reached only if no error
+ End If
+
+Finally:
+ IsADate = bADate
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+NotADate:
+ On Error GoTo 0 &apos; Reset the error object
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.IsADate
+
+REM -----------------------------------------------------------------------------
+Public Function IsAlpha(Optional ByRef InputStr As Variant) As Boolean
+&apos;&apos;&apos; Return True if all characters in the string are alphabetic
+&apos;&apos;&apos; Alphabetic characters are those characters defined in the Unicode character database as “Letter”
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if the string is alphabetic and there is at least one character, False otherwise
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.IsAlpha(&quot;àénΣlPµ&quot;) returns True
+&apos;&apos;&apos; Note:
+&apos;&apos;&apos; Use SF_String.IsRegex(&quot;...&quot;, REGEXALPHA) to limit characters to latin alphabet
+
+Dim bAlpha As Boolean &apos; Return value
+Dim lLength As Long &apos; Length of InputStr
+Dim oChar As Object &apos; com.sun.star.i18n.CharacterClassification
+Dim oLocale As Object &apos; com.sun.star.lang.Locale
+Dim lType As Long &apos; com.sun.star.i18n.KCharacterType
+Dim lLETTER As Long : lLETTER = com.sun.star.i18n.KCharacterType.LETTER
+Dim i As Long
+Const cstThisSub = &quot;String.IsAlpha&quot;
+Const cstSubArgs = &quot;InputStr&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bAlpha = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ End If
+
+Try:
+ lLength = Len(InputStr)
+ If lLength &gt; 0 Then
+ Set oLocale = SF_Utils._GetUNOService(&quot;SystemLocale&quot;)
+ Set oChar = SF_Utils._GetUNOService(&quot;CharacterClass&quot;)
+ For i = 0 To lLength - 1
+ lType = oChar.getCharacterType(InputStr, i, oLocale)
+ bAlpha = ( (lType And lLETTER) = lLETTER )
+ If Not bAlpha Then Exit For
+ Next i
+ End If
+
+Finally:
+ IsAlpha = bAlpha
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.IsAlpha
+
+REM -----------------------------------------------------------------------------
+Public Function IsAlphaNum(Optional ByRef InputStr As Variant) As Boolean
+&apos;&apos;&apos; Return True if all characters in the string are alphabetic, digits or &quot;_&quot; (underscore)
+&apos;&apos;&apos; The first character must not be a digit
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if the string is alphanumeric and there is at least one character, False otherwise
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.IsAlphaNum(&quot;_ABC_123456_abcàénΣlPµ&quot;) returns True
+
+Dim bAlphaNum As Boolean &apos; Return value
+Dim sInputStr As String &apos; Alias of InputStr without underscores
+Dim sFirst As String &apos; Leftmost character of InputStr
+Dim lLength As Long &apos; Length of InputStr
+Dim oChar As Object &apos; com.sun.star.i18n.CharacterClassification
+Dim oLocale As Object &apos; com.sun.star.lang.Locale
+Dim lType As Long &apos; com.sun.star.i18n.KCharacterType
+Dim lLETTER As Long : lLETTER = com.sun.star.i18n.KCharacterType.LETTER
+Dim lDIGIT As Long : lDIGIT = com.sun.star.i18n.KCharacterType.DIGIT
+Dim i As Long
+Const cstThisSub = &quot;String.IsAlphaNum&quot;
+Const cstSubArgs = &quot;InputStr&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bAlphaNum = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ End If
+
+Try:
+ lLength = Len(InputStr)
+ If lLength &gt; 0 Then
+ sFirst = Left(InputStr, 1)
+ bAlphanum = ( sFirst &lt; &quot;0&quot; Or sFirst &gt; &quot;9&quot; )
+ If bAlphaNum Then
+ sInputStr = Replace(InputStr, &quot;_&quot;, &quot;A&quot;) &apos; Replace by an arbitrary alphabetic character
+ Set oLocale = SF_Utils._GetUNOService(&quot;SystemLocale&quot;)
+ Set oChar = SF_Utils._GetUNOService(&quot;CharacterClass&quot;)
+ For i = 0 To lLength - 1
+ lType = oChar.getCharacterType(sInputStr, i, oLocale)
+ bAlphaNum = ( (lType And lLETTER) = lLETTER _
+ Or (lType And lDIGIT) = lDIGIT )
+ If Not bAlphaNum Then Exit For
+ Next i
+ End If
+ End If
+
+Finally:
+ IsAlphaNum = bAlphaNum
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.IsAlphaNum
+
+REM -----------------------------------------------------------------------------
+Public Function IsAscii(Optional ByRef InputStr As Variant) As Boolean
+&apos;&apos;&apos; Return True if all characters in the string are Ascii characters
+&apos;&apos;&apos; Ascii characters are those characters defined between &amp;H00 and &amp;H7F
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if the string is Ascii and there is at least one character, False otherwise
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.IsAscii(&quot;a%?,25&quot;) returns True
+
+Dim bAscii As Boolean &apos; Return value
+Dim lLength As Long &apos; Length of InputStr
+Dim sChar As String &apos; Single character
+Dim i As Long
+Const cstThisSub = &quot;String.IsAscii&quot;
+Const cstSubArgs = &quot;InputStr&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bAscii = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ End If
+
+Try:
+ lLength = Len(InputStr)
+ If lLength &gt; 0 Then
+ For i = 1 To lLength
+ sChar = Mid(InputStr, i, 1)
+ bAscii = ( Asc(sChar) &lt;= 127 )
+ If Not bAscii Then Exit For
+ Next i
+ End If
+
+Finally:
+ IsAscii = bAscii
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.IsAscii
+
+REM -----------------------------------------------------------------------------
+Public Function IsDigit(Optional ByRef InputStr As Variant) As Boolean
+&apos;&apos;&apos; Return True if all characters in the string are digits
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if the string contains only digits and there is at least one character, False otherwise
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.IsDigit(&quot;123456&quot;) returns True
+
+Dim bDigit As Boolean &apos; Return value
+Const cstThisSub = &quot;String.IsDigit&quot;
+Const cstSubArgs = &quot;InputStr&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bDigit = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ End If
+
+Try:
+ If Len(InputStr) &gt; 0 Then bDigit = SF_String.IsRegex(InputStr, REGEXDIGITS, CaseSensitive := False)
+
+Finally:
+ IsDigit = bDigit
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.IsDigit
+
+REM -----------------------------------------------------------------------------
+Public Function IsEmail(Optional ByRef InputStr As Variant) As Boolean
+&apos;&apos;&apos; Return True if the string is a valid email address
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if the string contains an email address and there is at least one character, False otherwise
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.IsEmail(&quot;first.last@something.org&quot;) returns True
+
+Dim bEmail As Boolean &apos; Return value
+Const cstThisSub = &quot;String.IsEmail&quot;
+Const cstSubArgs = &quot;InputStr&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bEmail = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ End If
+
+Try:
+ If Len(InputStr) &gt; 0 Then bEmail = SF_String.IsRegex(InputStr, REGEXEMAIL, CaseSensitive := False)
+
+Finally:
+ IsEmail = bEmail
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.IsEmail
+
+REM -----------------------------------------------------------------------------
+Public Function IsFileName(Optional ByRef InputStr As Variant _
+ , Optional ByVal OSName As Variant _
+ ) As Boolean
+&apos;&apos;&apos; Return True if the string is a valid filename in a given operating system
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string
+&apos;&apos;&apos; OSName: Windows, Linux, macOS or Solaris
+&apos;&apos;&apos; The default is the current operating system on which the script is run
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if the string contains a valid filename and there is at least one character, False otherwise
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.IsFileName(&quot;/home/a file name.odt&quot;, &quot;LINUX&quot;) returns True
+
+Dim bFileName As Boolean &apos; Return value
+Dim sRegex As String &apos; Regex to apply depending on OS
+Const cstThisSub = &quot;String.IsFileName&quot;
+Const cstSubArgs = &quot;InputStr, [OSName=&quot;&quot;Windows&quot;&quot;|&quot;&quot;Linux&quot;&quot;|&quot;&quot;macOS&quot;&quot;|Solaris&quot;&quot;]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bFileName = False
+
+Check:
+ If IsMissing(OSName) Or IsEmpty(OSName) Then
+ If _SF_.OSname = &quot;&quot; Then _SF_.OSName = SF_Platform.OSName
+ OSName = _SF_.OSName
+ End If
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(OSName, &quot;OSName&quot;, V_STRING, Array(&quot;Windows&quot;, &quot;Linux&quot;, &quot;macOS&quot;, &quot;Solaris&quot;)) Then GoTo Finally
+ End If
+
+Try:
+ If Len(InputStr) &gt; 0 Then
+ Select Case UCase(OSName)
+ Case &quot;LINUX&quot;, &quot;MACOS&quot;, &quot;SOLARIS&quot; : sRegex = REGEXFILELINUX
+ Case &quot;WINDOWS&quot; : sRegex = REGEXFILEWIN
+ End Select
+ bFileName = SF_String.IsRegex(InputStr, sRegex, CaseSensitive := False)
+ End If
+
+Finally:
+ IsFileName = bFileName
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.IsFileName
+
+REM -----------------------------------------------------------------------------
+Public Function IsHexDigit(Optional ByRef InputStr As Variant) As Boolean
+&apos;&apos;&apos; Return True if all characters in the string are hexadecimal digits
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if the string contains only hexadecimal igits and there is at least one character
+&apos;&apos;&apos; The prefixes &quot;0x&quot; and &quot;&amp;H&quot; are admitted
+&apos;&apos;&apos; False otherwise
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.IsHexDigit(&quot;&amp;H00FF&quot;) returns True
+
+Dim bHexDigit As Boolean &apos; Return value
+Const cstThisSub = &quot;String.IsHexDigit&quot;
+Const cstSubArgs = &quot;InputStr&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bHexDigit = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ End If
+
+Try:
+ If Len(InputStr) &gt; 0 Then bHexDigit = SF_String.IsRegex(InputStr, REGEXHEXA, CaseSensitive := False)
+
+Finally:
+ IsHexDigit = bHexDigit
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.IsHexDigit
+
+REM -----------------------------------------------------------------------------
+Public Function IsIBAN(Optional ByVal InputStr As Variant) As Boolean
+&apos;&apos;&apos; Returns True if the input string is a valid International Bank Account Number
+&apos;&apos;&apos; Read https://en.wikipedia.org/wiki/International_Bank_Account_Number
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if the string contains a valid IBAN number. The comparison is not case-sensitive
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.IsIBAN(&quot;BR15 0000 0000 0000 1093 2840 814 P2&quot;) returns True
+
+Dim bIBAN As Boolean &apos; Return value
+Dim sIBAN As String &apos; Transformed input string
+Dim sChar As String &apos; A single character
+Dim sLetter As String &apos; Integer representation of letters
+Dim iIndex As Integer &apos; Index in IBAN string
+Dim sLong As String &apos; String representation of a Long
+Dim iModulo97 As Integer &apos; Remainder of division by 97
+Dim i As Integer
+Const cstThisSub = &quot;String.IsIBAN&quot;
+Const cstSubArgs = &quot;InputStr&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bIBAN = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ End If
+
+Try:
+ sIBAN = &quot;&quot;
+ &apos; 1. Remove spaces. Check that the total IBAN length is correct as per the country. If not, the IBAN is invalid
+ &apos; NOT DONE: Country specific
+ sIBAN = Replace(InputStr, &quot; &quot;, &quot;&quot;)
+ If Len(sIBAN) &lt; 5 Or Len(sIBAN) &gt; 34 Then GoTo Finally
+
+ &apos; 2. Move the four initial characters to the end of the string. String is case-insensitive
+ sIBAN = UCase(Mid(sIBAN, 5) &amp; Left(sIBAN, 4))
+
+ &apos; 3. Replace each letter in the string with two digits, thereby expanding the string, where A = 10, B = 11, ..., Z = 35
+ iIndex = 1
+ Do While iIndex &lt; Len(sIBAN)
+ sChar = Mid(sIBAN, iIndex, 1)
+ If sChar &gt;= &quot;A&quot; And sChar &lt;= &quot;Z&quot; Then
+ sLetter = CStr(Asc(sChar) - Asc(&quot;A&quot;) + 10)
+ sIBAN = Left(sIBAN, iIndex - 1) &amp; sLetter &amp; Mid(sIBAN, iIndex + 1)
+ iIndex = iIndex + 2
+ ElseIf sChar &lt; &quot;0&quot; Or sChar &gt; &quot;9&quot; Then &apos; Remove any non-alphanumeric character
+ GoTo Finally
+ Else
+ iIndex = iIndex + 1
+ End If
+ Loop
+
+ &apos; 4. Interpret the string as a decimal integer and compute the remainder of that number on division by 97
+ &apos; Computation is done in chunks of 9 digits
+ iIndex = 3
+ sLong = Left(sIBAN, 2)
+ Do While iIndex &lt;= Len(sIBAN)
+ sLong = sLong &amp; Mid(sIBAN, iIndex, 7)
+ iModulo97 = CLng(sLong) Mod 97
+ iIndex = iIndex + Len(sLong) - 2
+ sLong = Right(&quot;0&quot; &amp; CStr(iModulo97), 2) &apos; Force leading zero
+ Loop
+
+ bIBAN = ( iModulo97 = 1 )
+
+Finally:
+ IsIBAN = bIBAN
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.IsIBAN
+
+REM -----------------------------------------------------------------------------
+Public Function IsIPv4(Optional ByRef InputStr As Variant) As Boolean
+&apos;&apos;&apos; Return True if the string is a valid IPv4 address
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if the string contains a valid IPv4 address and there is at least one character, False otherwise
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.IsIPv4(&quot;192.168.1.50&quot;) returns True
+
+Dim bIPv4 As Boolean &apos; Return value
+Const cstThisSub = &quot;String.IsIPv4&quot;
+Const cstSubArgs = &quot;InputStr&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bIPv4 = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ End If
+
+Try:
+ If Len(InputStr) &gt; 0 Then bIPv4 = SF_String.IsRegex(InputStr, REGEXIPV4, CaseSensitive := False)
+
+Finally:
+ IsIPv4 = bIPv4
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.IsIPv4
+
+REM -----------------------------------------------------------------------------
+Public Function IsLike(Optional ByRef InputStr As Variant _
+ , Optional ByVal Pattern As Variant _
+ , Optional ByVal CaseSensitive As Variant _
+ ) As Boolean
+&apos;&apos;&apos; Returns True if the whole input string matches a given pattern containing wildcards
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string
+&apos;&apos;&apos; Pattern: the pattern as a string
+&apos;&apos;&apos; Admitted wildcard are: the &quot;?&quot; represents any single character
+&apos;&apos;&apos; the &quot;*&quot; represents zero, one, or multiple characters
+&apos;&apos;&apos; CaseSensitive: default = False
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if a match is found
+&apos;&apos;&apos; Zero-length input or pattern strings always return False
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.IsLike(&quot;aAbB&quot;, &quot;?A*&quot;) returns True
+&apos;&apos;&apos; SF_String.IsLike(&quot;C:\a\b\c\f.odb&quot;, &quot;?:*.*&quot;) returns True
+
+Dim bLike As Boolean &apos; Return value
+&apos; Build an equivalent regular expression by escaping the special characters present in Pattern
+Dim sRegex As String &apos; Equivalent regular expression
+Const cstSpecialChars = &quot;\,^,$,.,|,+,(,),[,{,?,*&quot; &apos; List of special chars in regular expressions
+Const cstEscapedChars = &quot;\\,\^,\$,\.,\|,\+,\(,\),\[,\{,.,.*&quot;
+
+Const cstThisSub = &quot;String.IsLike&quot;
+Const cstSubArgs = &quot;InputStr, Pattern, [CaseSensitive=False]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bLike = False
+
+Check:
+ If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(Pattern, &quot;Pattern&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
+ End If
+
+Try:
+ If Len(InputStr) &gt; 0 And Len(Pattern) &gt; 0 Then
+ &apos; Substitute special chars by escaped chars
+ sRegex = SF_String.ReplaceStr(Pattern, Split(cstSPecialChars, &quot;,&quot;), Split(cstEscapedChars, &quot;,&quot;))
+ bLike = SF_String.IsRegex(InputStr, sRegex, CaseSensitive)
+ End If
+
+Finally:
+ IsLike = bLike
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.IsLike
+
+REM -----------------------------------------------------------------------------
+Public Function IsLower(Optional ByRef InputStr As Variant) As Boolean
+&apos;&apos;&apos; Return True if all characters in the string are in lower case
+&apos;&apos;&apos; Non alphabetic characters are ignored
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if the string contains only lower case characters and there is at least one character, False otherwise
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.IsLower(&quot;abc&apos;(-xyz&quot;) returns True
+
+Dim bLower As Boolean &apos; Return value
+Const cstThisSub = &quot;String.IsLower&quot;
+Const cstSubArgs = &quot;InputStr&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bLower = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ End If
+
+Try:
+ If Len(InputStr) &gt; 0 Then bLower = ( StrComp(InputStr, LCase(InputStr), 1) = 0 )
+
+Finally:
+ IsLower = bLower
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.IsLower
+
+REM -----------------------------------------------------------------------------
+Public Function IsPrintable(Optional ByRef InputStr As Variant) As Boolean
+&apos;&apos;&apos; Return True if all characters in the string are printable
+&apos;&apos;&apos; In particular, control characters (Ascii &lt;= 1F) are not printable
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if the string is printable and there is at least one character, False otherwise
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.IsPrintable(&quot;àén ΣlPµ Русский&quot;) returns True
+
+Dim bPrintable As Boolean &apos; Return value
+Dim lLength As Long &apos; Length of InputStr
+Dim oChar As Object &apos; com.sun.star.i18n.CharacterClassification
+Dim oLocale As Object &apos; com.sun.star.lang.Locale
+Dim lType As Long &apos; com.sun.star.i18n.KCharacterType
+Dim sChar As String &apos; A single character
+Dim lPRINTABLE As Long : lPRINTABLE = com.sun.star.i18n.KCharacterType.PRINTABLE
+Dim i As Long
+Const cstThisSub = &quot;String.IsPrintable&quot;
+Const cstSubArgs = &quot;InputStr&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bPrintable = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ End If
+
+Try:
+ lLength = Len(InputStr)
+ If lLength &gt; 0 Then
+ Set oLocale = SF_Utils._GetUNOService(&quot;SystemLocale&quot;)
+ Set oChar = SF_Utils._GetUNOService(&quot;CharacterClass&quot;)
+ For i = 0 To lLength - 1
+ sChar = Mid(InputStr, i + 1, 1)
+ lType = oChar.getCharacterType(sChar, 0, oLocale)
+ &apos; Parenthses (), [], {} have a KCharacterType = 0
+ bPrintable = ( (lType And lPRINTABLE) = lPRINTABLE Or (lType = 0 And Asc(sChar) &lt;= 127) )
+ If Not bPrintable Then Exit For
+ Next i
+ End If
+
+Finally:
+ IsPrintable = bPrintable
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.IsPrintable
+
+REM -----------------------------------------------------------------------------
+Public Function IsRegex(Optional ByRef InputStr As Variant _
+ , Optional ByVal Regex As Variant _
+ , Optional ByVal CaseSensitive As Variant _
+ ) As Boolean
+&apos;&apos;&apos; Returns True if the whole input string matches a given regular expression
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string
+&apos;&apos;&apos; Regex: the regular expression as a string
+&apos;&apos;&apos; CaseSensitive: default = False
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if a match is found
+&apos;&apos;&apos; Zero-length input or regex strings always return False
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.IsRegex(&quot;aAbB&quot;, &quot;[A-Za-z]+&quot;) returns True
+
+Dim bRegex As Boolean &apos; Return value
+Dim lStart As Long &apos; Must be 1
+Dim sMatch As String &apos; Matching string
+Const cstBegin = &quot;^&quot; &apos; Beginning of line symbol
+Const cstEnd = &quot;$&quot; &apos; End of line symbol
+Const cstThisSub = &quot;String.IsRegex&quot;
+Const cstSubArgs = &quot;InputStr, Regex, [CaseSensitive=False]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bRegex = False
+
+Check:
+ If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(Regex, &quot;Regex&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
+ End If
+
+Try:
+ If Len(InputStr) &gt; 0 And Len(Regex) &gt; 0 Then
+ &apos; Whole string must match Regex
+ lStart = 1
+ If Left(Regex, 1) &lt;&gt; cstBegin Then Regex = cstBegin &amp; Regex
+ If Right(Regex, 1) &lt;&gt; cstEnd Then Regex = Regex &amp; cstEnd
+ sMatch = SF_String.FindRegex(InputStr, Regex, lStart, CaseSensitive)
+ &apos; Match ?
+ bRegex = ( lStart = 1 And Len(sMatch) = Len(InputStr) )
+ End If
+
+Finally:
+ IsRegex = bRegex
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.IsRegex
+
+REM -----------------------------------------------------------------------------
+Public Function IsSheetName(Optional ByRef InputStr As Variant) As Boolean
+&apos;&apos;&apos; Return True if the input string can serve as a valid Calc sheet name
+&apos;&apos;&apos; The sheet name must not contain the characters [ ] * ? : / \
+&apos;&apos;&apos; or the character &apos; (apostrophe) as first or last character.
+
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if the string is validated as a potential Calc sheet name, False otherwise
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.IsSheetName(&quot;1àbc + &quot;&quot;def&quot;&quot;&quot;) returns True
+
+Dim bSheetName As Boolean &apos; Return value
+Const cstThisSub = &quot;String.IsSheetName&quot;
+Const cstSubArgs = &quot;InputStr&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bSheetName = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ End If
+
+Try:
+ If Len(InputStr) &gt; 0 Then
+ If Left(InputStr, 1) = &quot;&apos;&quot; Or Right(InputStr, 1) = &quot;&apos;&quot; Then
+ ElseIf InStr(InputStr, &quot;[&quot;) _
+ + InStr(InputStr, &quot;]&quot;) _
+ + InStr(InputStr, &quot;*&quot;) _
+ + InStr(InputStr, &quot;?&quot;) _
+ + InStr(InputStr, &quot;:&quot;) _
+ + InStr(InputStr, &quot;/&quot;) _
+ + InStr(InputStr, &quot;\&quot;) _
+ = 0 Then
+ bSheetName = True
+ End If
+ End If
+
+Finally:
+ IsSheetName = bSheetName
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.IsSheetName
+
+REM -----------------------------------------------------------------------------
+Public Function IsTitle(Optional ByRef InputStr As Variant) As Boolean
+&apos;&apos;&apos; Return True if the 1st character of every word is in upper case and the other characters are in lower case
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if the string is capitalized and there is at least one character, False otherwise
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.IsTitle(&quot;This Is A Title For Jean-Pierre&quot;) returns True
+
+Dim bTitle As Boolean &apos; Return value
+Const cstThisSub = &quot;String.IsTitle&quot;
+Const cstSubArgs = &quot;InputStr&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bTitle = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ End If
+
+Try:
+ If Len(InputStr) &gt; 0 Then bTitle = ( StrComp(InputStr, SF_String.Capitalize(InputStr), 1) = 0 )
+
+Finally:
+ IsTitle = bTitle
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.IsTitle
+
+REM -----------------------------------------------------------------------------
+Public Function IsUpper(Optional ByRef InputStr As Variant) As Boolean
+&apos;&apos;&apos; Return True if all characters in the string are in upper case
+&apos;&apos;&apos; Non alphabetic characters are ignored
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if the string contains only upper case characters and there is at least one character, False otherwise
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.IsUpper(&quot;ABC&apos;(-XYZ&quot;) returns True
+
+Dim bUpper As Boolean &apos; Return value
+Const cstThisSub = &quot;String.IsUpper&quot;
+Const cstSubArgs = &quot;InputStr&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bUpper = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ End If
+
+Try:
+ If Len(InputStr) &gt; 0 Then bUpper = ( StrComp(InputStr, UCase(InputStr), 1) = 0 )
+
+Finally:
+ IsUpper = bUpper
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.IsUpper
+
+REM -----------------------------------------------------------------------------
+Public Function IsUrl(Optional ByRef InputStr As Variant) As Boolean
+&apos;&apos;&apos; Return True if the string is a valid absolute URL (Uniform Resource Locator)
+&apos;&apos;&apos; The parsing is done by the ParseStrict method of the URLTransformer UNO service
+&apos;&apos;&apos; https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1util_1_1XURLTransformer.html
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if the string contains a URL and there is at least one character, False otherwise
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.IsUrl(&quot;http://foo.bar/?q=Test%20URL-encoded%20stuff&quot;) returns True
+
+Dim bUrl As Boolean &apos; Return value
+Const cstThisSub = &quot;String.IsUrl&quot;
+Const cstSubArgs = &quot;InputStr&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bUrl = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ End If
+
+Try:
+ If Len(InputStr) &gt; 0 Then bUrl = ( Len(SF_FileSystem._ParseUrl(InputStr).Main) &gt; 0 )
+
+Finally:
+ IsUrl = bUrl
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.IsUrl
+
+REM -----------------------------------------------------------------------------
+Public Function IsWhitespace(Optional ByRef InputStr As Variant) As Boolean
+&apos;&apos;&apos; Return True if all characters in the string are whitespaces
+&apos;&apos;&apos; Whitespaces include Space(32), HT(9), LF(10), VT(11), FF(12), CR(13), Next Line(133), No-break space(160),
+&apos;&apos;&apos; Line separator(8232), Paragraph separator(8233)
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if the string contains only whitespaces and there is at least one character, False otherwise
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.IsWhitespace(&quot; &quot; &amp; Chr(9) &amp; Chr(10)) returns True
+
+Dim bWhitespace As Boolean &apos; Return value
+Const cstThisSub = &quot;String.IsWhitespace&quot;
+Const cstSubArgs = &quot;InputStr&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bWhitespace = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ End If
+
+Try:
+ If Len(InputStr) &gt; 0 Then bWhitespace = SF_String.IsRegex(InputStr, REGEXWHITESPACES, CaseSensitive := False)
+
+Finally:
+ IsWhitespace = bWhitespace
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.IsWhitespace
+
+REM -----------------------------------------------------------------------------
+Public Function JustifyCenter(Optional ByRef InputStr As Variant _
+ , Optional ByVal Length As Variant _
+ , Optional ByVal Padding As Variant _
+ ) As String
+&apos;&apos;&apos; Return the input string center justified
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string
+&apos;&apos;&apos; Length: the resulting string length (default = length of input string)
+&apos;&apos;&apos; Padding: the padding (single) character (default = the ascii space)
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The input string without its leading and trailing white spaces
+&apos;&apos;&apos; completed left and right up to a total length of Length with the character Padding
+&apos;&apos;&apos; If the input string is empty, the returned string is empty too
+&apos;&apos;&apos; If the requested length is shorter than the center justified input string,
+&apos;&apos;&apos; then the returned string is truncated
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.JustifyCenter(&quot; ABCDE &quot;, Padding := &quot;x&quot;) returns &quot;xxABCDEFxx&quot;
+
+Dim sJustify As String &apos; Return value
+Dim lLength As Long &apos; Length of input string
+Dim lJustLength As Long &apos; Length of trimmed input string
+Dim sPadding As String &apos; Series of Padding characters
+Const cstThisSub = &quot;String.JustifyCenter&quot;
+Const cstSubArgs = &quot;InputStr, [length=Len(InputStr)], [Padding=&quot;&quot; &quot;&quot;]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sJustify = &quot;&quot;
+
+Check:
+ If IsMissing(Length) Or IsEmpty(Length) Then Length = 0
+ If IsMissing(Padding) Or IsMissing(Padding) Then Padding = &quot; &quot;
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(Length, &quot;Length&quot;, V_NUMERIC) Then GoTo Finally
+ If Not SF_Utils._Validate(Padding, &quot;Padding&quot;, V_STRING) Then GoTo Finally
+ End If
+ If Len(Padding) = 0 Then Padding = &quot; &quot; Else Padding = Left(Padding, 1)
+
+Try:
+ lLength = Len(InputStr)
+ If Length = 0 Then Length = lLength
+ If lLength &gt; 0 Then
+ sJustify = SF_String.TrimExt(InputStr) &apos; Trim left and right
+ lJustLength = Len(sJustify)
+ If lJustLength &gt; Length Then
+ sJustify = Mid(sJustify, Int((lJustLength - Length) / 2) + 1, Length)
+ ElseIf lJustLength &lt; Length Then
+ sPadding = String(Int((Length - lJustLength) / 2), Padding)
+ sJustify = sPadding &amp; sJustify &amp; sPadding
+ If Len(sJustify) &lt; Length Then sJustify = sJustify &amp; Padding &apos; One Padding char is lacking when lJustLength is odd
+ End If
+ End If
+
+Finally:
+ JustifyCenter = sJustify
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.JustifyCenter
+
+REM -----------------------------------------------------------------------------
+Public Function JustifyLeft(Optional ByRef InputStr As Variant _
+ , Optional ByVal Length As Variant _
+ , Optional ByVal Padding As Variant _
+ ) As String
+&apos;&apos;&apos; Return the input string left justified
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string
+&apos;&apos;&apos; Length: the resulting string length (default = length of input string)
+&apos;&apos;&apos; Padding: the padding (single) character (default = the ascii space)
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The input string without its leading white spaces
+&apos;&apos;&apos; filled up to a total length of Length with the character Padding
+&apos;&apos;&apos; If the input string is empty, the returned string is empty too
+&apos;&apos;&apos; If the requested length is shorter than the left justified input string,
+&apos;&apos;&apos; then the returned string is truncated
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.JustifyLeft(&quot; ABCDE &quot;, Padding := &quot;x&quot;) returns &quot;ABCDE xxx&quot;
+
+Dim sJustify As String &apos; Return value
+Dim lLength As Long &apos; Length of input string
+Const cstThisSub = &quot;String.JustifyLeft&quot;
+Const cstSubArgs = &quot;InputStr, [length=Len(InputStr)], [Padding=&quot;&quot; &quot;&quot;]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sJustify = &quot;&quot;
+
+Check:
+ If IsMissing(Length) Or IsEmpty(Length) Then Length = 0
+ If IsMissing(Padding) Or IsMissing(Padding) Then Padding = &quot; &quot;
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(Length, &quot;Length&quot;, V_NUMERIC) Then GoTo Finally
+ If Not SF_Utils._Validate(Padding, &quot;Padding&quot;, V_STRING) Then GoTo Finally
+ End If
+ If Len(Padding) = 0 Then Padding = &quot; &quot; Else Padding = Left(Padding, 1)
+
+Try:
+ lLength = Len(InputStr)
+ If Length = 0 Then Length = lLength
+ If lLength &gt; 0 Then
+ sJustify = SF_String.ReplaceRegex(InputStr, REGEXLTRIM, &quot;&quot;) &apos; Trim left
+ If Len(sJustify) &gt;= Length Then
+ sJustify = Left(sJustify, Length)
+ Else
+ sJustify = sJustify &amp; String(Length - Len(sJustify), Padding)
+ End If
+ End If
+
+Finally:
+ JustifyLeft = sJustify
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.JustifyLeft
+
+REM -----------------------------------------------------------------------------
+Public Function JustifyRight(Optional ByRef InputStr As Variant _
+ , Optional ByVal Length As Variant _
+ , Optional ByVal Padding As Variant _
+ ) As String
+&apos;&apos;&apos; Return the input string right justified
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string
+&apos;&apos;&apos; Length: the resulting string length (default = length of input string)
+&apos;&apos;&apos; Padding: the padding (single) character (default = the ascii space)
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The input string without its trailing white spaces
+&apos;&apos;&apos; preceded up to a total length of Length with the character Padding
+&apos;&apos;&apos; If the input string is empty, the returned string is empty too
+&apos;&apos;&apos; If the requested length is shorter than the right justified input string,
+&apos;&apos;&apos; then the returned string is right-truncated
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.JustifyRight(&quot; ABCDE &quot;, Padding := &quot;x&quot;) returns &quot;x ABCDE&quot;
+
+Dim sJustify As String &apos; Return value
+Dim lLength As Long &apos; Length of input string
+Const cstThisSub = &quot;String.JustifyRight&quot;
+Const cstSubArgs = &quot;InputStr, [length=Len(InputStr)], [Padding=&quot;&quot; &quot;&quot;]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sJustify = &quot;&quot;
+
+Check:
+ If IsMissing(Length) Or IsEmpty(Length) Then Length = 0
+ If IsMissing(Padding) Or IsMissing(Padding) Then Padding = &quot; &quot;
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(Length, &quot;Length&quot;, V_NUMERIC) Then GoTo Finally
+ If Not SF_Utils._Validate(Padding, &quot;Padding&quot;, V_STRING) Then GoTo Finally
+ End If
+ If Len(Padding) = 0 Then Padding = &quot; &quot; Else Padding = Left(Padding, 1)
+
+Try:
+ lLength = Len(InputStr)
+ If Length = 0 Then Length = lLength
+ If lLength &gt; 0 Then
+ sJustify = SF_String.ReplaceRegex(InputStr, REGEXRTRIM, &quot;&quot;) &apos; Trim right
+ If Len(sJustify) &gt;= Length Then
+ sJustify = Right(sJustify, Length)
+ Else
+ sJustify = String(Length - Len(sJustify), Padding) &amp; sJustify
+ End If
+ End If
+
+Finally:
+ JustifyRight = sJustify
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.JustifyRight
+
+REM -----------------------------------------------------------------------------
+Public Function Methods() As Variant
+&apos;&apos;&apos; Return the list of public methods of the String service as an array
+
+ Methods = Array( _
+ &quot;Capitalize&quot; _
+ , &quot;Count&quot; _
+ , &quot;EndWith&quot; _
+ , &quot;Escape&quot; _
+ , &quot;ExpandTabs&quot; _
+ , &quot;FilterNotPrintable&quot; _
+ , &quot;FindRegex&quot; _
+ , &quot;HashStr&quot; _
+ , &quot;HtmlEncode&quot; _
+ , &quot;IsADate&quot; _
+ , &quot;IsAlpha&quot; _
+ , &quot;IsAlphaNum&quot; _
+ , &quot;IsAscii&quot; _
+ , &quot;IsDigit&quot; _
+ , &quot;IsEmail&quot; _
+ , &quot;IsFileName&quot; _
+ , &quot;IsHexDigit&quot; _
+ , &quot;IsIPv4&quot; _
+ , &quot;IsLike&quot; _
+ , &quot;IsLower&quot; _
+ , &quot;IsPrintable&quot; _
+ , &quot;IsRegex&quot; _
+ , &quot;IsSheetName&quot; _
+ , &quot;IsTitle&quot; _
+ , &quot;IsUpper&quot; _
+ , &quot;IsUrl&quot; _
+ , &quot;IsWhitespace&quot; _
+ , &quot;JustifyCenter&quot; _
+ , &quot;JustifyLeft&quot; _
+ , &quot;JustifyRight&quot; _
+ , &quot;Quote&quot; _
+ , &quot;ReplaceChar&quot; _
+ , &quot;ReplaceRegex&quot; _
+ , &quot;ReplaceStr&quot; _
+ , &quot;Represent&quot; _
+ , &quot;Reverse&quot; _
+ , &quot;SplitLines&quot; _
+ , &quot;SplitNotQuoted&quot; _
+ , &quot;StartsWith&quot; _
+ , &quot;TrimExt&quot; _
+ , &quot;Unescape&quot; _
+ , &quot;Unquote&quot; _
+ , &quot;Wrap&quot; _
+ )
+
+End Function &apos; ScriptForge.SF_String.Methods
+
+REM -----------------------------------------------------------------------------
+Public Function Properties() As Variant
+&apos;&apos;&apos; Return the list or properties as an array
+
+ Properties = Array( _
+ &quot;sfCR&quot; _
+ , &quot;sfCRLF&quot; _
+ , &quot;sfLF&quot; _
+ , &quot;sfNEWLINE&quot; _
+ , &quot;sfTAB&quot; _
+ )
+
+End Function &apos; ScriptForge.SF_Session.Properties
+
+REM -----------------------------------------------------------------------------
+Public Function Quote(Optional ByRef InputStr As Variant _
+ , Optional ByVal QuoteChar As String _
+ ) As String
+&apos;&apos;&apos; Return the input string surrounded with double quotes
+&apos;&apos;&apos; Used f.i. to prepare a string field to be stored in a csv-like file
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string
+&apos;&apos;&apos; QuoteChar: either &quot; (default) or &apos;
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; Existing - including leading and/or trailing - double quotes are doubled
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.Quote(&quot;àé&quot;&quot;n ΣlPµ Русский&quot;) returns &quot;&quot;&quot;àé&quot;&quot;&quot;&quot;n ΣlPµ Русский&quot;&quot;&quot;
+
+Dim sQuote As String &apos; Return value
+Const cstDouble = &quot;&quot;&quot;&quot; : Const cstSingle = &quot;&apos;&quot;
+Const cstEscape = &quot;\&quot;
+Const cstThisSub = &quot;String.Quote&quot;
+Const cstSubArgs = &quot;InputStr&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sQuote = &quot;&quot;
+
+Check:
+ If IsMissing(QuoteChar) Or IsEmpty(QuoteChar) Then QuoteChar = cstDouble
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(QuoteChar, &quot;QuoteChar&quot;, V_STRING, Array(cstDouble, cstSingle)) Then GoTo Finally
+ End If
+
+Try:
+ If QuoteChar = cstDouble Then
+ sQuote = cstDouble &amp; Replace(InputStr, cstDouble, cstDouble &amp; cstDouble) &amp; cstDouble
+ Else
+ sQuote = Replace(InputStr, cstEscape, cstEscape &amp; cstEscape)
+ sQuote = cstSingle &amp; Replace(sQuote, cstSingle, cstEscape &amp; cstSingle) &amp; cstSingle
+ End If
+
+Finally:
+ Quote = sQuote
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.Quote
+
+REM -----------------------------------------------------------------------------
+Public Function ReplaceChar(Optional ByRef InputStr As Variant _
+ , Optional ByVal Before As Variant _
+ , Optional ByVal After As Variant _
+ ) As String
+&apos;&apos;&apos; Replace in InputStr all occurrences of any character from Before
+&apos;&apos;&apos; by the corresponding character in After
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string on which replacements should occur
+&apos;&apos;&apos; Before: a string of characters to replace 1 by 1 in InputStr
+&apos;&apos;&apos; After: the replacing characters
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The new string after replacement of Nth character of Before by the Nth character of After
+&apos;&apos;&apos; Replacements are done one by one =&gt; potential overlaps
+&apos;&apos;&apos; If the length of Before is larger than the length of After,
+&apos;&apos;&apos; the residual characters of Before are replaced by the last character of After
+&apos;&apos;&apos; The input string when Before is the zero-length string
+&apos;&apos;&apos; Examples: easily remove accents
+&apos;&apos;&apos; SF_String.ReplaceChar(&quot;Protégez votre vie privée&quot;, &quot;àâãçèéêëîïôöûüýÿ&quot;, &quot;aaaceeeeiioouuyy&quot;)
+&apos;&apos;&apos; returns &quot;Protegez votre vie privee&quot;
+&apos;&apos;&apos; SF_String.ReplaceChar(&quot;Protégez votre vie privée&quot;, SF_String.CHARSWITHACCENT, SF_String.CHARSWITHOUTACCENT)
+
+Dim sOutput As String &apos; Return value
+Dim iCaseSensitive As Integer &apos; Always 0 (True)
+Dim sBefore As String &apos; A single character extracted from InputStr
+Dim sAfter As String &apos; A single character extracted from After
+Dim lInStr As Long &apos; Output of InStr()
+Dim i As Long
+Const cstThisSub = &quot;String.ReplaceChar&quot;
+Const cstSubArgs = &quot;InputStr, Before, After&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sOutput = &quot;&quot;
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(Before, &quot;Before&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(After, &quot;After&quot;, V_STRING) Then GoTo Finally
+ End If
+
+Try:
+ &apos; Replace standard function =&gt; Replace(string, before, after, start, occurrences, casesensitive)
+ sOutput = InputStr
+ iCaseSensitive = 0
+
+ &apos; Replace one by one up length of Before and After
+ If Len(Before) &gt; 0 Then
+ i = 1
+ Do While i &lt;= Len(sOutput)
+ sBefore = Mid(sOutput, i, 1)
+ lInStr = InStr(1, Before, sBefore, iCaseSensitive)
+ If lInStr &gt; 0 Then
+ If Len(After) = 0 Then
+ sAfter = &quot;&quot;
+ ElseIf lInStr &gt; Len(After) Then
+ sAfter = Right(After, 1)
+ Else
+ sAfter = Mid(After, lInStr, 1)
+ End If
+ sOutput = Left(sOutput, i - 1) &amp; Replace(sOutput, sBefore, sAfter, i, Empty, iCaseSensitive)
+ End If
+ i = i + 1
+ Loop
+ End If
+
+Finally:
+ ReplaceChar = sOutput
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.ReplaceChar
+
+REM -----------------------------------------------------------------------------
+Public Function ReplaceRegex(Optional ByRef InputStr As Variant _
+ , Optional ByVal Regex As Variant _
+ , Optional ByRef NewStr As Variant _
+ , Optional ByVal CaseSensitive As Variant _
+ ) As String
+&apos;&apos;&apos; Replace in InputStr all occurrences of a given regular expression by NewStr
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string where replacements should occur
+&apos;&apos;&apos; Regex: the regular expression
+&apos;&apos;&apos; NewStr: the replacing string
+&apos;&apos;&apos; CaseSensitive: default = False
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The new string after all replacements
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.ReplaceRegex(&quot;Lorem ipsum dolor sit amet, consectetur adipiscing elit.&quot;, &quot;[a-z]&quot;, &quot;x&quot;, CaseSensitive := True)
+&apos;&apos;&apos; returns &quot;Lxxxx xxxxx xxxxx xxx xxxx, xxxxxxxxxxx xxxxxxxxxx xxxx.&quot;
+&apos;&apos;&apos; SF_String.ReplaceRegex(&quot;Lorem ipsum dolor sit amet, consectetur adipiscing elit.&quot;, &quot;\b[a-z]+\b&quot;, &quot;x&quot;, CaseSensitive := False)
+&apos;&apos;&apos; returns &quot;x x x x x, x x x.&quot; (each word is replaced by x)
+
+
+Dim sOutput As String &apos; Return value
+Dim lStartOld As Long &apos; Previous start of search
+Dim lStartNew As Long &apos; Next start of search
+Dim sSubstring As String &apos; Substring to replace
+Const cstThisSub = &quot;String.ReplaceRegex&quot;
+Const cstSubArgs = &quot;InputStr, Regex, NewStr, [CaseSensitive=False]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sOutput = &quot;&quot;
+
+Check:
+ If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(Regex, &quot;Regex&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(NewStr, &quot;NewStr&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
+ End If
+
+Try:
+ sOutput = &quot;&quot;
+ lStartNew = 1
+ lStartOld = 1
+
+ Do While lStartNew &gt;= 1 And lStartNew &lt;= Len(InputStr)
+ sSubstring = SF_String.FindRegex(InputStr, Regex, lStartNew, CaseSensitive)
+ If lStartNew = 0 Then &apos; Regex not found
+ &apos; Copy remaining substring of InputStr before leaving
+ sOutput = sOutput &amp; Mid(InputStr, lStartOld)
+ Exit Do
+ End If
+ &apos; Append the interval between 2 occurrences and the replacing string
+ If lStartNew &gt; lStartOld Then sOutput = sOutput &amp; Mid(InputStr, lStartOld, lStartNew - lStartOld)
+ sOutput = sOutput &amp; NewStr
+ lStartOld = lStartNew + Len(sSubstring)
+ lStartNew = lStartOld
+ Loop
+
+Finally:
+ ReplaceRegex = sOutput
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.ReplaceRegex
+
+REM -----------------------------------------------------------------------------
+Public Function ReplaceStr(Optional ByRef InputStr As Variant _
+ , Optional ByVal OldStr As Variant _
+ , Optional ByVal NewStr As Variant _
+ , Optional ByVal Occurrences As Variant _
+ , Optional ByVal CaseSensitive As Variant _
+ ) As String
+&apos;&apos;&apos; Replace in InputStr some or all occurrences of OldStr by NewStr
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string on which replacements should occur
+&apos;&apos;&apos; OldStr: the string to replace or a 1D array of strings to replace
+&apos;&apos;&apos; Zero-length strings are ignored
+&apos;&apos;&apos; NewStr: the replacing string or a 1D array of replacing strings
+&apos;&apos;&apos; If OldStr is an array
+&apos;&apos;&apos; each occurrence of any of the items of OldStr is replaced by NewStr
+&apos;&apos;&apos; If OldStr and NewStr are arrays
+&apos;&apos;&apos; replacements occur one by one up to the UBound of NewStr
+&apos;&apos;&apos; remaining OldStr(ings) are replaced by the last element of NewStr
+&apos;&apos;&apos; Occurrences: the maximum number of replacements (0, default, = all occurrences)
+&apos;&apos;&apos; Is applied for each single replacement when OldStr is an array
+&apos;&apos;&apos; CaseSensitive: True or False (default)
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The new string after replacements
+&apos;&apos;&apos; Replacements are done one by one when OldStr is an array =&gt; potential overlaps
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.ReplaceStr(&quot;abCcdefghHij&quot;, Array(&quot;c&quot;, &quot;h&quot;), Array(&quot;Y&quot;, &quot;Z&quot;), CaseSensitive := False) returns &quot;abYYdefgZZij&quot;
+
+Dim sOutput As String &apos; Return value
+Dim iCaseSensitive As Integer &apos; Integer alias for boolean CaseSensitive
+Dim vOccurrences As Variant &apos; Variant alias for Integer Occurrences
+Dim sNewStr As String &apos; Alias for a NewStr item
+Dim i As Long, j As Long
+Const cstThisSub = &quot;String.ReplaceStr&quot;
+Const cstSubArgs = &quot;InputStr, OldStr, NewStr, [Occurrences=0], [CaseSensitive=False]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sOutput = &quot;&quot;
+
+Check:
+ If IsMissing(Occurrences) Or IsEmpty(Occurrences) Then Occurrences = 0
+ If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ If IsArray(OldStr) Then
+ If Not SF_Utils._ValidateArray(OldStr, &quot;OldStr&quot;, 1, V_STRING, True) Then GoTo Finally
+ Else
+ If Not SF_Utils._Validate(OldStr, &quot;OldStr&quot;, V_STRING) Then GoTo Finally
+ End If
+ If IsArray(NewStr) Then
+ If Not SF_Utils._ValidateArray(NewStr, &quot;NewStr&quot;, 1, V_STRING, True) Then GoTo Finally
+ Else
+ If Not SF_Utils._Validate(NewStr, &quot;NewStr&quot;, V_STRING) Then GoTo Finally
+ End If
+ If Not SF_Utils._Validate(Occurrences, &quot;Occurrences&quot;, V_NUMERIC) Then GoTo Finally
+ If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
+ End If
+
+Try:
+ &apos; Replace standard function =&gt; Replace(string, before, after, start, occurrences, casesensitive)
+ sOutput = InputStr
+ iCaseSensitive = Iif(CaseSensitive, 0, 1) &apos; 1 = False ;)
+ vOccurrences = Iif(Occurrences = 0, Empty, Occurrences) &apos; Empty = no limit
+ If Not IsArray(OldStr) Then OldStr = Array(OldStr)
+ If Not IsArray(NewStr) Then NewStr = Array(NewStr)
+
+ &apos; Replace one by one up to UBounds of Old and NewStr
+ j = LBound(NewStr) - 1
+ For i = LBound(OldStr) To UBound(OldStr)
+ j = j + 1
+ If j &lt;= UBound(NewStr) Then sNewStr = NewStr(j) &apos; Else do not change
+ If StrComp(OldStr(i), sNewStr, 1) &lt;&gt; 0 Then
+ sOutput = Replace(sOutput, OldStr(i), sNewStr, 1, vOccurrences, iCaseSensitive)
+ End If
+ Next i
+
+Finally:
+ ReplaceStr = sOutput
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.ReplaceStr
+
+REM -----------------------------------------------------------------------------
+Public Function Represent(Optional ByRef AnyValue As Variant _
+ , Optional ByVal MaxLength As Variant _
+ ) As String
+&apos;&apos;&apos; Return a readable (string) form of the argument, truncated at MaxLength
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; AnyValue: really any value (object, date, whatever)
+&apos;&apos;&apos; MaxLength: the maximum length of the resulting string (Default = 0, unlimited)
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The argument converted or transformed into a string of a maximum length = MaxLength
+&apos;&apos;&apos; Objects are surrounded with square brackets ([])
+&apos;&apos;&apos; In strings, tabs and line breaks are replaced by \t, \n or \r
+&apos;&apos;&apos; If the effective length exceeds MaxLength, the final part of the string is replaced by &quot; ... (N)&quot;
+&apos;&apos;&apos; where N = the total length of the string before truncation
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.Represent(&quot;this is a usual string&quot;) returns &quot;this is a usual string&quot;
+&apos;&apos;&apos; SF_String.Represent(&quot;this is a usual string&quot;, 15) returns &quot;this i ... (22)&quot;
+&apos;&apos;&apos; SF_String.Represent(&quot;this is a&quot; &amp; Chr(10) &amp; &quot; 2-lines string&quot;) returns &quot;this is a\n 2-lines string&quot;
+&apos;&apos;&apos; SF_String.Represent(Empty) returns &quot;[EMPTY]&quot;
+&apos;&apos;&apos; SF_String.Represent(Null) returns &quot;[NULL]&quot;
+&apos;&apos;&apos; SF_String.Represent(Pi) returns &quot;3.142&quot;
+&apos;&apos;&apos; SF_String.Represent(CreateUnoService(&quot;com.sun.star.util.PathSettings&quot;)) returns &quot;[com.sun.star.comp.framework.PathSettings]&quot;
+&apos;&apos;&apos; SF_String.Represent(Array(1, 2, &quot;Text&quot; &amp; Chr(9) &amp; &quot;here&quot;)) returns &quot;[ARRAY] (0:2) (1, 2, Text\there)&quot;
+&apos;&apos;&apos; Dim myDict As Variant : myDict = CreateScriptService(&quot;Dictionary&quot;)
+&apos;&apos;&apos; myDict.Add(&quot;A&quot;, 1) : myDict.Add(&quot;B&quot;, 2)
+&apos;&apos;&apos; SF_String.Represent(myDict) returns &quot;[Dictionary] (&quot;A&quot;:1, &quot;B&quot;:2)&quot;
+
+Dim sRepr As String &apos; Return value
+Const cstThisSub = &quot;String.Represent&quot;
+Const cstSubArgs = &quot;AnyValue, [MaxLength=0]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sRepr = &quot;&quot;
+
+Check:
+ If IsMissing(AnyValue) Then AnyValue = Empty
+ If IsMissing(MaxLength) Or IsEmpty(MaxLength) Then MaxLength = 0
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(MaxLength, &quot;MaxLength&quot;, V_NUMERIC) Then GoTo Finally
+ End If
+
+Try:
+ sRepr = SF_Utils._Repr(AnyValue, MaxLength)
+ If MaxLength &gt; 0 And MaxLength &lt; Len(sRepr) Then sRepr = sRepr &amp; &quot; ... (&quot; &amp; Len(sRepr) &amp; &quot;)&quot;
+
+Finally:
+ Represent = sRepr
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.Represent
+
+REM -----------------------------------------------------------------------------
+Public Function Reverse(Optional ByRef InputStr As Variant) As String
+&apos;&apos;&apos; Return the input string in reversed order
+&apos;&apos;&apos; It is equivalent to the standard StrReverse Basic function
+&apos;&apos;&apos; The latter requires the OpTion VBASupport 1 statement to be present in the module
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The input string in reversed order
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.Reverse(&quot;abcdefghij&quot;) returns &quot;jihgfedcba&quot;
+
+Dim sReversed As String &apos; Return value
+Dim lLength As Long &apos; Length of input string
+Dim i As Long
+Const cstThisSub = &quot;String.Reverse&quot;
+Const cstSubArgs = &quot;InputSt&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sReversed = &quot;&quot;
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ End If
+
+Try:
+ lLength = Len(InputStr)
+ If lLength &gt; 0 Then
+ sReversed = Space(lLength)
+ For i = 1 To lLength
+ Mid(sReversed, i, 1) = Mid(InputStr, lLength - i + 1)
+ Next i
+ End If
+
+Finally:
+ Reverse = sReversed
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.Reverse
+
+REM -----------------------------------------------------------------------------
+Public Function SetProperty(Optional ByVal PropertyName As Variant _
+ , Optional ByRef Value As Variant _
+ ) As Boolean
+&apos;&apos;&apos; Set a new value to the given property
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; PropertyName: the name of the property as a string
+&apos;&apos;&apos; Value: its new value
+&apos;&apos;&apos; Exceptions
+&apos;&apos;&apos; ARGUMENTERROR The property does not exist
+
+Const cstThisSub = &quot;String.SetProperty&quot;
+Const cstSubArgs = &quot;PropertyName, Value&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ SetProperty = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
+ End If
+
+Try:
+ Select Case UCase(PropertyName)
+ Case Else
+ End Select
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.SetProperty
+
+REM -----------------------------------------------------------------------------
+Public Function SplitLines(Optional ByRef InputStr As Variant _
+ , Optional ByVal KeepBreaks As Variant _
+ ) As Variant
+&apos;&apos;&apos; Return an array of the lines in a string, breaking at line boundaries
+&apos;&apos;&apos; Line boundaries include LF(10), VT(12), CR(13), LF+CR, File separator(28), Group separator(29), Record separator(30),
+&apos;&apos;&apos; Next Line(133), Line separator(8232), Paragraph separator(8233)
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string
+&apos;&apos;&apos; KeepBreaks: when True, line breaks are preserved in the output array (default = False)
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; An array of all the individual lines
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.SplitLines(&quot;Line1&quot; &amp; Chr(10) &amp; &quot;Line2&quot; &amp; Chr(13) &amp; &quot;Line3&quot;) returns (&quot;Line1&quot;, &quot;Line2&quot;, &quot;Line3&quot;)
+&apos;&apos;&apos; SF_String.SplitLines(&quot;Line1&quot; &amp; Chr(10) &amp; &quot;Line2&quot; &amp; Chr(13) &amp; &quot;Line3&quot; &amp; Chr(10)) returns (&quot;Line1&quot;, &quot;Line2&quot;, &quot;Line3&quot;, &quot;&quot;)
+
+Dim vSplit As Variant &apos; Return value
+Dim vLineBreaks As Variant &apos; Array of recognized line breaks
+Dim vTokenizedBreaks As Variant &apos; Array of line breaks extended with tokens
+Dim sAlias As String &apos; Alias for input string
+&apos; The procedure uses (dirty) placeholders to identify line breaks
+&apos; The used tokens are presumed unlikely present in text strings
+Dim sTokenCRLF As String &apos; Token to identify combined CR + LF
+Dim sToken As String &apos; Token to identify any line break
+Dim i As Long
+Const cstThisSub = &quot;String.SplitLines&quot;
+Const cstSubArgs = &quot;InputStr, [KeepBreaks=False]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vSplit = Array()
+
+Check:
+ If IsMissing(KeepBreaks) Or IsEmpty(KeepBreaks) Then KeepBreaks = False
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(KeepBreaks, &quot;KeepBreaks&quot;, V_BOOLEAN) Then GoTo Finally
+ End If
+
+Try:
+ &apos; In next list CR + LF must precede CR and LF
+ vLineBreaks = Array(SF_String.sfCRLF, SF_String.sfLF, Chr(12), SF_String.sfCR _
+ , Chr(28), Chr(29), Chr(30), Chr(133), Chr(8232), Chr(8233))
+
+ If KeepBreaks = False Then
+ &apos; Replace line breaks by linefeeds and split on linefeeds
+ vSplit = Split(SF_String.ReplaceStr(InputStr, vLineBreaks, SF_String.sfLF, CaseSensitive := False), SF_String.sfLF)
+ Else
+ sTokenCRLF = Chr(1) &amp; &quot;$&quot; &amp; Chr(2) &amp; &quot;*&quot; &amp; Chr(3) &amp; &quot;$&quot; &amp; Chr(1)
+ sToken = Chr(1) &amp; &quot;$&quot; &amp; Chr(2) &amp; &quot;*&quot; &amp; Chr(3) &amp; &quot;$&quot; &amp; Chr(2)
+ vTokenizedBreaks = Array() : ReDim vTokenizedBreaks(0 To UBound(vLineBreaks))
+ &apos; Extend breaks with token
+ For i = 0 To UBound(vLineBreaks)
+ vTokenizedBreaks(i) = Iif(i = 0, sTokenCRLF, vLineBreaks(i)) &amp; sToken
+ Next i
+ sAlias = SF_String.ReplaceStr(InputStr, vLineBreaks, vTokenizedBreaks, CaseSensitive := False)
+ &apos; Suppress CRLF tokens and split
+ vSplit = Split(Replace(sAlias, sTokenCRLF, SF_String.sfCRLF), sToken)
+ End If
+
+Finally:
+ SplitLines = vSplit
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.SplitLines
+
+REM -----------------------------------------------------------------------------
+Public Function SplitNotQuoted(Optional ByRef InputStr As Variant _
+ , Optional ByVal Delimiter As Variant _
+ , Optional ByVal Occurrences As Variant _
+ , Optional ByVal QuoteChar As Variant _
+ ) As Variant
+&apos;&apos;&apos; Split a string on Delimiter into an array. If Delimiter is part of a quoted (sub)string, it is ignored
+&apos;&apos;&apos; (used f.i. for parsing of csv-like records)
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string
+&apos;&apos;&apos; Might contain quoted substrings:
+&apos;&apos;&apos; The quoting character must be the double quote (&quot;)
+&apos;&apos;&apos; To preserve a quoting character inside the quoted substring, use (\) or (&quot;) as escape character
+&apos;&apos;&apos; =&gt; [str\&quot;i&quot;&quot;ng] means [str&quot;i&quot;ng]
+&apos;&apos;&apos; Delimiter: A string of one or more characters that is used to delimit the input string
+&apos;&apos;&apos; The default is the space character
+&apos;&apos;&apos; Occurrences: The number of substrings to return (Default = 0, meaning no limit)
+&apos;&apos;&apos; QuoteChar: The quoting character, either &quot; (default) or &apos;
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; An array whose items are chunks of the input string, Delimiter not included
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.SplitNotQuoted(&quot;abc def ghi&quot;) returns (&quot;abc&quot;, &quot;def&quot;, &quot;ghi&quot;)
+&apos;&apos;&apos; SF_String.SplitNotQuoted(&quot;abc,&quot;&quot;def,ghi&quot;&quot;&quot;, &quot;,&quot;) returns (&quot;abc&quot;, &quot;&quot;&quot;def,ghi&quot;&quot;&quot;)
+&apos;&apos;&apos; SF_String.SplitNotQuoted(&quot;abc,&quot;&quot;def\&quot;&quot;,ghi&quot;&quot;&quot;, &quot;,&quot;) returns (&quot;abc&quot;, &quot;&quot;&quot;def\&quot;&quot;,ghi&quot;&quot;&quot;)
+&apos;&apos;&apos; SF_String.SplitNotQuoted(&quot;abc,&quot;&quot;def\&quot;&quot;,ghi&quot;&quot;&quot;&quot;,&quot;, &quot;,&quot;) returns (&quot;abc&quot;, &quot;&quot;&quot;def\&quot;&quot;,ghi&quot;&quot;&quot;, &quot;&quot;)
+
+Dim vSplit As Variant &apos; Return value
+Dim lDelimLen As Long &apos; Length of Delimiter
+Dim vStart As Variant &apos; Array of start positions of quoted strings
+Dim vEnd As Variant &apos; Array of end positions of quoted strings
+Dim lInStr As Long &apos; InStr() on input string
+Dim lInStrPrev As Long &apos; Previous value of lInputStr
+Dim lBound As Long &apos; UBound of vStart and vEnd
+Dim lMin As Long &apos; Lower bound to consider when searching vStart and vEnd
+Dim oCharacterClass As Object &apos; com.sun.star.i18n.CharacterClassification
+Dim oLocale As Object &apos; com.sun.star.lang.Locale
+Dim oParse As Object &apos; com.sun.star.i18n.ParseResult
+Dim sChunk As String &apos; Substring of InputStr
+Dim bSplit As Boolean &apos; New chunk found or not
+Dim i As Long
+Const cstDouble = &quot;&quot;&quot;&quot; : Const cstSingle = &quot;&apos;&quot;
+Const cstThisSub = &quot;String.SplitNotQuoted&quot;
+Const cstSubArgs = &quot;InputStr, [Delimiter=&quot;&quot; &quot;&quot;], [Occurrences=0], [QuoteChar=&quot;&quot;&quot; &amp; cstDouble &amp; &quot;&quot;&quot;]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vSplit = Array()
+
+Check:
+ If IsMissing(Delimiter) Or IsEmpty(Delimiter) Then Delimiter = &quot; &quot;
+ If IsMissing(Occurrences) Or IsEmpty(Occurrences) Then Occurrences = 0
+ If IsMissing(QuoteChar) Or IsEmpty(QuoteChar) Then QuoteChar = cstDouble
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(Delimiter, &quot;Delimiter&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(Occurrences, &quot;Occurrences&quot;, V_NUMERIC) Then GoTo Finally
+ If Not SF_Utils._Validate(QuoteChar, &quot;QuoteChar&quot;, V_STRING, Array(cstDouble, cstSingle)) Then GoTo Finally
+ End If
+ If Len(Delimiter) = 0 Then Delimiter = &quot; &quot;
+
+Try:
+ If Occurrences = 1 Or InStr(1, InputStr, Delimiter, 0) = 0 Then &apos; No reason to split
+ vSplit = Array(InputStr)
+ ElseIf InStr(1, InputStr, QuoteChar, 0) = 0 Then &apos; No reason to make a complex split
+ If Occurrences &gt; 0 Then vSplit = Split(InputStr, Delimiter, Occurrences) Else vSplit = Split(InputStr, Delimiter)
+ Else
+ If Occurrences &lt; 0 Then Occurrences = 0
+ Set oCharacterClass = SF_Utils._GetUNOService(&quot;CharacterClass&quot;)
+ Set oLocale = SF_Utils._GetUNOService(&quot;SystemLocale&quot;)
+
+ &apos; Build an array of start/end positions of quoted strings containing at least 1x the Delimiter
+ vStart = Array() : vEnd = Array()
+ lInStr = InStr(1, InputStr, QuoteChar)
+ Do While lInStr &gt; 0
+ lBound = UBound(vStart)
+ &apos; https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1i18n_1_1XCharacterClassification.html#ad5f1be91fbe86853200391f828d4166b
+ Set oParse = oCharacterClass.parsePredefinedToken( _
+ Iif(QuoteChar = cstDouble, com.sun.star.i18n.KParseType.DOUBLE_QUOTE_STRING, com.sun.star.i18n.KParseType.SINGLE_QUOTE_NAME) _
+ , InputStr, lInStr - 1, oLocale, 0, &quot;&quot;, 0, &quot;&quot;)
+ If oParse.CharLen &gt; 0 Then &apos; Is parsing successful ?
+ &apos; Is there some delimiter ?
+ If InStr(1, oParse.DequotedNameOrString, Delimiter, 0) &gt; 0 Then
+ vStart = SF_Array.Append(vStart, lInStr + 0)
+ vEnd = SF_Array.Append(vEnd, lInStr + oParse.CharLen - 1)
+ End If
+ lInStr = InStr(lInStr + oParse.CharLen, InputStr, QuoteChar)
+ Else
+ lInStr = 0
+ End If
+ Loop
+
+ lBound = UBound(vStart)
+ lDelimLen = Len(Delimiter)
+ If lBound &lt; 0 Then &apos; Usual split is applicable
+ vSplit = Split(InputStr, Delimiter, Occurrences)
+ Else
+ &apos; Split chunk by chunk
+ lMin = 0
+ lInStrPrev = 0
+ lInStr = InStr(1, InputStr, Delimiter, 0)
+ Do While lInStr &gt; 0
+ If Occurrences &gt; 0 And Occurrences = UBound(vSplit) - 1 Then Exit Do
+ bSplit = False
+ &apos; Ignore found Delimiter if in quoted string
+ For i = lMin To lBound
+ If lInStr &lt; vStart(i) Then
+ bSplit = True
+ Exit For
+ ElseIf lInStr &gt; vStart(i) And lInStr &lt; vEnd (i) Then
+ Exit For
+ Else
+ lMin = i + 1
+ If i = lBound Then bSplit = True Else bSplit = ( lInStr &lt; vStart(lMin) )
+ End If
+ Next i
+ &apos; Build next chunk and store in split array
+ If bSplit Then
+ If lInStrPrev = 0 Then &apos; First chunk
+ sChunk = Left(InputStr, lInStr - 1)
+ Else
+ sChunk = Mid(InputStr, lInStrPrev + lDelimLen, lInStr - lInStrPrev - lDelimLen)
+ End If
+ vSplit = SF_Array.Append(vSplit, sChunk &amp; &quot;&quot;)
+ lInStrPrev = lInStr
+ End If
+ lInStr = InStr(lInStr + lDelimLen, InputStr, Delimiter, 0)
+ Loop
+ If Occurrences = 0 Or Occurrences &gt; UBound(vSplit) + 1 Then
+ sChunk = Mid(InputStr, lInStrPrev + lDelimLen) &apos; Append last chunk
+ vSplit = SF_Array.Append(vSplit, sChunk &amp; &quot;&quot;)
+ End If
+ End If
+ End If
+
+Finally:
+ SplitNotQuoted = vSplit
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.SplitNotQuoted
+
+REM -----------------------------------------------------------------------------
+Public Function StartsWith(Optional ByRef InputStr As Variant _
+ , Optional ByVal Substring As Variant _
+ , Optional ByVal CaseSensitive As Variant _
+ ) As Boolean
+&apos;&apos;&apos; Returns True if the first characters of InputStr are identical to Substring
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string
+&apos;&apos;&apos; Substring: the prefixing characters
+&apos;&apos;&apos; CaseSensitive: default = False
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if the comparison is satisfactory
+&apos;&apos;&apos; False if either InputStr or Substring have a length = 0
+&apos;&apos;&apos; False if Substr is longer than InputStr
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.StartsWith(&quot;abcdefg&quot;, &quot;ABC&quot;) returns True
+&apos;&apos;&apos; SF_String.StartsWith(&quot;abcdefg&quot;, &quot;ABC&quot;, CaseSensitive := True) returns False
+
+Dim bStartsWith As Boolean &apos; Return value
+Dim lSub As Long &apos; Length of SUbstring
+Const cstThisSub = &quot;String.StartsWith&quot;
+Const cstSubArgs = &quot;InputStr, Substring, [CaseSensitive=False]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bStartsWith = False
+
+Check:
+ If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(Substring, &quot;Substring&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
+ End If
+
+Try:
+ lSub = Len(Substring)
+ If Len(InputStr) &gt; 0 And lSub &gt; 0 And lSub &lt;= Len(InputStr) Then
+ bStartsWith = ( StrComp(Left(InputStr, lSub), Substring, Iif(CaseSensitive, 1, 0)) = 0 )
+ End If
+
+Finally:
+ StartsWith = bStartsWith
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.StartsWith
+
+REM -----------------------------------------------------------------------------
+Public Function TrimExt(Optional ByRef InputStr As Variant) As String
+&apos;&apos;&apos; Return the input string without its leading and trailing whitespaces
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The input string without its leading and trailing white spaces
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.TrimExt(&quot; ABCDE&quot; &amp; Chr(9) &amp; Chr(10) &amp; Chr(13) &amp; &quot; &quot;) returns &quot;ABCDE&quot;
+
+Dim sTrim As String &apos; Return value
+Const cstThisSub = &quot;String.TrimExt&quot;
+Const cstSubArgs = &quot;InputStr&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sTrim = &quot;&quot;
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ End If
+
+Try:
+ If Len(InputStr) &gt; 0 Then
+ sTrim = SF_String.ReplaceRegex(InputStr, REGEXLTRIM, &quot;&quot;) &apos; Trim left
+ sTrim = SF_String.ReplaceRegex(sTrim, REGEXRTRIM, &quot;&quot;) &apos; Trim right
+ End If
+
+Finally:
+ TrimExt = sTrim
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.TrimExt
+
+REM -----------------------------------------------------------------------------
+Public Function Unescape(Optional ByRef InputStr As Variant) As String
+&apos;&apos;&apos; Convert any escaped characters in the input string
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The input string after replacement of \\, \n, \r, \t sequences
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.Unescape(&quot;abc\n\tdef\\n&quot;) returns &quot;abc&quot; &amp; Chr(10) &amp; Chr(9) &amp; &quot;def\n&quot;
+
+Dim sUnescape As String &apos; Return value
+Dim sToken As String &apos; Placeholder unlikely to be present in input string
+Const cstThisSub = &quot;String.Unescape&quot;
+Const cstSubArgs = &quot;InputStr&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sUnescape = &quot;&quot;
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ End If
+
+Try:
+ sToken = Chr(1) &amp; &quot;$&quot; &amp; Chr(2) &amp; &quot;*&quot; &amp; Chr(3) &amp; &quot;$&quot; &amp; Chr(1) &apos; Placeholder for &quot;\\&quot;
+ sUnescape = SF_String.ReplaceStr( InputStr _
+ , Array(&quot;\\&quot;, &quot;\n&quot;, &quot;\r&quot;, &quot;\t&quot;, sToken) _
+ , Array(sToken, SF_String.sfLF, SF_String.sfCR, SF_String.sfTAB, &quot;\&quot;) _
+ )
+
+Finally:
+ Unescape = sUnescape
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.Unescape
+
+REM -----------------------------------------------------------------------------
+Public Function Unquote(Optional ByRef InputStr As Variant _
+ , Optional ByVal QuoteChar As String _
+ ) As String
+&apos;&apos;&apos; Reset a quoted string to its original content
+&apos;&apos;&apos; (used f.i. for parsing of csv-like records)
+&apos;&apos;&apos; When the input string contains the quote character, the latter must be escaped:
+&apos;&apos;&apos; - QuoteChar = double quote, by doubling it (&quot;&quot;)
+&apos;&apos;&apos; - QuoteChar = single quote, with a preceding backslash (\&apos;)
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string
+&apos;&apos;&apos; QuoteChar: either &quot; (default) or &apos;
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The input string after removal of leading/trailing quotes and escaped single/double quotes
+&apos;&apos;&apos; The input string if not a quoted string
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.Unquote(&quot;&quot;&quot;àé&quot;&quot;&quot;&quot;n ΣlPµ Русский&quot;&quot;&quot;) returns &quot;àé&quot;&quot;n ΣlPµ Русский&quot;
+
+Dim sUnquote As String &apos; Return value
+Dim oCharacterClass As Object &apos; com.sun.star.i18n.CharacterClassification
+Dim oLocale As Object &apos; com.sun.star.lang.Locale
+Dim oParse As Object &apos; com.sun.star.i18n.ParseResult
+Const cstDouble = &quot;&quot;&quot;&quot; : Const cstSingle = &quot;&apos;&quot;
+Const cstThisSub = &quot;String.Unquote&quot;
+Const cstSubArgs = &quot;InputStr&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sUnquote = &quot;&quot;
+
+Check:
+ If IsMissing(QuoteChar) Or IsEmpty(QuoteChar) Then QuoteChar = cstDouble
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(QuoteChar, &quot;QuoteChar&quot;, V_STRING, Array(cstDouble, cstSingle)) Then GoTo Finally
+ End If
+
+Try:
+ If Left(InputStr, 1) &lt;&gt; QuoteChar Then &apos; No need to parse further
+ sUnquote = InputStr
+ Else
+ Set oCharacterClass = SF_Utils._GetUNOService(&quot;CharacterClass&quot;)
+ Set oLocale = SF_Utils._GetUNOService(&quot;SystemLocale&quot;)
+
+ &apos; https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1i18n_1_1XCharacterClassification.html#ad5f1be91fbe86853200391f828d4166b
+ Set oParse = oCharacterClass.parsePredefinedToken( _
+ Iif(QuoteChar = cstDouble, com.sun.star.i18n.KParseType.DOUBLE_QUOTE_STRING, com.sun.star.i18n.KParseType.SINGLE_QUOTE_NAME) _
+ , InputStr, 0, oLocale, 0, &quot;&quot;, 0, &quot;&quot;)
+ If oParse.CharLen &gt; 0 Then &apos; Is parsing successful ?
+ sUnquote = oParse.DequotedNameOrString
+ Else
+ sUnquote = InputStr
+ End If
+ End If
+
+Finally:
+ Unquote = sUnquote
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.Unquote
+
+REM -----------------------------------------------------------------------------
+Public Function Wrap(Optional ByRef InputStr As Variant _
+ , Optional ByVal Width As Variant _
+ , Optional ByVal TabSize As Variant _
+ ) As Variant
+&apos;&apos;&apos; Wraps every single paragraph in text (a string) so every line is at most Width characters long
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; InputStr: the input string
+&apos;&apos;&apos; Width: the maximum number of characters in each line, default = 70
+&apos;&apos;&apos; TabSize: before wrapping the text, the existing TAB (Chr(9)) characters are replaced with spaces.
+&apos;&apos;&apos; TabSize defines the TAB positions at TabSize + 1, 2 * TabSize + 1 , ... N * TabSize + 1
+&apos;&apos;&apos; Default = 8
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; Returns a zero-based array of output lines, without final newlines except the pre-existing line-breaks
+&apos;&apos;&apos; Tabs are expanded. Symbolic line breaks are replaced by their hard equivalents
+&apos;&apos;&apos; If the wrapped output has no content, the returned array is empty.
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_String.Wrap(&quot;Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit...&quot;, 20)
+
+Dim vWrap As Variant &apos; Return value
+Dim vWrapLines &apos; Input string split on line breaks
+Dim sWrap As String &apos; Intermediate string
+Dim sLine As String &apos; Line after splitting on line breaks
+Dim lPos As Long &apos; Position in sLine already wrapped
+Dim lStart As Long &apos; Start position before and after regex search
+Dim sSpace As String &apos; Next whitespace
+Dim sChunk As String &apos; Next wrappable text chunk
+Const cstThisSub = &quot;String.Wrap&quot;
+Const cstSubArgs = &quot;InputStr, [Width=70], [TabSize=8]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vWrap = Array()
+
+Check:
+ If IsMissing(Width) Or IsEmpty(Width) Then Width = 70
+ If IsMissing(TabSize) Or IsEmpty(TabSize) Then TabSize = 8
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(Width, &quot;Width&quot;, V_NUMERIC) Then GoTo Finally
+ If Not SF_Utils._Validate(TabSize, &quot;TabSize&quot;, V_NUMERIC) Then GoTo Finally
+ End If
+
+Try:
+ If Len(InputStr) &gt; 0 Then
+ sWrap = SF_String.Unescape(InputStr) &apos; Replace symbolic breaks
+ sWrap = SF_String.ExpandTabs(sWrap, TabSize) &apos; Interpret TABs to have a meaningful Width
+ &apos; First, split full string
+ vWrapLines = SF_String.SplitLines(sWrap, KeepBreaks := True) &apos; Keep pre-existing breaks
+ If UBound(vWrapLines) = 0 And Len(sWrap) &lt;= Width Then &apos; Output a single line
+ vWrap = Array(sWrap)
+ Else
+ &apos; Second, split each line on Width
+ For Each sLine In vWrapLines
+ If Len(sLine) &lt;= Width Then
+ If UBound(vWrap) &lt; 0 Then vWrap = Array(sLine) Else vWrap = SF_Array.Append(vWrap, sLine)
+ Else
+ &apos; Scan sLine and accumulate found substrings up to Width
+ lStart = 1
+ lPos = 0
+ sWrap = &quot;&quot;
+ Do While lStart &lt;= Len(sLine)
+ sSpace = SF_String.FindRegex(sLine, REGEXSPACES, lStart)
+ If lStart = 0 Then lStart = Len(sLine) + 1
+ sChunk = Mid(sLine, lPos + 1, lStart - 1 - lPos + Len(sSpace))
+ If Len(sWrap) + Len(sChunk) &lt; Width Then &apos; Add chunk to current piece of line
+ sWrap = sWrap &amp; sChunk
+ Else &apos; Save current line and initialize next one
+ If UBound(vWrap) &lt; 0 Then vWrap = Array(sWrap) Else vWrap = SF_Array.Append(vWrap, sWrap)
+ sWrap = sChunk
+ End If
+ lPos = lPos + Len(sChunk)
+ lStart = lPos + 1
+ Loop
+ &apos; Add last chunk
+ If Len(sWrap) &gt; 0 Then
+ If UBound(vWrap) &lt; 0 Then vWrap = Array(sWrap) Else vWrap = SF_Array.Append(vWrap, sWrap)
+ End If
+ End If
+ Next sLine
+ End If
+ End If
+
+Finally:
+ Wrap = vWrap
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_String.Wrap
+
+REM ============================================================= PRIVATE METHODS
+
+REM -----------------------------------------------------------------------------
+Private Function _Repr(ByRef pvString As String) As String
+&apos;&apos;&apos; Convert an arbitrary string to a readable string, typically for debugging purposes (DebugPrint ...)
+&apos;&apos;&apos; Carriage Returns are replaced by \r. Other line breaks are replaced by \n
+&apos;&apos;&apos; Tabs are replaced by \t
+&apos;&apos;&apos; Backslashes are doubled
+&apos;&apos;&apos; Other non printable characters are replaced by \x00 to \xFF or \x0000 to \xFFFF
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; pvString: the string to make readable
+&apos;&apos;&apos; Return:
+&apos;&apos;&apos; the converted string
+
+Dim sString As String &apos; Return value
+Dim sChar As String &apos; A single character
+Dim lAsc As Long &apos; Ascii value
+Dim lPos As Long &apos; Position in sString
+Dim i As Long
+
+ &apos; Process TABs, CRs and LFs
+ sString = Replace(Replace(Replace(pvString, &quot;\&quot;, &quot;\\&quot;), SF_String.sfCR, &quot;\r&quot;), SF_String.sfTAB, &quot;\t&quot;)
+ sString = Join(SF_String.SplitLines(sString, KeepBreaks := False), &quot;\n&quot;)
+ &apos; Process not printable characters
+ If Len(sString) &gt; 0 Then
+ lPos = 1
+ Do While lPos &lt;= Len(sString)
+ sChar = Mid(sString, lPos, 1)
+ If Not SF_String.IsPrintable(sChar) Then
+ lAsc = Asc(sChar)
+ sChar = &quot;\x&quot; &amp; Iif(lAsc &lt; 255, Right(&quot;00&quot; &amp; Hex(lAsc), 2), Right(&quot;0000&quot; &amp; Hex(lAsc), 4))
+ If lPos &lt; Len(sString) Then
+ sString = Left(sString, lPos - 1) &amp; sChar &amp; Mid(sString, lPos + 1)
+ Else
+ sString = Left(sString, lPos - 1) &amp; sChar
+ End If
+ End If
+ lPos = lPos + Len(sChar)
+ Loop
+ End If
+
+ _Repr = sString
+
+End Function &apos; ScriptForge.SF_String._Repr
+
+REM ================================================ END OF SCRIPTFORGE.SF_STRING
+</script:module> \ No newline at end of file
diff --git a/wizards/source/scriptforge/SF_TextStream.xba b/wizards/source/scriptforge/SF_TextStream.xba
new file mode 100644
index 000000000..35f1b6fb2
--- /dev/null
+++ b/wizards/source/scriptforge/SF_TextStream.xba
@@ -0,0 +1,702 @@
+<?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_TextStream" 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 ClassModule
+
+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_TextStream
+&apos;&apos;&apos; =============
+&apos;&apos;&apos; Class instantiated by the
+&apos;&apos;&apos; SF_FileSystem.CreateTextFile
+&apos;&apos;&apos; SF_FileSystem.OpenTextFile
+&apos;&apos;&apos; methods to facilitate the sequential processing of text files
+&apos;&apos;&apos; All open/read/write/close operations are presumed to happen during the same macro run
+&apos;&apos;&apos; The encoding to be used may be chosen by the user
+&apos;&apos;&apos; The list is in the Name column of https://www.iana.org/assignments/character-sets/character-sets.xhtml
+&apos;&apos;&apos; Note that probably not all values are available
+&apos;&apos;&apos; Line delimiters may be chosen by the user
+&apos;&apos;&apos; In input, CR, LF or CR+LF are supported
+&apos;&apos;&apos; In output, the default value is the usual newline on the actual operating system (see SF_FileSystem.sfNEWLINE)
+&apos;&apos;&apos;
+&apos;&apos;&apos; The design choices are largely inspired by
+&apos;&apos;&apos; https://docs.microsoft.com/en-us/office/vba/language/reference/user-interface-help/textstream-object
+&apos;&apos;&apos; The implementation is mainly based on the XTextInputStream and XTextOutputStream UNO interfaces
+&apos;&apos;&apos; https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1io_1_1XTextInputStream.html
+&apos;&apos;&apos; https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1io_1_1XTextOutputStream.html
+&apos;&apos;&apos;
+&apos;&apos;&apos; Instantiation example:
+&apos;&apos;&apos; Dim FSO As Object, myFile As Object
+&apos;&apos;&apos; Set FSO = CreateScriptService(&quot;FileSystem&quot;)
+&apos;&apos;&apos; Set myFile = FSO.OpenTextFile(&quot;C:\Temp\ThisFile.txt&quot;, FSO.ForReading) &apos; Once per file
+&apos;&apos;&apos;
+&apos;&apos;&apos; Detailed user documentation:
+&apos;&apos;&apos; https://help.libreoffice.org/latest/en-US/text/sbasic/shared/03/sf_textstream.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 FILENOTOPENERROR = &quot;FILENOTOPENERROR&quot; &apos; The file is already closed
+Const FILEOPENMODEERROR = &quot;FILEOPENMODEERROR&quot; &apos; The file is open in incompatible mode
+Const ENDOFFILEERROR = &quot;ENDOFFILEERROR&quot; &apos; When file was read, an end-of-file was encountered
+
+REM ============================================================= PRIVATE MEMBERS
+
+Private [Me] As Object
+Private [_Parent] As Object
+Private ObjectType As String &apos; Must be TEXTSTREAM
+Private ServiceName As String
+Private _FileName As String &apos; File where it is about
+Private _IOMode As Integer &apos; ForReading, ForWriting or ForAppending
+Private _Encoding As String &apos; https://www.iana.org/assignments/character-sets/character-sets.xhtml
+Private _NewLine As String &apos; Line break in write mode
+Private _FileExists As Boolean &apos; True if file exists before open
+Private _LineNumber As Long &apos; Number of lines read or written
+Private _FileHandler As Object &apos; com.sun.star.io.XInputStream or
+ &apos; com.sun.star.io.XOutputStream or
+ &apos; com.sun.star.io.XStream
+Private _InputStream As Object &apos; com.sun.star.io.TextInputStream
+Private _OutputStream As Object &apos; com.sun.star.io.TextOutputStream
+Private _ForceBlankLine As Boolean &apos; Workaround: XTextInputStream misses last line if file ends with newline
+
+REM ============================================================ MODULE CONSTANTS
+
+REM ===================================================== CONSTRUCTOR/DESTRUCTOR
+
+REM -----------------------------------------------------------------------------
+Private Sub Class_Initialize()
+ Set [Me] = Nothing
+ Set [_Parent] = Nothing
+ ObjectType = &quot;TEXTSTREAM&quot;
+ ServiceName = &quot;ScriptForge.TextStream&quot;
+ _FileName = &quot;&quot;
+ _IOMode = -1
+ _Encoding = &quot;&quot;
+ _NewLine = &quot;&quot;
+ _FileExists = False
+ _LineNumber = 0
+ Set _FileHandler = Nothing
+ Set _InputStream = Nothing
+ Set _OutputStream = Nothing
+ _ForceBlankLine = False
+End Sub &apos; ScriptForge.SF_TextStream Constructor
+
+REM -----------------------------------------------------------------------------
+Private Sub Class_Terminate()
+ Call Class_Initialize()
+End Sub &apos; ScriptForge.SF_TextStream Destructor
+
+REM -----------------------------------------------------------------------------
+Public Function Dispose() As Variant
+ Call Class_Terminate()
+ Set Dispose = Nothing
+End Function &apos; ScriptForge.SF_TextStream Explicit Destructor
+
+REM ================================================================== PROPERTIES
+
+REM -----------------------------------------------------------------------------
+Property Get AtEndOfStream() As Boolean
+&apos;&apos;&apos; In reading mode, True indicates that the end of the file has been reached
+&apos;&apos;&apos; In write and append modes, or if the file is not ready =&gt; always True
+&apos;&apos;&apos; The property should be invoked BEFORE each ReadLine() method:
+&apos;&apos;&apos; A ReadLine() executed while AtEndOfStream is True will raise an error
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; Dim sLine As String
+&apos;&apos;&apos; Do While Not myFile.AtEndOfStream
+&apos;&apos;&apos; sLine = myFile.ReadLine()
+&apos;&apos;&apos; &apos; ...
+&apos;&apos;&apos; Loop
+
+ AtEndOfStream = _PropertyGet(&quot;AtEndOfStream&quot;)
+
+End Property &apos; ScriptForge.SF_TextStream.AtEndOfStream
+
+REM -----------------------------------------------------------------------------
+Property Get Encoding() As String
+&apos;&apos;&apos; Returns the name of the text file either in url or in native operating system format
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; Dim myFile As Object
+&apos;&apos;&apos; FSO.FileNaming = &quot;SYS&quot;
+&apos;&apos;&apos; Set myFile = FSO.OpenTextFile(&quot;C:\Temp\myFile.txt&quot;)
+&apos;&apos;&apos; MsgBox myFile.Encoding &apos; UTF-8
+
+ Encoding = _PropertyGet(&quot;Encoding&quot;)
+
+End Property &apos; ScriptForge.SF_TextStream.Encoding
+
+REM -----------------------------------------------------------------------------
+Property Get FileName() As String
+&apos;&apos;&apos; Returns the name of the text file either in url or in native operating system format
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; Dim myFile As Object
+&apos;&apos;&apos; FSO.FileNaming = &quot;SYS&quot;
+&apos;&apos;&apos; Set myFile = FSO.OpenTextFile(&quot;C:\Temp\myFile.txt&quot;)
+&apos;&apos;&apos; MsgBox myFile.FileName &apos; C:\Temp\myFile.txt
+
+ FileName = _PropertyGet(&quot;FileName&quot;)
+
+End Property &apos; ScriptForge.SF_TextStream.FileName
+
+REM -----------------------------------------------------------------------------
+Property Get IOMode() As String
+&apos;&apos;&apos; Returns either &quot;READ&quot;, &quot;WRITE&quot; or &quot;APPEND&quot;
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; Dim myFile As Object
+&apos;&apos;&apos; FSO.FileNaming = &quot;SYS&quot;
+&apos;&apos;&apos; Set myFile = FSO.OpenTextFile(&quot;C:\Temp\myFile.txt&quot;)
+&apos;&apos;&apos; MsgBox myFile.IOMode &apos; READ
+
+ IOMode = _PropertyGet(&quot;IOMode&quot;)
+
+End Property &apos; ScriptForge.SF_TextStream.IOMode
+
+REM -----------------------------------------------------------------------------
+Property Get Line() As Long
+&apos;&apos;&apos; Returns the number of lines read or written so far
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; Dim myFile As Object
+&apos;&apos;&apos; FSO.FileNaming = &quot;SYS&quot;
+&apos;&apos;&apos; Set myFile = FSO.OpenTextFile(&quot;C:\Temp\myFile.txt&quot;, FSO.ForAppending)
+&apos;&apos;&apos; MsgBox myFile.Line &apos; The number of lines already present in myFile
+
+ Line = _PropertyGet(&quot;Line&quot;)
+
+End Property &apos; ScriptForge.SF_TextStream.Line
+
+REM -----------------------------------------------------------------------------
+Property Get NewLine() As Variant
+&apos;&apos;&apos; Returns the current character string to be inserted between 2 successive written lines
+&apos;&apos;&apos; The default value is the native line separator in the current operating system
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; MsgBox myFile.NewLine
+
+ NewLine = _PropertyGet(&quot;NewLine&quot;)
+
+End Property &apos; ScriptForge.SF_TextStream.NewLine (get)
+
+REM -----------------------------------------------------------------------------
+Property Let NewLine(ByVal pvLineBreak As Variant)
+&apos;&apos;&apos; Sets the current character string to be inserted between 2 successive written lines
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; myFile.NewLine = Chr(13) &amp; Chr(10)
+
+Const cstThisSub = &quot;TextStream.setNewLine&quot;
+
+ SF_Utils._EnterFunction(cstThisSub)
+ If VarType(pvLineBreak) = V_STRING Then _NewLine = pvLineBreak
+ SF_Utils._ExitFunction(cstThisSub)
+
+End Property &apos; ScriptForge.SF_TextStream.NewLine (let)
+
+REM ===================================================================== METHODS
+
+REM -----------------------------------------------------------------------------
+Public Function CloseFile() As Boolean
+&apos;&apos;&apos; Empties the output buffer if relevant. Closes the actual input or output stream
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if the closure was successful
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; FILENOTOPENERROR Nothing found to close
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; myFile.CloseFile()
+
+Dim bClose As Boolean &apos; Return value
+Const cstThisSub = &quot;TextStream.CloseFile&quot;
+Const cstSubArgs = &quot;&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bClose = False
+
+Check:
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+ If Not _IsFileOpen() Then GoTo Finally
+
+Try:
+ If Not IsNull(_InputStream) Then _InputStream.closeInput()
+ If Not IsNull(_OutputStream) Then
+ _OutputStream.flush()
+ _OutputStream.closeOutput()
+ End If
+ Set _InputStream = Nothing
+ Set _OutputStream = Nothing
+ Set _FileHandler = Nothing
+ bClose = True
+
+Finally:
+ CloseFile = bClose
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_TextStream.CloseFile
+
+REM -----------------------------------------------------------------------------
+Public Function GetProperty(Optional ByVal PropertyName As Variant) As Variant
+&apos;&apos;&apos; Return the actual value of the given property
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; PropertyName: the name of the property as a string
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The actual value of the property
+&apos;&apos;&apos; If the property does not exist, returns Null
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; see the exceptions of the individual properties
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; myModel.GetProperty(&quot;MyProperty&quot;)
+
+Const cstThisSub = &quot;TextStream.GetProperty&quot;
+Const cstSubArgs = &quot;&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ GetProperty = Null
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
+ End If
+
+Try:
+ GetProperty = _PropertyGet(PropertyName)
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_TextStream.GetProperty
+
+REM -----------------------------------------------------------------------------
+Public Function Methods() As Variant
+&apos;&apos;&apos; Return the list of public methods of the Model service as an array
+
+ Methods = Array( _
+ &quot;CloseFile&quot; _
+ , &quot;ReadAll&quot; _
+ , &quot;readLine&quot; _
+ , &quot;SkipLine&quot; _
+ , &quot;WriteBlankLines&quot; _
+ , &quot;WriteLine&quot; _
+ )
+
+End Function &apos; ScriptForge.SF_TextStream.Methods
+
+REM -----------------------------------------------------------------------------
+Public Function Properties() As Variant
+&apos;&apos;&apos; Return the list or properties of the Timer class as an array
+
+ Properties = Array( _
+ &quot;AtEndOfStream&quot; _
+ , &quot;Encoding&quot; _
+ , &quot;FileName&quot; _
+ , &quot;IOMode&quot; _
+ , &quot;Line&quot; _
+ , &quot;NewLine&quot; _
+ )
+
+End Function &apos; ScriptForge.SF_TextStream.Properties
+
+REM -----------------------------------------------------------------------------
+Public Function ReadAll() As String
+&apos;&apos;&apos; Returns all the remaining lines in the text stream as one string. Line breaks are NOT removed
+&apos;&apos;&apos; The resulting string can be split in lines
+&apos;&apos;&apos; either by using the usual Split Basic builtin function if the line delimiter is known
+&apos;&apos;&apos; or with the SF_String.SplitLines method
+&apos;&apos;&apos; For large files, using the ReadAll method wastes memory resources.
+&apos;&apos;&apos; Other techniques should be used to input a file, such as reading a file line-by-line
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The read lines. The string may be empty.
+&apos;&apos;&apos; Note that the Line property in incremented only by 1
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; FILENOTOPENERROR File not open or already closed
+&apos;&apos;&apos; FILEOPENMODEERROR File opened in write or append modes
+&apos;&apos;&apos; ENDOFFILEERROR Previous reads already reached the end of the file
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; Dim a As String
+&apos;&apos;&apos; a = myFile.ReadAll()
+
+Dim sRead As String &apos; Return value
+Const cstThisSub = &quot;TextStream.ReadAll&quot;
+Const cstSubArgs = &quot;&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sRead = &quot;&quot;
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not _IsFileOpen(&quot;READ&quot;) Then GoTo Finally
+ If _InputStream.isEOF() Then GoTo CatchEOF
+ End If
+
+Try:
+ sRead = _InputStream.readString(Array(), False)
+ _LineNumber = _LineNumber + 1
+
+Finally:
+ ReadAll = sRead
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchEOF:
+ SF_Exception.RaiseFatal(ENDOFFILEERROR, FileName)
+ GoTo Finally
+End Function &apos; ScriptForge.SF_TextStream.ReadAll
+
+REM -----------------------------------------------------------------------------
+Public Function ReadLine() As String
+&apos;&apos;&apos; Returns the next line in the text stream as a string. Line breaks are removed.
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The read line. The string may be empty.
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; FILENOTOPENERROR File not open or already closed
+&apos;&apos;&apos; FILEOPENMODEERROR File opened in write or append modes
+&apos;&apos;&apos; ENDOFFILEERROR Previous reads already reached the end of the file
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; Dim a As String
+&apos;&apos;&apos; a = myFile.ReadLine()
+
+Dim sRead As String &apos; Return value
+Dim iRead As Integer &apos; Length of line break
+Const cstThisSub = &quot;TextStream.ReadLine&quot;
+Const cstSubArgs = &quot;&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sRead = &quot;&quot;
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not _IsFileOpen(&quot;READ&quot;) Then GoTo Finally
+ If AtEndOfStream Then GoTo CatchEOF
+ End If
+
+Try:
+ &apos; When the text file ends with a line break,
+ &apos; XTextInputStream.readLine() returns the line break together with the last line
+ &apos; Hence the workaround to force a blank line at the end
+ If _ForceBlankLine Then
+ sRead = &quot;&quot;
+ _ForceBlankLine = False
+ Else
+ sRead = _InputStream.readLine()
+ &apos; The isEOF() is set immediately after having read the last line
+ If _InputStream.isEOF() And Len(sRead) &gt; 0 Then
+ iRead = 0
+ If SF_String.EndsWith(sRead, SF_String.sfCRLF) Then
+ iRead = 2
+ ElseIf SF_String.EndsWith(sRead, SF_String.sfLF) Or SF_String.EndsWith(sRead, SF_String.sfCR) Then
+ iRead = 1
+ End If
+ If iRead &gt; 0 Then
+ sRead = Left(sRead, Len(sRead) - iRead)
+ _ForceBlankLine = True &apos; Provision for a last empty line at the next read loop
+ End If
+ End If
+ End If
+ _LineNumber = _LineNumber + 1
+
+Finally:
+ ReadLine = sRead
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchEOF:
+ SF_Exception.RaiseFatal(ENDOFFILEERROR, FileName)
+ GoTo Finally
+End Function &apos; ScriptForge.SF_TextStream.ReadLine
+
+REM -----------------------------------------------------------------------------
+Public Function SetProperty(Optional ByVal PropertyName As Variant _
+ , Optional ByRef Value As Variant _
+ ) As Boolean
+&apos;&apos;&apos; Set a new value to the given property
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; PropertyName: the name of the property as a string
+&apos;&apos;&apos; Value: its new value
+&apos;&apos;&apos; Exceptions
+&apos;&apos;&apos; ARGUMENTERROR The property does not exist
+
+Dim bSet As Boolean &apos; Return value
+Const cstThisSub = &quot;TextStream.SetProperty&quot;
+Const cstSubArgs = &quot;PropertyName, Value&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bSet = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
+ End If
+
+Try:
+ bSet = True
+ Select Case UCase(PropertyName)
+ Case &quot;NEWLINE&quot;
+ If Not SF_Utils._Validate(Value, &quot;Value&quot;, V_STRING) Then GoTo Catch
+ NewLine = Value
+ Case Else
+ bSet = False
+ End Select
+
+Finally:
+ SetProperty = bSet
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_TextStream.SetProperty
+
+REM -----------------------------------------------------------------------------
+Public Sub SkipLine()
+&apos;&apos;&apos; Skips the next line when reading a TextStream file.
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; FILENOTOPENERROR File not open or already closed
+&apos;&apos;&apos; FILEOPENMODEERROR File opened in write or append modes
+&apos;&apos;&apos; ENDOFFILEERROR Previous reads already reached the end of the file
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; myFile.SkipLine()
+
+Dim sRead As String &apos; Read buffer
+Const cstThisSub = &quot;TextStream.SkipLine&quot;
+Const cstSubArgs = &quot;&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not _IsFileOpen(&quot;READ&quot;) Then GoTo Finally
+ If Not _ForceBlankLine Then &apos; The file ends with a newline =&gt; return one empty line more
+ If _InputStream.isEOF() Then GoTo CatchEOF
+ End If
+ End If
+
+Try:
+ sRead = ReadLine()
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Sub
+Catch:
+ GoTo Finally
+CatchEOF:
+ SF_Exception.RaiseFatal(ENDOFFILEERROR, FileName)
+ GoTo Finally
+End Sub &apos; ScriptForge.SF_TextStream.SkipLine
+
+REM -----------------------------------------------------------------------------
+Public Sub WriteBlankLines(Optional ByVal Lines As Variant)
+&apos;&apos;&apos; Writes a number of empty lines in the output stream
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Lines: the number of lines to write
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; FILENOTOPENERROR File not open or already closed
+&apos;&apos;&apos; FILEOPENMODEERROR File opened in read mode
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; myFile.WriteBlankLines(10)
+Dim i As Long
+Const cstThisSub = &quot;TextStream.WriteBlankLines&quot;
+Const cstSubArgs = &quot;Lines&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not _IsFileOpen(&quot;WRITE&quot;) Then GoTo Finally
+ If Not SF_Utils._Validate(Lines, &quot;Lines&quot;, V_NUMERIC) Then GoTo Finally
+ End If
+
+Try:
+ For i = 1 To Lines
+ _OutputStream.writeString(_NewLine)
+ Next i
+ _LineNumber = _LineNumber + Lines
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Sub
+Catch:
+ GoTo Finally
+End Sub &apos; ScriptForge.SF_TextStream.WriteBlankLines
+
+REM -----------------------------------------------------------------------------
+Public Sub WriteLine(Optional ByVal Line As Variant)
+&apos;&apos;&apos; Writes the given line to the output stream. A newline is inserted if relevant
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Line: the line to write, may be empty
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; FILENOTOPENERROR File not open or already closed
+&apos;&apos;&apos; FILEOPENMODEERROR File opened in in read mode
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; myFile.WriteLine(&quot;Next line&quot;)
+Dim i As Long
+Const cstThisSub = &quot;TextStream.WriteLine&quot;
+Const cstSubArgs = &quot;Line&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not _IsFileOpen(&quot;WRITE&quot;) Then GoTo Finally
+ If Not SF_Utils._Validate(Line, &quot;Line&quot;, V_STRING) Then GoTo Finally
+ End If
+
+Try:
+ _OutputStream.writeString(Iif(_LineNumber &gt; 0, _NewLine, &quot;&quot;) &amp; Line)
+ _LineNumber = _LineNumber + 1
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Sub
+Catch:
+ GoTo Finally
+End Sub &apos; ScriptForge.SF_TextStream.WriteLine
+
+REM =========================================================== PRIVATE FUNCTIONS
+
+REM -----------------------------------------------------------------------------
+Public Sub _Initialize()
+&apos;&apos;&apos; Opens file and setup input and/or output streams (ForAppending requires both)
+
+Dim oSfa As Object &apos; com.sun.star.ucb.SimpleFileAccess
+
+ &apos; Default newline related to current operating system
+ _NewLine = SF_String.sfNEWLINE
+
+ Set oSfa = SF_Utils._GetUNOService(&quot;FileAccess&quot;)
+
+ &apos; Setup input and/or output streams based on READ/WRITE/APPEND IO modes
+ Select Case _IOMode
+ Case SF_FileSystem.ForReading
+ Set _FileHandler = oSfa.openFileRead(_FileName)
+ Set _InputStream = CreateUnoService(&quot;com.sun.star.io.TextInputStream&quot;)
+ _InputStream.setInputStream(_FileHandler)
+ Case SF_FileSystem.ForWriting
+ &apos; Output file is deleted beforehand
+ If _FileExists Then oSfa.kill(_FileName)
+ Set _FileHandler = oSfa.openFileWrite(_FileName)
+ Set _OutputStream = CreateUnoService(&quot;com.sun.star.io.TextOutputStream&quot;)
+ _OutputStream.setOutputStream(_FileHandler)
+ Case SF_FileSystem.ForAppending
+ Set _FileHandler = oSfa.openFileReadWrite(_FileName)
+ Set _InputStream = CreateUnoService(&quot;com.sun.star.io.TextInputStream&quot;)
+ Set _OutputStream = CreateUnoService(&quot;com.sun.star.io.TextOutputStream&quot;)
+ _InputStream.setInputStream(_FileHandler)
+ &apos; Position at end of file: Skip and count existing lines
+ _LineNumber = 0
+ Do While Not _InputStream.isEOF()
+ _InputStream.readLine()
+ _LineNumber = _LineNumber + 1
+ Loop
+ _OutputStream.setOutputStream(_FileHandler)
+ End Select
+
+ If _Encoding = &quot;&quot; Then _Encoding = &quot;UTF-8&quot;
+ If Not IsNull(_InputStream) Then _InputStream.setEncoding(_Encoding)
+ If Not IsNull(_OutputStream) Then _OutputStream.setEncoding(_Encoding)
+
+End Sub &apos; ScriptForge.SF_TextStream._Initialize
+
+REM -----------------------------------------------------------------------------
+Private Function _IsFileOpen(Optional ByVal psMode As String) As Boolean
+&apos;&apos;&apos; Checks if file is open with the right mode (READ or WRITE)
+&apos;&apos;&apos; Raises an exception if the file is not open at all or not in the right mode
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; psMode: READ or WRITE or zero-length string
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; FILENOTOPENERROR File not open or already closed
+&apos;&apos;&apos; FILEOPENMODEERROR File opened in incompatible mode
+
+ _IsFileOpen = False
+ If IsMissing(psMode) Then psMode = &quot;&quot;
+ If IsNull(_InputStream) And IsNull(_OutputStream) Then GoTo CatchNotOpen
+ Select Case psMode
+ Case &quot;READ&quot;
+ If IsNull(_InputStream) Then GoTo CatchOpenMode
+ If _IOMode &lt;&gt; SF_FileSystem.ForReading Then GoTo CatchOpenMode
+ Case &quot;WRITE&quot;
+ If IsNull(_OutputStream) Then GoTo CatchOpenMode
+ If _IOMode = SF_FileSystem.ForReading Then GoTo CatchOpenMode
+ Case Else
+ End Select
+ _IsFileOpen = True
+
+Finally:
+ Exit Function
+CatchNotOpen:
+ SF_Exception.RaiseFatal(FILENOTOPENERROR, FileName)
+ GoTo Finally
+CatchOpenMode:
+ SF_Exception.RaiseFatal(FILEOPENMODEERROR, FileName, IOMode)
+ GoTo Finally
+End Function &apos; ScriptForge.SF_TextStream._IsFileOpen
+
+REM -----------------------------------------------------------------------------
+Private Function _PropertyGet(Optional ByVal psProperty As String)
+&apos;&apos;&apos; Return the value of the named property
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; psProperty: the name of the property
+
+Dim cstThisSub As String
+Dim cstSubArgs As String
+
+ cstThisSub = &quot;TextStream.get&quot; &amp; psProperty
+ cstSubArgs = &quot;&quot;
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+ Select Case UCase(psProperty)
+ Case UCase(&quot;AtEndOfStream&quot;)
+ Select Case _IOMode
+ Case SF_FileSystem.ForReading
+ If IsNull(_InputStream) Then _PropertyGet = True Else _PropertyGet = CBool(_InputStream.isEOF() And Not _ForceBlankLine)
+ Case Else : _PropertyGet = True
+ End Select
+ Case UCase(&quot;Encoding&quot;)
+ _PropertyGet = _Encoding
+ Case UCase(&quot;FileName&quot;)
+ _PropertyGet = SF_FileSystem._ConvertFromUrl(_FileName) &apos; Depends on FileNaming
+ Case UCase(&quot;IOMode&quot;)
+ With SF_FileSystem
+ Select Case _IOMode
+ Case .ForReading : _PropertyGet = &quot;READ&quot;
+ Case .ForWriting : _PropertyGet = &quot;WRITE&quot;
+ Case .ForAppending : _PropertyGet = &quot;APPEND&quot;
+ Case Else : _PropertyGet = &quot;&quot;
+ End Select
+ End With
+ Case UCase(&quot;Line&quot;)
+ _PropertyGet = _LineNumber
+ Case UCase(&quot;NewLine&quot;)
+ _PropertyGet = _NewLine
+ Case Else
+ _PropertyGet = Null
+ End Select
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+End Function &apos; ScriptForge.SF_TextStream._PropertyGet
+
+REM -----------------------------------------------------------------------------
+Private Function _Repr() As String
+&apos;&apos;&apos; Convert the TextStream instance to a readable string, typically for debugging purposes (DebugPrint ...)
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Return:
+&apos;&apos;&apos; &quot;[TextStream]: File name, IOMode, LineNumber&quot;
+
+ _Repr = &quot;[TextStream]: &quot; &amp; FileName &amp; &quot;,&quot; &amp; IOMode &amp; &quot;,&quot; &amp; CStr(Line)
+
+End Function &apos; ScriptForge.SF_TextStream._Repr
+
+REM ============================================ END OF SCRIPTFORGE.SF_TextStream
+</script:module> \ No newline at end of file
diff --git a/wizards/source/scriptforge/SF_Timer.xba b/wizards/source/scriptforge/SF_Timer.xba
new file mode 100644
index 000000000..2b3286e04
--- /dev/null
+++ b/wizards/source/scriptforge/SF_Timer.xba
@@ -0,0 +1,466 @@
+<?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_Timer" 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 ClassModule
+
+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_Timer
+&apos;&apos;&apos; ========
+&apos;&apos;&apos; Class for management of scripts execution performance
+&apos;&apos;&apos; A Timer measures durations. It can be suspended, resumed, restarted
+&apos;&apos;&apos; Duration properties are expressed in seconds with a precision of 3 decimal digits
+&apos;&apos;&apos;
+&apos;&apos;&apos; Service invocation example:
+&apos;&apos;&apos; Dim myTimer As Variant
+&apos;&apos;&apos; myTimer = CreateScriptService(&quot;Timer&quot;)
+&apos;&apos;&apos; myTimer = CreateScriptService(&quot;Timer&quot;, True) &apos; =&gt; To start timer immediately
+&apos;&apos;&apos;
+&apos;&apos;&apos; Detailed user documentation:
+&apos;&apos;&apos; https://help.libreoffice.org/latest/en-US/text/sbasic/shared/03/sf_timer.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
+
+REM ============================================================= PRIVATE MEMBERS
+
+Private [Me] As Object
+Private [_Parent] As Object
+Private ObjectType As String &apos; Must be &quot;TIMER&quot;
+Private ServiceName As String
+Private _TimerStatus As Integer &apos; inactive, started, suspended or stopped
+Private _StartTime As Double &apos; Moment when timer started, restarted
+Private _EndTime As Double &apos; Moment when timer stopped
+Private _SuspendTime As Double &apos; Moment when timer suspended
+Private _SuspendDuration As Double &apos; Duration of suspended status as a difference of times
+
+REM ============================================================ MODULE CONSTANTS
+
+Private Const STATUSINACTIVE = 0
+Private Const STATUSSTARTED = 1
+Private Const STATUSSUSPENDED = 2
+Private Const STATUSSTOPPED = 3
+
+Private Const DSECOND As Double = 1 / (24 * 60 * 60) &apos; Duration of 1 second as compared to 1.0 = 1 day
+
+REM ===================================================== CONSTRUCTOR/DESTRUCTOR
+
+REM -----------------------------------------------------------------------------
+Private Sub Class_Initialize()
+ Set [Me] = Nothing
+ Set [_Parent] = Nothing
+ ObjectType = &quot;TIMER&quot;
+ ServiceName = &quot;ScriptForge.Timer&quot;
+ _TimerStatus = STATUSINACTIVE
+ _StartTime = 0
+ _EndTime = 0
+ _SuspendTime = 0
+ _SuspendDuration = 0
+End Sub &apos; ScriptForge.SF_Timer Constructor
+
+REM -----------------------------------------------------------------------------
+Private Sub Class_Terminate()
+ Call Class_Initialize()
+End Sub &apos; ScriptForge.SF_Timer Destructor
+
+REM -----------------------------------------------------------------------------
+Public Function Dispose() As Variant
+ Call Class_Terminate()
+ Set Dispose = Nothing
+End Function &apos; ScriptForge.SF_Timer Explicit destructor
+
+REM ================================================================== PROPERTIES
+
+REM -----------------------------------------------------------------------------
+Public Function Duration() As Double
+&apos;&apos;&apos; Returns the actual (out of suspensions) time elapsed since start or between start and stop
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; A Double expressing the duration in seconds
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; myTimer.Duration returns 1.234 (1 sec, 234 ms)
+
+ Duration = _PropertyGet(&quot;Duration&quot;)
+
+End Function &apos; ScriptForge.SF_Timer.Duration
+
+REM -----------------------------------------------------------------------------
+Property Get IsStarted() As Boolean
+&apos;&apos;&apos; Returns True if timer is started or suspended
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; myTimer.IsStarted
+
+ IsStarted = _PropertyGet(&quot;IsStarted&quot;)
+
+End Property &apos; ScriptForge.SF_Timer.IsStarted
+
+REM -----------------------------------------------------------------------------
+Property Get IsSuspended() As Boolean
+&apos;&apos;&apos; Returns True if timer is started and suspended
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; myTimer.IsSuspended
+
+ IsSuspended = _PropertyGet(&quot;IsSuspended&quot;)
+
+End Property &apos; ScriptForge.SF_Timer.IsSuspended
+
+REM -----------------------------------------------------------------------------
+Public Function SuspendDuration() As Double
+&apos;&apos;&apos; Returns the actual time elapsed while suspended since start or between start and stop
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; A Double expressing the duration in seconds
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; myTimer.SuspendDuration returns 1.234 (1 sec, 234 ms)
+
+ SuspendDuration = _PropertyGet(&quot;SuspendDuration&quot;)
+
+End Function &apos; ScriptForge.SF_Timer.SuspendDuration
+
+REM -----------------------------------------------------------------------------
+Public Function TotalDuration() As Double
+&apos;&apos;&apos; Returns the actual time elapsed (including suspensions) since start or between start and stop
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; A Double expressing the duration in seconds
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; myTimer.TotalDuration returns 1.234 (1 sec, 234 ms)
+
+ TotalDuration = _PropertyGet(&quot;TotalDuration&quot;)
+
+End Function &apos; ScriptForge.SF_Timer.TotalDuration
+
+REM ===================================================================== METHODS
+
+REM -----------------------------------------------------------------------------
+Public Function Continue() As Boolean
+&apos;&apos;&apos; Halt suspension of a running timer
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if successful, False if the timer is not suspended
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; myTimer.Continue()
+
+Const cstThisSub = &quot;Timer.Continue&quot;
+Const cstSubArgs = &quot;&quot;
+
+Check:
+ Continue = False
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Try:
+ If _TimerStatus = STATUSSUSPENDED Then
+ _TimerStatus = STATUSSTARTED
+ _SuspendDuration = _SuspendDuration + _Now() - _SuspendTime
+ _SuspendTime = 0
+ Continue = True
+ End If
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+End Function &apos; ScriptForge.SF_Timer.Continue
+
+REM -----------------------------------------------------------------------------
+Public Function GetProperty(Optional ByVal PropertyName As Variant) As Variant
+&apos;&apos;&apos; Return the actual value of the given property
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; PropertyName: the name of the property as a string
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The actual value of the property
+&apos;&apos;&apos; Exceptions
+&apos;&apos;&apos; ARGUMENTERROR The property does not exist
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; myTimer.GetProperty(&quot;Duration&quot;)
+
+Const cstThisSub = &quot;Timer.GetProperty&quot;
+Const cstSubArgs = &quot;PropertyName&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ GetProperty = Null
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
+ End If
+
+Try:
+ GetProperty = _PropertyGet(PropertyName)
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Timer.Properties
+
+REM -----------------------------------------------------------------------------
+Public Function Methods() As Variant
+&apos;&apos;&apos; Return the list or methods of the Timer class as an array
+
+ Methods = Array( _
+ &quot;Continue&quot; _
+ , &quot;Restart&quot; _
+ , &quot;Start&quot; _
+ , &quot;Suspend&quot; _
+ , &quot;Terminate&quot; _
+ )
+
+End Function &apos; ScriptForge.SF_Timer.Methods
+
+REM -----------------------------------------------------------------------------
+Public Function Properties() As Variant
+&apos;&apos;&apos; Return the list or properties of the Timer class as an array
+
+ Properties = Array( _
+ &quot;Duration&quot; _
+ , &quot;IsStarted&quot; _
+ , &quot;IsSuspended&quot; _
+ , &quot;SuspendDuration&quot; _
+ , &quot;TotalDuration&quot; _
+ )
+
+End Function &apos; ScriptForge.SF_Timer.Properties
+
+REM -----------------------------------------------------------------------------
+Public Function Restart() As Boolean
+&apos;&apos;&apos; Terminate the timer and restart a new clean timer
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if successful, False if the timer is inactive
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; myTimer.Restart()
+
+Const cstThisSub = &quot;Timer.Restart&quot;
+Const cstSubArgs = &quot;&quot;
+
+Check:
+ Restart = False
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Try:
+ If _TimerStatus &lt;&gt; STATUSINACTIVE Then
+ If _TimerStatus &lt;&gt; STATUSSTOPPED Then Terminate()
+ Start()
+ Restart = True
+ End If
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+End Function &apos; ScriptForge.SF_Timer.Restart
+
+REM -----------------------------------------------------------------------------
+Public Function SetProperty(Optional ByVal PropertyName As Variant _
+ , Optional ByRef Value As Variant _
+ ) As Boolean
+&apos;&apos;&apos; Set a new value to the given property
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; PropertyName: the name of the property as a string
+&apos;&apos;&apos; Value: its new value
+&apos;&apos;&apos; Exceptions
+&apos;&apos;&apos; ARGUMENTERROR The property does not exist
+
+Const cstThisSub = &quot;Timer.SetProperty&quot;
+Const cstSubArgs = &quot;PropertyName, Value&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ SetProperty = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
+ End If
+
+Try:
+ Select Case UCase(PropertyName)
+ Case Else
+ End Select
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Timer.SetProperty
+
+REM -----------------------------------------------------------------------------
+Public Function Start() As Boolean
+&apos;&apos;&apos; Start a new clean timer
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if successful, False if the timer is already started
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; myTimer.Start()
+
+Const cstThisSub = &quot;Timer.Start&quot;
+Const cstSubArgs = &quot;&quot;
+
+Check:
+ Start = False
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Try:
+ If _TimerStatus = STATUSINACTIVE Or _TimerStatus = STATUSSTOPPED Then
+ _TimerStatus = STATUSSTARTED
+ _StartTime = _Now()
+ _EndTime = 0
+ _SuspendTime = 0
+ _SuspendDuration = 0
+ Start = True
+ End If
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+End Function &apos; ScriptForge.SF_Timer.Start
+
+REM -----------------------------------------------------------------------------
+Public Function Suspend() As Boolean
+&apos;&apos;&apos; Suspend a running timer
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if successful, False if the timer is not started or already suspended
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; myTimer.Suspend()
+
+Const cstThisSub = &quot;Timer.Suspend&quot;
+Const cstSubArgs = &quot;&quot;
+
+Check:
+ Suspend = False
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Try:
+ If _TimerStatus = STATUSSTARTED Then
+ _TimerStatus = STATUSSUSPENDED
+ _SuspendTime = _Now()
+ Suspend = True
+ End If
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+End Function &apos; ScriptForge.SF_Timer.Suspend
+
+REM -----------------------------------------------------------------------------
+Public Function Terminate() As Boolean
+&apos;&apos;&apos; Terminate a running timer
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if successful, False if the timer is neither started nor suspended
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; myTimer.Terminate()
+
+Const cstThisSub = &quot;Timer.Terminate&quot;
+Const cstSubArgs = &quot;&quot;
+
+Check:
+ Terminate = False
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Try:
+ If _TimerStatus = STATUSSTARTED Or _TimerStatus = STATUSSUSPENDED Then
+ If _TimerSTatus = STATUSSUSPENDED Then Continue()
+ _TimerStatus = STATUSSTOPPED
+ _EndTime = _Now()
+ Terminate = True
+ End If
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+End Function &apos; ScriptForge.SF_Timer.Terminate
+
+REM =========================================================== PRIVATE FUNCTIONS
+
+REM -----------------------------------------------------------------------------
+Private Function _Now() As Double
+&apos;&apos;&apos; Returns the current date and time
+&apos;&apos;&apos; Uses the Calc NOW() function to get a higher precision than the usual Basic Now() function
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The actual time as a number
+&apos;&apos;&apos; The integer part represents the date, the decimal part represents the time
+
+ _Now = SF_Session.ExecuteCalcFunction(&quot;NOW&quot;)
+
+End Function &apos; ScriptForge.SF_Timer._Now
+
+REM -----------------------------------------------------------------------------
+Private Function _PropertyGet(Optional ByVal psProperty As String)
+&apos;&apos;&apos; Return the named property
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; psProperty: the name of the property
+
+Dim dDuration As Double &apos; Computed duration
+Dim cstThisSub As String
+Dim cstSubArgs As String
+
+ cstThisSub = &quot;Timer.get&quot; &amp; psProperty
+ cstSubArgs = &quot;&quot;
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+ Select Case UCase(psProperty)
+ Case UCase(&quot;Duration&quot;)
+ Select Case _TimerStatus
+ Case STATUSINACTIVE : dDuration = 0.0
+ Case STATUSSTARTED
+ dDuration = _Now() - _StartTime - _SuspendDuration
+ Case STATUSSUSPENDED
+ dDuration = _SuspendTime - _StartTime - _SuspendDuration
+ Case STATUSSTOPPED
+ dDuration = _EndTime - _StartTime - _SuspendDuration
+ End Select
+ _PropertyGet = Fix(dDuration * 1000 / DSECOND) / 1000
+ Case UCase(&quot;IsStarted&quot;)
+ _PropertyGet = CBool( _TimerStatus = STATUSSTARTED Or _TimerStatus = STATUSSUSPENDED )
+ Case UCase(&quot;IsSuspended&quot;)
+ _PropertyGet = CBool( _TimerStatus = STATUSSUSPENDED )
+ Case UCase(&quot;SuspendDuration&quot;)
+ Select Case _TimerStatus
+ Case STATUSINACTIVE : dDuration = 0.0
+ Case STATUSSTARTED, STATUSSTOPPED
+ dDuration = _SuspendDuration
+ Case STATUSSUSPENDED
+ dDuration = _Now() - _SuspendTime + _SuspendDuration
+ End Select
+ _PropertyGet = Fix(dDuration * 1000 / DSECOND) / 1000
+ Case UCase(&quot;TotalDuration&quot;)
+ Select Case _TimerStatus
+ Case STATUSINACTIVE : dDuration = 0.0
+ Case STATUSSTARTED, STATUSSUSPENDED
+ dDuration = _Now() - _StartTime
+ Case STATUSSTOPPED
+ dDuration = _EndTime - _StartTime
+ End Select
+ _PropertyGet = Fix(dDuration * 1000 / DSECOND) / 1000
+ End Select
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+End Function &apos; ScriptForge.SF_Timer._PropertyGet
+
+REM -----------------------------------------------------------------------------
+Private Function _Repr() As String
+&apos;&apos;&apos; Convert the Timer instance to a readable string, typically for debugging purposes (DebugPrint ...)
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Return:
+&apos;&apos;&apos; &quot;[Timer] Duration:xxx.yyy
+
+Const cstTimer = &quot;[Timer] Duration: &quot;
+Const cstMaxLength = 50 &apos; Maximum length for items
+
+ _Repr = cstTimer &amp; Replace(SF_Utils._Repr(Duration), &quot;.&quot;, &quot;&quot;&quot;&quot;)
+
+End Function &apos; ScriptForge.SF_Timer._Repr
+
+REM ============================================ END OF SCRIPTFORGE.SF_TIMER
+</script:module> \ No newline at end of file
diff --git a/wizards/source/scriptforge/SF_UI.xba b/wizards/source/scriptforge/SF_UI.xba
new file mode 100644
index 000000000..c8a7f9a8f
--- /dev/null
+++ b/wizards/source/scriptforge/SF_UI.xba
@@ -0,0 +1,1350 @@
+<?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_UI" 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_UI
+&apos;&apos;&apos; =====
+&apos;&apos;&apos; Singleton class module for the identification and the manipulation of the
+&apos;&apos;&apos; different windows composing the whole LibreOffice application:
+&apos;&apos;&apos; - Windows selection
+&apos;&apos;&apos; - Windows moving and resizing
+&apos;&apos;&apos; - Statusbar settings
+&apos;&apos;&apos; - Creation of new windows
+&apos;&apos;&apos; - Access to the underlying &quot;documents&quot;
+&apos;&apos;&apos;
+&apos;&apos;&apos; WindowName: how to designate a window. It can be either
+&apos;&apos;&apos; a full FileName given in the notation indicated by the current value of SF_FileSystem.FileNaming
+&apos;&apos;&apos; or the last component of the full FileName or even only its BaseName
+&apos;&apos;&apos; or the title of the window
+&apos;&apos;&apos; or, for new documents, something like &quot;Untitled 1&quot;
+&apos;&apos;&apos; or one of the special windows &quot;BASICIDE&quot; and &quot;WELCOMESCREEN&quot;
+&apos;&apos;&apos; The window search is case-sensitive
+&apos;&apos;&apos;
+&apos;&apos;&apos; Service invocation example:
+&apos;&apos;&apos; Dim ui As Variant
+&apos;&apos;&apos; ui = CreateScriptService(&quot;UI&quot;)
+&apos;&apos;&apos;
+&apos;&apos;&apos; Detailed user documentation:
+&apos;&apos;&apos; https://help.libreoffice.org/latest/en-US/text/sbasic/shared/03/sf_ui.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;&apos;&apos;&apos;
+
+REM ================================================================== EXCEPTIONS
+
+Const DOCUMENTERROR = &quot;DOCUMENTERROR&quot; &apos; Requested document was not found
+Const DOCUMENTCREATIONERROR = &quot;DOCUMENTCREATIONERROR&quot; &apos; Incoherent arguments, new document could not be created
+Const DOCUMENTOPENERROR = &quot;DOCUMENTOPENERROR&quot; &apos; Document could not be opened, check the arguments
+Const BASEDOCUMENTOPENERROR = &quot;BASEDOCUMENTOPENERROR&quot; &apos; Id. for Base document
+Const UNKNOWNFILEERROR = &quot;UNKNOWNFILEERROR&quot; &apos; Calc datasource does not exist
+
+REM ============================================================= PRIVATE MEMBERS
+
+Type Window
+ Component As Object &apos; com.sun.star.lang.XComponent
+ Frame As Object &apos; com.sun.star.comp.framework.Frame
+ WindowName As String &apos; Object Name
+ WindowTitle As String &apos; Only mean to identify new documents
+ WindowFileName As String &apos; URL of file name
+ DocumentType As String &apos; Writer, Calc, ...
+End Type
+
+&apos; The progress/status bar of the active window
+&apos;Private oStatusBar As Object &apos; com.sun.star.task.XStatusIndicator
+
+REM ============================================================ MODULE CONSTANTS
+
+&apos; Special windows
+Const BASICIDE = &quot;BASICIDE&quot;
+Const WELCOMESCREEN = &quot;WELCOMESCREEN&quot;
+
+&apos; Document types (only if not 1 of the special windows)
+Const BASEDOCUMENT = &quot;Base&quot;
+Const CALCDOCUMENT = &quot;Calc&quot;
+Const DRAWDOCUMENT = &quot;Draw&quot;
+Const IMPRESSDOCUMENT = &quot;Impress&quot;
+Const MATHDOCUMENT = &quot;Math&quot;
+Const WRITERDOCUMENT = &quot;Writer&quot;
+
+&apos; Window subtypes - Not supported yet
+Const BASETABLE = &quot;BASETABLE&quot;
+Const BASEQUERY = &quot;BASEQUERY&quot;
+Const BASEREPORT = &quot;BASEREPORT&quot;
+Const BASEDIAGRAM = &quot;BASEDIAGRAM&quot;
+
+&apos; Macro execution modes
+Const cstMACROEXECNORMAL = 0 &apos; Default, execution depends on user configuration and choice
+Const cstMACROEXECNEVER = 1 &apos; Macros are not executed
+Const cstMACROEXECALWAYS = 2 &apos; Macros are always executed
+
+REM ===================================================== CONSTRUCTOR/DESTRUCTOR
+
+REM -----------------------------------------------------------------------------
+Public Function Dispose() As Variant
+ Set Dispose = Nothing
+End Function &apos; ScriptForge.SF_UI Explicit destructor
+
+REM ================================================================== PROPERTIES
+
+REM -----------------------------------------------------------------------------
+Public Function ActiveWindow() As String
+&apos;&apos;&apos; Returns a valid WindowName for the currently active window
+&apos;&apos;&apos; When &quot;&quot; is returned, the window could not be identified
+
+Dim vWindow As Window &apos; A component
+Dim oComp As Object &apos; com.sun.star.lang.XComponent
+
+ Set oComp = StarDesktop.CurrentComponent
+ If Not IsNull(oComp) Then
+ vWindow = SF_UI._IdentifyWindow(oComp)
+ With vWindow
+ If Len(.WindowFileName) &gt; 0 Then
+ ActiveWindow = SF_FileSystem._ConvertFromUrl(.WindowFileName)
+ ElseIf Len(.WindowName) &gt; 0 Then
+ ActiveWindow = .WindowName
+ ElseIf Len(.WindowTitle) &gt; 0 Then
+ ActiveWindow = .WindowTitle
+ Else
+ ActiveWindow = &quot;&quot;
+ End If
+ End With
+ End If
+
+End Function &apos; ScriptForge.SF_UI.ActiveWindow
+
+REM -----------------------------------------------------------------------------
+Property Get Height() As Long
+&apos;&apos;&apos; Returns the height of the active window
+Dim oPosSize As Object &apos; com.sun.star.awt.Rectangle
+ Set oPosSize = SF_UI._PosSize()
+ If Not IsNull(oPosSize) Then Height = oPosSize.Height Else Height = -1
+End Property &apos; ScriptForge.SF_UI.Height
+
+REM -----------------------------------------------------------------------------
+Property Get MACROEXECALWAYS As Integer
+&apos;&apos;&apos; Macros are always executed
+ MACROEXECALWAYS = cstMACROEXECALWAYS
+End Property &apos; ScriptForge.SF_UI.MACROEXECALWAYS
+
+REM -----------------------------------------------------------------------------
+Property Get MACROEXECNEVER As Integer
+&apos;&apos;&apos; Macros are not executed
+ MACROEXECNEVER = cstMACROEXECNEVER
+End Property &apos; ScriptForge.SF_UI.MACROEXECNEVER
+
+REM -----------------------------------------------------------------------------
+Property Get MACROEXECNORMAL As Integer
+&apos;&apos;&apos; Default, execution depends on user configuration and choice
+ MACROEXECNORMAL = cstMACROEXECNORMAL
+End Property &apos; ScriptForge.SF_UI.MACROEXECNORMAL
+
+REM -----------------------------------------------------------------------------
+Property Get ObjectType As String
+&apos;&apos;&apos; Only to enable object representation
+ ObjectType = &quot;SF_UI&quot;
+End Property &apos; ScriptForge.SF_UI.ObjectType
+
+REM -----------------------------------------------------------------------------
+Property Get ServiceName As String
+&apos;&apos;&apos; Internal use
+ ServiceName = &quot;ScriptForge.UI&quot;
+End Property &apos; ScriptForge.SF_UI.ServiceName
+
+REM -----------------------------------------------------------------------------
+Property Get Width() As Long
+&apos;&apos;&apos; Returns the width of the active window
+Dim oPosSize As Object &apos; com.sun.star.awt.Rectangle
+ Set oPosSize = SF_UI._PosSize()
+ If Not IsNull(oPosSize) Then Width = oPosSize.Width Else Width = -1
+End Property &apos; ScriptForge.SF_UI.Width
+
+REM -----------------------------------------------------------------------------
+Property Get X() As Long
+&apos;&apos;&apos; Returns the X coordinate of the active window
+Dim oPosSize As Object &apos; com.sun.star.awt.Rectangle
+ Set oPosSize = SF_UI._PosSize()
+ If Not IsNull(oPosSize) Then X = oPosSize.X Else X = -1
+End Property &apos; ScriptForge.SF_UI.X
+
+REM -----------------------------------------------------------------------------
+Property Get Y() As Long
+&apos;&apos;&apos; Returns the Y coordinate of the active window
+Dim oPosSize As Object &apos; com.sun.star.awt.Rectangle
+ Set oPosSize = SF_UI._PosSize()
+ If Not IsNull(oPosSize) Then Y = oPosSize.Y Else Y = -1
+End Property &apos; ScriptForge.SF_UI.Y
+
+REM ===================================================================== METHODS
+
+REM -----------------------------------------------------------------------------
+Public Function Activate(Optional ByVal WindowName As Variant) As Boolean
+&apos;&apos;&apos; Make the specified window active
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; WindowName: see definitions
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if the given window is found and can be activated
+&apos;&apos;&apos; There is no change in the actual user interface if no window matches the selection
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; ui.Activate(&quot;C:\Me\My file.odt&quot;)
+
+Dim bActivate As Boolean &apos; Return value
+Dim oEnum As Object &apos; com.sun.star.container.XEnumeration
+Dim oComp As Object &apos; com.sun.star.lang.XComponent
+Dim vWindow As Window &apos; A single component
+Dim oContainer As Object &apos; com.sun.star.awt.XWindow
+Const cstThisSub = &quot;UI.Activate&quot;
+Const cstSubArgs = &quot;WindowName&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bActivate = False
+
+Check:
+ If IsMissing(WindowName) Or IsEmpty(WindowName) Then WindowName = &quot;&quot;
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateFile(WindowName, &quot;WindowName&quot;) Then GoTo Finally
+ End If
+
+Try:
+ Set oEnum = StarDesktop.Components().createEnumeration
+ Do While oEnum.hasMoreElements
+ Set oComp = oEnum.nextElement
+ vWindow = SF_UI._IdentifyWindow(oComp)
+ With vWindow
+ &apos; Does the current window match the arguments ?
+ If (Len(.WindowFileName) &gt; 0 And .WindowFileName = SF_FileSystem._ConvertToUrl(WindowName)) _
+ Or (Len(.WindowName) &gt; 0 And .WindowName = WindowName) _
+ Or (Len(.WindowTitle) &gt; 0 And .WindowTitle = WindowName) Then
+ Set oContainer = vWindow.Frame.ContainerWindow
+ With oContainer
+ If .isVisible() = False Then .setVisible(True)
+ .IsMinimized = False
+ .setFocus()
+ .toFront() &apos; Force window change in Linux
+ Wait 1 &apos; Bypass desynchro issue in Linux
+ End With
+ bActivate = True
+ Exit Do
+ End If
+ End With
+ Loop
+
+Finally:
+ Activate = bActivate
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_UI.Activate
+
+REM -----------------------------------------------------------------------------
+Public Function CreateBaseDocument(Optional ByVal FileName As Variant _
+ , Optional ByVal EmbeddedDatabase As Variant _
+ , Optional ByVal RegistrationName As Variant _
+ , Optional ByVal CalcFileName As Variant _
+ ) As Object
+&apos;&apos;&apos; Create a new LibreOffice Base document embedding an empty database of the given type
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; FileName: Identifies the file to create. It must follow the SF_FileSystem.FileNaming notation
+&apos;&apos;&apos; If the file already exists, it is overwritten without warning
+&apos;&apos;&apos; EmbeddedDatabase: either &quot;HSQLDB&quot; (default) or &quot;FIREBIRD&quot; or &quot;CALC&quot;
+&apos;&apos;&apos; RegistrationName: the name used to store the new database in the databases register
+&apos;&apos;&apos; If &quot;&quot; (default), no registration takes place
+&apos;&apos;&apos; If the name already exists it is overwritten without warning
+&apos;&apos;&apos; CalcFileName: only when EmbedddedDatabase = &quot;CALC&quot;, the name of the file containing the tables as Calc sheets
+&apos;&apos;&apos; The name of the file must be given in SF_FileSystem.FileNaming notation
+&apos;&apos;&apos; The file must exist
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; A SFDocuments.SF_Document object or one of its subclasses
+&apos;&apos;&apos; Exceptions
+&apos;&apos;&apos; UNKNOWNFILEERROR Calc datasource does not exist
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; Dim myBase As Object, myCalcBase As Object
+&apos;&apos;&apos; Set myBase = ui.CreateBaseDocument(&quot;C:\Databases\MyBaseFile.odb&quot;, &quot;FIREBIRD&quot;)
+&apos;&apos;&apos; Set myCalcBase = ui.CreateBaseDocument(&quot;C:\Databases\MyCalcBaseFile.odb&quot;, &quot;CALC&quot;, , &quot;C:\Databases\MyCalcFile.ods&quot;)
+
+Dim oCreate As Variant &apos; Return value
+Dim oDBContext As Object &apos; com.sun.star.sdb.DatabaseContext
+Dim oDatabase As Object &apos; com.sun.star.comp.dba.ODatabaseSource
+Dim oComp As Object &apos; Loaded component com.sun.star.lang.XComponent
+Dim sFileName As String &apos; Alias of FileName
+Dim FSO As Object &apos; Alias for FileSystem service
+Const cstDocType = &quot;private:factory/s&quot;
+Const cstThisSub = &quot;UI.CreateBaseDocument&quot;
+Const cstSubArgs = &quot;FileName, [EmbeddedDatabase=&quot;&quot;HSQLDB&quot;&quot;|&quot;&quot;FIREBIRD&quot;&quot;|&quot;&quot;CALC&quot;&quot;], [RegistrationName=&quot;&quot;&quot;&quot;], [CalcFileName]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ Set oCreate = Nothing
+ Set FSO = CreateScriptService(&quot;FileSystem&quot;)
+
+Check:
+ If IsMissing(EmbeddedDatabase) Or IsEmpty(EmbeddedDatabase) Then EmbeddedDatabase = &quot;HSQLDB&quot;
+ If IsMissing(RegistrationName) Or IsEmpty(RegistrationName) Then RegistrationName = &quot;&quot;
+ If IsMissing(CalcFileName) Or IsEmpty(CalcFileName) Then CalcFileName = &quot;&quot;
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateFile(FileName, &quot;FileName&quot;) Then GoTo Finally
+ If Not SF_Utils._Validate(EmbeddedDatabase, &quot;EmbeddedDatabase&quot;, V_STRING, Array(&quot;CALC&quot;, &quot;HSQLDB&quot;, &quot;FIREBIRD&quot;)) Then GoTo Finally
+ If Not SF_Utils._Validate(RegistrationName, &quot;RegistrationName&quot;, V_STRING) Then GoTo Finally
+ If UCase(EmbeddedDatabase) = &quot;CALC&quot; Then
+ If Not SF_Utils._ValidateFile(CalcFileName, &quot;CalcFileName&quot;) Then GoTo Finally
+ If Not FSO.FileExists(CalcFileName) Then GoTo CatchNotExists
+ End If
+ End If
+
+Try:
+ Set oDBContext = SF_Utils._GetUNOService(&quot;DatabaseContext&quot;)
+ With oDBContext
+ Set oDatabase = .createInstance()
+ &apos; Build the url link to the database
+ Select Case UCase(EmbeddedDatabase)
+ Case &quot;HSQLDB&quot;, &quot;FIREBIRD&quot;
+ oDatabase.URL = &quot;sdbc:embedded:&quot; &amp; LCase(EmbeddedDatabase)
+ Case &quot;CALC&quot;
+ oDatabase.URL = &quot;sdbc:calc:&quot; &amp; FSO._ConvertToUrl(CalcFileName)
+ End Select
+ &apos; Create empty Base document
+ sFileName = FSO._ConvertToUrl(FileName)
+ &apos; An existing file is overwritten without warning
+ If FSO.FileExists(FileName) Then FSO.DeleteFile(FileName)
+ If FSO.FileExists(FileName &amp; &quot;.lck&quot;) Then FSO.DeleteFile(FileName &amp; &quot;.lck&quot;)
+ oDatabase.DatabaseDocument.storeAsURL(sFileName, Array(SF_Utils._MakePropertyValue(&quot;Overwrite&quot;, True)))
+ &apos; Register database if requested
+ If Len(RegistrationName) &gt; 0 Then
+ If .hasRegisteredDatabase(RegistrationName) Then
+ .changeDatabaseLocation(RegistrationName, sFileName)
+ Else
+ .registerDatabaseLocation(RegistrationName, sFileName)
+ End If
+ End If
+ End With
+
+ Set oCreate = OpenBaseDocument(FileName)
+
+Finally:
+ Set CreateBaseDocument = oCreate
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchNotExists:
+ SF_Exception.RaiseFatal(UNKNOWNFILEERROR, &quot;CalcFileName&quot;, CalcFileName)
+ GoTo Finally
+End Function &apos; ScriptForge.SF_UI.CreateBaseDocument
+
+REM -----------------------------------------------------------------------------
+Public Function CreateDocument(Optional ByVal DocumentType As Variant _
+ , Optional ByVal TemplateFile As Variant _
+ , Optional ByVal Hidden As Variant _
+ ) As Object
+&apos;&apos;&apos; Create a new LibreOffice document of a given type or based on a given template
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; DocumentType: &quot;Calc&quot;, &quot;Writer&quot;, etc. If absent, a TemplateFile must be given
+&apos;&apos;&apos; TemplateFile: the full FileName of the template to build the new document on
+&apos;&apos;&apos; If the file does not exist, the argument is ignored
+&apos;&apos;&apos; The &quot;FileSystem&quot; service provides the TemplatesFolder and UserTemplatesFolder
+&apos;&apos;&apos; properties to help to build the argument
+&apos;&apos;&apos; Hidden: if True, open in the background (default = False)
+&apos;&apos;&apos; To use with caution: activation or closure can only happen programmatically
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; A SFDocuments.SF_Document object or one of its subclasses
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; DOCUMENTCREATIONERROR Wrong arguments
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; Dim myDoc1 As Object, myDoc2 As Object, FSO As Object
+&apos;&apos;&apos; Set myDoc1 = ui.CreateDocument(&quot;Calc&quot;)
+&apos;&apos;&apos; Set FSO = CreateScriptService(&quot;FileSystem&quot;)
+&apos;&apos;&apos; Set myDoc2 = ui.CreateDocument(, FSO.BuildPath(FSO.TemplatesFolder, &quot;personal/CV.ott&quot;))
+
+Dim oCreate As Variant &apos; Return value
+Dim vProperties As Variant &apos; Array of com.sun.star.beans.PropertyValue
+Dim bTemplateExists As Boolean &apos; True if TemplateFile is valid
+Dim sNew As String &apos; File url
+Dim oComp As Object &apos; Loaded component com.sun.star.lang.XComponent
+Const cstDocType = &quot;private:factory/s&quot;
+Const cstThisSub = &quot;UI.CreateDocument&quot;
+Const cstSubArgs = &quot;[DocumentType=&quot;&quot;&quot;&quot;], [TemplateFile=&quot;&quot;&quot;&quot;], [Hidden=False]&quot;
+
+&apos;&gt;&gt;&gt; If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ Set oCreate = Nothing
+
+Check:
+ If IsMissing(DocumentType) Or IsEmpty(DocumentType) Then DocumentType = &quot;&quot;
+ If IsMissing(TemplateFile) Or IsEmpty(TemplateFile) Then TemplateFile = &quot;&quot;
+ If IsMissing(Hidden) Or IsEmpty(Hidden) Then Hidden = False
+
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(DocumentType, &quot;DocumentType&quot;, V_STRING _
+ , Array(&quot;&quot;, BASEDOCUMENT, CALCDOCUMENT, DRAWDOCUMENT _
+ , IMPRESSDOCUMENT, MATHDOCUMENT, WRITERDOCUMENT)) Then GoTo Finally
+ If Not SF_Utils._ValidateFile(TemplateFile, &quot;TemplateFile&quot;, , True) Then GoTo Finally
+ If Not SF_Utils._Validate(Hidden, &quot;Hidden&quot;, V_BOOLEAN) Then GoTo Finally
+ End If
+
+ If Len(DocumentType) + Len(TemplateFile) = 0 Then GoTo CatchError
+ If Len(TemplateFile) &gt; 0 Then bTemplateExists = SF_FileSystem.FileExists(TemplateFile) Else bTemplateExists = False
+ If Len(DocumentType) = 0 Then
+ If Not bTemplateExists Then GoTo CatchError
+ End If
+
+Try:
+ If bTemplateExists Then sNew = SF_FileSystem._ConvertToUrl(TemplateFile) Else sNew = cstDocType &amp; LCase(DocumentType)
+ vProperties = Array( _
+ SF_Utils._MakePropertyValue(&quot;AsTemplate&quot;, bTemplateExists) _
+ , SF_Utils._MakePropertyValue(&quot;Hidden&quot;, Hidden) _
+ )
+ Set oComp = StarDesktop.loadComponentFromURL(sNew, &quot;_blank&quot;, 0, vProperties)
+ If Not IsNull(oComp) Then Set oCreate = CreateScriptService(&quot;SFDocuments.Document&quot;, oComp)
+
+Finally:
+ Set CreateDocument = oCreate
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchError:
+ SF_Exception.RaiseFatal(DOCUMENTCREATIONERROR, &quot;DocumentType&quot;, DocumentType, &quot;TemplateFile&quot;, TemplateFile)
+ GoTo Finally
+End Function &apos; ScriptForge.SF_UI.CreateDocument
+
+REM -----------------------------------------------------------------------------
+Public Function Documents() As Variant
+&apos;&apos;&apos; Returns the list of the currently open documents. Special windows are ignored.
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; A zero-based 1D array of filenames (in SF_FileSystem.FileNaming notation)
+&apos;&apos;&apos; or of window titles for unsaved documents
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; Dim vDocs As Variant, sDoc As String
+&apos;&apos;&apos; vDocs = ui.Documents()
+&apos;&apos;&apos; For each sDoc In vDocs
+&apos;&apos;&apos; ...
+
+Dim vDocuments As Variant &apos; Return value
+Dim oEnum As Object &apos; com.sun.star.container.XEnumeration
+Dim oComp As Object &apos; com.sun.star.lang.XComponent
+Dim vWindow As Window &apos; A single component
+Const cstThisSub = &quot;UI.Documents&quot;
+Const cstSubArgs = &quot;&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vDocuments = Array()
+
+Check:
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+Try:
+ Set oEnum = StarDesktop.Components().createEnumeration
+ Do While oEnum.hasMoreElements
+ Set oComp = oEnum.nextElement
+ vWindow = SF_UI._IdentifyWindow(oComp)
+ With vWindow
+ If Len(.WindowFileName) &gt; 0 Then
+ vDocuments = SF_Array.Append(vDocuments, SF_FileSystem._ConvertFromUrl(.WindowFileName))
+ ElseIf Len(.WindowTitle) &gt; 0 Then
+ vDocuments = SF_Array.Append(vDocuments, .WindowTitle)
+ End If
+ End With
+ Loop
+
+Finally:
+ Documents = vDocuments
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_UI.Documents
+
+REM -----------------------------------------------------------------------------
+Public Function GetDocument(Optional ByVal WindowName As Variant) As Variant
+&apos;&apos;&apos; Returns a SFDocuments.Document object referring to the active window or the given window
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; WindowName: when a string, see definitions. If absent the active window is considered.
+&apos;&apos;&apos; when an object, must be a UNO object of types
+&apos;&apos;&apos; com.sun.star.lang.XComponent or com.sun.star.comp.dba.ODatabaseDocument
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; DOCUMENTERROR The targeted window could not be found
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; Dim oDoc As Object
+&apos;&apos;&apos; Set oDoc = ui.GetDocument &apos; or Set oDoc = ui.GetDocument(ThisComponent)
+&apos;&apos;&apos; oDoc.Save()
+
+Dim oDocument As Object &apos; Return value
+Const cstThisSub = &quot;UI.GetDocument&quot;
+Const cstSubArgs = &quot;[WindowName]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ Set oDocument = Nothing
+
+Check:
+ If IsMissing(WindowName) Or IsEmpty(WindowName) Then WindowName = &quot;&quot;
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(WindowName, &quot;WindowName&quot;, Array(V_STRING, V_OBJECT)) Then GoTo Finally
+ If VarType(WindowName) = V_STRING Then
+ If Not SF_Utils._ValidateFile(WindowName, &quot;WindowName&quot;, , True) Then GoTo Finally
+ End If
+ End If
+
+Try:
+ Set oDocument = SF_Services.CreateScriptService(&quot;SFDocuments.Document&quot;, WindowName)
+ If IsNull(oDocument) Then GoTo CatchDeliver
+
+Finally:
+ Set GetDocument = oDocument
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchDeliver:
+ SF_Exception.RaiseFatal(DOCUMENTERROR, &quot;WindowName&quot;, WindowName)
+ GoTo Finally
+End Function &apos; ScriptForge.SF_UI.GetDocument
+
+REM -----------------------------------------------------------------------------
+Public Function GetProperty(Optional ByVal PropertyName As Variant) As Variant
+&apos;&apos;&apos; Return the actual value of the given property
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; PropertyName: the name of the property as a string
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The actual value of the property
+&apos;&apos;&apos; Exceptions
+&apos;&apos;&apos; ARGUMENTERROR The property does not exist
+
+Const cstThisSub = &quot;UI.GetProperty&quot;
+Const cstSubArgs = &quot;PropertyName&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ GetProperty = Null
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
+ End If
+
+Try:
+ Select Case UCase(PropertyName)
+ Case &quot;ACTIVEWINDOW&quot; : GetProperty = ActiveWindow()
+ Case &quot;HEIGHT&quot; : GetProperty = SF_UI.Height
+ Case &quot;WIDTH&quot; : GetProperty = SF_UI.Width
+ Case &quot;X&quot; : GetProperty = SF_UI.X
+ Case &quot;Y&quot; : GetProperty = SF_UI.Y
+
+ Case Else
+ End Select
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_UI.GetProperty
+
+REM -----------------------------------------------------------------------------
+Public Sub Maximize(Optional ByVal WindowName As Variant)
+&apos;&apos;&apos; Maximizes the active window or the given window
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; WindowName: see definitions. If absent the active window is considered
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; ui.Maximize
+&apos;&apos;&apos; ...
+
+Dim oEnum As Object &apos; com.sun.star.container.XEnumeration
+Dim oComp As Object &apos; com.sun.star.lang.XComponent
+Dim vWindow As Window &apos; A single component
+Dim oContainer As Object &apos; com.sun.star.awt.XWindow
+Dim bFound As Boolean &apos; True if window found
+Const cstThisSub = &quot;UI.Maximize&quot;
+Const cstSubArgs = &quot;[WindowName]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+
+Check:
+ If IsMissing(WindowName) Or IsEmpty(WindowName) Then WindowName = &quot;&quot;
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateFile(WindowName, &quot;WindowName&quot;, , True) Then GoTo Finally
+ End If
+
+Try:
+ bFound = False
+ If Len(WindowName) &gt; 0 Then
+ Set oEnum = StarDesktop.Components().createEnumeration
+ Do While oEnum.hasMoreElements And Not bFound
+ Set oComp = oEnum.nextElement
+ vWindow = SF_UI._IdentifyWindow(oComp)
+ With vWindow
+ &apos; Does the current window match the arguments ?
+ If (Len(.WindowFileName) &gt; 0 And .WindowFileName = SF_FileSystem.ConvertToUrl(WindowName)) _
+ Or (Len(.WindowName) &gt; 0 And .WindowName = WindowName) _
+ Or (Len(.WindowTitle) &gt; 0 And .WindowTitle = WindowName) Then bFound = True
+ End With
+ Loop
+ Else
+ vWindow = SF_UI._IdentifyWindow(StarDesktop.CurrentComponent)
+ bFound = True
+ End If
+
+ If bFound Then
+ Set oContainer = vWindow.Frame.ContainerWindow
+ oContainer.IsMaximized = True
+ End If
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Sub
+Catch:
+ GoTo Finally
+End Sub &apos; ScriptForge.SF_UI.Maximize
+
+REM -----------------------------------------------------------------------------
+Public Sub Minimize(Optional ByVal WindowName As Variant)
+&apos;&apos;&apos; Minimizes the current window or the given window
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; WindowName: see definitions. If absent the current window is considered
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; ui.Minimize(&quot;myFile.ods&quot;)
+&apos;&apos;&apos; ...
+
+Dim oEnum As Object &apos; com.sun.star.container.XEnumeration
+Dim oComp As Object &apos; com.sun.star.lang.XComponent
+Dim vWindow As Window &apos; A single component
+Dim oContainer As Object &apos; com.sun.star.awt.XWindow
+Dim bFound As Boolean &apos; True if window found
+Const cstThisSub = &quot;UI.Minimize&quot;
+Const cstSubArgs = &quot;[WindowName]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+
+Check:
+ If IsMissing(WindowName) Or IsEmpty(WindowName) Then WindowName = &quot;&quot;
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateFile(WindowName, &quot;WindowName&quot;, , True) Then GoTo Finally
+ End If
+
+Try:
+ bFound = False
+ If Len(WindowName) &gt; 0 Then
+ Set oEnum = StarDesktop.Components().createEnumeration
+ Do While oEnum.hasMoreElements And Not bFound
+ Set oComp = oEnum.nextElement
+ vWindow = SF_UI._IdentifyWindow(oComp)
+ With vWindow
+ &apos; Does the current window match the arguments ?
+ If (Len(.WindowFileName) &gt; 0 And .WindowFileName = SF_FileSystem.ConvertToUrl(WindowName)) _
+ Or (Len(.WindowName) &gt; 0 And .WindowName = WindowName) _
+ Or (Len(.WindowTitle) &gt; 0 And .WindowTitle = WindowName) Then bFound = True
+ End With
+ Loop
+ Else
+ vWindow = SF_UI._IdentifyWindow(StarDesktop.CurrentComponent)
+ bFound = True
+ End If
+
+ If bFound Then
+ Set oContainer = vWindow.Frame.ContainerWindow
+ oContainer.IsMinimized = True
+ End If
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Sub
+Catch:
+ GoTo Finally
+End Sub &apos; ScriptForge.SF_UI.Minimize
+
+REM -----------------------------------------------------------------------------
+Public Function Methods() As Variant
+&apos;&apos;&apos; Return the list of public methods of the UI service as an array
+
+ Methods = Array(&quot;Activate&quot; _
+ , &quot;CreateBaseDocument&quot; _
+ , &quot;CreateDocument&quot; _
+ , &quot;Documents&quot; _
+ , &quot;GetDocument&quot; _
+ , &quot;Maximize&quot; _
+ , &quot;Minimize&quot; _
+ , &quot;OpenBaseDocument&quot; _
+ , &quot;OpenDocument&quot; _
+ , &quot;Resize&quot; _
+ , &quot;RunCommand&quot; _
+ , &quot;SetStatusbar&quot; _
+ , &quot;ShowProgressBar&quot; _
+ , &quot;WindowExists&quot; _
+ )
+
+End Function &apos; ScriptForge.SF_UI.Methods
+
+REM -----------------------------------------------------------------------------
+Public Function OpenBaseDocument(Optional ByVal FileName As Variant _
+ , Optional ByVal RegistrationName As Variant _
+ , Optional ByVal MacroExecution As Variant _
+ ) As Object
+&apos;&apos;&apos; Open an existing LibreOffice Base document and return a SFDocuments.Document object
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; FileName: Identifies the file to open. It must follow the SF_FileSystem.FileNaming notation
+&apos;&apos;&apos; RegistrationName: the name of a registered database
+&apos;&apos;&apos; It is ignored if FileName &lt;&gt; &quot;&quot;
+&apos;&apos;&apos; MacroExecution: one of the MACROEXECxxx constants
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; A SFDocuments.SF_Base object
+&apos;&apos;&apos; Null if the opening failed, including when due to a user decision
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; BASEDOCUMENTOPENERROR Wrong arguments
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; Dim mBasec As Object, FSO As Object
+&apos;&apos;&apos; Set myBase = ui.OpenBaseDocument(&quot;C:\Temp\myDB.odb&quot;, MacroExecution := ui.MACROEXECNEVER)
+
+Dim oOpen As Variant &apos; Return value
+Dim vProperties As Variant &apos; Array of com.sun.star.beans.PropertyValue
+Dim oDBContext As Object &apos; com.sun.star.sdb.DatabaseContext
+Dim oComp As Object &apos; Loaded component com.sun.star.lang.XComponent
+Dim sFile As String &apos; Alias for FileName
+Dim iMacro As Integer &apos; Alias for MacroExecution
+Const cstThisSub = &quot;UI.OpenBaseDocument&quot;
+Const cstSubArgs = &quot;[FileName=&quot;&quot;&quot;&quot;], [RegistrationName=&quot;&quot;&quot;&quot;], [MacroExecution=0|1|2]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ Set oOpen = Nothing
+
+Check:
+ If IsMissing(FileName) Or IsEmpty(FileName) Then FileName = &quot;&quot;
+ If IsMissing(RegistrationName) Or IsEmpty(RegistrationName) Then RegistrationName = &quot;&quot;
+ If IsMissing(MacroExecution) Or IsEmpty(MacroExecution) Then MacroExecution = MACROEXECNORMAL
+
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateFile(FileName, &quot;FileName&quot;, , True) Then GoTo Finally
+ If Not SF_Utils._Validate(RegistrationName, &quot;RegistrationName&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(MacroExecution, &quot;MacroExecution&quot;, V_NUMERIC _
+ , Array(MACROEXECNORMAL, MACROEXECNEVER, MACROEXECALWAYS)) Then GoTo Finally
+ End If
+
+ &apos; Check the existence of FileName
+ If Len(FileName) = 0 Then &apos; FileName has precedence over RegistrationName
+ If Len(RegistrationName) = 0 Then GoTo CatchError
+ Set oDBContext = SF_Utils._GetUNOService(&quot;DatabaseContext&quot;)
+ If Not oDBContext.hasRegisteredDatabase(RegistrationName) Then GoTo CatchError
+ FileName = SF_FileSystem._ConvertFromUrl(oDBContext.getDatabaseLocation(RegistrationName))
+ End If
+ If Not SF_FileSystem.FileExists(FileName) Then GoTo CatchError
+
+Try:
+ With com.sun.star.document.MacroExecMode
+ Select Case MacroExecution
+ Case 0 : iMacro = .USE_CONFIG
+ Case 1 : iMacro = .NEVER_EXECUTE
+ Case 2 : iMacro = .ALWAYS_EXECUTE_NO_WARN
+ End Select
+ End With
+
+ vProperties = Array(SF_Utils._MakePropertyValue(&quot;MacroExecutionMode&quot;, iMacro))
+
+ sFile = SF_FileSystem._ConvertToUrl(FileName)
+ Set oComp = StarDesktop.loadComponentFromURL(sFile, &quot;_blank&quot;, 0, vProperties)
+ If Not IsNull(oComp) Then Set oOpen = CreateScriptService(&quot;SFDocuments.Document&quot;, oComp)
+
+Finally:
+ Set OpenBaseDocument = oOpen
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchError:
+ SF_Exception.RaiseFatal(BASEDOCUMENTOPENERROR, &quot;FileName&quot;, FileName, &quot;RegistrationName&quot;, RegistrationName)
+ GoTo Finally
+End Function &apos; ScriptForge.SF_UI.OpenBaseDocument
+
+REM -----------------------------------------------------------------------------
+Public Function OpenDocument(Optional ByVal FileName As Variant _
+ , Optional ByVal Password As Variant _
+ , Optional ByVal ReadOnly As Variant _
+ , Optional ByVal Hidden As Variant _
+ , Optional ByVal MacroExecution As Variant _
+ , Optional ByVal FilterName As Variant _
+ , Optional ByVal FilterOptions As Variant _
+ ) As Object
+&apos;&apos;&apos; Open an existing LibreOffice document with the given options
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; FileName: Identifies the file to open. It must follow the SF_FileSystem.FileNaming notation
+&apos;&apos;&apos; Password: To use when the document is protected
+&apos;&apos;&apos; If wrong or absent while the document is protected, the user will be prompted to enter a password
+&apos;&apos;&apos; ReadOnly: Default = False
+&apos;&apos;&apos; Hidden: if True, open in the background (default = False)
+&apos;&apos;&apos; To use with caution: activation or closure can only happen programmatically
+&apos;&apos;&apos; MacroExecution: one of the MACROEXECxxx constants
+&apos;&apos;&apos; FilterName: the name of a filter that should be used for loading the document
+&apos;&apos;&apos; If present, the filter must exist
+&apos;&apos;&apos; FilterOptions: an optional string of options associated with the filter
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; A SFDocuments.SF_Document object or one of its subclasses
+&apos;&apos;&apos; Null if the opening failed, including when due to a user decision
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; DOCUMENTOPENERROR Wrong arguments
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; Dim myDoc As Object, FSO As Object
+&apos;&apos;&apos; Set myDoc = ui.OpenDocument(&quot;C:\Temp\myFile.odt&quot;, MacroExecution := ui.MACROEXECNEVER)
+
+Dim oOpen As Variant &apos; Return value
+Dim oFilterFactory As Object &apos; com.sun.star.document.FilterFactory
+Dim vProperties As Variant &apos; Array of com.sun.star.beans.PropertyValue
+Dim oComp As Object &apos; Loaded component com.sun.star.lang.XComponent
+Dim sFile As String &apos; Alias for FileName
+Dim iMacro As Integer &apos; Alias for MacroExecution
+Const cstThisSub = &quot;UI.OpenDocument&quot;
+Const cstSubArgs = &quot;FileName, [Password=&quot;&quot;&quot;&quot;], [ReadOnly=False], [Hidden=False], [MacroExecution=0|1|2], [FilterName=&quot;&quot;&quot;&quot;], [FilterOptions=&quot;&quot;&quot;&quot;]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ Set oOpen = Nothing
+
+Check:
+ If IsMissing(Password) Or IsEmpty(Password) Then Password = &quot;&quot;
+ If IsMissing(ReadOnly) Or IsEmpty(ReadOnly) Then ReadOnly = False
+ If IsMissing(Hidden) Or IsEmpty(Hidden) Then Hidden = False
+ If IsMissing(MacroExecution) Or IsEmpty(MacroExecution) Then MacroExecution = MACROEXECNORMAL
+ If IsMissing(FilterName) Or IsEmpty(FilterName) Then FilterName = &quot;&quot;
+ If IsMissing(FilterOptions) Or IsEmpty(FilterOptions) Then FilterOptions = &quot;&quot;
+
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateFile(FileName, &quot;FileName&quot;) Then GoTo Finally
+ If Not SF_Utils._Validate(Password, &quot;Password&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(ReadOnly, &quot;ReadOnly&quot;, V_BOOLEAN) Then GoTo Finally
+ If Not SF_Utils._Validate(Hidden, &quot;Hidden&quot;, V_BOOLEAN) Then GoTo Finally
+ If Not SF_Utils._Validate(MacroExecution, &quot;MacroExecution&quot;, V_NUMERIC _
+ , Array(MACROEXECNORMAL, MACROEXECNEVER, MACROEXECALWAYS)) Then GoTo Finally
+ If Not SF_Utils._Validate(FilterName, &quot;FilterName&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(FilterOptions, &quot;FilterOptions&quot;, V_STRING) Then GoTo Finally
+ End If
+
+ &apos; Check the existence of FileName and FilterName
+ If Not SF_FileSystem.FileExists(FileName) Then GoTo CatchError
+ If Len(FilterName) &gt; 0 Then
+ Set oFilterFactory = SF_Utils._GetUNOService(&quot;FilterFactory&quot;)
+ If Not oFilterFactory.hasByName(FilterName) Then GoTo CatchError
+ End If
+
+Try:
+ With com.sun.star.document.MacroExecMode
+ Select Case MacroExecution
+ Case 0 : iMacro = .USE_CONFIG
+ Case 1 : iMacro = .NEVER_EXECUTE
+ Case 2 : iMacro = .ALWAYS_EXECUTE_NO_WARN
+ End Select
+ End With
+
+ vProperties = Array( _
+ SF_Utils._MakePropertyValue(&quot;ReadOnly&quot;, ReadOnly) _
+ , SF_Utils._MakePropertyValue(&quot;Hidden&quot;, Hidden) _
+ , SF_Utils._MakePropertyValue(&quot;MacroExecutionMode&quot;, iMacro) _
+ , SF_Utils._MakePropertyValue(&quot;FilterName&quot;, FilterName) _
+ , SF_Utils._MakePropertyValue(&quot;FilterOptions&quot;, FilterOptions) _
+ )
+ If Len(Password) &gt; 0 Then &apos; Password is to add only if &lt;&gt; &quot;&quot; !?
+ vProperties = SF_Array.Append(vProperties, SF_Utils._MakePropertyValue(&quot;Password&quot;, Password))
+ End If
+
+ sFile = SF_FileSystem._ConvertToUrl(FileName)
+ Set oComp = StarDesktop.loadComponentFromURL(sFile, &quot;_blank&quot;, 0, vProperties)
+ If Not IsNull(oComp) Then Set oOpen = CreateScriptService(&quot;SFDocuments.Document&quot;, oComp)
+
+Finally:
+ Set OpenDocument = oOpen
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchError:
+ SF_Exception.RaiseFatal(DOCUMENTOPENERROR, &quot;FileName&quot;, FileName, &quot;Password&quot;, Password, &quot;FilterName&quot;, FilterName)
+ GoTo Finally
+End Function &apos; ScriptForge.SF_UI.OpenDocument
+
+REM -----------------------------------------------------------------------------
+Public Function Properties() As Variant
+&apos;&apos;&apos; Return the list or properties of the Timer class as an array
+
+ Properties = Array( _
+ &quot;ActiveWindow&quot; _
+ , &quot;Height&quot; _
+ , &quot;Width&quot; _
+ , &quot;X&quot; _
+ , &quot;Y&quot; _
+ )
+
+End Function &apos; ScriptForge.SF_UI.Properties
+
+REM -----------------------------------------------------------------------------
+Public Sub Resize(Optional ByVal Left As Variant _
+ , Optional ByVal Top As Variant _
+ , Optional ByVal Width As Variant _
+ , Optional ByVal Height As Variant _
+ )
+&apos;&apos;&apos; Resizes and/or moves the active window. Negative arguments are ignored.
+&apos;&apos;&apos; If the window was minimized or without arguments, it is restored
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Left, Top: Distances from top and left edges of the screen
+&apos;&apos;&apos; Width, Height: Dimensions of the window
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; ui.Resize(10,,500) &apos; Top and Height are unchanged
+&apos;&apos;&apos; ...
+
+Dim vWindow As Window &apos; A single component
+Dim oContainer As Object &apos; com.sun.star.awt.XWindow
+Dim iPosSize As Integer &apos; Computes which of the 4 arguments should be considered
+Const cstThisSub = &quot;UI.Resize&quot;
+Const cstSubArgs = &quot;[Left], [Top], [Width], [Height]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+
+Check:
+ If IsMissing(Left) Or IsEmpty(Left) Then Left = -1
+ If IsMissing(Top) Or IsEmpty(Top) Then Top = -1
+ If IsMissing(Width) Or IsEmpty(Width) Then Width = -1
+ If IsMissing(Height) Or IsEmpty(Height) Then Height = -1
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(Left, &quot;Left&quot;, V_NUMERIC) Then GoTo Finally
+ If Not SF_Utils._Validate(Top, &quot;Top&quot;, V_NUMERIC) Then GoTo Finally
+ If Not SF_Utils._Validate(Width, &quot;Width&quot;, V_NUMERIC) Then GoTo Finally
+ If Not SF_Utils._Validate(Height, &quot;Height&quot;, V_NUMERIC) Then GoTo Finally
+ End If
+
+Try:
+ vWindow = SF_UI._IdentifyWindow(StarDesktop.CurrentComponent)
+ If Not IsNull(vWindow.Frame) Then
+ Set oContainer = vWindow.Frame.ContainerWindow
+ iPosSize = 0
+ If Left &gt;= 0 Then iPosSize = iPosSize + com.sun.star.awt.PosSize.X
+ If Top &gt;= 0 Then iPosSize = iPosSize + com.sun.star.awt.PosSize.Y
+ If Width &gt; 0 Then iPosSize = iPosSize + com.sun.star.awt.PosSize.WIDTH
+ If Height &gt; 0 Then iPosSize = iPosSize + com.sun.star.awt.PosSize.HEIGHT
+ With oContainer
+ .IsMaximized = False
+ .IsMinimized = False
+ .setPosSize(Left, Top, Width, Height, iPosSize)
+ End With
+ End If
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Sub
+Catch:
+ GoTo Finally
+End Sub &apos; ScriptForge.SF_UI.Resize
+
+REM -----------------------------------------------------------------------------
+Public Sub RunCommand(Optional ByVal Command As Variant _
+ , ParamArray Args As Variant _
+ )
+&apos;&apos;&apos; Run on the current window the given menu command. The command is executed with or without arguments
+&apos;&apos;&apos; A few typical commands:
+&apos;&apos;&apos; About, Delete, Edit, Undo, Copy, Paste, ...
+&apos;&apos;&apos; Dozens can be found on next page: https://wiki.documentfoundation.org/Development/DispatchCommands
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Command: Case-sensitive. The command itself is not checked.
+&apos;&apos;&apos; If the command does not contain the &quot;.uno:&quot; prefix, it is added.
+&apos;&apos;&apos; If nothing happens, then the command is probably wrong
+&apos;&apos;&apos; Args: Pairs of arguments name (string), value (any)
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; ui.RunCommand(&quot;BasicIDEAppear&quot;, _
+&apos;&apos;&apos; &quot;Document&quot;, &quot;LibreOffice Macros &amp; Dialogs&quot;, _
+&apos;&apos;&apos; &quot;LibName&quot;, &quot;ScriptForge&quot;, _
+&apos;&apos;&apos; &quot;Name&quot;, &quot;SF_Session&quot;, _
+&apos;&apos;&apos; &quot;Line&quot;, 600)
+
+Dim oDispatch &apos; com.sun.star.frame.DispatchHelper
+Dim vProps As Variant &apos; Array of PropertyValues
+Dim vValue As Variant &apos; A single value argument
+Dim sCommand As String &apos; Alias of Command
+Dim i As Long
+Const cstPrefix = &quot;.uno:&quot;
+
+Const cstThisSub = &quot;UI.RunCommand&quot;
+Const cstSubArgs = &quot;Command, [arg0Name, arg0Value], [arg1Name, arg1Value], ...&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(Command, &quot;Command&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._ValidateArray(Args, &quot;Args&quot;, 1) Then GoTo Finally
+ For i = 0 To UBound(Args) - 1 Step 2
+ If Not SF_Utils._Validate(Args(i), &quot;Arg&quot; &amp; CStr(i/2) &amp; &quot;Name&quot;, V_STRING) Then GoTo Finally
+ Next i
+ End If
+
+Try:
+ &apos; Build array of property values
+ vProps = Array()
+ For i = 0 To UBound(Args) - 1 Step 2
+ If IsEmpty(Args(i + 1)) Then vValue = Null Else vValue = Args(i + 1)
+ vProps = SF_Array.Append(vProps, SF_Utils._MakePropertyValue(Args(i), vValue))
+ Next i
+ Set oDispatch = SF_Utils._GetUNOService(&quot;DispatchHelper&quot;)
+ If SF_String.StartsWith(Command, cstPrefix) Then sCommand = Command Else sCommand = cstPrefix &amp; Command
+ oDispatch.executeDispatch(StarDesktop.ActiveFrame, sCommand, &quot;&quot;, 0, vProps)
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Sub
+Catch:
+ GoTo Finally
+End Sub &apos; ScriptForge.SF_UI.RunCommand
+
+REM -----------------------------------------------------------------------------
+Public Function SetProperty(Optional ByVal PropertyName As Variant _
+ , Optional ByRef Value As Variant _
+ ) As Boolean
+&apos;&apos;&apos; Set a new value to the given property
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; PropertyName: the name of the property as a string
+&apos;&apos;&apos; Value: its new value
+&apos;&apos;&apos; Exceptions
+&apos;&apos;&apos; ARGUMENTERROR The property does not exist
+
+Const cstThisSub = &quot;UI.SetProperty&quot;
+Const cstSubArgs = &quot;PropertyName, Value&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ SetProperty = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
+ End If
+
+Try:
+ Select Case UCase(PropertyName)
+ Case Else
+ End Select
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_UI.SetProperty
+
+REM -----------------------------------------------------------------------------
+Public Sub SetStatusbar(Optional ByVal Text As Variant _
+ , Optional ByVal Percentage As Variant _
+ )
+&apos;&apos;&apos; Display a text and a progressbar in the status bar of the active window
+&apos;&apos;&apos; Any subsequent calls in the same macro run refer to the same status bar of the same window,
+&apos;&apos;&apos; even if the window is not active anymore
+&apos;&apos;&apos; A call without arguments resets the status bar to its normal state.
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Text: the optional text to be displayed before the progress bar
+&apos;&apos;&apos; Percentage: the optional degree of progress between 0 and 100
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; Dim i As Integer
+&apos;&apos;&apos; For i = 0 To 100
+&apos;&apos;&apos; ui.SetStatusbar(&quot;Progress ...&quot;, i)
+&apos;&apos;&apos; Wait 50
+&apos;&apos;&apos; Next i
+&apos;&apos;&apos; ui.SetStatusbar
+
+Dim oComp As Object
+Dim oControl As Object
+Dim oStatusbar As Object
+Const cstThisSub = &quot;UI.SetStatusbar&quot;
+Const cstSubArgs = &quot;[Text], [Percentage]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+
+Check:
+ If IsMissing(Text) Or IsEmpty(Text) Then Text = &quot;&quot;
+ If IsMissing(Percentage) Or IsEmpty(Percentage) Then Percentage = -1
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(Text, &quot;Text&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(Percentage, &quot;Percentage&quot;, V_NUMERIC) Then GoTo Finally
+ End If
+
+Try:
+ Set oStatusbar = _SF_.Statusbar
+ With oStatusbar
+ If IsNull(oStatusbar) Then &apos; Initial call
+ Set oComp = StarDesktop.CurrentComponent
+ If Not IsNull(oComp) Then
+ Set oControl = Nothing
+ If SF_Session.HasUnoProperty(oComp, &quot;CurrentController&quot;) Then Set oControl = oComp.CurrentController
+ If Not IsNull(oControl) Then
+ If SF_Session.HasUnoMethod(oControl, &quot;getStatusIndicator&quot;) Then oStatusbar = oControl.getStatusIndicator()
+ End If
+ End If
+ If Not IsNull(oStatusbar) Then
+ .start(&quot;&quot;, 100)
+ End If
+ End If
+ If Not IsNull(oStatusbar) Then
+ If Len(Text) = 0 And Percentage = -1 Then
+ .end()
+ Set oStatusbar = Nothing
+ Else
+ If Len(Text) &gt; 0 Then .setText(Text)
+ If Percentage &gt;= 0 And Percentage &lt;= 100 Then .setValue(Percentage)
+ End If
+ End If
+ End With
+
+Finally:
+ Set _SF_.Statusbar = oStatusbar
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Sub
+Catch:
+ GoTo Finally
+End Sub &apos; ScriptForge.SF_UI.SetStatusbar
+
+REM -----------------------------------------------------------------------------
+Public Sub ShowProgressBar(Optional Title As Variant _
+ , Optional ByVal Text As Variant _
+ , Optional ByVal Percentage As Variant _
+ , Optional ByRef _Context As Variant _
+ )
+&apos;&apos;&apos; Display a non-modal dialog box. Specify its title, an explicatory text and the progress on a progressbar
+&apos;&apos;&apos; A call without arguments erases the progress bar dialog.
+&apos;&apos;&apos; The box will anyway vanish at the end of the macro run.
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Title: the title appearing on top of the dialog box (Default = &quot;ScriptForge&quot;)
+&apos;&apos;&apos; Text: the optional text to be displayed above the progress bar (default = zero-length string)
+&apos;&apos;&apos; Percentage: the degree of progress between 0 and 100. Default = 0
+&apos;&apos;&apos; _Context: from Python, the XComponentXontext (FOR INTERNAL USE ONLY)
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; Dim i As Integer
+&apos;&apos;&apos; For i = 0 To 100
+&apos;&apos;&apos; ui.ShowProgressBar(, &quot;Progress ... &quot; &amp; i &amp; &quot;/100&quot;, i)
+&apos;&apos;&apos; Wait 50
+&apos;&apos;&apos; Next i
+&apos;&apos;&apos; ui.ShowProgressBar
+
+Dim bFirstCall As Boolean &apos; True at first invocation of method
+Dim oDialog As Object &apos; SFDialogs.Dialog object
+Dim oFixedText As Object &apos; SFDialogs.DialogControl object
+Dim oProgressBar As Object &apos; SFDialogs.DialogControl object
+Dim sTitle As String &apos; Alias of Title
+Const cstThisSub = &quot;UI.ShowProgressBar&quot;
+Const cstSubArgs = &quot;[Title], [Text], [Percentage]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+
+Check:
+ If IsMissing(Title) Or IsEmpty(Title) Then Title = &quot;&quot;
+ If IsMissing(Text) Or IsEmpty(Text) Then Text = &quot;&quot;
+ If IsMissing(Percentage) Or IsEmpty(Percentage) Then Percentage = -1
+ If IsMissing(_Context) Or IsEmpty(_Context) Then _Context = Nothing
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(Title, &quot;Title&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(Text, &quot;Text&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(Percentage, &quot;Percentage&quot;, V_NUMERIC) Then GoTo Finally
+ End If
+
+Try:
+ With _SF_
+ Set oDialog = .ProgressBarDialog
+ Set oFixedText = .ProgressBarText
+ Set oProgressBar = .ProgressBarBar
+ End With
+ With oDialog
+ bFirstCall = ( IsNull(oDialog) )
+ If Not bFirstCall Then bFirstCall = Not ._IsStillAlive(False) &apos; False to not raise an error
+ If bFirstCall Then Set oDialog = CreateScriptService(&quot;SFDialogs.Dialog&quot;, &quot;GlobalScope&quot;, &quot;ScriptForge&quot;, &quot;dlgProgress&quot;, _Context)
+
+ If Not IsNull(oDialog) Then
+ If Len(Title) = 0 And Len(Text) = 0 And Percentage = -1 Then
+ Set oDialog = .Dispose()
+ Else
+ .Caption = Iif(Len(Title) &gt; 0, Title, &quot;ScriptForge&quot;)
+ If bFirstCall Then
+ Set oFixedText = .Controls(&quot;ProgressText&quot;)
+ Set oProgressBar = .Controls(&quot;ProgressBar&quot;)
+ .Execute(Modal := False)
+ End If
+ If Len(Text) &gt; 0 Then oFixedText.Caption = Text
+ oProgressBar.Value = Iif(Percentage &gt;= 0 And Percentage &lt;= 100, Percentage, 0)
+ End If
+ End If
+ End With
+
+Finally:
+ With _SF_
+ Set .ProgressBarDialog = oDialog
+ Set .ProgressBarText = oFixedText
+ Set .ProgressBarBar = oProgressBar
+ End With
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Sub
+Catch:
+ GoTo Finally
+End Sub &apos; ScriptForge.SF_UI.ShowProgressBar
+
+REM -----------------------------------------------------------------------------
+Public Function WindowExists(Optional ByVal WindowName As Variant) As Boolean
+&apos;&apos;&apos; Returns True if the specified window exists
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; WindowName: see definitions
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if the given window is found
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; ui.WindowExists(&quot;C:\Me\My file.odt&quot;)
+
+Dim bWindowExists As Boolean &apos; Return value
+Dim oEnum As Object &apos; com.sun.star.container.XEnumeration
+Dim oComp As Object &apos; com.sun.star.lang.XComponent
+Dim vWindow As Window &apos; A single component
+Dim oContainer As Object &apos; com.sun.star.awt.XWindow
+Const cstThisSub = &quot;UI.WindowExists&quot;
+Const cstSubArgs = &quot;WindowName&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bWindowExists = False
+
+Check:
+ If IsMissing(WindowName) Or IsEmpty(WindowName) Then WindowName = &quot;&quot;
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateFile(WindowName, &quot;WindowName&quot;) Then GoTo Finally
+ End If
+
+Try:
+ Set oEnum = StarDesktop.Components().createEnumeration
+ Do While oEnum.hasMoreElements
+ Set oComp = oEnum.nextElement
+ vWindow = SF_UI._IdentifyWindow(oComp)
+ With vWindow
+ &apos; Does the current window match the arguments ?
+ If (Len(.WindowFileName) &gt; 0 And .WindowFileName = SF_FileSystem.ConvertToUrl(WindowName)) _
+ Or (Len(.WindowName) &gt; 0 And .WindowName = WindowName) _
+ Or (Len(.WindowTitle) &gt; 0 And .WindowTitle = WindowName) Then
+ bWindowExists = True
+ Exit Do
+ End If
+ End With
+ Loop
+
+Finally:
+ WindowExists = bWindowExists
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_UI.WindowExists
+
+REM =========================================================== PRIVATE FUNCTIONS
+
+REM -----------------------------------------------------------------------------
+Public Sub _CloseProgressBar(Optional ByRef poEvent As Object)
+&apos;&apos;&apos; Triggered by the Close button in the dlgProgress dialog
+&apos;&apos;&apos; to simply close the dialog
+
+ ShowProgressBar() &apos; Without arguments =&gt; close the dialog
+
+End Sub &apos; ScriptForge.SF_UI._CloseProgressBar
+
+REM -----------------------------------------------------------------------------
+Public Function _IdentifyWindow(ByRef poComponent As Object) As Object
+&apos;&apos;&apos; Return a Window object (definition on top of module) based on component given as argument
+&apos;&apos;&apos; Is a shortcut to explore the most relevant properties or objects bound to a UNO component
+
+Dim oWindow As Window &apos; Return value
+Dim sImplementation As String &apos; Component&apos;s implementationname
+Dim sIdentifier As String &apos; Component&apos;s identifier
+Dim vArg As Variant &apos; One single item of the Args UNO property
+Dim FSO As Object &apos; Alias for SF_FileSystem
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ Set _IdentifyWindow = Nothing
+ sImplementation = &quot;&quot; : sIdentifier = &quot;&quot;
+
+ Set FSO = SF_FileSystem
+ With oWindow
+ Set .Frame = Nothing
+ Set .Component = Nothing
+ .WindowName = &quot;&quot;
+ .WindowTitle = &quot;&quot;
+ .WindowFileName = &quot;&quot;
+ .DocumentType = &quot;&quot;
+ If IsNull(poComponent) Then GoTo Finally
+ If SF_Session.HasUnoProperty(poComponent, &quot;ImplementationName&quot;) Then sImplementation = poComponent.ImplementationName
+ If SF_Session.HasUnoProperty(poComponent, &quot;Identifier&quot;) Then sIdentifier = poComponent.Identifier
+ Set .Component = poComponent
+ Select Case sImplementation
+ Case &quot;com.sun.star.comp.basic.BasicIDE&quot;
+ .WindowName = BASICIDE
+ Case &quot;com.sun.star.comp.dba.ODatabaseDocument&quot; &apos; No identifier
+ .WindowFileName = SF_Utils._GetPropertyValue(poComponent.Args, &quot;URL&quot;)
+ If Len(.WindowFileName) &gt; 0 Then .WindowName = FSO.GetName(FSO._ConvertFromUrl(.WindowFileName))
+ .DocumentType = BASEDOCUMENT
+ Case &quot;org.openoffice.comp.dbu.ODatasourceBrowser&quot;
+ Case &quot;org.openoffice.comp.dbu.OTableDesign&quot;, &quot;org.openoffice.comp.dbu.OQueryDesign&quot; &apos; Table or Query in Edit mode
+ Case &quot;org.openoffice.comp.dbu.ORelationDesign&quot;
+ Case &quot;com.sun.star.comp.sfx2.BackingComp&quot; &apos; Welcome screen
+ Set .Frame = poComponent.Frame
+ .WindowName = WELCOMESCREEN
+ Case Else
+ If Len(sIdentifier) &gt; 0 Then
+ &apos; Do not use URL : it contains the TemplateFile when new documents are created from a template
+ .WindowFileName = poComponent.Location
+ If Len(.WindowFileName) &gt; 0 Then .WindowName = FSO.GetName(FSO._ConvertFromUrl(.WindowFileName))
+ If SF_Session.HasUnoProperty(poComponent, &quot;Title&quot;) Then .WindowTitle = poComponent.Title
+ Select Case sIdentifier
+ Case &quot;com.sun.star.sdb.FormDesign&quot; &apos; Form
+ Case &quot;com.sun.star.sdb.TextReportDesign&quot; &apos; Report
+ Case &quot;com.sun.star.text.TextDocument&quot; &apos; Writer
+ .DocumentType = WRITERDOCUMENT
+ Case &quot;com.sun.star.sheet.SpreadsheetDocument&quot; &apos; Calc
+ .DocumentType = CALCDOCUMENT
+ Case &quot;com.sun.star.presentation.PresentationDocument&quot; &apos; Impress
+ .DocumentType = IMPRESSDOCUMENT
+ Case &quot;com.sun.star.drawing.DrawingDocument&quot; &apos; Draw
+ .DocumentType = DRAWDOCUMENT
+ Case &quot;com.sun.star.formula.FormulaProperties&quot; &apos; Math
+ .DocumentType = MATHDOCUMENT
+ Case Else
+ End Select
+ End If
+ End Select
+ If IsNull(.Frame) Then
+ If Not IsNull(poComponent.CurrentController) Then Set .Frame = poComponent.CurrentController.Frame
+ End If
+ End With
+
+Finally:
+ Set _IdentifyWindow = oWindow
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_UI._IdentifyWindow
+
+REM -----------------------------------------------------------------------------
+Public Function _PosSize() As Object
+&apos;&apos;&apos; Returns the PosSize structure of the active window
+
+Dim vWindow As Window &apos; A single component
+Dim oContainer As Object &apos; com.sun.star.awt.XWindow
+Dim oPosSize As Object &apos; com.sun.star.awt.Rectangle
+
+ Set oPosSize = Nothing
+
+Try:
+ vWindow = SF_UI._IdentifyWindow(StarDesktop.CurrentComponent)
+ If Not IsNull(vWindow.Frame) Then
+ Set oContainer = vWindow.Frame.ContainerWindow
+ Set oPosSize = oContainer.getPosSize()
+ End If
+
+Finally:
+ Set _PosSize = oPosSize
+ Exit Function
+End Function &apos; ScriptForge.SF_UI._PosSize
+
+REM -----------------------------------------------------------------------------
+Private Function _Repr() As String
+&apos;&apos;&apos; Convert the UI instance to a readable string, typically for debugging purposes (DebugPrint ...)
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Return:
+&apos;&apos;&apos; &quot;[UI]&quot;
+
+ _Repr = &quot;[UI]&quot;
+
+End Function &apos; ScriptForge.SF_UI._Repr
+
+REM ============================================ END OF SCRIPTFORGE.SF_UI
+</script:module> \ No newline at end of file
diff --git a/wizards/source/scriptforge/SF_Utils.xba b/wizards/source/scriptforge/SF_Utils.xba
new file mode 100644
index 000000000..91b703c46
--- /dev/null
+++ b/wizards/source/scriptforge/SF_Utils.xba
@@ -0,0 +1,1113 @@
+<?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_Utils" 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 Explicit
+Option Private Module
+
+&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_Utils
+&apos;&apos;&apos; ========
+&apos;&apos;&apos; FOR INTERNAL USE ONLY
+&apos;&apos;&apos; Groups all private functions used by the official modules
+&apos;&apos;&apos; Declares the Global variable _SF_
+&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 ===================================================================== GLOBALS
+
+Global _SF_ As Variant &apos; SF_Root (Basic) object)
+
+&apos;&apos;&apos; ScriptForge version
+Const SF_Version = &quot;7.4&quot;
+
+&apos;&apos;&apos; Standard symbolic names for VarTypes
+&apos; V_EMPTY = 0
+&apos; V_NULL = 1
+&apos; V_INTEGER = 2
+&apos; V_LONG = 3
+&apos; V_SINGLE = 4
+&apos; V_DOUBLE = 5
+&apos; V_CURRENCY = 6
+&apos; V_DATE = 7
+&apos; V_STRING = 8
+&apos;&apos;&apos; Additional symbolic names for VarTypes
+Global Const V_OBJECT = 9
+Global Const V_BOOLEAN = 11
+Global Const V_VARIANT = 12
+Global Const V_BYTE = 17
+Global Const V_USHORT = 18
+Global Const V_ULONG = 19
+Global Const V_BIGINT = 35
+Global Const V_DECIMAL = 37
+Global Const V_ARRAY = 8192
+&apos;&apos;&apos; Fictive VarTypes
+Global Const V_NUMERIC = 99 &apos; Synonym of any numeric value [returned by _VarTypeExt()]
+Global Const V_NOTHING = 101 &apos; Object categories [returned by _VarTypeObj()] Null object
+Global Const V_UNOOBJECT = 102 &apos; Uno object or Uno structure
+Global Const V_SFOBJECT = 103 &apos; ScriptForge object: has ObjectType and ServiceName properties
+Global Const V_BASICOBJECT = 104 &apos; User Basic object
+
+Type _ObjectDescriptor &apos; Returned by the _VarTypeObj() method
+ iVarType As Integer &apos; One of the V_NOTHING, V_xxxOBJECT constants
+ sObjectType As String &apos; Either &quot;&quot; or &quot;com.sun.star...&quot; or a ScriptForge object type (ex. &quot;SF_SESSION&quot; or &quot;DICTIONARY&quot;)
+ sServiceName As String &apos; Either &quot;&quot; or the service name of a ScriptForge object type (ex. &quot;ScriptForge.Exception&quot;-
+End Type
+
+REM ================================================================== EXCEPTIONS
+
+Const MISSINGARGERROR = &quot;MISSINGARGERROR&quot; &apos; A mandatory argument is missing
+Const ARGUMENTERROR = &quot;ARGUMENTERROR&quot; &apos; An argument does not pass the _Validate() validation
+Const ARRAYERROR = &quot;ARRAYERROR&quot; &apos; An argument does not pass the _ValidateArray() validation
+Const FILEERROR = &quot;FILEERROR&quot; &apos; An argument does not pass the _ValidateFile() validation
+
+REM =========================================pvA==================== PRIVATE METHODS
+
+REM -----------------------------------------------------------------------------
+Public Function _CDateToIso(pvDate As Variant) As Variant
+&apos;&apos;&apos; Returns a string representation of the given Basic date
+&apos;&apos;&apos; Dates as strings are essential in property values, where Basic dates are evil
+
+Dim sIsoDate As Variant &apos; Return value
+
+ If VarType(pvDate) = V_DATE Then
+ If Year(pvDate) &lt; 1900 Then &apos; Time only
+ sIsoDate = Right(&quot;0&quot; &amp; Hour(pvDate), 2) &amp; &quot;:&quot; &amp; Right(&quot;0&quot; &amp; Minute(pvDate), 2) &amp; &quot;:&quot; &amp; Right(&quot;0&quot; &amp; Second(pvDate), 2)
+ ElseIf Hour(pvDate) + Minute(pvDate) + Second(pvDate) = 0 Then &apos; Date only
+ sIsoDate = Year(pvDate) &amp; &quot;-&quot; &amp; Right(&quot;0&quot; &amp; Month(pvDate), 2) &amp; &quot;-&quot; &amp; Right(&quot;0&quot; &amp; Day(pvDate), 2)
+ Else
+ sIsoDate = Year(pvDate) &amp; &quot;-&quot; &amp; Right(&quot;0&quot; &amp; Month(pvDate), 2) &amp; &quot;-&quot; &amp; Right(&quot;0&quot; &amp; Day(pvDate), 2) _
+ &amp; &quot; &quot; &amp; Right(&quot;0&quot; &amp; Hour(pvDate), 2) &amp; &quot;:&quot; &amp; Right(&quot;0&quot; &amp; Minute(pvDate), 2) _
+ &amp; &quot;:&quot; &amp; Right(&quot;0&quot; &amp; Second(pvDate), 2)
+ End If
+ Else
+ sIsoDate = pvDate
+ End If
+
+ _CDateToIso = sIsoDate
+
+End Function &apos; ScriptForge.SF_Utils._CDateToIso
+
+REM -----------------------------------------------------------------------------
+Public Function _CDateToUnoDate(pvDate As Variant) As Variant
+&apos;&apos;&apos; Returns a UNO com.sun.star.util.DateTime/Date/Time object depending on the given date
+&apos;&apos;&apos; by using the appropriate CDateToUnoDateXxx builtin function
+&apos;&apos;&apos; UNO dates are essential in property values, where Basic dates are evil
+
+Dim vUnoDate As Variant &apos; Return value
+
+ If VarType(pvDate) = V_DATE Then
+ If Year(pvDate) &lt; 1900 Then
+ vUnoDate = CDateToUnoTime(pvDate)
+ ElseIf Hour(pvDate) + Minute(pvDate) + Second(pvDate) = 0 Then
+ vUnoDate = CDateToUnoDate(pvDate)
+ Else
+ vUnoDate = CDateToUnoDateTime(pvDate)
+ End If
+ Else
+ vUnoDate = pvDate
+ End If
+
+ _CDateToUnoDate = vUnoDate
+
+End Function &apos; ScriptForge.SF_Utils._CDateToUnoDate
+
+REM -----------------------------------------------------------------------------
+Public Function _CPropertyValue(ByRef pvValue As Variant) As Variant
+&apos;&apos;&apos; Set a value of a correct type in a com.sun.star.beans.PropertyValue
+&apos;&apos;&apos; Date BASIC variables give error. Change them to UNO types
+&apos;&apos;&apos; Empty arrays should be replaced by Null
+
+Dim vValue As Variant &apos; Return value
+
+ If VarType(pvValue) = V_DATE Then
+ vValue = SF_Utils._CDateToUnoDate(pvValue)
+ ElseIf IsArray(pvValue) Then
+ If UBound(pvValue, 1) &lt; LBound(pvValue, 1) Then vValue = Null Else vValue = pvValue
+ Else
+ vValue = pvValue
+ End If
+ _CPropertyValue() = vValue
+
+End Function &apos; ScriptForge.SF_Utils._CPropertyValue
+
+REM -----------------------------------------------------------------------------
+Public Function _CStrToDate(ByRef pvStr As String) As Date
+&apos;&apos;&apos; Attempt to convert the input string to a Date variable with the CDate builtin function
+&apos;&apos;&apos; If not successful, returns conventionally -1 (29/12/1899)
+&apos;&apos;&apos; Date patterns: YYYY-MM-DD, HH:MM:DD and YYYY-MM-DD HH:MM:DD
+
+Dim dDate As Date &apos; Return value
+Const cstNoDate = -1
+
+ dDate = cstNoDate
+Try:
+ On Local Error Resume Next
+ dDate = CDate(pvStr)
+
+Finally:
+ _CStrToDate = dDate
+ Exit Function
+End Function &apos; ScriptForge.SF_Utils._CStrToDate
+
+REM -----------------------------------------------------------------------------
+Public Function _EnterFunction(ByVal psSub As String, Optional ByVal psArgs As String)
+&apos;&apos;&apos; Called on top of each public function
+&apos;&apos;&apos; Used to trace routine in/outs (debug mode)
+&apos;&apos;&apos; and to allow the explicit mention of the user call which caused an error
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; psSub = the called Sub/Function/Property, usually something like &quot;service.sub&quot;
+&apos;&apos;&apos; Return: True when psSub is called from a user script
+&apos;&apos;&apos; Used to bypass the validation of the arguments when unnecessary
+
+ If IsEmpty(_SF_) Or IsNull(_SF_) Then SF_Utils._InitializeRoot() &apos; First use of ScriptForge during current LibO session
+ If IsMissing(psArgs) Then psArgs = &quot;&quot;
+ With _SF_
+ If .StackLevel = 0 Then
+ .MainFunction = psSub
+ .MainFunctionArgs = psArgs
+ _EnterFunction = True
+ Else
+ _EnterFunction = False
+ End If
+ .StackLevel = .StackLevel + 1
+ If .DebugMode Then ._AddToConsole(&quot;==&gt; &quot; &amp; psSub &amp; &quot;(&quot; &amp; .StackLevel &amp; &quot;)&quot;)
+ End With
+
+End Function &apos; ScriptForge.SF_Utils._EnterFunction
+
+REM -----------------------------------------------------------------------------
+Public Function _ErrorHandling(Optional ByVal pbErrorHandler As Boolean) As Boolean
+&apos;&apos;&apos; Error handling is normally ON and can be set OFF for debugging purposes
+&apos;&apos;&apos; Each user visible routine starts with a call to this function to enable/disable
+&apos;&apos;&apos; standard handling of internal errors
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; pbErrorHandler = if present, set its value
+&apos;&apos;&apos; Return: the current value of the error handler
+
+ If IsEmpty(_SF_) Or IsNull(_SF_) Then SF_Utils._InitializeRoot() &apos; First use of ScriptForge during current LibO session
+ If Not IsMissing(pbErrorHandler) Then _SF_.ErrorHandler = pbErrorHandler
+ _ErrorHandling = _SF_.ErrorHandler
+
+End Function &apos; ScriptForge.SF_Utils._ErrorHandling
+
+REM -----------------------------------------------------------------------------
+Public Sub _ExitFunction(ByVal psSub As String)
+&apos;&apos;&apos; Called in the Finally block of each public function
+&apos;&apos;&apos; Manage ScriptForge internal aborts
+&apos;&apos;&apos; Resets MainFunction (root) when exiting the method called by a user script
+&apos;&apos;&apos; Used to trace routine in/outs (debug mode)
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; psSub = the called Sub/Function/Property, usually something like &quot;service.sub&quot;
+
+ If IsEmpty(_SF_) Or IsNull(_SF_) Then SF_Utils._InitializeRoot() &apos; Useful only when current module has been recompiled
+ With _SF_
+ If Err &gt; 0 Then
+ SF_Exception.RaiseAbort(psSub)
+ End If
+ If .StackLevel = 1 Then
+ .MainFunction = &quot;&quot;
+ .MainFunctionArgs = &quot;&quot;
+ End If
+ If .DebugMode Then ._AddToConsole(&quot;&lt;== &quot; &amp; psSub &amp; &quot;(&quot; &amp; .StackLevel &amp; &quot;)&quot;)
+ If .StackLevel &gt; 0 Then .StackLevel = .StackLevel - 1
+ End With
+
+End Sub &apos; ScriptForge.SF_Utils._ExitFunction
+
+REM -----------------------------------------------------------------------------
+Public Sub _ExportScriptForgePOTFile(ByVal FileName As String)
+&apos;&apos;&apos; Export the ScriptForge POT file related to its own user interface
+&apos;&apos;&apos; Should be called only before issuing new ScriptForge releases only
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; FileName: the resulting file. If it exists, is overwritten without warning
+
+Dim sHeader As String &apos; The specific header to insert
+
+ sHeader = &quot;&quot; _
+ &amp; &quot;*********************************************************************\n&quot; _
+ &amp; &quot;*** The ScriptForge library and its associated libraries ***\n&quot; _
+ &amp; &quot;*** are part of the LibreOffice project. ***\n&quot; _
+ &amp; &quot;*********************************************************************\n&quot; _
+ &amp; &quot;\n&quot; _
+ &amp; &quot;ScriptForge Release &quot; &amp; SF_Version &amp; &quot;\n&quot; _
+ &amp; &quot;-----------------------&quot;
+
+Try:
+ With _SF_
+ If Not IsNull(.LocalizedInterface) Then .LocalizedInterface.Dispose()
+ ._LoadLocalizedInterface(psMode := &quot;ADDTEXT&quot;) &apos; Force reload of labels from the code
+ .LocalizedInterface.ExportToPOTFile(FileName, Header := sHeader)
+ End With
+
+Finally:
+ Exit Sub
+End Sub &apos; ScriptForge.SF_Utils._ExportScriptForgePOTFile
+
+REM -----------------------------------------------------------------------------
+Public Function _GetPropertyValue(ByRef pvArgs As Variant, ByVal psName As String) As Variant
+&apos;&apos;&apos; Returns the Value corresponding to the given name
+&apos;&apos;&apos; Args
+&apos;&apos;&apos; pvArgs: a zero_based array of PropertyValues
+&apos;&apos;&apos; psName: the comparison is not case-sensitive
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; Zero-length string if not found
+
+Dim vValue As Variant &apos; Return value
+Dim i As Long
+
+ vValue = &quot;&quot;
+ If IsArray(pvArgs) Then
+ For i = LBound(pvArgs) To UBound(pvArgs)
+ If UCase(psName) = UCase(pvArgs(i).Name) Then
+ vValue = pvArgs(i).Value
+ Exit For
+ End If
+ Next i
+ End If
+ _GetPropertyValue = vValue
+
+End Function &apos; ScriptForge.SF_Utils._GetPropertyValue
+
+REM -----------------------------------------------------------------------------
+Public Function _GetRegistryKeyContent(ByVal psKeyName as string _
+ , Optional pbForUpdate as Boolean _
+ ) As Variant
+&apos;&apos;&apos; Implement a ConfigurationProvider service
+&apos;&apos;&apos; Derived from the Tools library
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; psKeyName: the name of the node in the configuration tree
+&apos;&apos;&apos; pbForUpdate: default = False
+
+Dim oConfigProvider as Object &apos; com.sun.star.configuration.ConfigurationProvider
+Dim vNodePath(0) as New com.sun.star.beans.PropertyValue
+Dim sConfig As String &apos; One of next 2 constants
+Const cstConfig = &quot;com.sun.star.configuration.ConfigurationAccess&quot;
+Const cstConfigUpdate = &quot;com.sun.star.configuration.ConfigurationUpdateAccess&quot;
+
+ Set oConfigProvider = _GetUNOService(&quot;ConfigurationProvider&quot;)
+ vNodePath(0).Name = &quot;nodepath&quot;
+ vNodePath(0).Value = psKeyName
+
+ If IsMissing(pbForUpdate) Then pbForUpdate = False
+ If pbForUpdate Then sConfig = cstConfigUpdate Else sConfig = cstConfig
+
+ Set _GetRegistryKeyContent = oConfigProvider.createInstanceWithArguments(sConfig, vNodePath())
+
+End Function &apos; ScriptForge.SF_Utils._GetRegistryKeyContent
+
+REM -----------------------------------------------------------------------------
+Private Function _GetSetting(ByVal psPreference As String, psProperty As String) As Variant
+&apos;&apos;&apos; Find in the configuration a specific setting based on its location in the
+&apos;&apos;&apos; settings registry
+
+Dim oConfigProvider As Object &apos; com.sun.star.configuration.ConfigurationProvider
+Dim vNodePath As Variant &apos; Array of com.sun.star.beans.PropertyValue
+
+ &apos; Derived from the Tools library
+ Set oConfigProvider = createUnoService(&quot;com.sun.star.configuration.ConfigurationProvider&quot;)
+ vNodePath = Array(SF_Utils._MakePropertyValue(&quot;nodepath&quot;, psPreference))
+
+ _GetSetting = oConfigProvider.createInstanceWithArguments( _
+ &quot;com.sun.star.configuration.ConfigurationAccess&quot;, vNodePath()).getByName(psProperty)
+
+End Function &apos; ScriptForge.SF_Utils._GetSetting
+
+REM -----------------------------------------------------------------------------
+Public Function _GetUNOService(ByVal psService As String _
+ , Optional ByVal pvArg As Variant _
+ ) As Object
+&apos;&apos;&apos; Create a UNO service
+&apos;&apos;&apos; Each service is called only once
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; psService: shortcut to service
+&apos;&apos;&apos; pvArg: some services might require an argument
+
+Dim sLocale As String &apos; fr-BE f.i.
+Dim oDefaultContext As Object
+
+ Set _GetUNOService = Nothing
+ With _SF_
+ Select Case psService
+ Case &quot;BrowseNodeFactory&quot;
+ Set oDefaultContext = GetDefaultContext()
+ If Not IsNull(oDefaultContext) Then Set _GetUNOService = oDefaultContext.getValueByName(&quot;/singletons/com.sun.star.script.browse.theBrowseNodeFactory&quot;)
+ Case &quot;CalendarImpl&quot;
+ If IsEmpty(.CalendarImpl) Or IsNull(.CalendarImpl) Then
+ Set .CalendarImpl = CreateUnoService(&quot;com.sun.star.i18n.CalendarImpl&quot;)
+ End If
+ Set _GetUNOService = .CalendarImpl
+ Case &quot;CharacterClass&quot;
+ If IsEmpty(.CharacterClass) Or IsNull(.CharacterClass) Then
+ Set .CharacterClass = CreateUnoService(&quot;com.sun.star.i18n.CharacterClassification&quot;)
+ End If
+ Set _GetUNOService = .CharacterClass
+ Case &quot;ConfigurationProvider&quot;
+ If IsEmpty(.ConfigurationProvider) Or IsNull(.ConfigurationProvider) Then
+ Set .ConfigurationProvider = CreateUnoService(&quot;com.sun.star.configuration.ConfigurationProvider&quot;)
+ End If
+ Set _GetUNOService = .ConfigurationProvider
+ Case &quot;CoreReflection&quot;
+ If IsEmpty(.CoreReflection) Or IsNull(.CoreReflection) Then
+ Set .CoreReflection = CreateUnoService(&quot;com.sun.star.reflection.CoreReflection&quot;)
+ End If
+ Set _GetUNOService = .CoreReflection
+ Case &quot;DatabaseContext&quot;
+ If IsEmpty(.DatabaseContext) Or IsNull(.DatabaseContext) Then
+ Set .DatabaseContext = CreateUnoService(&quot;com.sun.star.sdb.DatabaseContext&quot;)
+ End If
+ Set _GetUNOService = .DatabaseContext
+ Case &quot;DispatchHelper&quot;
+ If IsEmpty(.DispatchHelper) Or IsNull(.DispatchHelper) Then
+ Set .DispatchHelper = CreateUnoService(&quot;com.sun.star.frame.DispatchHelper&quot;)
+ End If
+ Set _GetUNOService = .DispatchHelper
+ Case &quot;FileAccess&quot;
+ If IsEmpty(.FileAccess) Or IsNull(.FileAccess) Then
+ Set .FileAccess = CreateUnoService(&quot;com.sun.star.ucb.SimpleFileAccess&quot;)
+ End If
+ Set _GetUNOService = .FileAccess
+ Case &quot;FilePicker&quot;
+ If IsEmpty(.FilePicker) Or IsNull(.FilePicker) Then
+ Set .FilePicker = CreateUnoService(&quot;com.sun.star.ui.dialogs.FilePicker&quot;)
+ End If
+ Set _GetUNOService = .FilePicker
+ Case &quot;FilterFactory&quot;
+ If IsEmpty(.FilterFactory) Or IsNull(.FilterFactory) Then
+ Set .FilterFactory = CreateUnoService(&quot;com.sun.star.document.FilterFactory&quot;)
+ End If
+ Set _GetUNOService = .FilterFactory
+ Case &quot;FolderPicker&quot;
+ If IsEmpty(.FolderPicker) Or IsNull(.FolderPicker) Then
+ Set .FolderPicker = CreateUnoService(&quot;com.sun.star.ui.dialogs.FolderPicker&quot;)
+ End If
+ Set _GetUNOService = .FolderPicker
+ Case &quot;FormatLocale&quot;
+ If IsEmpty(.FormatLocale) Or IsNull(.FormatLocale) Then
+ .FormatLocale = CreateUnoStruct(&quot;com.sun.star.lang.Locale&quot;)
+ &apos; 1st and 2nd chance
+ sLocale = SF_Utils._GetSetting(&quot;org.openoffice.Setup/L10N&quot;, &quot;ooSetupSystemLocale&quot;)
+ If Len(sLocale) = 0 Then sLocale = SF_Utils._GetSetting(&quot;org.openoffice.System/L10N&quot;, &quot;UILocale&quot;)
+ .FormatLocale.Language = Split(sLocale, &quot;-&quot;)(0) &apos; Language is most often 2 chars long, but not always
+ .FormatLocale.Country = Right(sLocale, 2)
+ End If
+ Set _GetUNOService = .FormatLocale
+ Case &quot;FunctionAccess&quot;
+ If IsEmpty(.FunctionAccess) Or IsNull(.FunctionAccess) Then
+ Set .FunctionAccess = CreateUnoService(&quot;com.sun.star.sheet.FunctionAccess&quot;)
+ End If
+ Set _GetUNOService = .FunctionAccess
+ Case &quot;GraphicExportFilter&quot;
+ If IsEmpty(.GraphicExportFilter) Or IsNull(.GraphicExportFilter) Then
+ Set .GraphicExportFilter = CreateUnoService(&quot;com.sun.star.drawing.GraphicExportFilter&quot;)
+ End If
+ Set _GetUNOService = .GraphicExportFilter
+ Case &quot;Introspection&quot;
+ If IsEmpty(.Introspection) Or IsNull(.Introspection) Then
+ Set .Introspection = CreateUnoService(&quot;com.sun.star.beans.Introspection&quot;)
+ End If
+ Set _GetUNOService = .Introspection
+ Case &quot;LocaleData&quot;
+ If IsEmpty(.LocaleData) Or IsNull(.LocaleData) Then
+ Set .LocaleData = CreateUnoService(&quot;com.sun.star.i18n.LocaleData&quot;)
+ End If
+ Set _GetUNOService = .LocaleData
+ Case &quot;MacroExpander&quot;
+ Set oDefaultContext = GetDefaultContext()
+ If Not IsNull(oDefaultContext) Then Set _GetUNOService = oDefaultContext.getValueByName(&quot;/singletons/com.sun.star.util.theMacroExpander&quot;)
+ Case &quot;MailService&quot;
+ If IsEmpty(.MailService) Or IsNull(.MailService) Then
+ If GetGuiType = 1 Then &apos; Windows
+ Set .MailService = CreateUnoService(&quot;com.sun.star.system.SimpleSystemMail&quot;)
+ Else
+ Set .MailService = CreateUnoService(&quot;com.sun.star.system.SimpleCommandMail&quot;)
+ End If
+ End If
+ Set _GetUNOService = .MailService
+ Case &quot;Number2Text&quot;
+ If IsEmpty(.Number2Text) Or IsNull(.Number2Text) Then
+ Set .Number2Text = CreateUnoService(&quot;com.sun.star.linguistic2.NumberText&quot;)
+ End If
+ Set _GetUNOService = .Number2Text
+ Case &quot;OfficeLocale&quot;
+ If IsEmpty(.OfficeLocale) Or IsNull(.OfficeLocale) Then
+ .OfficeLocale = CreateUnoStruct(&quot;com.sun.star.lang.Locale&quot;)
+ &apos; 1st and 2nd chance
+ sLocale = SF_Utils._GetSetting(&quot;org.openoffice.Setup/L10N&quot;, &quot;ooLocale&quot;)
+ If Len(sLocale) = 0 Then sLocale = SF_Utils._GetSetting(&quot;org.openoffice.System/L10N&quot;, &quot;UILocale&quot;)
+ .OfficeLocale.Language = Split(sLocale, &quot;-&quot;)(0) &apos; Language is most often 2 chars long, but not always
+ .OfficeLocale.Country = Right(sLocale, 2)
+ End If
+ Set _GetUNOService = .OfficeLocale
+ Case &quot;PackageInformationProvider&quot;
+ If IsEmpty(.PackageProvider) Or IsNull(.PackageProvider) Then
+ Set .PackageProvider = GetDefaultContext.getByName(&quot;/singletons/com.sun.star.deployment.PackageInformationProvider&quot;)
+ End If
+ Set _GetUNOService = .PackageProvider
+ Case &quot;PathSettings&quot;
+ If IsEmpty(.PathSettings) Or IsNull(.PathSettings) Then
+ Set .PathSettings = CreateUnoService(&quot;com.sun.star.util.PathSettings&quot;)
+ End If
+ Set _GetUNOService = .PathSettings
+ Case &quot;PathSubstitution&quot;
+ If IsEmpty(.PathSubstitution) Or IsNull(.PathSubstitution) Then
+ Set .PathSubstitution = CreateUnoService(&quot;com.sun.star.util.PathSubstitution&quot;)
+ End If
+ Set _GetUNOService = .PathSubstitution
+ Case &quot;PrinterServer&quot;
+ If IsEmpty(.PrinterServer) Or IsNull(.PrinterServer) Then
+ Set .PrinterServer = CreateUnoService(&quot;com.sun.star.awt.PrinterServer&quot;)
+ End If
+ Set _GetUNOService = .PrinterServer
+ Case &quot;ScriptProvider&quot;
+ If IsMissing(pvArg) Then pvArg = SF_Session.SCRIPTISAPPLICATION
+ Select Case LCase(pvArg)
+ Case SF_Session.SCRIPTISEMBEDDED &apos; Document
+ If Not IsNull(ThisComponent) Then Set _GetUNOService = ThisComponent.getScriptProvider()
+ Case Else
+ If IsEmpty(.ScriptProvider) Or IsNull(.ScriptProvider) Then
+ Set .ScriptProvider = _
+ CreateUnoService(&quot;com.sun.star.script.provider.MasterScriptProviderFactory&quot;).createScriptProvider(&quot;&quot;)
+ End If
+ Set _GetUNOService = .ScriptProvider
+ End Select
+ Case &quot;SearchOptions&quot;
+ If IsEmpty(.SearchOptions) Or IsNull(.SearchOptions) Then
+ Set .SearchOptions = New com.sun.star.util.SearchOptions
+ With .SearchOptions
+ .algorithmType = com.sun.star.util.SearchAlgorithms.REGEXP
+ .searchFlag = 0
+ End With
+ End If
+ Set _GetUNOService = .SearchOptions
+ Case &quot;SystemLocale&quot;, &quot;Locale&quot;
+ If IsEmpty(.SystemLocale) Or IsNull(.SystemLocale) Then
+ .SystemLocale = CreateUnoStruct(&quot;com.sun.star.lang.Locale&quot;)
+ sLocale = SF_Utils._GetSetting(&quot;org.openoffice.System/L10N&quot;, &quot;SystemLocale&quot;)
+ .SystemLocale.Language = Split(sLocale, &quot;-&quot;)(0) &apos; Language is most often 2 chars long, but not always
+ .SystemLocale.Country = Right(sLocale, 2)
+ End If
+ Set _GetUNOService = .SystemLocale
+ Case &quot;SystemShellExecute&quot;
+ If IsEmpty(.SystemShellExecute) Or IsNull(.SystemShellExecute) Then
+ Set .SystemShellExecute = CreateUnoService(&quot;com.sun.star.system.SystemShellExecute&quot;)
+ End If
+ Set _GetUNOService = .SystemShellExecute
+ Case &quot;TextSearch&quot;
+ If IsEmpty(.TextSearch) Or IsNull(.TextSearch) Then
+ Set .TextSearch = CreateUnoService(&quot;com.sun.star.util.TextSearch&quot;)
+ End If
+ Set _GetUNOService = .TextSearch
+ Case &quot;Toolkit&quot;
+ If IsEmpty(.Toolkit) Or IsNull(.Toolkit) Then
+ Set .Toolkit = CreateUnoService(&quot;com.sun.star.awt.Toolkit&quot;)
+ End If
+ Set _GetUNOService = .Toolkit
+ Case &quot;URLTransformer&quot;
+ If IsEmpty(.URLTransformer) Or IsNull(.URLTransformer) Then
+ Set .URLTransformer = CreateUnoService(&quot;com.sun.star.util.URLTransformer&quot;)
+ End If
+ Set _GetUNOService = .URLTransformer
+ Case Else
+ End Select
+ End With
+
+End Function &apos; ScriptForge.SF_Utils._GetUNOService
+
+REM -----------------------------------------------------------------------------
+Public Sub _InitializeRoot(Optional ByVal pbForce As Boolean)
+&apos;&apos;&apos; Initialize _SF_ as SF_Root basic object
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; pbForce = True forces the reinit (default = False)
+
+ If IsMissing(pbForce) Then pbForce = False
+ If pbForce Then Set _SF_ = Nothing
+ If IsEmpty(_SF_) Or IsNull(_SF_) Then
+ Set _SF_ = New SF_Root
+ Set _SF_.[Me] = _SF_
+ End If
+
+End Sub &apos; ScriptForge.SF_Utils._InitializeRoot
+
+REM -----------------------------------------------------------------------------
+Public Function _MakePropertyValue(ByVal psName As String _
+ , ByRef pvValue As Variant _
+ ) As com.sun.star.beans.PropertyValue
+&apos;&apos;&apos; Create and return a new com.sun.star.beans.PropertyValue
+
+Dim oPropertyValue As New com.sun.star.beans.PropertyValue
+
+ With oPropertyValue
+ .Name = psName
+ .Value = SF_Utils._CPropertyValue(pvValue)
+ End With
+ _MakePropertyValue() = oPropertyValue
+
+End Function &apos; ScriptForge.SF_Utils._MakePropertyValue
+
+REM -----------------------------------------------------------------------------
+Public Function _Repr(ByVal pvArg As Variant, Optional ByVal plMax As Long) As String
+&apos;&apos;&apos; Convert pvArg into a readable string (truncated if length &gt; plMax)
+&apos;&apos;&apos; Args
+&apos;&apos;&apos; pvArg: may be of any type
+&apos;&apos;&apos; plMax: maximum length of the resulting string (default = 32K)
+
+Dim sArg As String &apos; Return value
+Dim oObject As Object &apos; Alias of argument to avoid &quot;Object variable not set&quot;
+Dim oObjectDesc As Object &apos; Object descriptor
+Dim sLength As String &apos; String length as a string
+Dim i As Long
+Const cstBasicObject = &quot;com.sun.star.script.NativeObjectWrapper&quot;
+
+Const cstMaxLength = 2^15 - 1 &apos; 32767
+Const cstByteLength = 25
+Const cstEtc = &quot; … &quot;
+
+ If IsMissing(plMax) Then plMax = cstMaxLength
+ If plMax = 0 Then plMax = cstMaxLength
+ If IsArray(pvArg) Then
+ sArg = SF_Array._Repr(pvArg)
+ Else
+ Select Case VarType(pvArg)
+ Case V_EMPTY : sArg = &quot;[EMPTY]&quot;
+ Case V_NULL : sArg = &quot;[NULL]&quot;
+ Case V_OBJECT
+ Set oObjectDesc = SF_Utils._VarTypeObj(pvArg)
+ With oObjectDesc
+ Select Case .iVarType
+ Case V_NOTHING : sArg = &quot;[NOTHING]&quot;
+ Case V_OBJECT, V_BASICOBJECT
+ sArg = &quot;[OBJECT]&quot;
+ Case V_UNOOBJECT : sArg = &quot;[&quot; &amp; .sObjectType &amp; &quot;]&quot;
+ Case V_SFOBJECT
+ If Left(.sObjectType, 3) = &quot;SF_&quot; Then &apos; Standard module
+ sArg = &quot;[&quot; &amp; .sObjectType &amp; &quot;]&quot;
+ Else &apos; Class module must have a _Repr() method
+ Set oObject = pvArg
+ sArg = oObject._Repr()
+ End If
+ End Select
+ End With
+ Case V_VARIANT : sArg = &quot;[VARIANT]&quot;
+ Case V_STRING
+ sArg = SF_String._Repr(pvArg)
+ Case V_BOOLEAN : sArg = Iif(pvArg, &quot;[TRUE]&quot;, &quot;[FALSE]&quot;)
+ Case V_BYTE : sArg = Right(&quot;00&quot; &amp; Hex(pvArg), 2)
+ Case V_SINGLE, V_DOUBLE, V_CURRENCY
+ sArg = Format(pvArg)
+ If InStr(1, sArg, &quot;E&quot;, 1) = 0 Then sArg = Format(pvArg, &quot;##0.0##&quot;)
+ sArg = Replace(sArg, &quot;,&quot;, &quot;.&quot;) &apos;Force decimal point
+ Case V_BIGINT : sArg = CStr(CLng(pvArg))
+ Case V_DATE : sArg = _CDateToIso(pvArg)
+ Case Else : sArg = CStr(pvArg)
+ End Select
+ End If
+ If Len(sArg) &gt; plMax Then
+ sLength = &quot;(&quot; &amp; Len(sArg) &amp; &quot;)&quot;
+ sArg = Left(sArg, plMax - Len(cstEtc) - Len(slength)) &amp; cstEtc &amp; sLength
+ End If
+ _Repr = sArg
+
+End Function &apos; ScriptForge.SF_Utils._Repr
+
+REM -----------------------------------------------------------------------------
+Private Function _ReprValues(Optional ByVal pvArgs As Variant _
+ , Optional ByVal plMax As Long _
+ ) As String
+&apos;&apos;&apos; Convert an array of values to a comma-separated list of readable strings
+
+Dim sValues As String &apos; Return value
+Dim sValue As String &apos; A single value
+Dim vValue As Variant &apos; A single item in the argument
+Dim i As Long &apos; Items counter
+Const cstMax = 20 &apos; Maximum length of single string
+Const cstContinue = &quot;…&quot; &apos; Unicode continuation char U+2026
+
+ _ReprValues = &quot;&quot;
+ If IsMissing(pvArgs) Then Exit Function
+ If Not IsArray(pvArgs) Then pvArgs = Array(pvArgs)
+ sValues = &quot;&quot;
+ For i = 0 To UBound(pvArgs)
+ vValue = pvArgs(i)
+ If i &lt; plMax Then
+ If VarType(vValue) = V_STRING Then sValue = &quot;&quot;&quot;&quot; &amp; vValue &amp; &quot;&quot;&quot;&quot; Else sValue = SF_Utils._Repr(vValue, cstMax)
+ If Len(sValues) = 0 Then sValues = sValue Else sValues = sValues &amp; &quot;, &quot; &amp; sValue
+ ElseIf i &lt; UBound(pvArgs) Then
+ sValues = sValues &amp; &quot;, &quot; &amp; cstContinue
+ Exit For
+ End If
+ Next i
+ _ReprValues = sValues
+
+End Function &apos; ScriptForge.SF_Utils._ReprValues
+
+REM -----------------------------------------------------------------------------
+Public Function _SetPropertyValue(ByVal pvPropertyValue As Variant _
+ , ByVal psName As String _
+ , ByRef pvValue As Variant _
+ ) As Variant
+&apos;&apos;&apos; Return the 1st argument (passed by reference), which is an array of property values
+&apos;&apos;&apos; If the property psName exists, update it with pvValue, otherwise create it on top of the returned array
+
+Dim oPropertyValue As New com.sun.star.beans.PropertyValue
+Dim lIndex As Long &apos; Found entry
+Dim vValue As Variant &apos; Alias of pvValue
+Dim vProperties As Variant &apos; Alias of pvPropertyValue
+Dim i As Long
+
+ lIndex = -1
+ vProperties = pvPropertyValue
+ For i = 0 To UBound(vProperties)
+ If vProperties(i).Name = psName Then
+ lIndex = i
+ Exit For
+ End If
+ Next i
+ If lIndex &lt; 0 Then &apos; Not found
+ lIndex = UBound(vProperties) + 1
+ ReDim Preserve vProperties(0 To lIndex)
+ Set oPropertyValue = SF_Utils._MakePropertyValue(psName, pvValue)
+ vProperties(lIndex) = oPropertyValue
+ vProperties = vProperties
+ Else &apos; psName exists already in array of property values
+ vProperties(lIndex).Value = SF_Utils._CPropertyValue(pvValue)
+ End If
+
+ _SetPropertyValue = vProperties
+
+End Function &apos; ScriptForge.SF_Utils._SetPropertyValue
+
+REM -----------------------------------------------------------------------------
+Private Function _TypeNames(Optional ByVal pvArgs As Variant) As String
+&apos;&apos;&apos; Converts the array of VarTypes to a comma-separated list of TypeNames
+
+Dim sTypes As String &apos; Return value
+Dim sType As String &apos; A single type
+Dim iType As Integer &apos; A single item of the argument
+
+ _TypeNames = &quot;&quot;
+ If IsMissing(pvArgs) Then Exit Function
+ If Not IsArray(pvArgs) Then pvArgs = Array(pvArgs)
+ sTypes = &quot;&quot;
+ For Each iType In pvArgs
+ Select Case iType
+ Case V_EMPTY : sType = &quot;Empty&quot;
+ Case V_NULL : sType = &quot;Null&quot;
+ Case V_INTEGER : sType = &quot;Integer&quot;
+ Case V_LONG : sType = &quot;Long&quot;
+ Case V_SINGLE : sType = &quot;Single&quot;
+ Case V_DOUBLE : sType = &quot;Double&quot;
+ Case V_CURRENCY : sType = &quot;Currency&quot;
+ Case V_DATE : sType = &quot;Date&quot;
+ Case V_STRING : sType = &quot;String&quot;
+ Case V_OBJECT : sType = &quot;Object&quot;
+ Case V_BOOLEAN : sType = &quot;Boolean&quot;
+ Case V_VARIANT : sType = &quot;Variant&quot;
+ Case V_DECIMAL : sType = &quot;Decimal&quot;
+ Case &gt;= V_ARRAY : sType = &quot;Array&quot;
+ Case V_NUMERIC : sType = &quot;Numeric&quot;
+ End Select
+ If Len(sTypes) = 0 Then sTypes = sType Else sTypes = sTypes &amp; &quot;, &quot; &amp; sType
+ Next iType
+ _TypeNames = sTypes
+
+End Function &apos; ScriptForge.SF_Utils._TypeNames
+
+REM -----------------------------------------------------------------------------
+Public Function _Validate(Optional ByRef pvArgument As Variant _
+ , ByVal psName As String _
+ , Optional ByVal pvTypes As Variant _
+ , Optional ByVal pvValues As Variant _
+ , Optional ByVal pvRegex As Variant _
+ , Optional ByVal pvObjectType As Variant _
+ ) As Boolean
+&apos;&apos;&apos; Validate the arguments set by user scripts
+&apos;&apos;&apos; The arguments of the function define the validation rules
+&apos;&apos;&apos; This function ignores arrays. Use _ValidateArray instead
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; pvArgument: the argument to (in)validate
+&apos;&apos;&apos; psName: the documented name of the argument (can be inserted in an error message)
+&apos;&apos;&apos; pvTypes: array of allowed VarTypes
+&apos;&apos;&apos; pvValues: array of allowed values
+&apos;&apos;&apos; pvRegex: regular expression to comply with
+&apos;&apos;&apos; pvObjectType: mandatory Basic class
+&apos;&apos;&apos; Return: True if validation OK
+&apos;&apos;&apos; Otherwise an error is raised
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; ARGUMENTERROR
+
+Dim iVarType As Integer &apos; Extended VarType of argument
+Dim bValid As Boolean &apos; Returned value
+Dim oObjectDescriptor As Object &apos; _ObjectDescriptor type
+Const cstMaxLength = 256 &apos; Maximum length of readable value
+Const cstMaxValues = 10 &apos; Maximum number of allowed items to list in an error message
+
+ &apos; To avoid useless recursions, keep main function, only increase stack depth
+ _SF_.StackLevel = _SF_.StackLevel + 1
+ On Local Error GoTo Finally &apos; Do never interrupt
+
+Try:
+ bValid = True
+ If IsMissing(pvArgument) Then GoTo CatchMissing
+ If IsMissing(pvRegex) Or IsEmpty(pvRegex) Then pvRegex = &quot;&quot;
+ If IsMissing(pvObjectType) Or IsEmpty(pvObjectType) Then pvObjectType = &quot;&quot;
+ iVarType = SF_Utils._VarTypeExt(pvArgument)
+
+ &apos; Arrays NEVER pass validation
+ If iVarType &gt;= V_ARRAY Then
+ bValid = False
+ Else
+ &apos; Check existence of argument
+ bValid = iVarType &lt;&gt; V_NULL And iVarType &lt;&gt; V_EMPTY
+ &apos; Check if argument&apos;s VarType is valid
+ If bValid And Not IsMissing(pvTypes) Then
+ If Not IsArray(pvTypes) Then bValid = ( pvTypes = iVarType ) Else bValid = SF_Array.Contains(pvTypes, iVarType)
+ End If
+ &apos; Check if argument&apos;s value is valid
+ If bValid And Not IsMissing(pvValues) Then
+ If Not IsArray(pvValues) Then pvValues = Array(pvValues)
+ bValid = SF_Array.Contains(pvValues, pvArgument, CaseSensitive := False)
+ End If
+ &apos; Check regular expression
+ If bValid And Len(pvRegex) &gt; 0 And iVarType = V_STRING Then
+ If Len(pvArgument) &gt; 0 Then bValid = SF_String.IsRegex(pvArgument, pvRegex, CaseSensitive := False)
+ End If
+ &apos; Check instance types
+ If bValid And Len(pvObjectType) &gt; 0 And iVarType = V_OBJECT Then
+ &apos;Set oArgument = pvArgument
+ Set oObjectDescriptor = SF_Utils._VarTypeObj(pvArgument)
+ bValid = ( oObjectDescriptor.iVarType = V_SFOBJECT )
+ If bValid Then bValid = ( oObjectDescriptor.sObjectType = pvObjectType )
+ End If
+ End If
+
+ If Not bValid Then
+ &apos;&apos;&apos; Library: ScriptForge
+ &apos;&apos;&apos; Service: Array
+ &apos;&apos;&apos; Method: Contains
+ &apos;&apos;&apos; Arguments: Array_1D, ToFind, [CaseSensitive=False], [SortOrder=&quot;&quot;]
+ &apos;&apos;&apos; A serious error has been detected on argument SortOrder
+ &apos;&apos;&apos; Rules: SortOrder is of type String
+ &apos;&apos;&apos; SortOrder must contain one of next values: &quot;ASC&quot;, &quot;DESC&quot;, &quot;&quot;
+ &apos;&apos;&apos; Actual value: &quot;Ascending&quot;
+ SF_Exception.RaiseFatal(ARGUMENTERROR _
+ , SF_Utils._Repr(pvArgument, cstMaxLength), psName, SF_Utils._TypeNames(pvTypes) _
+ , SF_Utils._ReprValues(pvValues, cstMaxValues), pvRegex, pvObjectType _
+ )
+ End If
+
+Finally:
+ _Validate = bValid
+ _SF_.StackLevel = _SF_.StackLevel - 1
+ Exit Function
+CatchMissing:
+ bValid = False
+ SF_Exception.RaiseFatal(MISSINGARGERROR, psName)
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Utils._Validate
+
+REM -----------------------------------------------------------------------------
+Public Function _ValidateArray(Optional ByRef pvArray As Variant _
+ , ByVal psName As String _
+ , Optional ByVal piDimensions As Integer _
+ , Optional ByVal piType As Integer _
+ , Optional ByVal pbNotNull As Boolean _
+ ) As Boolean
+&apos;&apos;&apos; Validate the (array) arguments set by user scripts
+&apos;&apos;&apos; The arguments of the function define the validation rules
+&apos;&apos;&apos; This function ignores non-arrays. Use _Validate instead
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; pvArray: the argument to (in)validate
+&apos;&apos;&apos; psName: the documented name of the array (can be inserted in an error message)
+&apos;&apos;&apos; piDimensions: the # of dimensions the array must have. 0 = Any (default)
+&apos;&apos;&apos; piType: (default = -1, i.e. not applicable)
+&apos;&apos;&apos; For 2D arrays, the 1st column is checked
+&apos;&apos;&apos; 0 =&gt; all items must be any out of next types: string, date or numeric,
+&apos;&apos;&apos; but homogeneously: all strings or all dates or all numeric
+&apos;&apos;&apos; V_STRING or V_DATE or V_NUMERIC =&gt; that specific type is required
+&apos;&apos;&apos; pbNotNull: piType must be &gt;=0, otherwise ignored
+&apos;&apos;&apos; If True: Empty, Null items are rejected
+&apos;&apos;&apos; Return: True if validation OK
+&apos;&apos;&apos; Otherwise an error is raised
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; ARRAYERROR
+
+Dim iVarType As Integer &apos; VarType of argument
+Dim vItem As Variant &apos; Array item
+Dim iItemType As Integer &apos; VarType of individual items of argument
+Dim iDims As Integer &apos; Number of dimensions of the argument
+Dim bValid As Boolean &apos; Returned value
+Dim iArrayType As Integer &apos; Static array type
+Dim iFirstItemType As Integer &apos; Type of 1st non-null/empty item
+Dim sType As String &apos; Allowed item types as a string
+Dim i As Long
+Const cstMaxLength = 256 &apos; Maximum length of readable value
+
+ &apos; To avoid useless recursions, keep main function, only increase stack depth
+
+ _SF_.StackLevel = _SF_.StackLevel + 1
+ On Local Error GoTo Finally &apos; Do never interrupt
+
+Try:
+ bValid = True
+ If IsMissing(pvArray) Then GoTo CatchMissing
+ If IsMissing(piDimensions) Then piDimensions = 0
+ If IsMissing(piType) Then piType = -1
+ If IsMissing(pbNotNull) Then pbNotNull = False
+ iVarType = VarType(pvArray)
+
+ &apos; Scalars NEVER pass validation
+ If iVarType &lt; V_ARRAY Then
+ bValid = False
+ Else
+ &apos; Check dimensions
+ iDims = SF_Array.CountDims(pvArray)
+ If iDims &gt; 2 Then bValid = False &apos; Only 1D and 2D arrays
+ If bValid And piDimensions &gt; 0 Then
+ bValid = ( iDims = piDimensions Or (iDims = 0 And piDimensions = 1) ) &apos; Allow empty vectors
+ End If
+ &apos; Check VarType and Empty/Null status of the array items
+ If bValid And iDims = 1 And piType &gt;= 0 Then
+ iArrayType = SF_Array._StaticType(pvArray)
+ If (piType = 0 And iArrayType &gt; 0) Or (piType &gt; 0 And iArrayType = piType) Then
+ &apos; If static array of the right VarType ..., OK
+ Else
+ &apos; Go through array and check individual items
+ iFirstItemType = -1
+ For i = LBound(pvArray, 1) To UBound(pvArray, 1)
+ If iDims = 1 Then vItem = pvArray(i) Else vItem = pvArray(i, LBound(pvArray, 2))
+ iItemType = SF_Utils._VarTypeExt(vItem)
+ If iItemType &gt; V_NULL Then &apos; Exclude Empty and Null
+ &apos; Initialization at first non-null item
+ If iFirstItemType &lt; 0 Then
+ iFirstItemType = iItemType
+ If piType &gt; 0 Then bValid = ( iFirstItemType = piType ) Else bValid = SF_Array.Contains(Array(V_STRING, V_DATE, V_NUMERIC), iFirstItemType)
+ Else
+ bValid = (iItemType = iFirstItemType)
+ End If
+ Else
+ bValid = Not pbNotNull
+ End If
+ If Not bValid Then Exit For
+ Next i
+ End If
+ End If
+ End If
+
+ If Not bValid Then
+ &apos;&apos;&apos; Library: ScriptForge
+ &apos;&apos;&apos; Service: Array
+ &apos;&apos;&apos; Method: Contains
+ &apos;&apos;&apos; Arguments: Array_1D, ToFind, [CaseSensitive=False], [SortOrder=&quot;&quot;|&quot;ASC&quot;|&quot;DESC&quot;]
+ &apos;&apos;&apos; An error was detected on argument Array_1D
+ &apos;&apos;&apos; Rules: Array_1D is of type Array
+ &apos;&apos;&apos; Array_1D must have maximum 1 dimension
+ &apos;&apos;&apos; Array_1D must have all elements of the same type: either String, Date or Numeric
+ &apos;&apos;&apos; Actual value: (0:2, 0:3)
+ sType = &quot;&quot;
+ If piType = 0 Then
+ sType = &quot;String, Date, Numeric&quot;
+ ElseIf piType &gt; 0 Then
+ sType = SF_Utils._TypeNames(piType)
+ End If
+ SF_Exception.RaiseFatal(ARRAYERROR _
+ , SF_Utils._Repr(pvArray, cstMaxLength), psName, piDimensions, sType, pbNotNull)
+ End If
+
+Finally:
+ _ValidateArray = bValid
+ _SF_.StackLevel = _SF_.StackLevel - 1
+ Exit Function
+CatchMissing:
+ bValid = False
+ SF_Exception.RaiseFatal(MISSINGARGERROR, psName)
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Utils._ValidateArray
+
+REM -----------------------------------------------------------------------------
+Public Function _ValidateFile(Optional ByRef pvArgument As Variant _
+ , ByVal psName As String _
+ , Optional ByVal pbWildCards As Boolean _
+ , Optional ByVal pbSpace As Boolean _
+ )
+&apos;&apos;&apos; Validate the argument as a valid FileName
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; pvArgument: the argument to (in)validate
+&apos;&apos;&apos; pbWildCards: if True, wildcard characters are accepted in the last component of the 1st argument
+&apos;&apos;&apos; pbSpace: if True, the argument may be an empty string. Default = False
+&apos;&apos;&apos; Return: True if validation OK
+&apos;&apos;&apos; Otherwise an error is raised
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; ARGUMENTERROR
+
+Dim iVarType As Integer &apos; VarType of argument
+Dim sFile As String &apos; Alias for argument
+Dim bValid As Boolean &apos; Returned value
+Dim sFileNaming As String &apos; Alias of SF_FileSystem.FileNaming
+Dim oArgument As Variant &apos; Workaround &quot;Object variable not set&quot; error on 1st executable statement
+Const cstMaxLength = 256 &apos; Maximum length of readable value
+
+ &apos; To avoid useless recursions, keep main function, only increase stack depth
+
+ _SF_.StackLevel = _SF_.StackLevel + 1
+ On Local Error GoTo Finally &apos; Do never interrupt
+
+Try:
+ bValid = True
+ If IsMissing(pvArgument) Then GoTo CatchMissing
+ If IsMissing(pbWildCards) Then pbWildCards = False
+ If IsMissing(pbSpace) Then pbSpace = False
+ iVarType = VarType(pvArgument)
+
+ &apos; Arrays NEVER pass validation
+ If iVarType &gt;= V_ARRAY Then
+ bValid = False
+ Else
+ &apos; Argument must be a string containing a valid file name
+ bValid = ( iVarType = V_STRING )
+ If bValid Then
+ bValid = ( Len(pvArgument) &gt; 0 Or pbSpace )
+ If bValid And Len(pvArgument) &gt; 0 Then
+ &apos; Wildcards are replaced by arbitrary alpha characters
+ If pbWildCards Then
+ sFile = Replace(Replace(pvArgument, &quot;?&quot;, &quot;Z&quot;), &quot;*&quot;, &quot;A&quot;)
+ Else
+ sFile = pvArgument
+ bValid = ( InStr(sFile, &quot;?&quot;) + InStr(sFile, &quot;*&quot;) = 0 )
+ End If
+ &apos; Check file format without wildcards
+ If bValid Then
+ With SF_FileSystem
+ sFileNaming = .FileNaming
+ Select Case sFileNaming
+ Case &quot;ANY&quot; : bValid = SF_String.IsUrl(ConvertToUrl(sFile))
+ Case &quot;URL&quot; : bValid = SF_String.IsUrl(sFile)
+ Case &quot;SYS&quot; : bValid = SF_String.IsFileName(sFile)
+ End Select
+ End With
+ End If
+ &apos; Check that wildcards are only present in last component
+ If bValid And pbWildCards Then
+ sFile = SF_FileSystem.GetParentFolderName(pvArgument)
+ bValid = ( InStr(sFile, &quot;*&quot;) + InStr(sFile, &quot;?&quot;) + InStr(sFile,&quot;%3F&quot;) = 0 ) &apos; ConvertToUrl replaces ? by %3F
+ End If
+ End If
+ End If
+ End If
+
+ If Not bValid Then
+ &apos;&apos;&apos; Library: ScriptForge
+ &apos;&apos;&apos; Service: FileSystem
+ &apos;&apos;&apos; Method: CopyFile
+ &apos;&apos;&apos; Arguments: Source, Destination
+ &apos;&apos;&apos; A serious error has been detected on argument Source
+ &apos;&apos;&apos; Rules: Source is of type String
+ &apos;&apos;&apos; Source must be a valid file name expressed in operating system notation
+ &apos;&apos;&apos; Source may contain one or more wildcard characters in its last component
+ &apos;&apos;&apos; Actual value: /home/jean-*/SomeFile.odt
+ SF_Exception.RaiseFatal(FILEERROR _
+ , SF_Utils._Repr(pvArgument, cstMaxLength), psName, pbWildCards)
+ End If
+
+Finally:
+ _ValidateFile = bValid
+ _SF_.StackLevel = _SF_.StackLevel - 1
+ Exit Function
+CatchMissing:
+ bValid = False
+ SF_Exception.RaiseFatal(MISSINGARGERROR, psName)
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Utils._ValidateFile
+
+REM -----------------------------------------------------------------------------
+Public Function _VarTypeExt(ByRef pvValue As Variant) As Integer
+&apos;&apos;&apos; Return the VarType of the argument but all numeric types are aggregated into V_NUMERIC
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; pvValue: value to examine
+&apos;&apos;&apos; Return:
+&apos;&apos;&apos; The extended VarType
+
+Dim iType As Integer &apos; VarType of argument
+
+ iType = VarType(pvValue)
+ Select Case iType
+ Case V_INTEGER, V_LONG, V_SINGLE, V_DOUBLE, V_CURRENCY, V_BIGINT, V_DECIMAL
+ _VarTypeExt = V_NUMERIC
+ Case Else : _VarTypeExt = iType
+ End Select
+
+End Function &apos; ScriptForge.SF_Utils._VarTypeExt
+
+REM -----------------------------------------------------------------------------
+Public Function _VarTypeObj(ByRef pvValue As Variant) As Object
+&apos;&apos;&apos; Inspect the argument that is supposed to be an Object
+&apos;&apos;&apos; Return the internal type of object as one of the values
+&apos;&apos;&apos; V_NOTHING Null object
+&apos;&apos;&apos; V_UNOOBJECT Uno object or Uno structure
+&apos;&apos;&apos; V_SFOBJECT ScriptForge object: has ObjectType and ServiceName properties
+&apos;&apos;&apos; V_BASICOBJECT User Basic object
+&apos;&apos;&apos; coupled with object type as a string (&quot;com.sun.star...&quot; or &quot;SF_...&quot; or &quot;... ScriptForge class ...&quot;)
+&apos;&apos;&apos; When the argument is not an Object, return the usual VarType() of the argument
+
+Dim oObjDesc As _ObjectDescriptor &apos; Return value
+Dim oValue As Object &apos; Alias of pvValue used to avoid &quot;Object variable not set&quot; error
+Dim sObjType As String &apos; The type of object is first derived as a string
+Dim oReflection As Object &apos; com.sun.star.reflection.CoreReflection
+Dim vClass As Variant &apos; com.sun.star.reflection.XIdlClass
+Dim bUno As Boolean &apos; True when object recognized as UNO object
+
+Const cstBasicClass = &quot;com.sun.star.script.NativeObjectWrapper&quot; &apos; Way to recognize Basic objects
+
+ On Local Error Resume Next &apos; Object type is established by trial and error
+
+Try:
+ With oObjDesc
+ .iVarType = VarType(pvValue)
+ .sObjectType = &quot;&quot;
+ .sServiceName = &quot;&quot;
+ bUno = False
+ If .iVarType = V_OBJECT Then
+ If IsNull(pvValue) Then
+ .iVarType = V_NOTHING
+ Else
+ Set oValue = pvValue
+ &apos; Try UNO type with usual ImplementationName property
+ .sObjectType = oValue.getImplementationName()
+ If .sObjectType = &quot;&quot; Then
+ &apos; Try UNO type with alternative CoreReflection trick
+ Set oReflection = SF_Utils._GetUNOService(&quot;CoreReflection&quot;)
+ vClass = oReflection.getType(oValue)
+ If vClass.TypeClass &gt;= com.sun.star.uno.TypeClass.STRUCT Then
+ .sObjectType = vClass.Name
+ bUno = True
+ End If
+ Else
+ bUno = True
+ End If
+ &apos; Identify Basic objects
+ If .sObjectType = cstBasicClass Then
+ bUno = False
+ &apos; Try if the Basic object has an ObjectType property
+ .sObjectType = oValue.ObjectType
+ .sServiceName = oValue.ServiceName
+ End If
+ &apos; Derive the return value from the object type
+ Select Case True
+ Case Len(.sObjectType) = 0 &apos; Do nothing (return V_OBJECT)
+ Case .sObjectType = cstBasicClass : .iVarType = V_BASICOBJECT
+ Case bUno : .iVarType = V_UNOOBJECT
+ Case Else : .iVarType = V_SFOBJECT
+ End Select
+ End If
+ End If
+ End With
+
+Finally:
+ Set _VarTypeObj = oObjDesc
+ Exit Function
+End Function &apos; ScriptForge.SF_Utils._VarTypeObj
+
+REM ================================================= END OF SCRIPTFORGE.SF_UTILS
+</script:module> \ No newline at end of file
diff --git a/wizards/source/scriptforge/_CodingConventions.xba b/wizards/source/scriptforge/_CodingConventions.xba
new file mode 100644
index 000000000..71fb42c77
--- /dev/null
+++ b/wizards/source/scriptforge/_CodingConventions.xba
@@ -0,0 +1,100 @@
+<?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="_CodingConventions" 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 =======================================================================================================================
+
+&apos;&apos;&apos;
+&apos; Conventions used in the coding of the *ScriptForge* library
+&apos; -----------------------------------------------------------
+&apos;&apos;&apos;
+&apos; Library and Modules
+&apos; ===================
+&apos; * Module names are all prefixed with &quot;SF_&quot;.
+&apos; * The *Option Explicit* statement is mandatory in every module.
+&apos; * The *Option Private Module* statement is recommended in internal modules.
+&apos; * A standard header presenting the module/class is mandatory
+&apos; * An end of file (eof) comment line is mandatory
+&apos; * Every module lists the constants that are related to it and documented as return values, arguments, etc.
+&apos; They are defined as *Global Const*.
+&apos; The scope of global constants being limited to one single library, their invocation from user scripts shall be qualified.
+&apos; * The Basic reserved words are *Proper-Cased*.
+&apos;&apos;&apos;
+&apos; Functions and Subroutines
+&apos; =========================
+&apos; * LibreOffice ignores the Private/Public attribute in Functions or Subs declarations.
+&apos; Nevertheless the attribute must be present.
+&apos; Rules to recognize their scope are:
+&apos; * Public + name starts with a letter
+&apos; The Sub/Function belongs to the official ScriptForge API.
+&apos; As such it may be called from any user script.
+&apos; * Public + name starts with an underscore &quot;_&quot;
+&apos; The Sub/Function may be called only from within the ScriptForge library.
+&apos; As such it MUST NOT be called from another library or from a user script,
+&apos; as there is no guarantee about the arguments, the semantic or even the existence of that piece of code in a later release.
+&apos; * Private - The Sub/Function name must start with an underscore &quot;_&quot;.
+&apos; The Sub/Function may be called only from the module in which it is located.
+&apos; * Functions and Subroutines belonging to the API (= &quot;standard&quot; functions/Subs) are defined in their module in alphabetical order.
+&apos; For class modules, all the properties precede the methods which precede the events.
+&apos; * Functions and Subroutines not belonging to the API are defined in their module in alphabetical order below the standard ones.
+&apos; * The return value of a function is always declared explicitly.
+&apos; * The parameters are always declared explicitly even if they&apos;re variants.
+&apos; * The Function and Sub declarations start at the 1st column of the line.
+&apos; * The End Function/Sub statement is followed by a comment reminding the name of the containing library.module and of the function or sub.
+&apos; If the Function/Sub is declared for the first time or modified in a release &gt; initial public release, the actual release number is mentioned as well.
+&apos;&apos;&apos;
+&apos; Variable declarations
+&apos; =====================
+&apos; * Variable names use only alpha characters, the underscore and digits (no accented characters).
+&apos; Exceptionally, names of private variables may be embraced with `[` and `]` if `Option Compatible` is present.
+&apos; * The Global, Dim and Const statements always start in the first column of the line.
+&apos; * The type (*Dim ... As ...*, *Function ... As ...*) is always declared explicitly, even if the type is Variant.
+&apos; * Variables are *Proper-Cased*. They are always preceded by a lower-case letter indicating their type.
+&apos; With next exception: variables i, j, k, l, m and n must be declared as integers or longs.
+&apos; &gt; b Boolean
+&apos; &gt; d Date
+&apos; &gt; v Variant
+&apos; &gt; o Object
+&apos; &gt; i Integer
+&apos; &gt; l Long
+&apos; &gt; s String
+&apos; Example:
+&apos; Dim sValue As String
+&apos; * Parameters are preceded by the letter *p* which itself precedes the single *typing letter*.
+&apos; In official methods, to match their published documentation, the *p* and the *typing letter* may be omitted. Like in:
+&apos; Private Function MyFunction(psValue As String) As Variant
+&apos; Public Function MyOfficialFunction(Value As String) As Variant
+&apos; * Global variables in the ScriptForge library are ALL preceded by an underscore &quot;_&quot; as NONE of them should be invoked from outside the library.
+&apos; * Constant values with a local scope are *Proper-Cased* and preceded by the letters *cst*.
+&apos; * Constants with a global scope are *UPPER-CASED*.
+&apos; Example:
+&apos; Global Const ACONSTANT = &quot;This is a global constant&quot;
+&apos; Function MyFunction(pocControl As Object, piValue) As Variant
+&apos; Dim iValue As Integer
+&apos; Const cstMyConstant = 3
+&apos;&apos;&apos;
+&apos; Indentation
+&apos; ===========
+&apos; Code shall be indented with TAB characters.
+&apos;&apos;&apos;
+&apos; Goto/Gosub
+&apos; ==========
+&apos; The *GoSub* … *Return* statement is forbidden.
+&apos; The *GoTo* statement is forbidden.
+&apos; However *GoTo* is highly recommended for *error* and *exception* handling.
+&apos;&apos;&apos;
+&apos; Comments (english only)
+&apos; ========
+&apos; * Every public routine should be documented with a python-like &quot;docstring&quot;:
+&apos; 1. Role of Sub/Function
+&apos; 2. List of arguments, mandatory/optional, role
+&apos; 3. Returned value(s) type and meaning
+&apos; 4. Examples when useful
+&apos; 5. Eventual specific exception codes
+&apos; * The &quot;docstring&quot; comments shall be marked by a triple (single) quote character at the beginning of the line
+&apos; * Meaningful variables shall be declared one per line. Comment on same line.
+&apos; * Comments about a code block should be left indented.
+&apos; If it concerns only the next line, no indent required (may also be put at the end of the line).
+&apos;&apos;&apos;
+</script:module> \ No newline at end of file
diff --git a/wizards/source/scriptforge/_ModuleModel.xba b/wizards/source/scriptforge/_ModuleModel.xba
new file mode 100644
index 000000000..135eced58
--- /dev/null
+++ b/wizards/source/scriptforge/_ModuleModel.xba
@@ -0,0 +1,221 @@
+<?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="_ModuleModel" 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 ClassModule
+&apos;Option Private Module
+
+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; ModuleModel (aka SF_Model)
+&apos;&apos;&apos; ===========
+&apos;&apos;&apos; Illustration of how the ScriptForge modules are structured
+&apos;&apos;&apos; Copy and paste this code in an empty Basic module to start a new service
+&apos;&apos;&apos; Comment in, comment out, erase what you want, but at the end respect the overall structure
+&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
+
+&apos;&apos;&apos; FAKENEWSERROR
+
+REM ============================================================= PRIVATE MEMBERS
+
+Private [Me] As Object &apos; Should be initialized immediately after the New statement
+ &apos; Dim obj As Object : Set obj = New SF_Model
+ &apos; Set obj.[Me] = obj
+Private [_Parent] As Object &apos; To keep trace of the instance having created a sub-instance
+ &apos; Set obj._Parent = [Me]
+Private ObjectType As String &apos; Must be UNIQUE
+
+REM ============================================================ MODULE CONSTANTS
+
+Private Const SOMECONSTANT = 1
+
+REM ====================================================== CONSTRUCTOR/DESTRUCTOR
+
+REM -----------------------------------------------------------------------------
+Private Sub Class_Initialize()
+ Set [Me] = Nothing
+ Set [_Parent] = Nothing
+ ObjectType = &quot;MODEL&quot;
+End Sub &apos; ScriptForge.SF_Model Constructor
+
+REM -----------------------------------------------------------------------------
+Private Sub Class_Terminate()
+ Call Class_Initialize()
+End Sub &apos; ScriptForge.SF_Model Destructor
+
+REM -----------------------------------------------------------------------------
+Public Function Dispose() As Variant
+ Call Class_Terminate()
+ Set Dispose = Nothing
+End Function &apos; ScriptForge.SF_Model Explicit Destructor
+
+REM ================================================================== PROPERTIES
+
+REM -----------------------------------------------------------------------------
+Property Get MyProperty() As Boolean
+&apos;&apos;&apos; Returns True or False
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; myModel.MyProperty
+
+ MyProperty = _PropertyGet(&quot;MyProperty&quot;)
+
+End Property &apos; ScriptForge.SF_Model.MyProperty
+
+REM ===================================================================== METHODS
+
+REM -----------------------------------------------------------------------------
+Public Function GetProperty(Optional ByVal PropertyName As Variant) As Variant
+&apos;&apos;&apos; Return the actual value of the given property
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; PropertyName: the name of the property as a string
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The actual value of the property
+&apos;&apos;&apos; If the property does not exist, returns Null
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; see the exceptions of the individual properties
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; myModel.GetProperty(&quot;MyProperty&quot;)
+
+Const cstThisSub = &quot;Model.GetProperty&quot;
+Const cstSubArgs = &quot;&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ GetProperty = Null
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
+ End If
+
+Try:
+ GetProperty = _PropertyGet(PropertyName)
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Model.GetProperty
+
+REM -----------------------------------------------------------------------------
+Public Function Methods() As Variant
+&apos;&apos;&apos; Return the list of public methods of the Model service as an array
+
+ Methods = Array( _
+ &quot;MyFunction&quot; _
+ , &quot;etc&quot; _
+ )
+
+End Function &apos; ScriptForge.SF_Model.Methods
+
+REM -----------------------------------------------------------------------------
+Public Function MyFunction(Optional ByVal Arg1 As Variant _
+ , Optional ByVal Arg2 As Variant _
+ ) As Variant
+&apos;&apos;&apos; Fictive function that concatenates Arg1 Arg2 times
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Arg1 String Text
+&apos;&apos;&apos; Arg2 Numeric Number of times (default = 2)
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The new string
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; FAKENEWSERROR
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; MyFunction(&quot;value1&quot;) returns &quot;value1value1&quot;
+
+Dim sOutput As String &apos; Output buffer
+Dim i As Integer
+Const cstThisSub = &quot;Model.myFunction&quot;
+Const cstSubArgs = &quot;Arg1, [Arg2=2]&quot;
+
+ &apos; _ErrorHandling returns False when, for debugging, the standard error handling is preferred
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ myFunction = &quot;&quot;
+
+Check:
+ If IsMissing(Arg2) Then Arg2 = 2
+ &apos; _EnterFunction returns True when current method is invoked from a user script
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ &apos; Check Arg1 is a string and Arg2 is a number.
+ &apos; Validation rules for scalars and arrays are described in SF_Utils
+ If Not SF_Utils._Validate(Arg1, &quot;Arg1&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(Arg2, &quot;Arg2&quot;, V_NUMERIC) Then GoTo Finally
+ &apos; Fatal error ?
+ If Arg2 &lt; 0 Then GoTo CatchFake
+ End If
+
+Try:
+ sOutput = &quot;&quot;
+ For i = 0 To Arg2
+ sOutput = sOutput &amp; Arg1
+ Next i
+ myFunction = sOutput
+
+Finally:
+ &apos; _ExitFunction manages internal (On Local) errors
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchFake:
+ SF_Exception.RaiseFatal(&quot;FAKENEWSERROR&quot;, cstThisSub)
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Model.myFunction
+
+REM -----------------------------------------------------------------------------
+Public Function Properties() As Variant
+&apos;&apos;&apos; Return the list or properties of the Model class as an array
+
+ Properties = Array( _
+ &quot;MyProperty&quot; _
+ , &quot;etc&quot; _
+ )
+
+End Function &apos; ScriptForge.SF_Model.Properties
+
+REM =========================================================== PRIVATE FUNCTIONS
+
+REM -----------------------------------------------------------------------------
+Private Function _PropertyGet(Optional ByVal psProperty As String) As Variant
+&apos;&apos;&apos; Return the value of the named property
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; psProperty: the name of the property
+
+Dim cstThisSub As String
+Const cstSubArgs = &quot;&quot;
+
+ cstThisSub = &quot;SF_Model.get&quot; &amp; psProperty
+ SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
+
+ Select Case psProperty
+ Case &quot;MyProperty&quot;
+ _PropertyGet = TBD
+ Case Else
+ _PropertyGet = Null
+ End Select
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+End Function &apos; ScriptForge.SF_Model._PropertyGet
+
+REM -----------------------------------------------------------------------------
+Private Function _Repr() As String
+&apos;&apos;&apos; Convert the Model instance to a readable string, typically for debugging purposes (DebugPrint ...)
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Return:
+&apos;&apos;&apos; &quot;[MODEL]: A readable string&quot;
+
+ _Repr = &quot;[MODEL]: A readable string&quot;
+
+End Function &apos; ScriptForge.SF_Model._Repr
+
+REM ============================================ END OF SCRIPTFORGE.SF_MODEL
+</script:module> \ No newline at end of file
diff --git a/wizards/source/scriptforge/__License.xba b/wizards/source/scriptforge/__License.xba
new file mode 100644
index 000000000..a81752525
--- /dev/null
+++ b/wizards/source/scriptforge/__License.xba
@@ -0,0 +1,25 @@
+<?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="__License" script:language="StarBasic" script:moduleType="normal">
+&apos;&apos;&apos; Copyright 2019-2022 Jean-Pierre LEDURE, Rafael LIMA, Alain ROMEDENNE
+
+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 =======================================================================================================================
+
+&apos;&apos;&apos; ScriptForge is distributed in the hope that it will be useful,
+&apos;&apos;&apos; but WITHOUT ANY WARRANTY; without even the implied warranty of
+&apos;&apos;&apos; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+&apos;&apos;&apos; ScriptForge is free software; you can redistribute it and/or modify it under the terms of either (at your option):
+
+&apos;&apos;&apos; 1) The Mozilla Public License, v. 2.0. If a copy of the MPL was not
+&apos;&apos;&apos; distributed with this file, you can obtain one at http://mozilla.org/MPL/2.0/ .
+
+&apos;&apos;&apos; 2) The GNU Lesser General Public License as published by
+&apos;&apos;&apos; the Free Software Foundation, either version 3 of the License, or
+&apos;&apos;&apos; (at your option) any later version. If a copy of the LGPL was not
+&apos;&apos;&apos; distributed with this file, see http://www.gnu.org/licenses/ .
+
+</script:module> \ No newline at end of file
diff --git a/wizards/source/scriptforge/dialog.xlb b/wizards/source/scriptforge/dialog.xlb
new file mode 100644
index 000000000..7b54d071c
--- /dev/null
+++ b/wizards/source/scriptforge/dialog.xlb
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE library:library PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "library.dtd">
+<library:library xmlns:library="http://openoffice.org/2000/library" library:name="ScriptForge" library:readonly="false" library:passwordprotected="false">
+ <library:element library:name="dlgConsole"/>
+ <library:element library:name="dlgProgress"/>
+</library:library> \ No newline at end of file
diff --git a/wizards/source/scriptforge/dlgConsole.xdl b/wizards/source/scriptforge/dlgConsole.xdl
new file mode 100644
index 000000000..64009f571
--- /dev/null
+++ b/wizards/source/scriptforge/dlgConsole.xdl
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE dlg:window PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "dialog.dtd">
+<dlg:window xmlns:dlg="http://openoffice.org/2000/dialog" xmlns:script="http://openoffice.org/2000/script" dlg:id="dlgConsole" dlg:left="114" dlg:top="32" dlg:width="321" dlg:height="239" dlg:closeable="true" dlg:moveable="true" dlg:title="ScriptForge">
+ <dlg:styles>
+ <dlg:style dlg:style-id="0" dlg:font-name="Courier New" dlg:font-stylename="Regular" dlg:font-family="modern"/>
+ </dlg:styles>
+ <dlg:bulletinboard>
+ <dlg:textfield dlg:style-id="0" dlg:id="ConsoleLines" dlg:tab-index="0" dlg:left="4" dlg:top="2" dlg:width="312" dlg:height="225" dlg:hscroll="true" dlg:vscroll="true" dlg:multiline="true" dlg:readonly="true"/>
+ <dlg:button dlg:id="CloseNonModalButton" dlg:tab-index="2" dlg:left="265" dlg:top="228" dlg:width="50" dlg:height="10" dlg:default="true" dlg:image-src="private:graphicrepository/cmd/sc_cancel.png">
+ <script:event script:event-name="on-performaction" script:macro-name="vnd.sun.star.script:ScriptForge.SF_Exception._CloseConsole?language=Basic&amp;location=application" script:language="Script"/>
+ </dlg:button>
+ <dlg:button dlg:id="CloseModalButton" dlg:tab-index="1" dlg:left="265" dlg:top="228" dlg:width="50" dlg:height="10" dlg:default="true" dlg:button-type="ok" dlg:image-src="private:graphicrepository/cmd/sc_cancel.png"/>
+ </dlg:bulletinboard>
+</dlg:window> \ No newline at end of file
diff --git a/wizards/source/scriptforge/dlgProgress.xdl b/wizards/source/scriptforge/dlgProgress.xdl
new file mode 100644
index 000000000..9d5f2776d
--- /dev/null
+++ b/wizards/source/scriptforge/dlgProgress.xdl
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE dlg:window PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "dialog.dtd">
+<dlg:window xmlns:dlg="http://openoffice.org/2000/dialog" xmlns:script="http://openoffice.org/2000/script" dlg:id="dlgProgress" dlg:left="180" dlg:top="90" dlg:width="275" dlg:height="37" dlg:closeable="true" dlg:moveable="true">
+ <dlg:bulletinboard>
+ <dlg:text dlg:id="ProgressText" dlg:tab-index="1" dlg:left="16" dlg:top="7" dlg:width="245" dlg:height="8" dlg:value="ProgressText" dlg:tabstop="true"/>
+ <dlg:progressmeter dlg:id="ProgressBar" dlg:tab-index="0" dlg:left="16" dlg:top="18" dlg:width="190" dlg:height="10" dlg:value="50"/>
+ <dlg:button dlg:id="CloseButton" dlg:tab-index="2" dlg:left="210" dlg:top="18" dlg:width="50" dlg:height="10" dlg:image-src="private:graphicrepository/cmd/sc_cancel.png">
+ <script:event script:event-name="on-performaction" script:macro-name="vnd.sun.star.script:ScriptForge.SF_UI._CloseProgressBar?language=Basic&amp;location=application" script:language="Script"/>
+ </dlg:button>
+ </dlg:bulletinboard>
+</dlg:window> \ No newline at end of file
diff --git a/wizards/source/scriptforge/po/ScriptForge.pot b/wizards/source/scriptforge/po/ScriptForge.pot
new file mode 100644
index 000000000..248d800c0
--- /dev/null
+++ b/wizards/source/scriptforge/po/ScriptForge.pot
@@ -0,0 +1,975 @@
+#
+# This pristine POT file has been generated by LibreOffice/ScriptForge
+# Full documentation is available on https://help.libreoffice.org/
+#
+# *********************************************************************
+# *** The ScriptForge library and its associated libraries ***
+# *** are part of the LibreOffice project. ***
+# *********************************************************************
+#
+# ScriptForge Release 7.4
+# -----------------------
+#
+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: 2022-05-04 18:07:20\n"
+"PO-Revision-Date: YYYY-MM-DD HH:MM:SS\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <EMAIL@ADDRESS>\n"
+"Language: en_US\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n > 1;\n"
+"X-Generator: LibreOffice - ScriptForge\n"
+"X-Accelerator-Marker: ~\n"
+
+#. Title in error message box
+#. %1: an error number
+#, kde-format
+msgctxt "ERRORNUMBER"
+msgid "Error %1"
+msgstr ""
+
+#. Error message box
+#. %1: a line number
+#, kde-format
+msgctxt "ERRORLOCATION"
+msgid "Location : %1"
+msgstr ""
+
+#. Logfile record
+#, kde-format
+msgctxt "LONGERRORDESC"
+msgid "Error %1 - Location = %2 - Description = %3"
+msgstr ""
+
+#. Any blocking error message
+msgctxt "STOPEXECUTION"
+msgid "THE EXECUTION IS CANCELLED."
+msgstr ""
+
+#. Any blocking error message
+#. %1: a method name
+#, kde-format
+msgctxt "NEEDMOREHELP"
+msgid "Do you want to receive more information about the '%1' method ?"
+msgstr ""
+
+#. SF_Exception.RaiseAbort error message
+msgctxt "INTERNALERROR"
+msgid ""
+"The ScriptForge library has crashed. The reason is unknown.\n"
+"Maybe a bug that could be reported on\n"
+" https://bugs.documentfoundation.org/\n"
+"\n"
+"More details : \n"
+"\n"
+""
+msgstr ""
+
+#. SF_Utils._Validate error message
+#. %1: probably ScriptForge
+#. %2: service or module name
+#. %3: property or method name where the error occurred
+#, kde-format
+msgctxt "VALIDATESOURCE"
+msgid ""
+"Library : %1\n"
+"Service : %2\n"
+"Method : %3"
+msgstr ""
+
+#. SF_Utils._Validate error message
+#. %1: list of arguments of the method
+#, kde-format
+msgctxt "VALIDATEARGS"
+msgid "Arguments: %1"
+msgstr ""
+
+#. SF_Utils._Validate error message
+#. %1: Wrong argument name
+#, kde-format
+msgctxt "VALIDATEERROR"
+msgid "A serious error has been detected in your code on argument : « %1 »."
+msgstr ""
+
+#. SF_Utils.Validate error message
+msgctxt "VALIDATIONRULES"
+msgid " Validation rules :"
+msgstr ""
+
+#. SF_Utils._Validate error message
+#. %1: Wrong argument name
+#. %2: Comma separated list of allowed types
+#, kde-format
+msgctxt "VALIDATETYPES"
+msgid " « %1 » must have next type (or one of next types) : %2"
+msgstr ""
+
+#. SF_Utils._Validate error message
+#. %1: Wrong argument name
+#. %2: Comma separated list of allowed values
+#, kde-format
+msgctxt "VALIDATEVALUES"
+msgid " « %1 » must contain one of next values : %2"
+msgstr ""
+
+#. SF_Utils._Validate error message
+#. %1: Wrong argument name
+#. %2: A regular expression
+#, kde-format
+msgctxt "VALIDATEREGEX"
+msgid " « %1 » must match next regular expression : %2"
+msgstr ""
+
+#. SF_Utils._Validate error message
+#. %1: Wrong argument name
+#. %2: The name of a Basic class
+#, kde-format
+msgctxt "VALIDATECLASS"
+msgid " « %1 » must be a Basic object of class : %2"
+msgstr ""
+
+#. SF_Utils._Validate error message
+#. %1: Wrong argument name
+#. %2: The value of the argument as a string
+#, kde-format
+msgctxt "VALIDATEACTUAL"
+msgid "The actual value of « %1 » is : '%2'"
+msgstr ""
+
+#. SF_Utils._Validate error message
+#. %1: Wrong argument name
+#, kde-format
+msgctxt "VALIDATEMISSING"
+msgid "The « %1 » argument is mandatory, yet it is missing."
+msgstr ""
+
+#. SF_Utils._ValidateArray error message
+#. %1: Wrong argument name
+#, kde-format
+msgctxt "VALIDATEARRAY"
+msgid " « %1 » must be an array."
+msgstr ""
+
+#. SF_Utils._ValidateArray error message
+#. %1: Wrong argument name
+#. %2: Number of dimensions of the array
+#, kde-format
+msgctxt "VALIDATEDIMS"
+msgid " « %1 » must have exactly %2 dimension(s)."
+msgstr ""
+
+#. SF_Utils._ValidateArray error message
+#. %1: Wrong argument name
+#. %2: Either one single type or 'String, Date, Numeric'
+#, kde-format
+msgctxt "VALIDATEALLTYPES"
+msgid " « %1 » must have all elements of the same type : %2"
+msgstr ""
+
+#. SF_Utils._ValidateArray error message
+#. %1: Wrong argument name
+#. NULL and EMPTY should not be translated
+#, kde-format
+msgctxt "VALIDATENOTNULL"
+msgid " « %1 » must not contain any NULL or EMPTY elements."
+msgstr ""
+
+#. SF_Utils._ValidateFile error message
+#. %1: Wrong argument name
+#. 'String' should not be translated
+#, kde-format
+msgctxt "VALIDATEFILE"
+msgid " « %1 » must be of type String."
+msgstr ""
+
+#. SF_Utils._ValidateFile error message
+#. %1: Wrong argument name
+#, kde-format
+msgctxt "VALIDATEFILESYS"
+msgid ""
+" « %1 » must be a valid file or folder name expressed in the "
+"operating system native notation."
+msgstr ""
+
+#. SF_Utils._ValidateFile error message
+#. %1: Wrong argument name
+#. 'URL' should not be translated
+#, kde-format
+msgctxt "VALIDATEFILEURL"
+msgid ""
+" « %1 » must be a valid file or folder name expressed in the "
+"portable URL notation."
+msgstr ""
+
+#. SF_Utils._ValidateFile error message
+#. %1: Wrong argument name
+#, kde-format
+msgctxt "VALIDATEFILEANY"
+msgid " « %1 » must be a valid file or folder name."
+msgstr ""
+
+#. SF_Utils._ValidateFile error message
+#. %1: Wrong argument name
+#. '(?, *)' is to be left as is
+#, kde-format
+msgctxt "VALIDATEWILDCARD"
+msgid ""
+" « %1 » may contain one or more wildcard characters (?, *) in "
+"its last path component only."
+msgstr ""
+
+#. SF_Array.RangeInit error message
+#. %1, %2, %3: Numeric values
+#. 'From', 'UpTo', 'ByStep' should not be translated
+#, kde-format
+msgctxt "ARRAYSEQUENCE"
+msgid ""
+"The respective values of 'From', 'UpTo' and 'ByStep' are incoherent.\n"
+"\n"
+" « From » = %1\n"
+" « UpTo » = %2\n"
+" « ByStep » = %3"
+msgstr ""
+
+#. SF_Array.AppendColumn (...) error message
+#. %1: 'Column' or 'Row' of a matrix
+#. %2, %3: array contents
+#. 'Array_2D' should not be translated
+#, kde-format
+msgctxt "ARRAYINSERT"
+msgid ""
+"The array and the vector to insert have incompatible sizes.\n"
+"\n"
+" « Array_2D » = %2\n"
+" « %1 » = %3"
+msgstr ""
+
+#. SF_Array.ExtractColumn (...) error message
+#. %1: 'Column' or 'Row' of a matrix
+#. %2, %3: array contents
+#. 'Array_2D' should not be translated
+#, kde-format
+msgctxt "ARRAYINDEX1"
+msgid ""
+"The given index does not fit within the bounds of the array.\n"
+"\n"
+" « Array_2D » = %2\n"
+" « %1 » = %3"
+msgstr ""
+
+#. SF_Array.ExtractColumn (...) error message
+#. %1: 'Column' or 'Row' of a matrix
+#. %2, %3: array contents
+#. '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_1D » = %1\n"
+" « From » = %2\n"
+" « UpTo » = %3"
+msgstr ""
+
+#. SF_Array.ImportFromCSVFile error message
+#. %1: a file name
+#. %2: numeric
+#. %3: a long string
+#, kde-format
+msgctxt "CSVPARSING"
+msgid ""
+"The given file could not be parsed as a valid CSV file.\n"
+"\n"
+" « File name » = %1\n"
+" Line number = %2\n"
+" Content = %3"
+msgstr ""
+
+#. SF_Dictionary Add/ReplaceKey error message
+#. %1: An identifier%2: a (potentially long) string
+#, kde-format
+msgctxt "DUPLICATEKEY"
+msgid ""
+"The insertion of a new key into a dictionary failed because the key "
+"already exists.\n"
+"Note that the comparison between keys is NOT case-sensitive.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+
+#. SF_Dictionary Remove/ReplaceKey/ReplaceItem error message
+#. %1: An identifier%2: a (potentially long) string
+#, kde-format
+msgctxt "UNKNOWNKEY"
+msgid ""
+"The requested key does not exist in the dictionary.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+
+#. SF_Dictionary Add/ReplaceKey error message
+#.
+msgctxt "INVALIDKEY"
+msgid ""
+"The insertion or the update of an entry into a dictionary failed "
+"because the given key contains only spaces."
+msgstr ""
+
+#. SF_FileSystem copy/move/delete error message
+#. %1: An identifier
+#. %2: A file name
+#, kde-format
+msgctxt "UNKNOWNFILE"
+msgid ""
+"The given file could not be found on your system.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+
+#. SF_FileSystem copy/move/delete error message
+#. %1: An identifier
+#. %2: A folder name
+#, kde-format
+msgctxt "UNKNOWNFOLDER"
+msgid ""
+"The given folder could not be found on your system.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+
+#. SF_FileSystem copy/move/delete error message
+#. %1: An identifier
+#. %2: A file name
+#, kde-format
+msgctxt "NOTAFILE"
+msgid ""
+"« %1 » contains the name of an existing folder, not that of a file.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+
+#. SF_FileSystem copy/move/delete error message
+#. %1: An identifier
+#. %2: A folder name
+#, kde-format
+msgctxt "NOTAFOLDER"
+msgid ""
+"« %1 » contains the name of an existing file, not that of a folder.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+
+#. SF_FileSystem copy/move/... error message
+#. %1: An identifier
+#. %2: A file name
+#, kde-format
+msgctxt "OVERWRITE"
+msgid ""
+"You tried to create a new file which already exists. Overwriting it "
+"has been rejected.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+
+#. SF_FileSystem copy/move/delete error message
+#. %1: An identifier
+#. %2: A file name
+#, kde-format
+msgctxt "READONLY"
+msgid ""
+"Copying or moving a file to a destination which has its read-only "
+"attribute set, or deleting such a file or folder is forbidden.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+
+#. SF_FileSystem copy/move/delete error message
+#. %1: An identifier
+#. %2: A file or folder name with wildcards
+#, kde-format
+msgctxt "NOFILEMATCH"
+msgid ""
+"When « %1 » contains wildcards. at least one file or folder must "
+"match the given filter. Otherwise the operation is rejected.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+
+#. SF_FileSystem CreateFolder error message
+#. %1: An identifier
+#. %2: A file or folder name
+#, kde-format
+msgctxt "FOLDERCREATION"
+msgid ""
+"« %1 » contains the name of an existing file or an existing folder. "
+"The operation is rejected.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+
+#. SF_Services.CreateScriptService error message
+#. %1: An identifier
+#. %2: A string
+#. %3: A Basic library name
+#. %4: A service (1 word) name
+#, kde-format
+msgctxt "UNKNOWNSERVICE"
+msgid ""
+"No service named '%4' has been registered for the library '%3'.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+
+#. SF_Services.CreateScriptService error message
+#. %1: An identifier
+#. %2: A string
+#. %3: A Basic library name
+#, kde-format
+msgctxt "SERVICESNOTLOADED"
+msgid ""
+"The library '%3' and its services could not been loaded.\n"
+"The reason is unknown.\n"
+"However, checking the '%3.SF_Services.RegisterScriptServices()' "
+"function and its return value can be a good starting point.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+
+#. SF_Session.ExecuteCalcFunction error message
+#. 'Calc' should not be translated
+#, kde-format
+msgctxt "CALCFUNC"
+msgid ""
+"The Calc '%1' function encountered an error. Either the given "
+"function does not exist or its arguments are invalid."
+msgstr ""
+
+#. SF_Session._GetScript error message
+#. %1: 'Basic' or 'Python'
+#. %2: An identifier
+#. %3: A string
+#. %4: An identifier
+#. %5: A string
+#, kde-format
+msgctxt "NOSCRIPT"
+msgid ""
+"The requested %1 script could not be located in the given libraries "
+"and modules.\n"
+"« %2 » = %3\n"
+"« %4 » = %5"
+msgstr ""
+
+#. SF_Session.ExecuteBasicScript error message
+#. %1: An identifier
+#. %2: A string
+#. %3: A (long) string
+#, kde-format
+msgctxt "SCRIPTEXEC"
+msgid ""
+"An exception occurred during the execution of the Basic script.\n"
+"Cause: %3\n"
+"« %1 » = %2"
+msgstr ""
+
+#. SF_Session.SendMail error message
+#. %1 = a mail address
+#, kde-format
+msgctxt "WRONGEMAIL"
+msgid ""
+"One of the email addresses has been found invalid.\n"
+"Invalid mail = « %1 »"
+msgstr ""
+
+#. SF_Session.SendMail error message
+msgctxt "SENDMAIL"
+msgid ""
+"The message could not be sent due to a system error.\n"
+"A possible cause is that LibreOffice could not find any mail client."
+msgstr ""
+
+#. SF_TextStream._IsFileOpen error message
+#. %1: A file name
+#, kde-format
+msgctxt "FILENOTOPEN"
+msgid ""
+"The requested file operation could not be executed because the file "
+"was closed previously.\n"
+"\n"
+"File name = '%1'"
+msgstr ""
+
+#. SF_TextStream._IsFileOpen error message
+#. %1: A file name
+#. %2: READ, WRITE or APPEND
+#, kde-format
+msgctxt "FILEOPENMODE"
+msgid ""
+"The requested file operation could not be executed because it is "
+"incompatible with the mode in which the file was opened.\n"
+"\n"
+"File name = '%1'\n"
+"Open mode = %2"
+msgstr ""
+
+#. SF_TextStream.ReadLine/ReadAll/SkipLine error message
+#. %1: A file name
+#, kde-format
+msgctxt "ENDOFFILE"
+msgid ""
+"The requested file read operation could not be completed because an "
+"unexpected end-of-file was encountered.\n"
+"\n"
+"File name = '%1'"
+msgstr ""
+
+#. SF_UI.GetDocument error message
+#. %1: An identifier
+#. %2: A string
+#, kde-format
+msgctxt "DOCUMENT"
+msgid ""
+"The requested document could not be found.\n"
+"\n"
+"%1 = '%2'"
+msgstr ""
+
+#. SF_UI.GetDocument error message
+#. %1: An identifier
+#. %2: A string
+#. %3: An identifier
+#. %4: A string
+#, kde-format
+msgctxt "DOCUMENTCREATION"
+msgid ""
+"The creation of a new document failed.\n"
+"Something must be wrong with some arguments.\n"
+"\n"
+"Either the document type is unknown, or no template file was given,\n"
+"or the given template file was not found on your system.\n"
+"\n"
+"%1 = '%2'\n"
+"%3 = '%4'"
+msgstr ""
+
+#. SF_UI.OpenDocument error message
+#. %1: An identifier
+#. %2: A string
+#. %3: An identifier
+#. %4: A string
+#. %5: An identifier
+#. %6: A string
+#, kde-format
+msgctxt "DOCUMENTOPEN"
+msgid ""
+"The opening of the document failed.\n"
+"Something must be wrong with some arguments.\n"
+"\n"
+"Either the file does not exist, or the password is wrong, or the "
+"given filter is invalid.\n"
+"\n"
+"%1 = '%2'\n"
+"%3 = '%4'\n"
+"%5 = '%6'"
+msgstr ""
+
+#. SF_UI.OpenDocument error message
+#. %1: An identifier
+#. %2: A string
+#. %3: An identifier
+#. %4: A string
+#, kde-format
+msgctxt "BASEDOCUMENTOPEN"
+msgid ""
+"The opening of the Base document failed.\n"
+"Something must be wrong with some arguments.\n"
+"\n"
+"Either the file does not exist, or the file is not registered under "
+"the given name.\n"
+"\n"
+"%1 = '%2'\n"
+"%3 = '%4'"
+msgstr ""
+
+#. SF_Document._IsStillAlive error message
+#. %1: A file name
+#, kde-format
+msgctxt "DOCUMENTDEAD"
+msgid ""
+"The requested action could not be executed because the document was "
+"closed inadvertently.\n"
+"\n"
+"The concerned document is '%1'"
+msgstr ""
+
+#. SF_Document.SaveAs error message
+#. %1: An identifier
+#. %2: A file name
+#.
+#, kde-format
+msgctxt "DOCUMENTSAVE"
+msgid ""
+"The document could not be saved.\n"
+"Either the document has been opened read-only, or the destination "
+"file has a read-only attribute set, or the file where to save to is "
+"undefined.\n"
+"\n"
+"%1 = '%2'"
+msgstr ""
+
+#. SF_Document.SaveAs error message
+#. %1: An identifier
+#. %2: A file name
+#. %3: An identifier
+#. %4: True or False
+#. %5: An identifier
+#. %6: A string
+#, kde-format
+msgctxt "DOCUMENTSAVEAS"
+msgid ""
+"The document could not be saved.\n"
+"Either the document must not be overwritten, or the destination file "
+"has a read-only attribute set, or the given filter is invalid.\n"
+"\n"
+"%1 = '%2'\n"
+"%3 = %4\n"
+"%5 = '%6'"
+msgstr ""
+
+#. SF_Document any update
+#. %1: An identifier
+#. %2: A file name
+#, kde-format
+msgctxt "DOCUMENTREADONLY"
+msgid ""
+"You tried to edit a document which is not modifiable. The document "
+"has not been changed.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+
+#. SF_Base GetDatabase
+#. %1: An identifier
+#. %2: A user name
+#. %3: An identifier
+#. %4: A password
+#. %5: A file name
+#, kde-format
+msgctxt "DBCONNECT"
+msgid ""
+"The database related to the actual Base document could not be "
+"retrieved.\n"
+"Check the connection/login parameters.\n"
+"\n"
+"« %1 » = '%2'\n"
+"« %3 » = '%4'\n"
+"« Document » = %5"
+msgstr ""
+
+#. SF_Calc _ParseAddress (sheet)
+#. %1: An identifier
+#. %2: A string
+#. %3: An identifier
+#. %4: A file name
+#, kde-format
+msgctxt "CALCADDRESS1"
+msgid ""
+"The given address does not correspond with a valid sheet name.\n"
+"\n"
+"« %1 » = %2\n"
+"« %3 » = %4"
+msgstr ""
+
+#. SF_Calc _ParseAddress (range)
+#. %1: An identifier
+#. %2: A string
+#. %3: An identifier
+#. %4: A file name
+#, kde-format
+msgctxt "CALCADDRESS2"
+msgid ""
+"The given address does not correspond with a valid range of cells.\n"
+"\n"
+"« %1 » = %2\n"
+"« %3 » = %4"
+msgstr ""
+
+#. SF_Calc InsertSheet
+#. %1: An identifier
+#. %2: A string
+#. %3: An identifier
+#. %4: A file name
+#, kde-format
+msgctxt "DUPLICATESHEET"
+msgid ""
+"There exists already in the document a sheet with the same name.\n"
+"\n"
+"« %1 » = %2\n"
+"« %3 » = %4"
+msgstr ""
+
+#. SF_Calc Offset
+#. %1: An identifier
+#. %2: A Calc reference
+#. %3: An identifier
+#. %4: A number
+#. %5: An identifier
+#. %6: A number
+#. %7: An identifier
+#. %8: A number
+#. %9: An identifier
+#. %10: A number
+#. %11: An identifier
+#. %12: A file name
+#, kde-format
+msgctxt "OFFSETADDRESS"
+msgid ""
+"The computed range falls beyond the sheet boundaries or is "
+"meaningless.\n"
+"\n"
+"« %1 » = %2\n"
+"« %3 » = %4\n"
+"« %5 » = %6\n"
+"« %7 » = %8\n"
+"« %9 » = %10\n"
+"« %11 » = %12"
+msgstr ""
+
+#. SF_Calc CreateChart
+#. %1: An identifier
+#. %2: A string
+#. %3: An identifier
+#. %4: A string
+#. %5: An identifier
+#. %6: A file name
+#, kde-format
+msgctxt "DUPLICATECHART"
+msgid ""
+"A chart with the same name exists already in the sheet.\n"
+"\n"
+"« %1 » = %2\n"
+"« %3 » = %4\n"
+"« %5 » = %6\n"
+""
+msgstr ""
+
+#. SF_Calc.ExportRangeToFile error message
+#. %1: An identifier
+#. %2: A file name
+#. %3: An identifier
+#. %4: True or False
+#.
+#, kde-format
+msgctxt "RANGEEXPORT"
+msgid ""
+"The given range could not be exported.\n"
+"Either the destination file must not be overwritten, or it has a "
+"read-only attribute set.\n"
+"\n"
+"%1 = '%2'\n"
+"%3 = %4"
+msgstr ""
+
+#. SF_Chart.ExportToFile error message
+#. %1: An identifier
+#. %2: A file name
+#. %3: An identifier
+#. %4: True or False
+#.
+#, kde-format
+msgctxt "CHARTEXPORT"
+msgid ""
+"The chart could not be exported.\n"
+"Either the destination file must not be overwritten, or it has a "
+"read-only attribute set.\n"
+"\n"
+"%1 = '%2'\n"
+"%3 = %4"
+msgstr ""
+
+#. SF_Dialog._IsStillAlive error message
+#. %1: An identifier%2: A file name
+#, kde-format
+msgctxt "FORMDEAD"
+msgid ""
+"The requested action could not be executed because the form is not "
+"open or the document was closed inadvertently.\n"
+"\n"
+"The concerned form is '%1' in document '%2'."
+msgstr ""
+
+#. SF_Form determination
+#. %1: A number
+#. %2: A sheet name
+#. %3: A file name
+#, kde-format
+msgctxt "CALCFORMNOTFOUND"
+msgid ""
+"The requested form could not be found in the Calc sheet. The given "
+"index is off-limits.\n"
+"\n"
+"The concerned Calc document is '%3'.\n"
+"\n"
+"The name of the sheet = '%2'\n"
+"The index = %1."
+msgstr ""
+
+#. SF_Form determination
+#. %1: A number
+#. %2: A file name
+#, kde-format
+msgctxt "WRITERFORMNOTFOUND"
+msgid ""
+"The requested form could not be found in the Writer document. The "
+"given index is off-limits.\n"
+"\n"
+"The concerned Writer document is '%2'.\n"
+"\n"
+"The index = %1."
+msgstr ""
+
+#. SF_Form determination
+#. %1: A number
+#. %2: A string
+#. %3: A file name
+#, kde-format
+msgctxt "BASEFORMNOTFOUND"
+msgid ""
+"The requested form could not be found in the form document '%2'. The "
+"given index is off-limits.\n"
+"\n"
+"The concerned Base document is '%3'.\n"
+"\n"
+"The index = %1."
+msgstr ""
+
+#. SF_Form determination
+#. %1: A form name
+#. %2: A form name
+#, kde-format
+msgctxt "SUBFORMNOTFOUND"
+msgid ""
+"The requested subform could not be found below the given main form.\n"
+"\n"
+"The main form = '%2'.\n"
+"The subform = '%1'."
+msgstr ""
+
+#. SF_FormControl property setting
+#. %1: An identifier
+#. %2: An identifier
+#. %3: A string
+#. %4: An identifier
+#, kde-format
+msgctxt "FORMCONTROLTYPE"
+msgid ""
+"The control '%1' in form '%2' is of type '%3'.\n"
+"The property or method '%4' is not applicable on that type of form "
+"controls."
+msgstr ""
+
+#. SF_Dialog creation
+#. %1: An identifier
+#. %2: A string
+#. %3: An identifier
+#. %4: A file name
+#. %5: An identifier
+#. %6: A string
+#. %7: An identifier
+#. %8: A string
+#, kde-format
+msgctxt "DIALOGNOTFOUND"
+msgid ""
+"The requested dialog could not be located in the given container or "
+"library.\n"
+"« %1 » = %2\n"
+"« %3 » = %4\n"
+"« %5 » = %6\n"
+"« %7 » = %8"
+msgstr ""
+
+#. SF_Dialog._IsStillAlive error message
+#. %1: An identifier
+#, kde-format
+msgctxt "DIALOGDEAD"
+msgid ""
+"The requested action could not be executed because the dialog was "
+"closed inadvertently.\n"
+"\n"
+"The concerned dialog is '%1'."
+msgstr ""
+
+#. SF_DialogControl property setting
+#. %1: An identifier
+#. %2: An identifier
+#. %3: A string
+#. %4: An identifier
+#, kde-format
+msgctxt "CONTROLTYPE"
+msgid ""
+"The control '%1' in dialog '%2' is of type '%3'.\n"
+"The property or method '%4' is not applicable on that type of dialog "
+"controls."
+msgstr ""
+
+#. SF_DialogControl add line in textbox
+#. %1: An identifier
+#. %2: An identifier
+#, kde-format
+msgctxt "TEXTFIELD"
+msgid ""
+"The control '%1' in dialog '%2' is not a multiline text field.\n"
+"The requested method could not be executed."
+msgstr ""
+
+#. SF_Database when running update SQL statement
+#. %1: The concerned method
+#, kde-format
+msgctxt "DBREADONLY"
+msgid ""
+"The database has been opened in read-only mode.\n"
+"The '%1' method must not be executed in this context."
+msgstr ""
+
+#. SF_Database can't interpret SQL statement
+#. %1: The statement
+#, kde-format
+msgctxt "SQLSYNTAX"
+msgid ""
+"An SQL statement could not be interpreted or executed by the "
+"database system.\n"
+"Check its syntax, table and/or field names, ...\n"
+"\n"
+"SQL Statement : « %1 »"
+msgstr ""
+
+#. SF_Exception.PythonShell error messageAPSO: to leave unchanged
+msgctxt "PYTHONSHELL"
+msgid ""
+"The APSO extension could not be located in your LibreOffice "
+"installation."
+msgstr ""
+
+#. SFUnitTest could not locate the library gven as argument
+#. %1: The name of the library
+#, kde-format
+msgctxt "UNITTESTLIBRARY"
+msgid ""
+"The requested library could not be located.\n"
+"The UnitTest service has not been initialized.\n"
+"\n"
+"Library name : « %1 »"
+msgstr ""
+
+#. SFUnitTest finds a RunTest() call in a inappropriate location
+#. %1: The name of a method
+#, kde-format
+msgctxt "UNITTESTMETHOD"
+msgid ""
+"The method '%1' is unexpected in the current context.\n"
+"The UnitTest service cannot proceed further with the on-going test."
+msgstr "" \ No newline at end of file
diff --git a/wizards/source/scriptforge/po/en.po b/wizards/source/scriptforge/po/en.po
new file mode 100644
index 000000000..248d800c0
--- /dev/null
+++ b/wizards/source/scriptforge/po/en.po
@@ -0,0 +1,975 @@
+#
+# This pristine POT file has been generated by LibreOffice/ScriptForge
+# Full documentation is available on https://help.libreoffice.org/
+#
+# *********************************************************************
+# *** The ScriptForge library and its associated libraries ***
+# *** are part of the LibreOffice project. ***
+# *********************************************************************
+#
+# ScriptForge Release 7.4
+# -----------------------
+#
+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: 2022-05-04 18:07:20\n"
+"PO-Revision-Date: YYYY-MM-DD HH:MM:SS\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <EMAIL@ADDRESS>\n"
+"Language: en_US\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n > 1;\n"
+"X-Generator: LibreOffice - ScriptForge\n"
+"X-Accelerator-Marker: ~\n"
+
+#. Title in error message box
+#. %1: an error number
+#, kde-format
+msgctxt "ERRORNUMBER"
+msgid "Error %1"
+msgstr ""
+
+#. Error message box
+#. %1: a line number
+#, kde-format
+msgctxt "ERRORLOCATION"
+msgid "Location : %1"
+msgstr ""
+
+#. Logfile record
+#, kde-format
+msgctxt "LONGERRORDESC"
+msgid "Error %1 - Location = %2 - Description = %3"
+msgstr ""
+
+#. Any blocking error message
+msgctxt "STOPEXECUTION"
+msgid "THE EXECUTION IS CANCELLED."
+msgstr ""
+
+#. Any blocking error message
+#. %1: a method name
+#, kde-format
+msgctxt "NEEDMOREHELP"
+msgid "Do you want to receive more information about the '%1' method ?"
+msgstr ""
+
+#. SF_Exception.RaiseAbort error message
+msgctxt "INTERNALERROR"
+msgid ""
+"The ScriptForge library has crashed. The reason is unknown.\n"
+"Maybe a bug that could be reported on\n"
+" https://bugs.documentfoundation.org/\n"
+"\n"
+"More details : \n"
+"\n"
+""
+msgstr ""
+
+#. SF_Utils._Validate error message
+#. %1: probably ScriptForge
+#. %2: service or module name
+#. %3: property or method name where the error occurred
+#, kde-format
+msgctxt "VALIDATESOURCE"
+msgid ""
+"Library : %1\n"
+"Service : %2\n"
+"Method : %3"
+msgstr ""
+
+#. SF_Utils._Validate error message
+#. %1: list of arguments of the method
+#, kde-format
+msgctxt "VALIDATEARGS"
+msgid "Arguments: %1"
+msgstr ""
+
+#. SF_Utils._Validate error message
+#. %1: Wrong argument name
+#, kde-format
+msgctxt "VALIDATEERROR"
+msgid "A serious error has been detected in your code on argument : « %1 »."
+msgstr ""
+
+#. SF_Utils.Validate error message
+msgctxt "VALIDATIONRULES"
+msgid " Validation rules :"
+msgstr ""
+
+#. SF_Utils._Validate error message
+#. %1: Wrong argument name
+#. %2: Comma separated list of allowed types
+#, kde-format
+msgctxt "VALIDATETYPES"
+msgid " « %1 » must have next type (or one of next types) : %2"
+msgstr ""
+
+#. SF_Utils._Validate error message
+#. %1: Wrong argument name
+#. %2: Comma separated list of allowed values
+#, kde-format
+msgctxt "VALIDATEVALUES"
+msgid " « %1 » must contain one of next values : %2"
+msgstr ""
+
+#. SF_Utils._Validate error message
+#. %1: Wrong argument name
+#. %2: A regular expression
+#, kde-format
+msgctxt "VALIDATEREGEX"
+msgid " « %1 » must match next regular expression : %2"
+msgstr ""
+
+#. SF_Utils._Validate error message
+#. %1: Wrong argument name
+#. %2: The name of a Basic class
+#, kde-format
+msgctxt "VALIDATECLASS"
+msgid " « %1 » must be a Basic object of class : %2"
+msgstr ""
+
+#. SF_Utils._Validate error message
+#. %1: Wrong argument name
+#. %2: The value of the argument as a string
+#, kde-format
+msgctxt "VALIDATEACTUAL"
+msgid "The actual value of « %1 » is : '%2'"
+msgstr ""
+
+#. SF_Utils._Validate error message
+#. %1: Wrong argument name
+#, kde-format
+msgctxt "VALIDATEMISSING"
+msgid "The « %1 » argument is mandatory, yet it is missing."
+msgstr ""
+
+#. SF_Utils._ValidateArray error message
+#. %1: Wrong argument name
+#, kde-format
+msgctxt "VALIDATEARRAY"
+msgid " « %1 » must be an array."
+msgstr ""
+
+#. SF_Utils._ValidateArray error message
+#. %1: Wrong argument name
+#. %2: Number of dimensions of the array
+#, kde-format
+msgctxt "VALIDATEDIMS"
+msgid " « %1 » must have exactly %2 dimension(s)."
+msgstr ""
+
+#. SF_Utils._ValidateArray error message
+#. %1: Wrong argument name
+#. %2: Either one single type or 'String, Date, Numeric'
+#, kde-format
+msgctxt "VALIDATEALLTYPES"
+msgid " « %1 » must have all elements of the same type : %2"
+msgstr ""
+
+#. SF_Utils._ValidateArray error message
+#. %1: Wrong argument name
+#. NULL and EMPTY should not be translated
+#, kde-format
+msgctxt "VALIDATENOTNULL"
+msgid " « %1 » must not contain any NULL or EMPTY elements."
+msgstr ""
+
+#. SF_Utils._ValidateFile error message
+#. %1: Wrong argument name
+#. 'String' should not be translated
+#, kde-format
+msgctxt "VALIDATEFILE"
+msgid " « %1 » must be of type String."
+msgstr ""
+
+#. SF_Utils._ValidateFile error message
+#. %1: Wrong argument name
+#, kde-format
+msgctxt "VALIDATEFILESYS"
+msgid ""
+" « %1 » must be a valid file or folder name expressed in the "
+"operating system native notation."
+msgstr ""
+
+#. SF_Utils._ValidateFile error message
+#. %1: Wrong argument name
+#. 'URL' should not be translated
+#, kde-format
+msgctxt "VALIDATEFILEURL"
+msgid ""
+" « %1 » must be a valid file or folder name expressed in the "
+"portable URL notation."
+msgstr ""
+
+#. SF_Utils._ValidateFile error message
+#. %1: Wrong argument name
+#, kde-format
+msgctxt "VALIDATEFILEANY"
+msgid " « %1 » must be a valid file or folder name."
+msgstr ""
+
+#. SF_Utils._ValidateFile error message
+#. %1: Wrong argument name
+#. '(?, *)' is to be left as is
+#, kde-format
+msgctxt "VALIDATEWILDCARD"
+msgid ""
+" « %1 » may contain one or more wildcard characters (?, *) in "
+"its last path component only."
+msgstr ""
+
+#. SF_Array.RangeInit error message
+#. %1, %2, %3: Numeric values
+#. 'From', 'UpTo', 'ByStep' should not be translated
+#, kde-format
+msgctxt "ARRAYSEQUENCE"
+msgid ""
+"The respective values of 'From', 'UpTo' and 'ByStep' are incoherent.\n"
+"\n"
+" « From » = %1\n"
+" « UpTo » = %2\n"
+" « ByStep » = %3"
+msgstr ""
+
+#. SF_Array.AppendColumn (...) error message
+#. %1: 'Column' or 'Row' of a matrix
+#. %2, %3: array contents
+#. 'Array_2D' should not be translated
+#, kde-format
+msgctxt "ARRAYINSERT"
+msgid ""
+"The array and the vector to insert have incompatible sizes.\n"
+"\n"
+" « Array_2D » = %2\n"
+" « %1 » = %3"
+msgstr ""
+
+#. SF_Array.ExtractColumn (...) error message
+#. %1: 'Column' or 'Row' of a matrix
+#. %2, %3: array contents
+#. 'Array_2D' should not be translated
+#, kde-format
+msgctxt "ARRAYINDEX1"
+msgid ""
+"The given index does not fit within the bounds of the array.\n"
+"\n"
+" « Array_2D » = %2\n"
+" « %1 » = %3"
+msgstr ""
+
+#. SF_Array.ExtractColumn (...) error message
+#. %1: 'Column' or 'Row' of a matrix
+#. %2, %3: array contents
+#. '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_1D » = %1\n"
+" « From » = %2\n"
+" « UpTo » = %3"
+msgstr ""
+
+#. SF_Array.ImportFromCSVFile error message
+#. %1: a file name
+#. %2: numeric
+#. %3: a long string
+#, kde-format
+msgctxt "CSVPARSING"
+msgid ""
+"The given file could not be parsed as a valid CSV file.\n"
+"\n"
+" « File name » = %1\n"
+" Line number = %2\n"
+" Content = %3"
+msgstr ""
+
+#. SF_Dictionary Add/ReplaceKey error message
+#. %1: An identifier%2: a (potentially long) string
+#, kde-format
+msgctxt "DUPLICATEKEY"
+msgid ""
+"The insertion of a new key into a dictionary failed because the key "
+"already exists.\n"
+"Note that the comparison between keys is NOT case-sensitive.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+
+#. SF_Dictionary Remove/ReplaceKey/ReplaceItem error message
+#. %1: An identifier%2: a (potentially long) string
+#, kde-format
+msgctxt "UNKNOWNKEY"
+msgid ""
+"The requested key does not exist in the dictionary.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+
+#. SF_Dictionary Add/ReplaceKey error message
+#.
+msgctxt "INVALIDKEY"
+msgid ""
+"The insertion or the update of an entry into a dictionary failed "
+"because the given key contains only spaces."
+msgstr ""
+
+#. SF_FileSystem copy/move/delete error message
+#. %1: An identifier
+#. %2: A file name
+#, kde-format
+msgctxt "UNKNOWNFILE"
+msgid ""
+"The given file could not be found on your system.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+
+#. SF_FileSystem copy/move/delete error message
+#. %1: An identifier
+#. %2: A folder name
+#, kde-format
+msgctxt "UNKNOWNFOLDER"
+msgid ""
+"The given folder could not be found on your system.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+
+#. SF_FileSystem copy/move/delete error message
+#. %1: An identifier
+#. %2: A file name
+#, kde-format
+msgctxt "NOTAFILE"
+msgid ""
+"« %1 » contains the name of an existing folder, not that of a file.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+
+#. SF_FileSystem copy/move/delete error message
+#. %1: An identifier
+#. %2: A folder name
+#, kde-format
+msgctxt "NOTAFOLDER"
+msgid ""
+"« %1 » contains the name of an existing file, not that of a folder.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+
+#. SF_FileSystem copy/move/... error message
+#. %1: An identifier
+#. %2: A file name
+#, kde-format
+msgctxt "OVERWRITE"
+msgid ""
+"You tried to create a new file which already exists. Overwriting it "
+"has been rejected.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+
+#. SF_FileSystem copy/move/delete error message
+#. %1: An identifier
+#. %2: A file name
+#, kde-format
+msgctxt "READONLY"
+msgid ""
+"Copying or moving a file to a destination which has its read-only "
+"attribute set, or deleting such a file or folder is forbidden.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+
+#. SF_FileSystem copy/move/delete error message
+#. %1: An identifier
+#. %2: A file or folder name with wildcards
+#, kde-format
+msgctxt "NOFILEMATCH"
+msgid ""
+"When « %1 » contains wildcards. at least one file or folder must "
+"match the given filter. Otherwise the operation is rejected.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+
+#. SF_FileSystem CreateFolder error message
+#. %1: An identifier
+#. %2: A file or folder name
+#, kde-format
+msgctxt "FOLDERCREATION"
+msgid ""
+"« %1 » contains the name of an existing file or an existing folder. "
+"The operation is rejected.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+
+#. SF_Services.CreateScriptService error message
+#. %1: An identifier
+#. %2: A string
+#. %3: A Basic library name
+#. %4: A service (1 word) name
+#, kde-format
+msgctxt "UNKNOWNSERVICE"
+msgid ""
+"No service named '%4' has been registered for the library '%3'.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+
+#. SF_Services.CreateScriptService error message
+#. %1: An identifier
+#. %2: A string
+#. %3: A Basic library name
+#, kde-format
+msgctxt "SERVICESNOTLOADED"
+msgid ""
+"The library '%3' and its services could not been loaded.\n"
+"The reason is unknown.\n"
+"However, checking the '%3.SF_Services.RegisterScriptServices()' "
+"function and its return value can be a good starting point.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+
+#. SF_Session.ExecuteCalcFunction error message
+#. 'Calc' should not be translated
+#, kde-format
+msgctxt "CALCFUNC"
+msgid ""
+"The Calc '%1' function encountered an error. Either the given "
+"function does not exist or its arguments are invalid."
+msgstr ""
+
+#. SF_Session._GetScript error message
+#. %1: 'Basic' or 'Python'
+#. %2: An identifier
+#. %3: A string
+#. %4: An identifier
+#. %5: A string
+#, kde-format
+msgctxt "NOSCRIPT"
+msgid ""
+"The requested %1 script could not be located in the given libraries "
+"and modules.\n"
+"« %2 » = %3\n"
+"« %4 » = %5"
+msgstr ""
+
+#. SF_Session.ExecuteBasicScript error message
+#. %1: An identifier
+#. %2: A string
+#. %3: A (long) string
+#, kde-format
+msgctxt "SCRIPTEXEC"
+msgid ""
+"An exception occurred during the execution of the Basic script.\n"
+"Cause: %3\n"
+"« %1 » = %2"
+msgstr ""
+
+#. SF_Session.SendMail error message
+#. %1 = a mail address
+#, kde-format
+msgctxt "WRONGEMAIL"
+msgid ""
+"One of the email addresses has been found invalid.\n"
+"Invalid mail = « %1 »"
+msgstr ""
+
+#. SF_Session.SendMail error message
+msgctxt "SENDMAIL"
+msgid ""
+"The message could not be sent due to a system error.\n"
+"A possible cause is that LibreOffice could not find any mail client."
+msgstr ""
+
+#. SF_TextStream._IsFileOpen error message
+#. %1: A file name
+#, kde-format
+msgctxt "FILENOTOPEN"
+msgid ""
+"The requested file operation could not be executed because the file "
+"was closed previously.\n"
+"\n"
+"File name = '%1'"
+msgstr ""
+
+#. SF_TextStream._IsFileOpen error message
+#. %1: A file name
+#. %2: READ, WRITE or APPEND
+#, kde-format
+msgctxt "FILEOPENMODE"
+msgid ""
+"The requested file operation could not be executed because it is "
+"incompatible with the mode in which the file was opened.\n"
+"\n"
+"File name = '%1'\n"
+"Open mode = %2"
+msgstr ""
+
+#. SF_TextStream.ReadLine/ReadAll/SkipLine error message
+#. %1: A file name
+#, kde-format
+msgctxt "ENDOFFILE"
+msgid ""
+"The requested file read operation could not be completed because an "
+"unexpected end-of-file was encountered.\n"
+"\n"
+"File name = '%1'"
+msgstr ""
+
+#. SF_UI.GetDocument error message
+#. %1: An identifier
+#. %2: A string
+#, kde-format
+msgctxt "DOCUMENT"
+msgid ""
+"The requested document could not be found.\n"
+"\n"
+"%1 = '%2'"
+msgstr ""
+
+#. SF_UI.GetDocument error message
+#. %1: An identifier
+#. %2: A string
+#. %3: An identifier
+#. %4: A string
+#, kde-format
+msgctxt "DOCUMENTCREATION"
+msgid ""
+"The creation of a new document failed.\n"
+"Something must be wrong with some arguments.\n"
+"\n"
+"Either the document type is unknown, or no template file was given,\n"
+"or the given template file was not found on your system.\n"
+"\n"
+"%1 = '%2'\n"
+"%3 = '%4'"
+msgstr ""
+
+#. SF_UI.OpenDocument error message
+#. %1: An identifier
+#. %2: A string
+#. %3: An identifier
+#. %4: A string
+#. %5: An identifier
+#. %6: A string
+#, kde-format
+msgctxt "DOCUMENTOPEN"
+msgid ""
+"The opening of the document failed.\n"
+"Something must be wrong with some arguments.\n"
+"\n"
+"Either the file does not exist, or the password is wrong, or the "
+"given filter is invalid.\n"
+"\n"
+"%1 = '%2'\n"
+"%3 = '%4'\n"
+"%5 = '%6'"
+msgstr ""
+
+#. SF_UI.OpenDocument error message
+#. %1: An identifier
+#. %2: A string
+#. %3: An identifier
+#. %4: A string
+#, kde-format
+msgctxt "BASEDOCUMENTOPEN"
+msgid ""
+"The opening of the Base document failed.\n"
+"Something must be wrong with some arguments.\n"
+"\n"
+"Either the file does not exist, or the file is not registered under "
+"the given name.\n"
+"\n"
+"%1 = '%2'\n"
+"%3 = '%4'"
+msgstr ""
+
+#. SF_Document._IsStillAlive error message
+#. %1: A file name
+#, kde-format
+msgctxt "DOCUMENTDEAD"
+msgid ""
+"The requested action could not be executed because the document was "
+"closed inadvertently.\n"
+"\n"
+"The concerned document is '%1'"
+msgstr ""
+
+#. SF_Document.SaveAs error message
+#. %1: An identifier
+#. %2: A file name
+#.
+#, kde-format
+msgctxt "DOCUMENTSAVE"
+msgid ""
+"The document could not be saved.\n"
+"Either the document has been opened read-only, or the destination "
+"file has a read-only attribute set, or the file where to save to is "
+"undefined.\n"
+"\n"
+"%1 = '%2'"
+msgstr ""
+
+#. SF_Document.SaveAs error message
+#. %1: An identifier
+#. %2: A file name
+#. %3: An identifier
+#. %4: True or False
+#. %5: An identifier
+#. %6: A string
+#, kde-format
+msgctxt "DOCUMENTSAVEAS"
+msgid ""
+"The document could not be saved.\n"
+"Either the document must not be overwritten, or the destination file "
+"has a read-only attribute set, or the given filter is invalid.\n"
+"\n"
+"%1 = '%2'\n"
+"%3 = %4\n"
+"%5 = '%6'"
+msgstr ""
+
+#. SF_Document any update
+#. %1: An identifier
+#. %2: A file name
+#, kde-format
+msgctxt "DOCUMENTREADONLY"
+msgid ""
+"You tried to edit a document which is not modifiable. The document "
+"has not been changed.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+
+#. SF_Base GetDatabase
+#. %1: An identifier
+#. %2: A user name
+#. %3: An identifier
+#. %4: A password
+#. %5: A file name
+#, kde-format
+msgctxt "DBCONNECT"
+msgid ""
+"The database related to the actual Base document could not be "
+"retrieved.\n"
+"Check the connection/login parameters.\n"
+"\n"
+"« %1 » = '%2'\n"
+"« %3 » = '%4'\n"
+"« Document » = %5"
+msgstr ""
+
+#. SF_Calc _ParseAddress (sheet)
+#. %1: An identifier
+#. %2: A string
+#. %3: An identifier
+#. %4: A file name
+#, kde-format
+msgctxt "CALCADDRESS1"
+msgid ""
+"The given address does not correspond with a valid sheet name.\n"
+"\n"
+"« %1 » = %2\n"
+"« %3 » = %4"
+msgstr ""
+
+#. SF_Calc _ParseAddress (range)
+#. %1: An identifier
+#. %2: A string
+#. %3: An identifier
+#. %4: A file name
+#, kde-format
+msgctxt "CALCADDRESS2"
+msgid ""
+"The given address does not correspond with a valid range of cells.\n"
+"\n"
+"« %1 » = %2\n"
+"« %3 » = %4"
+msgstr ""
+
+#. SF_Calc InsertSheet
+#. %1: An identifier
+#. %2: A string
+#. %3: An identifier
+#. %4: A file name
+#, kde-format
+msgctxt "DUPLICATESHEET"
+msgid ""
+"There exists already in the document a sheet with the same name.\n"
+"\n"
+"« %1 » = %2\n"
+"« %3 » = %4"
+msgstr ""
+
+#. SF_Calc Offset
+#. %1: An identifier
+#. %2: A Calc reference
+#. %3: An identifier
+#. %4: A number
+#. %5: An identifier
+#. %6: A number
+#. %7: An identifier
+#. %8: A number
+#. %9: An identifier
+#. %10: A number
+#. %11: An identifier
+#. %12: A file name
+#, kde-format
+msgctxt "OFFSETADDRESS"
+msgid ""
+"The computed range falls beyond the sheet boundaries or is "
+"meaningless.\n"
+"\n"
+"« %1 » = %2\n"
+"« %3 » = %4\n"
+"« %5 » = %6\n"
+"« %7 » = %8\n"
+"« %9 » = %10\n"
+"« %11 » = %12"
+msgstr ""
+
+#. SF_Calc CreateChart
+#. %1: An identifier
+#. %2: A string
+#. %3: An identifier
+#. %4: A string
+#. %5: An identifier
+#. %6: A file name
+#, kde-format
+msgctxt "DUPLICATECHART"
+msgid ""
+"A chart with the same name exists already in the sheet.\n"
+"\n"
+"« %1 » = %2\n"
+"« %3 » = %4\n"
+"« %5 » = %6\n"
+""
+msgstr ""
+
+#. SF_Calc.ExportRangeToFile error message
+#. %1: An identifier
+#. %2: A file name
+#. %3: An identifier
+#. %4: True or False
+#.
+#, kde-format
+msgctxt "RANGEEXPORT"
+msgid ""
+"The given range could not be exported.\n"
+"Either the destination file must not be overwritten, or it has a "
+"read-only attribute set.\n"
+"\n"
+"%1 = '%2'\n"
+"%3 = %4"
+msgstr ""
+
+#. SF_Chart.ExportToFile error message
+#. %1: An identifier
+#. %2: A file name
+#. %3: An identifier
+#. %4: True or False
+#.
+#, kde-format
+msgctxt "CHARTEXPORT"
+msgid ""
+"The chart could not be exported.\n"
+"Either the destination file must not be overwritten, or it has a "
+"read-only attribute set.\n"
+"\n"
+"%1 = '%2'\n"
+"%3 = %4"
+msgstr ""
+
+#. SF_Dialog._IsStillAlive error message
+#. %1: An identifier%2: A file name
+#, kde-format
+msgctxt "FORMDEAD"
+msgid ""
+"The requested action could not be executed because the form is not "
+"open or the document was closed inadvertently.\n"
+"\n"
+"The concerned form is '%1' in document '%2'."
+msgstr ""
+
+#. SF_Form determination
+#. %1: A number
+#. %2: A sheet name
+#. %3: A file name
+#, kde-format
+msgctxt "CALCFORMNOTFOUND"
+msgid ""
+"The requested form could not be found in the Calc sheet. The given "
+"index is off-limits.\n"
+"\n"
+"The concerned Calc document is '%3'.\n"
+"\n"
+"The name of the sheet = '%2'\n"
+"The index = %1."
+msgstr ""
+
+#. SF_Form determination
+#. %1: A number
+#. %2: A file name
+#, kde-format
+msgctxt "WRITERFORMNOTFOUND"
+msgid ""
+"The requested form could not be found in the Writer document. The "
+"given index is off-limits.\n"
+"\n"
+"The concerned Writer document is '%2'.\n"
+"\n"
+"The index = %1."
+msgstr ""
+
+#. SF_Form determination
+#. %1: A number
+#. %2: A string
+#. %3: A file name
+#, kde-format
+msgctxt "BASEFORMNOTFOUND"
+msgid ""
+"The requested form could not be found in the form document '%2'. The "
+"given index is off-limits.\n"
+"\n"
+"The concerned Base document is '%3'.\n"
+"\n"
+"The index = %1."
+msgstr ""
+
+#. SF_Form determination
+#. %1: A form name
+#. %2: A form name
+#, kde-format
+msgctxt "SUBFORMNOTFOUND"
+msgid ""
+"The requested subform could not be found below the given main form.\n"
+"\n"
+"The main form = '%2'.\n"
+"The subform = '%1'."
+msgstr ""
+
+#. SF_FormControl property setting
+#. %1: An identifier
+#. %2: An identifier
+#. %3: A string
+#. %4: An identifier
+#, kde-format
+msgctxt "FORMCONTROLTYPE"
+msgid ""
+"The control '%1' in form '%2' is of type '%3'.\n"
+"The property or method '%4' is not applicable on that type of form "
+"controls."
+msgstr ""
+
+#. SF_Dialog creation
+#. %1: An identifier
+#. %2: A string
+#. %3: An identifier
+#. %4: A file name
+#. %5: An identifier
+#. %6: A string
+#. %7: An identifier
+#. %8: A string
+#, kde-format
+msgctxt "DIALOGNOTFOUND"
+msgid ""
+"The requested dialog could not be located in the given container or "
+"library.\n"
+"« %1 » = %2\n"
+"« %3 » = %4\n"
+"« %5 » = %6\n"
+"« %7 » = %8"
+msgstr ""
+
+#. SF_Dialog._IsStillAlive error message
+#. %1: An identifier
+#, kde-format
+msgctxt "DIALOGDEAD"
+msgid ""
+"The requested action could not be executed because the dialog was "
+"closed inadvertently.\n"
+"\n"
+"The concerned dialog is '%1'."
+msgstr ""
+
+#. SF_DialogControl property setting
+#. %1: An identifier
+#. %2: An identifier
+#. %3: A string
+#. %4: An identifier
+#, kde-format
+msgctxt "CONTROLTYPE"
+msgid ""
+"The control '%1' in dialog '%2' is of type '%3'.\n"
+"The property or method '%4' is not applicable on that type of dialog "
+"controls."
+msgstr ""
+
+#. SF_DialogControl add line in textbox
+#. %1: An identifier
+#. %2: An identifier
+#, kde-format
+msgctxt "TEXTFIELD"
+msgid ""
+"The control '%1' in dialog '%2' is not a multiline text field.\n"
+"The requested method could not be executed."
+msgstr ""
+
+#. SF_Database when running update SQL statement
+#. %1: The concerned method
+#, kde-format
+msgctxt "DBREADONLY"
+msgid ""
+"The database has been opened in read-only mode.\n"
+"The '%1' method must not be executed in this context."
+msgstr ""
+
+#. SF_Database can't interpret SQL statement
+#. %1: The statement
+#, kde-format
+msgctxt "SQLSYNTAX"
+msgid ""
+"An SQL statement could not be interpreted or executed by the "
+"database system.\n"
+"Check its syntax, table and/or field names, ...\n"
+"\n"
+"SQL Statement : « %1 »"
+msgstr ""
+
+#. SF_Exception.PythonShell error messageAPSO: to leave unchanged
+msgctxt "PYTHONSHELL"
+msgid ""
+"The APSO extension could not be located in your LibreOffice "
+"installation."
+msgstr ""
+
+#. SFUnitTest could not locate the library gven as argument
+#. %1: The name of the library
+#, kde-format
+msgctxt "UNITTESTLIBRARY"
+msgid ""
+"The requested library could not be located.\n"
+"The UnitTest service has not been initialized.\n"
+"\n"
+"Library name : « %1 »"
+msgstr ""
+
+#. SFUnitTest finds a RunTest() call in a inappropriate location
+#. %1: The name of a method
+#, kde-format
+msgctxt "UNITTESTMETHOD"
+msgid ""
+"The method '%1' is unexpected in the current context.\n"
+"The UnitTest service cannot proceed further with the on-going test."
+msgstr "" \ No newline at end of file
diff --git a/wizards/source/scriptforge/po/pt.po b/wizards/source/scriptforge/po/pt.po
new file mode 100644
index 000000000..a40aafd4c
--- /dev/null
+++ b/wizards/source/scriptforge/po/pt.po
@@ -0,0 +1,1141 @@
+#
+# This pristine POT file has been generated by LibreOffice/ScriptForge
+# Full documentation is available on https://help.libreoffice.org/
+#
+# *********************************************************************
+# *** The ScriptForge library and its associated libraries ***
+# *** are part of the LibreOffice project. ***
+# *********************************************************************
+#
+# ScriptForge Release 7.3
+# -----------------------
+#
+msgid ""
+msgstr ""
+"Project-Id-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-06-19 16:57:15\n"
+"PO-Revision-Date: 2021-06-28 18:30-0300\n"
+"Language-Team: LANGUAGE <EMAIL@ADDRESS>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+"X-Generator: Poedit 3.0\n"
+"X-Accelerator-Marker: ~\n"
+"Last-Translator: \n"
+"Language: pt_BR\n"
+
+#. Text in close buttons of progress and console dialog boxes
+msgctxt "CLOSEBUTTON"
+msgid "Close"
+msgstr "Fechar"
+
+#. Title in error message box
+#. %1: an error number
+#, kde-format
+msgctxt "ERRORNUMBER"
+msgid "Error %1"
+msgstr "Erro %1"
+
+#. Error message box
+#. %1: a line number
+#, kde-format
+msgctxt "ERRORLOCATION"
+msgid "Location : %1"
+msgstr "Localização : %1"
+
+#. Logfile record
+#, kde-format
+msgctxt "LONGERRORDESC"
+msgid "Error %1 - Location = %2 - Description = %3"
+msgstr "Erro %1 - Localização = %2 - Descrição = %3"
+
+#. SF_Utils._Validate error message
+msgctxt "STOPEXECUTION"
+msgid "THE EXECUTION IS CANCELLED."
+msgstr "A EXECUÇÃO FOI CANCELADA."
+
+#. SF_Exception.RaiseAbort error message
+msgctxt "INTERNALERROR"
+msgid ""
+"The ScriptForge library has crashed. The reason is unknown.\n"
+"Maybe a bug that could be reported on\n"
+" https://bugs.documentfoundation.org/\n"
+"\n"
+"More details : \n"
+"\n"
+msgstr ""
+"A biblioteca ScriptForge encontrou um erro grave. A razão é desconhecida.\n"
+"Talvez seja um bug que pode ser relatado em\n"
+" https://bugs.documentfoundation.org/\n"
+"\n"
+"Mais detalhes: \n"
+"\n"
+
+#. SF_Utils._Validate error message
+#. %1: probably ScriptForge
+#. %2: service or module name
+#. %3: property or method name where the error occurred
+#, kde-format
+msgctxt "VALIDATESOURCE"
+msgid ""
+"Library : %1\n"
+"Service : %2\n"
+"Method : %3"
+msgstr ""
+"Biblioteca : %1\n"
+"Serviço : %2\n"
+"Método : %3"
+
+#. SF_Utils._Validate error message
+#. %1: list of arguments of the method
+#, kde-format
+msgctxt "VALIDATEARGS"
+msgid "Arguments: %1"
+msgstr "Argumentos: %1"
+
+#. SF_Utils._Validate error message
+#. %1: Wrong argument name
+#, kde-format
+msgctxt "VALIDATEERROR"
+msgid "A serious error has been detected in your code on argument : « %1 »."
+msgstr "Um erro grave foi detectado em seu código no argumento : « %1»."
+
+#. SF_Utils.Validate error message
+msgctxt "VALIDATIONRULES"
+msgid " Validation rules :"
+msgstr " Regras de validação:"
+
+#. SF_Utils._Validate error message
+#. %1: Wrong argument name
+#. %2: Comma separated list of allowed types
+#, kde-format
+msgctxt "VALIDATETYPES"
+msgid " « %1 » must have next type (or one of next types) : %2"
+msgstr ""
+" « %1 » deve ter o seguinte tipo (ou um dos tipos a seguir) : %2"
+
+#. SF_Utils._Validate error message
+#. %1: Wrong argument name
+#. %2: Comma separated list of allowed values
+#, kde-format
+msgctxt "VALIDATEVALUES"
+msgid " « %1 » must contain one of next values : %2"
+msgstr " « %1 » deve conter um dos seguintes valores : %2"
+
+#. SF_Utils._Validate error message
+#. %1: Wrong argument name
+#. %2: A regular expression
+#, kde-format
+msgctxt "VALIDATEREGEX"
+msgid " « %1 » must match next regular expression : %2"
+msgstr " « %1 » deve corresponder à seguinte expressão regular : %2"
+
+#. SF_Utils._Validate error message
+#. %1: Wrong argument name
+#. %2: The name of a Basic class
+#, kde-format
+msgctxt "VALIDATECLASS"
+msgid " « %1 » must be a Basic object of class : %2"
+msgstr " « %1 » deve ser um objeto ou classe Basic : %2"
+
+#. SF_Utils._Validate error message
+#. %1: Wrong argument name
+#. %2: The value of the argument as a string
+#, kde-format
+msgctxt "VALIDATEACTUAL"
+msgid "The actual value of « %1 » is : '%2'"
+msgstr "O valor atual de « %1 » é : '%2'"
+
+#. SF_Utils._Validate error message
+#. %1: Wrong argument name
+#, kde-format
+msgctxt "VALIDATEMISSING"
+msgid "The « %1 » argument is mandatory, yet it is missing."
+msgstr "O argumento « %1 » é obrigatório, porém está ausente."
+
+#. SF_Utils._ValidateArray error message
+#. %1: Wrong argument name
+#, kde-format
+msgctxt "VALIDATEARRAY"
+msgid " « %1 » must be an array."
+msgstr " « %1 » deve ser um array."
+
+#. SF_Utils._ValidateArray error message
+#. %1: Wrong argument name
+#. %2: Number of dimensions of the array
+#, kde-format
+msgctxt "VALIDATEDIMS"
+msgid " « %1 » must have exactly %2 dimension(s)."
+msgstr " « %1 » deve ter exatamente %2 dimensão(ões)."
+
+#. SF_Utils._ValidateArray error message
+#. %1: Wrong argument name
+#. %2: Either one single type or 'String, Date, Numeric'
+#, kde-format
+msgctxt "VALIDATEALLTYPES"
+msgid " « %1 » must have all elements of the same type : %2"
+msgstr " « %1 » deve ter todos os elementos de um mesmo tipo : %2"
+
+#. SF_Utils._ValidateArray error message
+#. %1: Wrong argument name
+#. NULL and EMPTY should not be translated
+#, kde-format
+msgctxt "VALIDATENOTNULL"
+msgid " « %1 » must not contain any NULL or EMPTY elements."
+msgstr " « %1 » não pode conter nenhum elemento NULL ou EMPTY."
+
+#. SF_Utils._ValidateFile error message
+#. %1: Wrong argument name
+#. 'String' should not be translated
+#, kde-format
+msgctxt "VALIDATEFILE"
+msgid " « %1 » must be of type String."
+msgstr " « %1 » deve ser to tipo String."
+
+#. SF_Utils._ValidateFile error message
+#. %1: Wrong argument name
+#, kde-format
+msgctxt "VALIDATEFILESYS"
+msgid ""
+" « %1 » must be a valid file or folder name expressed in the "
+"operating system native notation."
+msgstr ""
+" « %1 » deve ser um nome válido de arquivo ou pasta expresso usando a "
+"notação do sistema operacional."
+
+#. SF_Utils._ValidateFile error message
+#. %1: Wrong argument name
+#. 'URL' should not be translated
+#, kde-format
+msgctxt "VALIDATEFILEURL"
+msgid ""
+" « %1 » must be a valid file or folder name expressed in the portable "
+"URL notation."
+msgstr ""
+" « %1 » deve ser um nome válido de arquivo ou pasta expresso usando a "
+"notação portável URL."
+
+#. SF_Utils._ValidateFile error message
+#. %1: Wrong argument name
+#, kde-format
+msgctxt "VALIDATEFILEANY"
+msgid " « %1 » must be a valid file or folder name."
+msgstr " « %1 » deve ser um nome válido de arquivo ou pasta."
+
+#. SF_Utils._ValidateFile error message
+#. %1: Wrong argument name
+#. '(?, *)' is to be left as is
+#, kde-format
+msgctxt "VALIDATEWILDCARD"
+msgid ""
+" « %1 » may contain one or more wildcard characters (?, *) in its "
+"last path component only."
+msgstr ""
+" « %1 » deve conter um ou mais caracteres coringa (?,*) apenas no "
+"último componente do caminho."
+
+#. SF_Array.RangeInit error message
+#. %1, %2, %3: Numeric values
+#. 'From', 'UpTo', 'ByStep' should not be translated
+#, kde-format
+msgctxt "ARRAYSEQUENCE"
+msgid ""
+"The respective values of 'From', 'UpTo' and 'ByStep' are incoherent.\n"
+"\n"
+" « From » = %1\n"
+" « UpTo » = %2\n"
+" « ByStep » = %3"
+msgstr ""
+"Os valores informados para 'From', 'UpTo' e 'ByStep' são incoerentes.\n"
+"\n"
+" « From » = %1\n"
+" « UpTo » = %2\n"
+" « ByStep » = %3"
+
+#. SF_Array.AppendColumn (...) error message
+#. %1: 'Column' or 'Row' of a matrix
+#. %2, %3: array contents
+#. 'Array_2D' should not be translated
+#, kde-format
+msgctxt "ARRAYINSERT"
+msgid ""
+"The array and the vector to insert have incompatible sizes.\n"
+"\n"
+" « Array_2D » = %2\n"
+" « %1 » = %3"
+msgstr ""
+"O array e vetor a serem inseridos têm tamanhos incompatíveis.\n"
+"\n"
+" « Array_2D » = %2\n"
+" « %1 » = %3"
+
+#. SF_Array.ExtractColumn (...) error message
+#. %1: 'Column' or 'Row' of a matrix
+#. %2, %3: array contents
+#. 'Array_2D' should not be translated
+#, kde-format
+msgctxt "ARRAYINDEX1"
+msgid ""
+"The given index does not fit within the bounds of the array.\n"
+"\n"
+" « Array_2D » = %2\n"
+" « %1 » = %3"
+msgstr ""
+"O índice fornecido não cabe nos limites do array.\n"
+"\n"
+" « Array_2D » = %2\n"
+" « %1 » = %3"
+
+#. SF_Array.ExtractColumn (...) error message
+#. %1: 'Column' or 'Row' of a matrix
+#. %2, %3: array contents
+#. '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_1D » = %1\n"
+" « From » = %2\n"
+" « UpTo » = %3"
+msgstr ""
+"Os limites fornecidos para o intervalo não cabem nos limites do array.\n"
+"\n"
+" « Array_1D » = %1\n"
+" « From » = %2\n"
+" « UpTo » = %3"
+
+#. SF_Array.ImportFromCSVFile error message
+#. %1: a file name
+#. %2: numeric
+#. %3: a long string
+#, kde-format
+msgctxt "CSVPARSING"
+msgid ""
+"The given file could not be parsed as a valid CSV file.\n"
+"\n"
+" « File name » = %1\n"
+" Line number = %2\n"
+" Content = %3"
+msgstr ""
+"O arquivo fornecido não pode ser processada como um arquivo CSV válido.\n"
+"\n"
+" « Arquivo » = %1\n"
+" Número da linha = %2\n"
+" Conteúdo = %3"
+
+#. SF_Dictionary Add/ReplaceKey error message
+#. %1: An identifier%2: a (potentially long) string
+#, kde-format
+msgctxt "DUPLICATEKEY"
+msgid ""
+"The insertion of a new key into a dictionary failed because the key already "
+"exists.\n"
+"Note that the comparison between keys is NOT case-sensitive.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+"A inserção de uma nova chave ao dicionário falhou porque a chave já existe.\n"
+"Note que comparações entre chaves não são sensíveis à caixa.\n"
+"\n"
+"« %1 » = %2"
+
+#. SF_Dictionary Remove/ReplaceKey/ReplaceItem error message
+#. %1: An identifier%2: a (potentially long) string
+#, kde-format
+msgctxt "UNKNOWNKEY"
+msgid ""
+"The requested key does not exist in the dictionary.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+"A chave requerida não existe no dicionário.\n"
+"\n"
+"« %1 » = %2"
+
+#. SF_Dictionary Add/ReplaceKey error message
+#.
+msgctxt "INVALIDKEY"
+msgid ""
+"The insertion or the update of an entry into a dictionary failed because the "
+"given key contains only spaces."
+msgstr ""
+"A inserção ou atualização de uma entrada em um dicionário falhou porque a "
+"chave fornecido contém apenas espaços."
+
+#. SF_FileSystem copy/move/delete error message
+#. %1: An identifier
+#. %2: A file name
+#, kde-format
+msgctxt "UNKNOWNFILE"
+msgid ""
+"The given file could not be found on your system.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+"O arquivo fornecido não foi encontrado em seu sistema.\n"
+"\n"
+"« %1 » = %2"
+
+#. SF_FileSystem copy/move/delete error message
+#. %1: An identifier
+#. %2: A folder name
+#, kde-format
+msgctxt "UNKNOWNFOLDER"
+msgid ""
+"The given folder could not be found on your system.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+"O diretório fornecido não foi encontrado em seu sistema.\n"
+"\n"
+"« %1 » = %2"
+
+#. SF_FileSystem copy/move/delete error message
+#. %1: An identifier
+#. %2: A file name
+#, kde-format
+msgctxt "NOTAFILE"
+msgid ""
+"« %1 » contains the name of an existing folder, not that of a file.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+"« %1 » contém o nome de um diretório existente em vez de conter o nome de um "
+"arquivo.\n"
+"\n"
+"« %1 » = %2"
+
+#. SF_FileSystem copy/move/delete error message
+#. %1: An identifier
+#. %2: A folder name
+#, kde-format
+msgctxt "NOTAFOLDER"
+msgid ""
+"« %1 » contains the name of an existing file, not that of a folder.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+"« %1 » contém o nome de um arquivo existente em vez de conter o nome de um "
+"diretório.\n"
+"\n"
+"« %1 » = %2"
+
+#. SF_FileSystem copy/move/... error message
+#. %1: An identifier
+#. %2: A file name
+#, kde-format
+msgctxt "OVERWRITE"
+msgid ""
+"You tried to create a new file which already exists. Overwriting it has been "
+"rejected.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+"Você tentou criar um novo arquivo que já existe. Sobrescrever o arquivo não "
+"foi permitido\n"
+"\n"
+"« %1 » = %2"
+
+#. SF_FileSystem copy/move/delete error message
+#. %1: An identifier
+#. %2: A file name
+#, kde-format
+msgctxt "READONLY"
+msgid ""
+"Copying or moving a file to a destination which has its read-only attribute "
+"set, or deleting such a file or folder is forbidden.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+"Copiar ou mover um arquivo para um destino que tem o atributo somente-"
+"leitura definido, bem como apagar tais arquivos ou pastas, não é permitido.\n"
+"\n"
+"« %1 » = %2"
+
+#. SF_FileSystem copy/move/delete error message
+#. %1: An identifier
+#. %2: A file or folder name with wildcards
+#, kde-format
+msgctxt "NOFILEMATCH"
+msgid ""
+"When « %1 » contains wildcards. at least one file or folder must match the "
+"given filter. Otherwise the operation is rejected.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+"Quando « %1 » contiver caracteres coringa, ao menos um arquivo ou pasta deve "
+"corresponder ao filtro especificado. Caso contrário, a operação será "
+"rejeitada.\n"
+"\n"
+"« %1 » = %2"
+
+#. SF_FileSystem CreateFolder error message
+#. %1: An identifier
+#. %2: A file or folder name
+#, kde-format
+msgctxt "FOLDERCREATION"
+msgid ""
+"« %1 » contains the name of an existing file or an existing folder. The "
+"operation is rejected.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+"« %1 » contém o nome de um arquivo ou pasta existente. A operação foi "
+"rejeitada.\n"
+"\n"
+"« %1 » = %2"
+
+#. SF_Services.CreateScriptService error message
+#. %1: An identifier
+#. %2: A string
+#. %3: A Basic library name
+#. %4: A service (1 word) name
+#, kde-format
+msgctxt "UNKNOWNSERVICE"
+msgid ""
+"No service named '%4' has been registered for the library '%3'.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+"Nenhum serviço com o nome '%4' foi registrado na biblioteca '%3'.\n"
+"\n"
+"« %1 » = %2"
+
+#. SF_Services.CreateScriptService error message
+#. %1: An identifier
+#. %2: A string
+#. %3: A Basic library name
+#, kde-format
+msgctxt "SERVICESNOTLOADED"
+msgid ""
+"The library '%3' and its services could not been loaded.\n"
+"The reason is unknown.\n"
+"However, checking the '%3.SF_Services.RegisterScriptServices()' function and "
+"its return value can be a good starting point.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+"A biblioteca '%3' e seus serviços não puderam ser carregados.\n"
+"A razão é desconhecida.\n"
+"Contudo, verificar a função '%3.SF_Services.RegisterScriptServices()' e seu "
+"valor de retorno pode ser um bom ponto de partida.\n"
+"\n"
+"« %1 » = %2"
+
+#. SF_Session.ExecuteCalcFunction error message
+#. 'Calc' should not be translated
+#, kde-format
+msgctxt "CALCFUNC"
+msgid ""
+"The Calc '%1' function encountered an error. Either the given function does "
+"not exist or its arguments are invalid."
+msgstr ""
+"A função Calc '%1' encontrou um erro. Ou a função dada não existe ou seus "
+"argumentos são inválidos."
+
+#. SF_Session._GetScript error message
+#. %1: 'Basic' or 'Python'
+#. %2: An identifier
+#. %3: A string
+#. %4: An identifier
+#. %5: A string
+#, kde-format
+msgctxt "NOSCRIPT"
+msgid ""
+"The requested %1 script could not be located in the given libraries and "
+"modules.\n"
+"« %2 » = %3\n"
+"« %4 » = %5"
+msgstr ""
+"O script %1 não pode ser localizado nas bibliotecas e módulos "
+"especificados.\n"
+"« %2 » = %3\n"
+"« %4 » = %5"
+
+#. SF_Session.ExecuteBasicScript error message
+#. %1: An identifier
+#. %2: A string
+#. %3: A (long) string
+#, kde-format
+msgctxt "SCRIPTEXEC"
+msgid ""
+"An exception occurred during the execution of the Basic script.\n"
+"Cause: %3\n"
+"« %1 » = %2"
+msgstr ""
+"Uma exceção ocorreu durante a execução do script Basic.\n"
+"Cause: %3\n"
+"« %1 » = %2"
+
+#. SF_Session.SendMail error message
+#. %1 = a mail address
+#, kde-format
+msgctxt "WRONGEMAIL"
+msgid ""
+"One of the email addresses has been found invalid.\n"
+"Invalid mail = « %1 »"
+msgstr ""
+"Um dos endereços de e-mail foram considerados inválidos.\n"
+"E-mail inválido = « %1 »"
+
+#. SF_Session.SendMail error message
+msgctxt "SENDMAIL"
+msgid ""
+"The message could not be sent due to a system error.\n"
+"A possible cause is that LibreOffice could not find any mail client."
+msgstr ""
+"Esta mensagem não pode ser enviada devido a um erro de sistema.\n"
+"Uma possível causa é que o LibreOffice não pode encontrar um cliente de e-"
+"mail."
+
+#. SF_TextStream._IsFileOpen error message
+#. %1: A file name
+#, kde-format
+msgctxt "FILENOTOPEN"
+msgid ""
+"The requested file operation could not be executed because the file was "
+"closed previously.\n"
+"\n"
+"File name = '%1'"
+msgstr ""
+"A operação de arquivo não pode ser executada porque o arquivo foi fechado "
+"previamente.\n"
+"\n"
+"Nome do arquivo = '%1'"
+
+#. SF_TextStream._IsFileOpen error message
+#. %1: A file name
+#. %2: READ, WRITE or APPEND
+#, kde-format
+msgctxt "FILEOPENMODE"
+msgid ""
+"The requested file operation could not be executed because it is "
+"incompatible with the mode in which the file was opened.\n"
+"\n"
+"File name = '%1'\n"
+"Open mode = %2"
+msgstr ""
+"A operação de arquivo não pode ser executada porque é incompatível com o "
+"modo de abertura do arquivo.\n"
+"\n"
+"Nome do arquivo = '%1'\n"
+"Modo de abertura = %2"
+
+#. SF_TextStream.ReadLine/ReadAll/SkipLine error message
+#. %1: A file name
+#, kde-format
+msgctxt "ENDOFFILE"
+msgid ""
+"The requested file read operation could not be completed because an "
+"unexpected end-of-file was encountered.\n"
+"\n"
+"File name = '%1'"
+msgstr ""
+"A operação de leitura de arquivo não pode ser completada porque um fim-de-"
+"arquivo inesperado foi encontrado.\n"
+"\n"
+"Nome do arquivo = '%1'"
+
+#. SF_UI.GetDocument error message
+#. %1: An identifier
+#. %2: A string
+#, kde-format
+msgctxt "DOCUMENT"
+msgid ""
+"The requested document could not be found.\n"
+"\n"
+"%1 = '%2'"
+msgstr ""
+"O documento desejado não pode ser encontrado.\n"
+"\n"
+"%1 = '%2'"
+
+#. SF_UI.GetDocument error message
+#. %1: An identifier
+#. %2: A string
+#. %3: An identifier
+#. %4: A string
+#, kde-format
+msgctxt "DOCUMENTCREATION"
+msgid ""
+"The creation of a new document failed.\n"
+"Something must be wrong with some arguments.\n"
+"\n"
+"Either the document type is unknown, or no template file was given,\n"
+"or the given template file was not found on your system.\n"
+"\n"
+"%1 = '%2'\n"
+"%3 = '%4'"
+msgstr ""
+"A criação de um novo documento falhou.\n"
+"Deve haver algo de errado com algum dos argumentos.\n"
+"\n"
+"Ou o tipo do documento é desconhecido, ou nenhum arquivo de template foi "
+"especificado,\n"
+"ou o arquivo do template especificado não foi encontrado no sistema.\n"
+"\n"
+"%1 = '%2'\n"
+"%3 = '%4'"
+
+#. SF_UI.OpenDocument error message
+#. %1: An identifier
+#. %2: A string
+#. %3: An identifier
+#. %4: A string
+#. %5: An identifier
+#. %6: A string
+#, kde-format
+msgctxt "DOCUMENTOPEN"
+msgid ""
+"The opening of the document failed.\n"
+"Something must be wrong with some arguments.\n"
+"\n"
+"Either the file does not exist, or the password is wrong, or the given "
+"filter is invalid.\n"
+"\n"
+"%1 = '%2'\n"
+"%3 = '%4'\n"
+"%5 = '%6'"
+msgstr ""
+"A abertura do documento falhou.\n"
+"Deve haver algo de errado com um ou mais argumentos.\n"
+"\n"
+"Ou o arquivo não existe, ou a senha está incorreta, ou o filtro especificado "
+"é inválido.\n"
+"\n"
+"%1 = '%2'\n"
+"%3 = '%4'\n"
+"%5 = '%6'"
+
+#. SF_UI.OpenDocument error message
+#. %1: An identifier
+#. %2: A string
+#. %3: An identifier
+#. %4: A string
+#, kde-format
+msgctxt "BASEDOCUMENTOPEN"
+msgid ""
+"The opening of the Base document failed.\n"
+"Something must be wrong with some arguments.\n"
+"\n"
+"Either the file does not exist, or the file is not registered under the "
+"given name.\n"
+"\n"
+"%1 = '%2'\n"
+"%3 = '%4'"
+msgstr ""
+"A abertura do documento Base falhou.\n"
+"Deve haver algo de errado em algum dos argumentos.\n"
+"\n"
+"Ou o arquivo não existe, ou o arquivo não está registrado com o nome "
+"informado.\n"
+"\n"
+"%1 = '%2'\n"
+"%3 = '%4'"
+
+#. SF_Document._IsStillAlive error message
+#. %1: A file name
+#, kde-format
+msgctxt "DOCUMENTDEAD"
+msgid ""
+"The requested action could not be executed because the document was closed "
+"inadvertently.\n"
+"\n"
+"The concerned document is '%1'"
+msgstr ""
+"A ação desejada não pode ser executada porque o documento foi fechado "
+"inesperadamente.\n"
+"\n"
+"O documento que gerou o erro foi '%1'"
+
+#. SF_Document.SaveAs error message
+#. %1: An identifier
+#. %2: A file name
+#.
+#, kde-format
+msgctxt "DOCUMENTSAVE"
+msgid ""
+"The document could not be saved.\n"
+"Either the document has been opened read-only, or the destination file has a "
+"read-only attribute set, or the file where to save to is undefined.\n"
+"\n"
+"%1 = '%2'"
+msgstr ""
+"O documento não pode ser salvo.\n"
+"Ou o documento foi aberto como somente-leitura, ou o arquivo de destino é "
+"somente leitura, ou o arquivo onde o documento será salvo é indefinido.\n"
+"\n"
+"%1 = '%2'"
+
+#. SF_Document.SaveAs error message
+#. %1: An identifier
+#. %2: A file name
+#. %3: An identifier
+#. %4: True or False
+#. %5: An identifier
+#. %6: A string
+#, kde-format
+msgctxt "DOCUMENTSAVEAS"
+msgid ""
+"The document could not be saved.\n"
+"Either the document must not be overwritten, or the destination file has a "
+"read-only attribute set, or the given filter is invalid.\n"
+"\n"
+"%1 = '%2'\n"
+"%3 = %4\n"
+"%5 = '%6'"
+msgstr ""
+"O documento não pode ser salvo.\n"
+"Ou o documento não pode ser sobrescrito, ou o arquivo de destino é somente "
+"leitura, ou o filtro especificado é inválido.\n"
+"\n"
+"%1 = '%2'\n"
+"%3 = %4\n"
+"%5 = '%6'"
+
+#. SF_Document any update
+#. %1: An identifier
+#. %2: A file name
+#, kde-format
+msgctxt "DOCUMENTREADONLY"
+msgid ""
+"You tried to edit a document which is not modifiable. The document has not "
+"been changed.\n"
+"\n"
+"« %1 » = %2"
+msgstr ""
+"Você tentou editar um documento que não é modificável. O documento não foi "
+"alterado.\n"
+"\n"
+"« %1 » = %2"
+
+#. SF_Base GetDatabase
+#. %1: An identifier
+#. %2: A user name
+#. %3: An identifier
+#. %4: A password
+#. %5: A file name
+#, kde-format
+msgctxt "DBCONNECT"
+msgid ""
+"The database related to the actual Base document could not be retrieved.\n"
+"Check the connection/login parameters.\n"
+"\n"
+"« %1 » = '%2'\n"
+"« %3 » = '%4'\n"
+"« Document » = %5"
+msgstr ""
+"O banco de dados associado ao documento Base atual não pode ser recuperado.\n"
+"Verifique os parâmetros de conexão e login.\n"
+"\n"
+"« %1 » = '%2'\n"
+"« %3 » = '%4'\n"
+"« Documento » = %5"
+
+#. SF_Calc _ParseAddress (sheet)
+#. %1: An identifier
+#. %2: A string
+#. %3: An identifier
+#. %4: A file name
+#, kde-format
+msgctxt "CALCADDRESS1"
+msgid ""
+"The given address does not correspond with a valid sheet name.\n"
+"\n"
+"« %1 » = %2\n"
+"« %3 » = %4"
+msgstr ""
+"O endereço fornecido não corresponde a um nome de planilha válido.\n"
+"\n"
+"« %1 » = %2\n"
+"« %3 » = %4"
+
+#. SF_Calc _ParseAddress (range)
+#. %1: An identifier
+#. %2: A string
+#. %3: An identifier
+#. %4: A file name
+#, kde-format
+msgctxt "CALCADDRESS2"
+msgid ""
+"The given address does not correspond with a valid range of cells.\n"
+"\n"
+"« %1 » = %2\n"
+"« %3 » = %4"
+msgstr ""
+"O endereço fornecido não corresponde a um intervalo de células válido.\n"
+"\n"
+"« %1 » = %2\n"
+"« %3 » = %4"
+
+#. SF_Calc InsertSheet
+#. %1: An identifier
+#. %2: A string
+#. %3: An identifier
+#. %4: A file name
+#, kde-format
+msgctxt "DUPLICATESHEET"
+msgid ""
+"There exists already in the document a sheet with the same name.\n"
+"\n"
+"« %1 » = %2\n"
+"« %3 » = %4"
+msgstr ""
+"Já existe no documento uma planilha com o mesmo nome.\n"
+"\n"
+"« %1 » = %2\n"
+"« %3 » = %4"
+
+#. SF_Calc Offset
+#. %1: An identifier
+#. %2: A Calc reference
+#. %3: An identifier
+#. %4: A number
+#. %5: An identifier
+#. %6: A number
+#. %7: An identifier
+#. %8: A number
+#. %9: An identifier
+#. %10: A number
+#. %11: An identifier
+#. %12: A file name
+#, kde-format
+msgctxt "OFFSETADDRESS"
+msgid ""
+"The computed range falls beyond the sheet boundaries or is meaningless.\n"
+"\n"
+"« %1 » = %2\n"
+"« %3 » = %4\n"
+"« %5 » = %6\n"
+"« %7 » = %8\n"
+"« %9 » = %10\n"
+"« %11 » = %12"
+msgstr ""
+"O intervalo computado vai além dos limites da planilha ou não tem sentido.\n"
+"\n"
+"« %1 » = %2\n"
+"« %3 » = %4\n"
+"« %5 » = %6\n"
+"« %7 » = %8\n"
+"« %9 » = %10\n"
+"« %11 » = %12"
+
+#. SF_Dialog._IsStillAlive error message
+#. %1: An identifier%2: A file name
+#, kde-format
+msgctxt "FORMDEAD"
+msgid ""
+"The requested action could not be executed because the form is not open or "
+"the document was closed inadvertently.\n"
+"\n"
+"The concerned form is '%1' in document '%2'."
+msgstr ""
+"A ação desejada não pode ser executada porque o formulário não está aberto "
+"ou o documento foi fechado inesperadamente.\n"
+"\n"
+"O formulário em questão é '%1' no documento '%2'."
+
+#. SF_Form determination
+#. %1: A number
+#. %2: A sheet name
+#. %3: A file name
+#, kde-format
+msgctxt "CALCFORMNOTFOUND"
+msgid ""
+"The requested form could not be found in the Calc sheet. The given index is "
+"off-limits.\n"
+"\n"
+"The concerned Calc document is '%3'.\n"
+"\n"
+"The name of the sheet = '%2'\n"
+"The index = %1."
+msgstr ""
+"O formulário desejado não pode ser encontrada na planilha Calc. O índice "
+"dado está além dos limites.\n"
+"\n"
+"O documento Calc em questão é '%3'.\n"
+"\n"
+"Nome da planilha = '%2'\n"
+"Índice da planilha = %1."
+
+#. SF_Form determination
+#. %1: A number
+#. %2: A file name
+#, kde-format
+msgctxt "WRITERFORMNOTFOUND"
+msgid ""
+"The requested form could not be found in the Writer document. The given "
+"index is off-limits.\n"
+"\n"
+"The concerned Writer document is '%2'.\n"
+"\n"
+"The index = %1."
+msgstr ""
+"O formulário desejado não pode ser encontrado no documento Writer. O índice "
+"informado está além dos limites.\n"
+"\n"
+"O document Writer em questão é '%2'.\n"
+"\n"
+"Índice do formulário = %1."
+
+#. SF_Form determination
+#. %1: A number
+#. %2: A string
+#. %3: A file name
+#, kde-format
+msgctxt "BASEFORMNOTFOUND"
+msgid ""
+"The requested form could not be found in the form document '%2'. The given "
+"index is off-limits.\n"
+"\n"
+"The concerned Base document is '%3'.\n"
+"\n"
+"The index = %1."
+msgstr ""
+"O formulário desejado não pode ser encontrado no documento de formulário "
+"'%2'. O índice informado está além dos limites.\n"
+"\n"
+"O documento Base em questão é '%3'.\n"
+"\n"
+"Índice do formulário = %1."
+
+#. SF_Form determination
+#. %1: A form name
+#. %2: A form name
+#, kde-format
+msgctxt "SUBFORMNOTFOUND"
+msgid ""
+"The requested subform could not be found below the given main form.\n"
+"\n"
+"The main form = '%2'.\n"
+"The subform = '%1'."
+msgstr ""
+"O sub-formulário desejado não pode ser encontrado como parte do formulário "
+"principal.\n"
+"\n"
+"Formulário principal = '%2'.\n"
+"Sub-formulário = '%1'."
+
+#. SF_FormControl property setting
+#. %1: An identifier
+#. %2: An identifier
+#. %3: A string
+#. %4: An identifier
+#, kde-format
+msgctxt "FORMCONTROLTYPE"
+msgid ""
+"The control '%1' in form '%2' is of type '%3'.\n"
+"The property or method '%4' is not applicable on that type of form controls."
+msgstr ""
+"O controle '%1' no formulário '%2' é do tipo '%3'.\n"
+"A propriedade ou método '%4' não é aplicável a este tipo de controle de "
+"formulário."
+
+#. SF_Dialog creation
+#. %1: An identifier
+#. %2: A string
+#. %3: An identifier
+#. %4: A file name
+#. %5: An identifier
+#. %6: A string
+#. %7: An identifier
+#. %8: A string
+#, kde-format
+msgctxt "DIALOGNOTFOUND"
+msgid ""
+"The requested dialog could not be located in the given container or "
+"library.\n"
+"« %1 » = %2\n"
+"« %3 » = %4\n"
+"« %5 » = %6\n"
+"« %7 » = %8"
+msgstr ""
+"O diálogo desejado não pode ser localizado no container ou biblioteca "
+"informado.\n"
+"« %1 » = %2\n"
+"« %3 » = %4\n"
+"« %5 » = %6\n"
+"« %7 » = %8"
+
+#. SF_Dialog._IsStillAlive error message
+#. %1: An identifier
+#, kde-format
+msgctxt "DIALOGDEAD"
+msgid ""
+"The requested action could not be executed because the dialog was closed "
+"inadvertently.\n"
+"\n"
+"The concerned dialog is '%1'."
+msgstr ""
+"A ação desejada não pode ser executada porque o diálogo foi fechado "
+"inesperadamente.\n"
+"\n"
+"O diálogo em questão é '%1'."
+
+#. SF_DialogControl property setting
+#. %1: An identifier
+#. %2: An identifier
+#. %3: A string
+#. %4: An identifier
+#, kde-format
+msgctxt "CONTROLTYPE"
+msgid ""
+"The control '%1' in dialog '%2' is of type '%3'.\n"
+"The property or method '%4' is not applicable on that type of dialog "
+"controls."
+msgstr ""
+"O controle '%1' no diálogo '%2' é do tipo '%3'.\n"
+"A propriedade ou método '%4' não é aplicável a este tipo de controle de "
+"diálogo."
+
+#. SF_DialogControl add line in textbox
+#. %1: An identifier
+#. %2: An identifier
+#, kde-format
+msgctxt "TEXTFIELD"
+msgid ""
+"The control '%1' in dialog '%2' is not a multiline text field.\n"
+"The requested method could not be executed."
+msgstr ""
+"O controle '%1' no diálogo '%2' não é uma caixa de edição de textos de "
+"múltiplas linhas.\n"
+"O método desejado não pode ser executado."
+
+#. SF_Database when running update SQL statement
+#. %1: The concerned method
+#, kde-format
+msgctxt "DBREADONLY"
+msgid ""
+"The database has been opened in read-only mode.\n"
+"The '%1' method must not be executed in this context."
+msgstr ""
+"O banco de dados foi aberto no modo somente-leitura.\n"
+"O método '%1' não pode ser executado neste contexto."
+
+#. SF_Database can't interpret SQL statement
+#. %1: The statement
+#, kde-format
+msgctxt "SQLSYNTAX"
+msgid ""
+"An SQL statement could not be interpreted or executed by the database "
+"system.\n"
+"Check its syntax, table and/or field names, ...\n"
+"\n"
+"SQL Statement : « %1 »"
+msgstr ""
+"Uma instrução SQL não pode ser interpretada ou executada pelo sistema de "
+"banco de dados.\n"
+"Verifique sua sintaxe, nomes de tabelas, campos, etc...\n"
+"\n"
+"Instrução SQL : « %1 »"
+
+#. SF_Exception.PythonShell error messageAPSO: to leave unchanged
+msgctxt "PYTHONSHELL"
+msgid ""
+"The APSO extension could not be located in your LibreOffice installation."
+msgstr ""
+"A extensão APSO não pode ser localizada sem sua instalação do LibreOffice."
diff --git a/wizards/source/scriptforge/python/ScriptForgeHelper.py b/wizards/source/scriptforge/python/ScriptForgeHelper.py
new file mode 100644
index 000000000..396273233
--- /dev/null
+++ b/wizards/source/scriptforge/python/ScriptForgeHelper.py
@@ -0,0 +1,317 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2019-2022 Jean-Pierre LEDURE, Rafael LIMA, 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/ .
+
+"""
+Collection of Python helper functions called from the ScriptForge Basic libraries
+to execute specific services that are not or not easily available from Basic directly.
+"""
+
+import getpass
+import os
+import platform
+import hashlib
+import filecmp
+import webbrowser
+import json
+
+
+class _Singleton(type):
+ """
+ A Singleton 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]
+
+
+# #################################################################
+# Dictionary service
+# #################################################################
+
+def _SF_Dictionary__ConvertToJson(propval, indent = None) -> str:
+ # used by Dictionary.ConvertToJson() Basic method
+ """
+ Given an array of PropertyValues as argument, convert it to a JSON string
+ """
+ # Array of property values => Dict(ionary) => JSON
+ pvDict = {}
+ for pv in propval:
+ pvDict[pv.Name] = pv.Value
+ return json.dumps(pvDict, indent=indent, skipkeys=True)
+
+
+def _SF_Dictionary__ImportFromJson(jsonstr: str): # used by Dictionary.ImportFromJson() Basic method
+ """
+ Given a JSON string as argument, convert it to a list of tuples (name, value)
+ The value must not be a (sub)dict. This doesn't pass the python-basic bridge.
+ """
+ # JSON => Dictionary => Array of tuples/lists
+ dico = json.loads(jsonstr)
+ result = []
+ for key in iter(dico):
+ value = dico[key]
+ item = value
+ if isinstance(value, dict): # check that first level is not itself a (sub)dict
+ item = None
+ elif isinstance(value, list): # check every member of the list is not a (sub)dict
+ for i in range(len(value)):
+ if isinstance(value[i], dict): value[i] = None
+ result.append((key, item))
+ return result
+
+
+# #################################################################
+# Exception service
+# #################################################################
+
+def _SF_Exception__PythonPrint(string: str) -> bool:
+ # used by SF_Exception.PythonPrint() Basic method
+ """
+ Write the argument to stdout.
+ If the APSO shell console is active, the argument will be displayed in the console window
+ """
+ print(string)
+ return True
+
+
+# #################################################################
+# FileSystem service
+# #################################################################
+
+def _SF_FileSystem__CompareFiles(filename1: str, filename2: str, comparecontents=True) -> bool:
+ # used by SF_FileSystem.CompareFiles() Basic method
+ """
+ Compare the 2 files, returning True if they seem equal, False otherwise.
+ By default, only their signatures (modification time, ...) are compared.
+ When comparecontents == True, their contents are compared.
+ """
+ try:
+ return filecmp.cmp(filename1, filename2, not comparecontents)
+ except Exception:
+ return False
+
+
+def _SF_FileSystem__GetFilelen(systemfilepath: str) -> str: # used by SF_FileSystem.GetFilelen() Basic method
+ return str(os.path.getsize(systemfilepath))
+
+
+def _SF_FileSystem__HashFile(filename: str, algorithm: str) -> str: # used by SF_FileSystem.HashFile() Basic method
+ """
+ Hash a given file with the given hashing algorithm
+ cfr. https://www.pythoncentral.io/hashing-files-with-python/
+ Example
+ hash = _SF_FileSystem__HashFile('myfile.txt','MD5')
+ """
+ algo = algorithm.lower()
+ try:
+ if algo in hashlib.algorithms_guaranteed:
+ BLOCKSIZE = 65535 # Provision for large size files
+ if algo == 'md5':
+ hasher = hashlib.md5()
+ elif algo == 'sha1':
+ hasher = hashlib.sha1()
+ elif algo == 'sha224':
+ hasher = hashlib.sha224()
+ elif algo == 'sha256':
+ hasher = hashlib.sha256()
+ elif algo == 'sha384':
+ hasher = hashlib.sha384()
+ elif algo == 'sha512':
+ hasher = hashlib.sha512()
+ else:
+ return ''
+ with open(filename, 'rb') as file: # open in binary mode
+ buffer = file.read(BLOCKSIZE)
+ while len(buffer) > 0:
+ hasher.update(buffer)
+ buffer = file.read(BLOCKSIZE)
+ return hasher.hexdigest()
+ else:
+ return ''
+ except Exception:
+ return ''
+
+
+# #################################################################
+# Platform service
+# #################################################################
+
+def _SF_Platform(propertyname: str): # used by SF_Platform Basic module
+ """
+ Switch between SF_Platform properties (read the documentation about the ScriptForge.Platform service)
+ """
+ pf = Platform()
+ if propertyname == 'Architecture':
+ return pf.Architecture
+ elif propertyname == 'ComputerName':
+ return pf.ComputerName
+ elif propertyname == 'CPUCount':
+ return pf.CPUCount
+ elif propertyname == 'CurrentUser':
+ return pf.CurrentUser
+ elif propertyname == 'Machine':
+ return pf.Machine
+ elif propertyname == 'OSName':
+ return pf.OSName
+ elif propertyname == 'OSPlatform':
+ return pf.OSPlatform
+ elif propertyname == 'OSRelease':
+ return pf.OSRelease
+ elif propertyname == 'OSVersion':
+ return pf.OSVersion
+ elif propertyname == 'Processor':
+ return pf.Processor
+ elif propertyname == 'PythonVersion':
+ return pf.PythonVersion
+ else:
+ return None
+
+
+class Platform(object, metaclass = _Singleton):
+ @property
+ def Architecture(self): return platform.architecture()[0]
+
+ @property # computer's network name
+ def ComputerName(self): return platform.node()
+
+ @property # number of CPU's
+ def CPUCount(self): return os.cpu_count()
+
+ @property
+ def CurrentUser(self):
+ try:
+ return getpass.getuser()
+ except Exception:
+ return ''
+
+ @property # machine type e.g. 'i386'
+ def Machine(self): return platform.machine()
+
+ @property # system/OS name e.g. 'Darwin', 'Java', 'Linux', ...
+ def OSName(self): return platform.system().replace('Darwin', 'macOS')
+
+ @property # underlying platform e.g. 'Windows-10-...'
+ def OSPlatform(self): return platform.platform(aliased = True)
+
+ @property # system's release e.g. '2.2.0'
+ def OSRelease(self): return platform.release()
+
+ @property # system's version
+ def OSVersion(self): return platform.version()
+
+ @property # real processor name e.g. 'amdk'
+ def Processor(self): return platform.processor()
+
+ @property # Python major.minor.patchlevel
+ def PythonVersion(self): return 'Python ' + platform.python_version()
+
+
+# #################################################################
+# Session service
+# #################################################################
+
+def _SF_Session__OpenURLInBrowser(url: str): # Used by SF_Session.OpenURLInBrowser() Basic method
+ """
+ Display url using the default browser
+ """
+ try:
+ webbrowser.open(url, new = 2)
+ finally:
+ return None
+
+
+# #################################################################
+# String service
+# #################################################################
+
+def _SF_String__HashStr(string: str, algorithm: str) -> str: # used by SF_String.HashStr() Basic method
+ """
+ Hash a given UTF-8 string with the given hashing algorithm
+ Example
+ hash = _SF_String__HashStr('This is a UTF-8 encoded string.','MD5')
+ """
+ algo = algorithm.lower()
+ try:
+ if algo in hashlib.algorithms_guaranteed:
+ ENCODING = 'utf-8'
+ bytestring = string.encode(ENCODING) # Hashing functions expect bytes, not strings
+ if algo == 'md5':
+ hasher = hashlib.md5(bytestring)
+ elif algo == 'sha1':
+ hasher = hashlib.sha1(bytestring)
+ elif algo == 'sha224':
+ hasher = hashlib.sha224(bytestring)
+ elif algo == 'sha256':
+ hasher = hashlib.sha256(bytestring)
+ elif algo == 'sha384':
+ hasher = hashlib.sha384(bytestring)
+ elif algo == 'sha512':
+ hasher = hashlib.sha512(bytestring)
+ else:
+ return ''
+ return hasher.hexdigest()
+ else:
+ return ''
+ except Exception:
+ return ''
+
+
+# #################################################################
+# lists the scripts, that shall be visible inside the Basic/Python IDE
+# #################################################################
+
+g_exportedScripts = ()
+
+if __name__ == "__main__":
+ print(_SF_Platform('Architecture'))
+ print(_SF_Platform('ComputerName'))
+ print(_SF_Platform('CPUCount'))
+ print(_SF_Platform('CurrentUser'))
+ print(_SF_Platform('Machine'))
+ print(_SF_Platform('OSName'))
+ print(_SF_Platform('OSPlatform'))
+ print(_SF_Platform('OSRelease'))
+ print(_SF_Platform('OSVersion'))
+ print(_SF_Platform('Processor'))
+ print(_SF_Platform('PythonVersion'))
+ #
+ print(hashlib.algorithms_guaranteed)
+ print(_SF_FileSystem__HashFile('/opt/libreoffice6.4/program/libbootstraplo.so', 'md5'))
+ print(_SF_FileSystem__HashFile('/opt/libreoffice6.4/share/Scripts/python/Capitalise.py', 'sha512'))
+ #
+ print(_SF_String__HashStr('œ∑¡™£¢∞§¶•ªº–≠œ∑´®†¥¨ˆøπ“‘åß∂ƒ©˙∆˚¬', 'MD5')) # 616eb9c513ad07cd02924b4d285b9987
+ #
+ # _SF_Session__OpenURLInBrowser('https://docs.python.org/3/library/webbrowser.html')
+ #
+ js = """
+ {"firstName": "John","lastName": "Smith","isAlive": true,"age": 27,
+ "address": {"streetAddress": "21 2nd Street","city": "New York","state": "NY","postalCode": "10021-3100"},
+ "phoneNumbers": [{"type": "home","number": "212 555-1234"},{"type": "office","number": "646 555-4567"}],
+ "children": ["Q", "M", "G", "T"],"spouse": null}
+ """
+ arr = _SF_Dictionary__ImportFromJson(js)
+ print(arr)
diff --git a/wizards/source/scriptforge/python/scriptforge.py b/wizards/source/scriptforge/python/scriptforge.py
new file mode 100644
index 000000000..ebc6f147c
--- /dev/null
+++ b/wizards/source/scriptforge/python/scriptforge.py
@@ -0,0 +1,2539 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2020-2022 Jean-Pierre LEDURE, Rafael LIMA, 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:
+ https://help.libreoffice.org/latest/en-US/text/sbasic/shared/03/sf_intro.html?DbPAR=BASIC
+ """
+
+import uno
+
+import datetime
+import time
+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
+ SCRIPTFORGEINITDONE = False
+
+ # #########################################################################
+ # Class constants
+ # #########################################################################
+ library = 'ScriptForge'
+ Version = '7.4' # Actual version number
+ #
+ # Basic dispatcher for Python scripts
+ basicdispatcher = '@application#ScriptForge.SF_PythonHelper._PythonDispatcher'
+ # Python helper functions module
+ pythonhelpermodule = 'ScriptForgeHelper.py'
+ #
+ # 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+++'
+ # Predefined references for services implemented as standard Basic modules
+ servicesmodules = dict([('ScriptForge.Array', 0),
+ ('ScriptForge.Exception', 1),
+ ('ScriptForge.FileSystem', 2),
+ ('ScriptForge.Platform', 3),
+ ('ScriptForge.Region', 4),
+ ('ScriptForge.Services', 5),
+ ('ScriptForge.Session', 6),
+ ('ScriptForge.String', 7),
+ ('ScriptForge.UI', 8)])
+
+ 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
+ #
+ # All properties and methods of the ScriptForge API are ProperCased
+ # Compute their synonyms as lowercased and camelCased names
+ ScriptForge.SetAttributeSynonyms()
+ #
+ ScriptForge.SCRIPTFORGEINITDONE = True
+
+ @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 SystemExit(
+ '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: Either
+ "application" a shared library (BASIC)
+ "share" a library of LibreOffice Macros (PYTHON)
+ :param script: Either
+ [@][scope#][library.]module.method - Must not be a class module or method
+ [@] means that the targeted method accepts ParamArray arguments (Basic only)
+ [scope#][directory/]module.py$method - Must be a method defined at module level
+ :return: the value returned by the invoked script, or an error if the script was not found
+ """
+
+ # The frequently called PythonDispatcher in the ScriptForge Basic library is cached to privilege performance
+ if cls.servicesdispatcher is not None and script == ScriptForge.basicdispatcher:
+ xscript = cls.servicesdispatcher
+ fullscript = script
+ paramarray = True
+ # Build the URI specification described in
+ # https://wiki.documentfoundation.org/Documentation/DevGuide/Scripting_Framework#Scripting_Framework_URI_Specification
+ elif len(script) > 0:
+ # Check ParamArray arguments
+ paramarray = False
+ if script[0] == '@':
+ script = script[1:]
+ paramarray = True
+ scope = ''
+ if '#' in script:
+ scope, script = script.split('#')
+ if '.py$' in script.lower(): # Python
+ if len(scope) == 0:
+ scope = 'share' # Default for Python
+ # Provide an alternate helper script depending on test context
+ if script.startswith(cls.pythonhelpermodule) and hasattr(cls, 'pythonhelpermodule2'):
+ script = cls.pythonhelpermodule2 + script[len(cls.pythonhelpermodule):]
+ if '#' in script:
+ scope, script = script.split('#')
+ uri = 'vnd.sun.star.script:{0}?language=Python&location={1}'.format(script, scope)
+ else: # Basic
+ if len(scope) == 0:
+ scope = 'application' # Default for Basic
+ lib = ''
+ if len(script.split('.')) < 3:
+ lib = cls.library + '.' # Default library = ScriptForge
+ uri = 'vnd.sun.star.script:{0}{1}?language=Basic&location={2}'.format(lib, script, scope)
+ # Get the script object
+ fullscript = ('@' if paramarray else '') + scope + ':' + script
+ try:
+ xscript = cls.scriptprovider.getScript(uri)
+ except Exception:
+ raise RuntimeError(
+ 'The script \'{0}\' could not be located in your LibreOffice installation'.format(script))
+ else: # Should not happen
+ return None
+
+ # At 1st execution of the common Basic dispatcher, buffer xscript
+ if fullscript == ScriptForge.basicdispatcher and cls.servicesdispatcher is None:
+ cls.servicesdispatcher = xscript
+
+ # Execute the script with the given arguments
+ # Packaging for script provider depends on presence of ParamArray arguments in the called Basic script
+ if paramarray:
+ 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 interpret 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 in the SFServices class
+ :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 occurrence 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)
+ #
+ # Interpret 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 SFServices()
+ servname = returntuple[cstService]
+ if servname not in cls.serviceslist:
+ # When service not found
+ raise RuntimeError("The service '" + servname + "' is not available in Python. Execution stops.")
+ subcls = cls.serviceslist[servname]
+ if subcls is not None:
+ return subcls(returntuple[cstValue], returntuple[cstType], returntuple[cstClass],
+ returntuple[cstName])
+ elif returntuple[cstVarType] >= ScriptForge.V_ARRAY:
+ # Intercept empty array
+ if isinstance(returntuple[cstValue], uno.ByteSequence):
+ return ()
+ elif returntuple[cstVarType] == ScriptForge.V_DATE:
+ dat = SFScriptForge.SF_Basic.CDateFromUnoDateTime(returntuple[cstValue])
+ return dat
+ else: # All other scalar values
+ pass
+ return returntuple[cstValue]
+
+ @staticmethod
+ def SetAttributeSynonyms():
+ """
+ A synonym of an attribute is either the lowercase or the camelCase form of its original ProperCase name.
+ In every subclass of SFServices:
+ 1) Fill the propertysynonyms dictionary with the synonyms of the properties listed in serviceproperties
+ Example:
+ serviceproperties = dict(ConfigFolder = False, InstallFolder = False)
+ propertysynonyms = dict(configfolder = 'ConfigFolder', installfolder = 'InstallFolder',
+ configFolder = 'ConfigFolder', installFolder = 'InstallFolder')
+ 2) Define new method attributes synonyms of the original methods
+ Example:
+ def CopyFile(...):
+ # etc ...
+ copyFile, copyfile = CopyFile, CopyFile
+ """
+ def camelCase(key):
+ return key[0].lower() + key[1:]
+
+ for cls in SFServices.__subclasses__():
+ # Synonyms of properties
+ if hasattr(cls, 'serviceproperties'):
+ dico = cls.serviceproperties
+ dicosyn = dict(zip(map(str.lower, dico.keys()), dico.keys())) # lower case
+ cc = dict(zip(map(camelCase, dico.keys()), dico.keys())) # camel Case
+ dicosyn.update(cc)
+ setattr(cls, 'propertysynonyms', dicosyn)
+ # Synonyms of methods. A method is a public callable attribute
+ methods = [method for method in dir(cls) if not method.startswith('_')]
+ for method in methods:
+ func = getattr(cls, method)
+ if callable(func):
+ # Assign to each synonym a reference to the original method
+ lc = method.lower()
+ setattr(cls, lc, func)
+ cc = camelCase(method)
+ if cc != lc:
+ setattr(cls, cc, func)
+ return
+
+ @staticmethod
+ def unpack_args(kwargs):
+ """
+ Convert a dictionary passed as argument to a list alternating keys and values
+ Example:
+ dict(A = 'a', B = 2) => 'A', 'a', 'B', 2
+ """
+ return [v for p in zip(list(kwargs.keys()), list(kwargs.values())) for v in p]
+
+
+# #####################################################################################################################
+# 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 3 class properties:
+ servicename (e.g. 'ScriptForge.FileSystem', 'ScriptForge.Basic')
+ servicesynonyms (e.g. 'FileSystem', 'Basic')
+ serviceimplementation: either 'python' or 'basic'
+ This is sufficient to register the service 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
+ InvokeBasicService(): 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 or usual 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
+ - object type ('SF_String', 'DICTIONARY', ...)
+ - class module: 1 for usual modules, 2 for class modules
+ - name (form, control, ... name) - may be blank
+
+ The role of the SFServices() superclass is mainly to propose a generic properties management
+ Properties are got and set following next strategy:
+ 1. Property names are controlled strictly ('Value' or 'value', 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
+ Property names are proper-cased
+ Conventionally, camel-cased and lower-cased synonyms are supported where relevant
+ a dictionary named 'serviceproperties' with keys = (proper-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
+ When
+ forceGetProperty = False # Standard behaviour
+ read-only serviceproperties are buffered in Python after their 1st get request to Basic
+ Otherwise set it to True to force a recomputation at each property getter invocation
+ If there is a need to handle a specific property in a specific manner:
+ @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 proper-cased, arguments are lower-cased
+ Conventionally, camel-cased and lower-cased homonyms are supported where relevant
+ 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
+ flgPost = 32 # The method or the property implies a hardcoded post-processing
+ flgDateArg = 64 # Invoked service method may contain a date argument
+ flgDateRet = 128 # Invoked service method can return a date
+ flgArrayArg = 512 # 1st argument can be a 2D array
+ flgArrayRet = 1024 # Invoked service method can return a 2D array (standard modules) or any array (class modules)
+ flgUno = 256 # Invoked service method/property can return a UNO object
+ flgObject = 2048 # 1st argument may be a Basic object
+ flgHardCode = 4096 # Force hardcoded call to method, avoid CallByName()
+ # Basic class type
+ moduleClass, moduleStandard = 2, 1
+ #
+ # Define the default behaviour for read-only properties: buffer their values in Python
+ forceGetProperty = False
+ # Empty dictionary for lower/camelcased homonyms or properties
+ propertysynonyms = {}
+ # 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')
+ # Shortcuts to script provider interfaces
+ SIMPLEEXEC = ScriptForge.InvokeSimpleScript
+ EXEC = ScriptForge.InvokeBasicService
+
+ 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 properties
+ """
+ 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)
+
+ 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
+ Due to the use of lower/camelcase synonyms, it is called for each variant of the same property
+ The method manages itself the buffering in __dict__ based on the official ProperCase property name
+ """
+ if name in self.propertysynonyms: # Reset real name if argument provided in lower or camel case
+ name = self.propertysynonyms[name]
+ if self.serviceimplementation == 'basic':
+ if name in ('serviceproperties', 'localProperties', 'internal_attributes', 'propertysynonyms',
+ 'forceGetProperty'):
+ pass
+ elif name in self.serviceproperties:
+ if self.forceGetProperty is False and self.serviceproperties[name] is False: # False = read-only
+ if name in self.__dict__:
+ return self.__dict__[name]
+ else:
+ # Get Property from Basic and store it
+ prop = self.GetProperty(name)
+ self.__dict__[name] = prop
+ return prop
+ else: # Get Property from Basic and do not store it
+ 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
+ Management of __dict__ is automatically done in the final usual object.__setattr__ method
+ """
+ if self.serviceimplementation == 'basic':
+ if name in ('serviceproperties', 'localProperties', 'internal_attributes', 'propertysynonyms',
+ 'forceGetProperty'):
+ pass
+ elif name[0:2] == '__' or name in self.internal_attributes or name in self.localProperties:
+ pass
+ elif name in self.serviceproperties or name in self.propertysynonyms:
+ if name in self.propertysynonyms: # Reset real name if argument provided in lower or camel case
+ name = self.propertysynonyms[name]
+ 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)
+ return
+ 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.objectreference >= len(ScriptForge.servicesmodules): # Do not dispose predefined module objects
+ self.ExecMethod(self.vbMethod, 'Dispose')
+ self.objectreference = -1
+
+ def ExecMethod(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, arg = None):
+ """
+ Get the given property from the Basic world
+ """
+ if self.serviceimplementation == 'basic':
+ # Conventionally properties starting with X (and only them) may return a UNO object
+ calltype = self.vbGet + (self.flgUno if propertyname[0] == 'X' else 0)
+ if arg is None:
+ return self.EXEC(self.objectreference, calltype, propertyname)
+ else: # There are a few cases (Calc ...) where GetProperty accepts an argument
+ return self.EXEC(self.objectreference, calltype, propertyname, arg)
+ return None
+
+ def Properties(self):
+ return list(self.serviceproperties)
+
+ def basicmethods(self):
+ if self.serviceimplementation == 'basic':
+ return self.ExecMethod(self.vbMethod + self.flgArrayRet, 'Methods')
+ else:
+ return []
+
+ def basicproperties(self):
+ if self.serviceimplementation == 'basic':
+ return self.ExecMethod(self.vbMethod + self.flgArrayRet, 'Properties')
+ else:
+ return []
+
+ def SetProperty(self, propertyname, value):
+ """
+ Set the given property to a new value in the Basic world
+ """
+ if self.serviceimplementation == 'basic':
+ flag = self.vbLet
+ if isinstance(value, datetime.datetime):
+ value = SFScriptForge.SF_Basic.CDateToUnoDateTime(value)
+ flag += self.flgDateArg
+ if repr(type(value)) == "<class 'pyuno'>":
+ flag += self.flgUno
+ return self.EXEC(self.objectreference, flag, propertyname, value)
+
+
+# #####################################################################################################################
+# SFScriptForge CLASS (alias of ScriptForge Basic library) ###
+# #####################################################################################################################
+class SFScriptForge:
+ pass
+
+ # #########################################################################
+ # SF_Array CLASS
+ # #########################################################################
+ class SF_Array(SFServices, metaclass = _Singleton):
+ """
+ Provides a collection of methods for manipulating and transforming arrays of one dimension (vectors)
+ and arrays of two dimensions (matrices). This includes set operations, sorting,
+ importing to and exporting from text files.
+ The Python version of the service provides a single method: ImportFromCSVFile
+ """
+ # Mandatory class properties for service registration
+ serviceimplementation = 'basic'
+ servicename = 'ScriptForge.Array'
+ servicesynonyms = ('array', 'scriptforge.array')
+ serviceproperties = dict()
+
+ def ImportFromCSVFile(self, filename, delimiter = ',', dateformat = ''):
+ """
+ Difference with the Basic version: dates are returned in their iso format,
+ not as any of the datetime objects.
+ """
+ return self.ExecMethod(self.vbMethod + self.flgArrayRet, 'ImportFromCSVFile',
+ filename, delimiter, dateformat)
+
+ # #########################################################################
+ # SF_Basic CLASS
+ # #########################################################################
+ class SF_Basic(SFServices, metaclass = _Singleton):
+ """
+ This service proposes a collection of Basic methods to be executed in a Python context
+ simulating 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')
+
+ The signatures of Basic builtin functions are derived from
+ core/basic/source/runtime/stdobj.cxx
+
+ Detailed user documentation:
+ https://help.libreoffice.org/latest/en-US/text/sbasic/shared/03/sf_basic.html?DbPAR=BASIC
+ """
+ # Mandatory class properties for service registration
+ serviceimplementation = 'python'
+ servicename = 'ScriptForge.Basic'
+ servicesynonyms = ('basic', '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
+
+ @classmethod
+ def CDate(cls, datevalue):
+ cdate = cls.SIMPLEEXEC(cls.module + '.PyCDate', datevalue)
+ return cls.CDateFromUnoDateTime(cdate)
+
+ @staticmethod
+ def CDateFromUnoDateTime(unodate):
+ """
+ Converts a UNO date/time representation to a datetime.datetime Python native object
+ :param unodate: com.sun.star.util.DateTime, com.sun.star.util.Date or com.sun.star.util.Time
+ :return: the equivalent datetime.datetime
+ """
+ date = datetime.datetime(1899, 12, 30, 0, 0, 0, 0) # Idem as Basic builtin TimeSeria() function
+ datetype = repr(type(unodate))
+ if 'com.sun.star.util.DateTime' in datetype:
+ if 1900 <= unodate.Year <= datetime.MAXYEAR:
+ date = datetime.datetime(unodate.Year, unodate.Month, unodate.Day, unodate.Hours,
+ unodate.Minutes, unodate.Seconds, int(unodate.NanoSeconds / 1000))
+ elif 'com.sun.star.util.Date' in datetype:
+ if 1900 <= unodate.Year <= datetime.MAXYEAR:
+ date = datetime.datetime(unodate.Year, unodate.Month, unodate.Day)
+ elif 'com.sun.star.util.Time' in datetype:
+ date = datetime.datetime(unodate.Hours, unodate.Minutes, unodate.Seconds,
+ int(unodate.NanoSeconds / 1000))
+ else:
+ return unodate # Not recognized as a UNO date structure
+ return date
+
+ @staticmethod
+ def CDateToUnoDateTime(date):
+ """
+ Converts a date representation into the ccom.sun.star.util.DateTime date format
+ Acceptable boundaries: year >= 1900 and <= 32767
+ :param date: datetime.datetime, datetime.date, datetime.time, float (time.time) or time.struct_time
+ :return: a com.sun.star.util.DateTime
+ """
+ unodate = uno.createUnoStruct('com.sun.star.util.DateTime')
+ unodate.Year, unodate.Month, unodate.Day, unodate.Hours, unodate.Minutes, unodate.Seconds, \
+ unodate.NanoSeconds, unodate.IsUTC = \
+ 1899, 12, 30, 0, 0, 0, 0, False # Identical to Basic TimeSerial() function
+
+ if isinstance(date, float):
+ date = time.localtime(date)
+ if isinstance(date, time.struct_time):
+ if 1900 <= date[0] <= 32767:
+ unodate.Year, unodate.Month, unodate.Day, unodate.Hours, unodate.Minutes, unodate.Seconds =\
+ date[0:6]
+ else: # Copy only the time related part
+ unodate.Hours, unodate.Minutes, unodate.Seconds = date[3:3]
+ elif isinstance(date, (datetime.datetime, datetime.date, datetime.time)):
+ if isinstance(date, (datetime.datetime, datetime.date)):
+ if 1900 <= date.year <= 32767:
+ unodate.Year, unodate.Month, unodate.Day = date.year, date.month, date.day
+ if isinstance(date, (datetime.datetime, datetime.time)):
+ unodate.Hours, unodate.Minutes, unodate.Seconds, unodate.NanoSeconds = \
+ date.hour, date.minute, date.second, date.microsecond * 1000
+ else:
+ return date # Not recognized as a date
+ return unodate
+
+ @classmethod
+ def ConvertFromUrl(cls, url):
+ return cls.SIMPLEEXEC(cls.module + '.PyConvertFromUrl', url)
+
+ @classmethod
+ def ConvertToUrl(cls, systempath):
+ return cls.SIMPLEEXEC(cls.module + '.PyConvertToUrl', systempath)
+
+ @classmethod
+ def CreateUnoService(cls, servicename):
+ return cls.SIMPLEEXEC(cls.module + '.PyCreateUnoService', servicename)
+
+ @classmethod
+ def DateAdd(cls, interval, number, date):
+ if isinstance(date, datetime.datetime):
+ date = cls.CDateToUnoDateTime(date)
+ dateadd = cls.SIMPLEEXEC(cls.module + '.PyDateAdd', interval, number, date)
+ return cls.CDateFromUnoDateTime(dateadd)
+
+ @classmethod
+ def DateDiff(cls, interval, date1, date2, firstdayofweek = 1, firstweekofyear = 1):
+ if isinstance(date1, datetime.datetime):
+ date1 = cls.CDateToUnoDateTime(date1)
+ if isinstance(date2, datetime.datetime):
+ date2 = cls.CDateToUnoDateTime(date2)
+ return cls.SIMPLEEXEC(cls.module + '.PyDateDiff', interval, date1, date2, firstdayofweek, firstweekofyear)
+
+ @classmethod
+ def DatePart(cls, interval, date, firstdayofweek = 1, firstweekofyear = 1):
+ if isinstance(date, datetime.datetime):
+ date = cls.CDateToUnoDateTime(date)
+ return cls.SIMPLEEXEC(cls.module + '.PyDatePart', interval, date, firstdayofweek, firstweekofyear)
+
+ @classmethod
+ def DateValue(cls, string):
+ if isinstance(string, datetime.datetime):
+ string = string.isoformat()
+ datevalue = cls.SIMPLEEXEC(cls.module + '.PyDateValue', string)
+ return cls.CDateFromUnoDateTime(datevalue)
+
+ @classmethod
+ def Format(cls, expression, format = ''):
+ if isinstance(expression, datetime.datetime):
+ expression = cls.CDateToUnoDateTime(expression)
+ return cls.SIMPLEEXEC(cls.module + '.PyFormat', expression, format)
+
+ @classmethod
+ def GetDefaultContext(cls):
+ return ScriptForge.componentcontext
+
+ @classmethod
+ def GetGuiType(cls):
+ return cls.SIMPLEEXEC(cls.module + '.PyGetGuiType')
+
+ @classmethod
+ def GetPathSeparator(cls):
+ return os.sep
+
+ @classmethod
+ def GetSystemTicks(cls):
+ return cls.SIMPLEEXEC(cls.module + '.PyGetSystemTicks')
+
+ class GlobalScope(object, metaclass = _Singleton):
+ @classmethod # Mandatory because the GlobalScope class is normally not instantiated
+ def BasicLibraries(cls):
+ return ScriptForge.InvokeSimpleScript(SFScriptForge.SF_Basic.module + '.PyGlobalScope', 'Basic')
+
+ @classmethod
+ def DialogLibraries(cls):
+ return ScriptForge.InvokeSimpleScript(SFScriptForge.SF_Basic.module + '.PyGlobalScope', 'Dialog')
+
+ @classmethod
+ def InputBox(cls, prompt, title = '', default = '', xpostwips = -1, ypostwips = -1):
+ if xpostwips < 0 or ypostwips < 0:
+ return cls.SIMPLEEXEC(cls.module + '.PyInputBox', prompt, title, default)
+ return cls.SIMPLEEXEC(cls.module + '.PyInputBox', prompt, title, default, xpostwips, ypostwips)
+
+ @classmethod
+ def MsgBox(cls, prompt, buttons = 0, title = ''):
+ return cls.SIMPLEEXEC(cls.module + '.PyMsgBox', prompt, buttons, title)
+
+ @classmethod
+ def Now(cls):
+ return datetime.datetime.now()
+
+ @classmethod
+ def RGB(cls, red, green, blue):
+ return int('%02x%02x%02x' % (red, green, blue), 16)
+
+ @property
+ def StarDesktop(self):
+ ctx = ScriptForge.componentcontext
+ if ctx is None:
+ return None
+ smgr = ctx.getServiceManager() # com.sun.star.lang.XMultiComponentFactory
+ DESK = 'com.sun.star.frame.Desktop'
+ desktop = smgr.createInstanceWithContext(DESK, ctx)
+ return desktop
+ starDesktop, stardesktop = StarDesktop, StarDesktop
+
+ @property
+ def ThisComponent(self):
+ """
+ When the current component is the Basic IDE, the ThisComponent object returns
+ in Basic the component owning the currently run user script.
+ Above behaviour cannot be reproduced in Python.
+ :return: the current component or None when not a document
+ """
+ comp = self.StarDesktop.getCurrentComponent()
+ if comp is None:
+ return None
+ impl = comp.ImplementationName
+ if impl in ('com.sun.star.comp.basic.BasicIDE', 'com.sun.star.comp.sfx2.BackingComp'):
+ return None # None when Basic IDE or welcome screen
+ return comp
+ thisComponent, thiscomponent = ThisComponent, ThisComponent
+
+ @property
+ def ThisDatabaseDocument(self):
+ """
+ When the current component is the Basic IDE, the ThisDatabaseDocument object returns
+ in Basic the database owning the currently run user script.
+ Above behaviour cannot be reproduced in Python.
+ :return: the current Base (main) component or None when not a Base document or one of its subcomponents
+ """
+ comp = self.ThisComponent # Get the current component
+ if comp is None:
+ return None
+ #
+ sess = CreateScriptService('Session')
+ impl, ident = '', ''
+ if sess.HasUnoProperty(comp, 'ImplementationName'):
+ impl = comp.ImplementationName
+ if sess.HasUnoProperty(comp, 'Identifier'):
+ ident = comp.Identifier
+ #
+ targetimpl = 'com.sun.star.comp.dba.ODatabaseDocument'
+ if impl == targetimpl: # The current component is the main Base window
+ return comp
+ # Identify resp. form, table/query, table/query in edit mode, report, relations diagram
+ if impl == 'SwXTextDocument' and ident == 'com.sun.star.sdb.FormDesign' \
+ or impl == 'org.openoffice.comp.dbu.ODatasourceBrowser' \
+ or impl in ('org.openoffice.comp.dbu.OTableDesign', 'org.openoffice.comp.dbu.OQuertDesign') \
+ or impl == 'SwXTextDocument' and ident == 'com.sun.star.sdb.TextReportDesign' \
+ or impl == 'org.openoffice.comp.dbu.ORelationDesign':
+ db = comp.ScriptContainer
+ if sess.HasUnoProperty(db, 'ImplementationName'):
+ if db.ImplementationName == targetimpl:
+ return db
+ return None
+ thisDatabaseDocument, thisdatabasedocument = ThisDatabaseDocument, ThisDatabaseDocument
+
+ @classmethod
+ def Xray(cls, unoobject = None):
+ return cls.SIMPLEEXEC('XrayTool._main.xray', unoobject)
+
+ # #########################################################################
+ # SF_Dictionary CLASS
+ # #########################################################################
+ class SF_Dictionary(SFServices, dict):
+ """
+ The service adds to a Python dict instance the interfaces for conversion to and from
+ a list of UNO PropertyValues
+
+ Usage:
+ dico = dict(A = 1, B = 2, C = 3)
+ myDict = CreateScriptService('Dictionary', dico) # Initialize myDict with the content of dico
+ myDict['D'] = 4
+ print(myDict) # {'A': 1, 'B': 2, 'C': 3, 'D': 4}
+ propval = myDict.ConvertToPropertyValues()
+ or
+ dico = dict(A = 1, B = 2, C = 3)
+ myDict = CreateScriptService('Dictionary') # Initialize myDict as an empty dict object
+ myDict.update(dico) # Load the values of dico into myDict
+ myDict['D'] = 4
+ print(myDict) # {'A': 1, 'B': 2, 'C': 3, 'D': 4}
+ propval = myDict.ConvertToPropertyValues()
+ """
+ # Mandatory class properties for service registration
+ serviceimplementation = 'python'
+ servicename = 'ScriptForge.Dictionary'
+ servicesynonyms = ('dictionary', 'scriptforge.dictionary')
+
+ def __init__(self, dic = None):
+ SFServices.__init__(self)
+ dict.__init__(self)
+ if dic is not None:
+ self.update(dic)
+
+ def ConvertToPropertyValues(self):
+ """
+ Store the content of the dictionary in an array of PropertyValues.
+ Each entry in the array is a com.sun.star.beans.PropertyValue.
+ he key is stored in Name, the value is stored in Value.
+
+ If one of the items has a type datetime, it is converted to a com.sun.star.util.DateTime structure.
+ If one of the items is an empty list, it is converted to None.
+
+ The resulting array is empty when the dictionary is empty.
+ """
+ result = []
+ for key in iter(self):
+ value = self[key]
+ item = value
+ if isinstance(value, dict): # check that first level is not itself a (sub)dict
+ item = None
+ elif isinstance(value, (tuple, list)): # check every member of the list is not a (sub)dict
+ if len(value) == 0: # Property values do not like empty lists
+ value = None
+ else:
+ for i in range(len(value)):
+ if isinstance(value[i], dict):
+ value[i] = None
+ item = value
+ elif isinstance(value, (datetime.datetime, datetime.date, datetime.time)):
+ item = SFScriptForge.SF_Basic.CDateToUnoDateTime(value)
+ pv = uno.createUnoStruct('com.sun.star.beans.PropertyValue')
+ pv.Name = key
+ pv.Value = item
+ result.append(pv)
+ return result
+
+ def ImportFromPropertyValues(self, propertyvalues, overwrite = False):
+ """
+ Inserts the contents of an array of PropertyValue objects into the current dictionary.
+ PropertyValue Names are used as keys in the dictionary, whereas Values contain the corresponding values.
+ Date-type values are converted to datetime.datetime instances.
+ :param propertyvalues: a list.tuple containing com.sun.star.beans.PropertyValue objects
+ :param overwrite: When True, entries with same name may exist in the dictionary and their values
+ are overwritten. When False (default), repeated keys are not overwritten.
+ :return: True when successful
+ """
+ result = []
+ for pv in iter(propertyvalues):
+ key = pv.Name
+ if overwrite is True or key not in self:
+ item = pv.Value
+ if 'com.sun.star.util.DateTime' in repr(type(item)):
+ item = datetime.datetime(item.Year, item.Month, item.Day,
+ item.Hours, item.Minutes, item.Seconds, int(item.NanoSeconds / 1000))
+ elif 'com.sun.star.util.Date' in repr(type(item)):
+ item = datetime.datetime(item.Year, item.Month, item.Day)
+ elif 'com.sun.star.util.Time' in repr(type(item)):
+ item = datetime.datetime(item.Hours, item.Minutes, item.Seconds, int(item.NanoSeconds / 1000))
+ result.append((key, item))
+ self.update(result)
+ return True
+
+ # #########################################################################
+ # SF_Exception CLASS
+ # #########################################################################
+ class SF_Exception(SFServices, metaclass = _Singleton):
+ """
+ The Exception service is a collection of methods for code debugging and error handling.
+
+ The Exception service console stores events, variable values and information about errors.
+ Use the console when the Python shell is not available, for example in Calc user defined functions (UDF)
+ or during events processing.
+ Use DebugPrint() method to aggregate additional user data of any type.
+
+ Console entries can be dumped to a text file or visualized in a dialogue.
+ """
+ # Mandatory class properties for service registration
+ serviceimplementation = 'basic'
+ servicename = 'ScriptForge.Exception'
+ servicesynonyms = ('exception', 'scriptforge.exception')
+ serviceproperties = dict()
+
+ def Console(self, modal = True):
+ # From Python, the current XComponentContext must be added as last argument
+ return self.ExecMethod(self.vbMethod, 'Console', modal, ScriptForge.componentcontext)
+
+ def ConsoleClear(self, keep = 0):
+ return self.ExecMethod(self.vbMethod, 'ConsoleClear', keep)
+
+ def ConsoleToFile(self, filename):
+ return self.ExecMethod(self.vbMethod, 'ConsoleToFile', filename)
+
+ def DebugDisplay(self, *args):
+ # Arguments are concatenated in a single string similar to what the Python print() function would produce
+ self.DebugPrint(*args)
+ param = '\n'.join(list(map(lambda a: a.strip("'") if isinstance(a, str) else repr(a), args)))
+ bas = CreateScriptService('ScriptForge.Basic')
+ return bas.MsgBox(param, bas.MB_OK + bas.MB_ICONINFORMATION, 'DebugDisplay')
+
+ def DebugPrint(self, *args):
+ # Arguments are concatenated in a single string similar to what the Python print() function would produce
+ # Avoid using repr() on strings to not have backslashes * 4
+ param = '\t'.join(list(map(lambda a: a.strip("'") if isinstance(a, str) else repr(a),
+ args))).expandtabs(tabsize = 4)
+ return self.ExecMethod(self.vbMethod, 'DebugPrint', param)
+
+ @classmethod
+ def PythonShell(cls, variables = None):
+ """
+ Open an APSO python shell window - Thanks to its authors Hanya/Tsutomu Uchino/Hubert Lambert
+ :param variables: Typical use
+ PythonShell.({**globals(), **locals()})
+ to push the global and local dictionaries to the shell window
+ """
+ if variables is None:
+ variables = locals()
+ # Is APSO installed ?
+ ctx = ScriptForge.componentcontext
+ ext = ctx.getByName('/singletons/com.sun.star.deployment.PackageInformationProvider')
+ apso = 'apso.python.script.organizer'
+ if len(ext.getPackageLocation(apso)) > 0:
+ # Directly derived from apso.oxt|python|scripts|tools.py$console
+ # we need to load apso before import statement
+ ctx.ServiceManager.createInstance('apso.python.script.organizer.impl')
+ # now we can use apso_utils library
+ from apso_utils import console
+ kwargs = {'loc': variables}
+ kwargs['loc'].setdefault('XSCRIPTCONTEXT', uno)
+ console(**kwargs)
+ # An interprocess call is necessary to allow a redirection of STDOUT and STDERR by APSO
+ # Choice is a minimalist call to a Basic routine: no arguments, a few lines of code
+ SFScriptForge.SF_Basic.GetGuiType()
+ else:
+ # The APSO extension could not be located in your LibreOffice installation
+ cls._RaiseFatal('SF_Exception.PythonShell', 'variables=None', 'PYTHONSHELLERROR')
+
+ @classmethod
+ def RaiseFatal(cls, errorcode, *args):
+ """
+ Generate a run-time error caused by an anomaly in a user script detected by ScriptForge
+ The message is logged in the console. The execution is STOPPED
+ For INTERNAL USE only
+ """
+ # Direct call because RaiseFatal forces an execution stop in Basic
+ if len(args) == 0:
+ args = (None,)
+ return cls.SIMPLEEXEC('@SF_Exception.RaiseFatal', (errorcode, *args)) # With ParamArray
+
+ @classmethod
+ def _RaiseFatal(cls, sub, subargs, errorcode, *args):
+ """
+ Wrapper of RaiseFatal(). Includes method and syntax of the failed Python routine
+ to simulate the exact behaviour of the Basic RaiseFatal() method
+ For INTERNAL USE only
+ """
+ ScriptForge.InvokeSimpleScript('ScriptForge.SF_Utils._EnterFunction', sub, subargs)
+ cls.RaiseFatal(errorcode, *args)
+ raise RuntimeError("The execution of the method '" + sub.split('.')[-1] + "' failed. Execution stops.")
+
+ # #########################################################################
+ # 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'
+ servicesynonyms = ('filesystem', 'scriptforge.filesystem')
+ serviceproperties = dict(FileNaming = True, ConfigFolder = False, ExtensionsFolder = False, HomeFolder = False,
+ InstallFolder = False, TemplatesFolder = False, TemporaryFolder = False,
+ UserTemplatesFolder = False)
+ # Force for each property to get its value from Basic - due to FileNaming updatability
+ forceGetProperty = True
+ # Open TextStream constants
+ ForReading, ForWriting, ForAppending = 1, 2, 8
+
+ def BuildPath(self, foldername, name):
+ return self.ExecMethod(self.vbMethod, 'BuildPath', foldername, name)
+
+ def CompareFiles(self, filename1, filename2, comparecontents = False):
+ py = ScriptForge.pythonhelpermodule + '$' + '_SF_FileSystem__CompareFiles'
+ if self.FileExists(filename1) and self.FileExists(filename2):
+ file1 = self._ConvertFromUrl(filename1)
+ file2 = self._ConvertFromUrl(filename2)
+ return self.SIMPLEEXEC(py, file1, file2, comparecontents)
+ else:
+ return False
+
+ def CopyFile(self, source, destination, overwrite = True):
+ return self.ExecMethod(self.vbMethod, 'CopyFile', source, destination, overwrite)
+
+ def CopyFolder(self, source, destination, overwrite = True):
+ return self.ExecMethod(self.vbMethod, 'CopyFolder', source, destination, overwrite)
+
+ def CreateFolder(self, foldername):
+ return self.ExecMethod(self.vbMethod, 'CreateFolder', foldername)
+
+ def CreateTextFile(self, filename, overwrite = True, encoding = 'UTF-8'):
+ return self.ExecMethod(self.vbMethod, 'CreateTextFile', filename, overwrite, encoding)
+
+ def DeleteFile(self, filename):
+ return self.ExecMethod(self.vbMethod, 'DeleteFile', filename)
+
+ def DeleteFolder(self, foldername):
+ return self.ExecMethod(self.vbMethod, 'DeleteFolder', foldername)
+
+ def ExtensionFolder(self, extension):
+ return self.ExecMethod(self.vbMethod, 'ExtensionFolder', extension)
+
+ def FileExists(self, filename):
+ return self.ExecMethod(self.vbMethod, 'FileExists', filename)
+
+ def Files(self, foldername, filter = ''):
+ return self.ExecMethod(self.vbMethod, 'Files', foldername, filter)
+
+ def FolderExists(self, foldername):
+ return self.ExecMethod(self.vbMethod, 'FolderExists', foldername)
+
+ def GetBaseName(self, filename):
+ return self.ExecMethod(self.vbMethod, 'GetBaseName', filename)
+
+ def GetExtension(self, filename):
+ return self.ExecMethod(self.vbMethod, 'GetExtension', filename)
+
+ def GetFileLen(self, filename):
+ py = ScriptForge.pythonhelpermodule + '$' + '_SF_FileSystem__GetFilelen'
+ if self.FileExists(filename):
+ file = self._ConvertFromUrl(filename)
+ return int(self.SIMPLEEXEC(py, file))
+ else:
+ return 0
+
+ def GetFileModified(self, filename):
+ return self.ExecMethod(self.vbMethod + self.flgDateRet, 'GetFileModified', filename)
+
+ def GetName(self, filename):
+ return self.ExecMethod(self.vbMethod, 'GetName', filename)
+
+ def GetParentFolderName(self, filename):
+ return self.ExecMethod(self.vbMethod, 'GetParentFolderName', filename)
+
+ def GetTempName(self):
+ return self.ExecMethod(self.vbMethod, 'GetTempName')
+
+ def HashFile(self, filename, algorithm):
+ py = ScriptForge.pythonhelpermodule + '$' + '_SF_FileSystem__HashFile'
+ if self.FileExists(filename):
+ file = self._ConvertFromUrl(filename)
+ return self.SIMPLEEXEC(py, file, algorithm.lower())
+ else:
+ return ''
+
+ def MoveFile(self, source, destination):
+ return self.ExecMethod(self.vbMethod, 'MoveFile', source, destination)
+
+ def MoveFolder(self, source, destination):
+ return self.ExecMethod(self.vbMethod, 'MoveFolder', source, destination)
+
+ def OpenTextFile(self, filename, iomode = 1, create = False, encoding = 'UTF-8'):
+ return self.ExecMethod(self.vbMethod, 'OpenTextFile', filename, iomode, create, encoding)
+
+ def PickFile(self, defaultfile = ScriptForge.cstSymEmpty, mode = 'OPEN', filter = ''):
+ return self.ExecMethod(self.vbMethod, 'PickFile', defaultfile, mode, filter)
+
+ def PickFolder(self, defaultfolder = ScriptForge.cstSymEmpty, freetext = ''):
+ return self.ExecMethod(self.vbMethod, 'PickFolder', defaultfolder, freetext)
+
+ def SubFolders(self, foldername, filter = ''):
+ return self.ExecMethod(self.vbMethod, 'SubFolders', foldername, filter)
+
+ @classmethod
+ def _ConvertFromUrl(cls, filename):
+ # Alias for same function in FileSystem Basic module
+ return cls.SIMPLEEXEC('ScriptForge.SF_FileSystem._ConvertFromUrl', filename)
+
+ # #########################################################################
+ # SF_L10N CLASS
+ # #########################################################################
+ class SF_L10N(SFServices):
+ """
+ This service provides a number of methods related to the translation of strings
+ with minimal impact on the program's source code.
+ The methods provided by the L10N service can be used mainly to:
+ Create POT files that can be used as templates for translation of all strings in the program.
+ Get translated strings at runtime for the language defined in the Locale property.
+ """
+ # Mandatory class properties for service registration
+ serviceimplementation = 'basic'
+ servicename = 'ScriptForge.L10N'
+ servicesynonyms = ('l10n', 'scriptforge.l10n')
+ serviceproperties = dict(Folder = False, Languages = False, Locale = False)
+
+ @classmethod
+ def ReviewServiceArgs(cls, foldername = '', locale = '', encoding = 'UTF-8',
+ locale2 = '', encoding2 = 'UTF-8'):
+ """
+ Transform positional and keyword arguments into positional only
+ """
+ return foldername, locale, encoding, locale2, encoding2
+
+ def AddText(self, context = '', msgid = '', comment = ''):
+ return self.ExecMethod(self.vbMethod, 'AddText', context, msgid, comment)
+
+ def AddTextsFromDialog(self, dialog):
+ dialogobj = dialog.objectreference if isinstance(dialog, SFDialogs.SF_Dialog) else dialog
+ return self.ExecMethod(self.vbMethod + self.flgObject, 'AddTextsFromDialog', dialogobj)
+
+ def ExportToPOTFile(self, filename, header = '', encoding= 'UTF-8'):
+ return self.ExecMethod(self.vbMethod, 'ExportToPOTFile', filename, header, encoding)
+
+ def GetText(self, msgid, *args):
+ return self.ExecMethod(self.vbMethod, 'GetText', msgid, *args)
+ _ = GetText
+
+ # #########################################################################
+ # SF_Platform CLASS
+ # #########################################################################
+ class SF_Platform(SFServices, metaclass = _Singleton):
+ """
+ The 'Platform' service implements a collection of properties about the actual execution environment
+ and context :
+ the hardware platform
+ the operating system
+ the LibreOffice version
+ the current user
+ All those properties are read-only.
+ The implementation is mainly based on the 'platform' module of the Python standard library
+ """
+ # Mandatory class properties for service registration
+ serviceimplementation = 'basic'
+ servicename = 'ScriptForge.Platform'
+ servicesynonyms = ('platform', 'scriptforge.platform')
+ serviceproperties = dict(Architecture = False, ComputerName = False, CPUCount = False, CurrentUser = False,
+ Extensions = False, FilterNames = False, Fonts = False, FormatLocale = False,
+ Locale = False, Machine = False, OfficeLocale = False, OfficeVersion = False,
+ OSName = False, OSPlatform = False, OSRelease = False, OSVersion = False,
+ Printers = False, Processor = False, PythonVersion = False, SystemLocale = False)
+ # Python helper functions
+ py = ScriptForge.pythonhelpermodule + '$' + '_SF_Platform'
+
+ @property
+ def Architecture(self):
+ return self.SIMPLEEXEC(self.py, 'Architecture')
+
+ @property
+ def ComputerName(self):
+ return self.SIMPLEEXEC(self.py, 'ComputerName')
+
+ @property
+ def CPUCount(self):
+ return self.SIMPLEEXEC(self.py, 'CPUCount')
+
+ @property
+ def CurrentUser(self):
+ return self.SIMPLEEXEC(self.py, 'CurrentUser')
+
+ @property
+ def Machine(self):
+ return self.SIMPLEEXEC(self.py, 'Machine')
+
+ @property
+ def OSName(self):
+ return self.SIMPLEEXEC(self.py, 'OSName')
+
+ @property
+ def OSPlatform(self):
+ return self.SIMPLEEXEC(self.py, 'OSPlatform')
+
+ @property
+ def OSRelease(self):
+ return self.SIMPLEEXEC(self.py, 'OSRelease')
+
+ @property
+ def OSVersion(self):
+ return self.SIMPLEEXEC(self.py, 'OSVersion')
+
+ @property
+ def Processor(self):
+ return self.SIMPLEEXEC(self.py, 'Processor')
+
+ @property
+ def PythonVersion(self):
+ return self.SIMPLEEXEC(self.py, 'PythonVersion')
+
+ # #########################################################################
+ # SF_Region CLASS
+ # #########################################################################
+ class SF_Region(SFServices, metaclass = _Singleton):
+ """
+ The "Region" service gathers a collection of functions about languages, countries and timezones
+ - Locales
+ - Currencies
+ - Numbers and dates formatting
+ - Calendars
+ - Timezones conversions
+ - Numbers transformed to text
+ """
+ # Mandatory class properties for service registration
+ serviceimplementation = 'basic'
+ servicename = 'ScriptForge.Region'
+ servicesynonyms = ('region', 'scriptforge.region')
+ serviceproperties = dict()
+
+ # Next functions are implemented in Basic as read-only properties with 1 argument
+ def Country(self, region = ''):
+ return self.GetProperty('Country', region)
+
+ def Currency(self, region = ''):
+ return self.GetProperty('Currency', region)
+
+ def DatePatterns(self, region = ''):
+ return self.GetProperty('DatePatterns', region)
+
+ def DateSeparator(self, region = ''):
+ return self.GetProperty('DateSeparator', region)
+
+ def DayAbbrevNames(self, region = ''):
+ return self.GetProperty('DayAbbrevNames', region)
+
+ def DayNames(self, region = ''):
+ return self.GetProperty('DayNames', region)
+
+ def DayNarrowNames(self, region = ''):
+ return self.GetProperty('DayNarrowNames', region)
+
+ def DecimalPoint(self, region = ''):
+ return self.GetProperty('DecimalPoint', region)
+
+ def Language(self, region = ''):
+ return self.GetProperty('Language', region)
+
+ def ListSeparator(self, region = ''):
+ return self.GetProperty('ListSeparator', region)
+
+ def MonthAbbrevNames(self, region = ''):
+ return self.GetProperty('MonthAbbrevNames', region)
+
+ def MonthNames(self, region = ''):
+ return self.GetProperty('MonthNames', region)
+
+ def MonthNarrowNames(self, region = ''):
+ return self.GetProperty('MonthNarrowNames', region)
+
+ def ThousandSeparator(self, region = ''):
+ return self.GetProperty('ThousandSeparator', region)
+
+ def TimeSeparator(self, region = ''):
+ return self.GetProperty('TimeSeparator', region)
+
+ # Usual methods
+ def DSTOffset(self, localdatetime, timezone, locale = ''):
+ if isinstance(localdatetime, datetime.datetime):
+ localdatetime = SFScriptForge.SF_Basic.CDateToUnoDateTime(localdatetime)
+ return self.ExecMethod(self.vbMethod + self.flgDateArg, 'DSTOffset', localdatetime, timezone, locale)
+
+ def LocalDateTime(self, utcdatetime, timezone, locale = ''):
+ if isinstance(utcdatetime, datetime.datetime):
+ utcdatetime = SFScriptForge.SF_Basic.CDateToUnoDateTime(utcdatetime)
+ localdate = self.ExecMethod(self.vbMethod + self.flgDateArg + self.flgDateRet, 'LocalDateTime',
+ utcdatetime, timezone, locale)
+ return SFScriptForge.SF_Basic.CDateFromUnoDateTime(localdate)
+
+ def Number2Text(self, number, locale = ''):
+ return self.ExecMethod(self.vbMethod, 'Number2Text', number, locale)
+
+ def TimeZoneOffset(self, timezone, locale = ''):
+ return self.ExecMethod(self.vbMethod, 'TimeZoneOffset', timezone, locale)
+
+ def UTCDateTime(self, localdatetime, timezone, locale = ''):
+ if isinstance(localdatetime, datetime.datetime):
+ localdatetime = SFScriptForge.SF_Basic.CDateToUnoDateTime(localdatetime)
+ utcdate = self.ExecMethod(self.vbMethod + self.flgDateArg + self.flgDateRet, 'UTCDateTime', localdatetime,
+ timezone, locale)
+ return SFScriptForge.SF_Basic.CDateFromUnoDateTime(utcdate)
+
+ def UTCNow(self, timezone, locale = ''):
+ now = self.ExecMethod(self.vbMethod + self.flgDateRet, 'UTCNow', timezone, locale)
+ return SFScriptForge.SF_Basic.CDateFromUnoDateTime(now)
+
+ # #########################################################################
+ # SF_Session CLASS
+ # #########################################################################
+ class SF_Session(SFServices, metaclass = _Singleton):
+ """
+ The Session service gathers various general-purpose methods about:
+ - UNO introspection
+ - the invocation of external scripts or programs
+ """
+ # Mandatory class properties for service registration
+ serviceimplementation = 'basic'
+ servicename = 'ScriptForge.Session'
+ servicesynonyms = ('session', 'scriptforge.session')
+ serviceproperties = dict()
+
+ # Class constants Where to find an invoked library ?
+ SCRIPTISEMBEDDED = 'document' # in the document
+ SCRIPTISAPPLICATION = 'application' # in any shared library (Basic)
+ SCRIPTISPERSONAL = 'user' # in My Macros (Python)
+ SCRIPTISPERSOXT = 'user:uno_packages' # in an extension installed for the current user (Python)
+ SCRIPTISSHARED = 'share' # in LibreOffice macros (Python)
+ SCRIPTISSHAROXT = 'share:uno_packages' # in an extension installed for all users (Python)
+ SCRIPTISOXT = 'uno_packages' # in an extension but the installation parameters are unknown (Python)
+
+ @classmethod
+ def ExecuteBasicScript(cls, scope = '', script = '', *args):
+ if scope is None or scope == '':
+ scope = cls.SCRIPTISAPPLICATION
+ if len(args) == 0:
+ args = (scope,) + (script,) + (None,)
+ else:
+ args = (scope,) + (script,) + args
+ # ExecuteBasicScript method has a ParamArray parameter in Basic
+ return cls.SIMPLEEXEC('@SF_Session.ExecuteBasicScript', args)
+
+ @classmethod
+ def ExecuteCalcFunction(cls, calcfunction, *args):
+ if len(args) == 0:
+ # Arguments of Calc functions are strings or numbers. None == Empty is a good alias for no argument
+ args = (calcfunction,) + (None,)
+ else:
+ args = (calcfunction,) + args
+ # ExecuteCalcFunction method has a ParamArray parameter in Basic
+ return cls.SIMPLEEXEC('@SF_Session.ExecuteCalcFunction', args)
+
+ @classmethod
+ def ExecutePythonScript(cls, scope = '', script = '', *args):
+ return cls.SIMPLEEXEC(scope + '#' + script, *args)
+
+ def HasUnoMethod(self, unoobject, methodname):
+ return self.ExecMethod(self.vbMethod, 'HasUnoMethod', unoobject, methodname)
+
+ def HasUnoProperty(self, unoobject, propertyname):
+ return self.ExecMethod(self.vbMethod, 'HasUnoProperty', unoobject, propertyname)
+
+ @classmethod
+ def OpenURLInBrowser(cls, url):
+ py = ScriptForge.pythonhelpermodule + '$' + '_SF_Session__OpenURLInBrowser'
+ return cls.SIMPLEEXEC(py, url)
+
+ def RunApplication(self, command, parameters):
+ return self.ExecMethod(self.vbMethod, 'RunApplication', command, parameters)
+
+ def SendMail(self, recipient, cc = '', bcc = '', subject = '', body = '', filenames = '', editmessage = True):
+ return self.ExecMethod(self.vbMethod, 'SendMail', recipient, cc, bcc, subject, body, filenames, editmessage)
+
+ def UnoObjectType(self, unoobject):
+ return self.ExecMethod(self.vbMethod, 'UnoObjectType', unoobject)
+
+ def UnoMethods(self, unoobject):
+ return self.ExecMethod(self.vbMethod, 'UnoMethods', unoobject)
+
+ def UnoProperties(self, unoobject):
+ return self.ExecMethod(self.vbMethod, 'UnoProperties', unoobject)
+
+ def WebService(self, uri):
+ return self.ExecMethod(self.vbMethod, 'WebService', uri)
+
+ # #########################################################################
+ # SF_String CLASS
+ # #########################################################################
+ class SF_String(SFServices, metaclass = _Singleton):
+ """
+ Focus on string manipulation, regular expressions, encodings and hashing algorithms.
+ The methods implemented in Basic that are redundant with Python builtin functions
+ are not duplicated
+ """
+ # Mandatory class properties for service registration
+ serviceimplementation = 'basic'
+ servicename = 'ScriptForge.String'
+ servicesynonyms = ('string', 'scriptforge.string')
+ serviceproperties = dict()
+
+ @classmethod
+ def HashStr(cls, inputstr, algorithm):
+ py = ScriptForge.pythonhelpermodule + '$' + '_SF_String__HashStr'
+ return cls.SIMPLEEXEC(py, inputstr, algorithm.lower())
+
+ def IsADate(self, inputstr, dateformat = 'YYYY-MM-DD'):
+ return self.ExecMethod(self.vbMethod, 'IsADate', inputstr, dateformat)
+
+ def IsEmail(self, inputstr):
+ return self.ExecMethod(self.vbMethod, 'IsEmail', inputstr)
+
+ def IsFileName(self, inputstr, osname = ScriptForge.cstSymEmpty):
+ return self.ExecMethod(self.vbMethod, 'IsFileName', inputstr, osname)
+
+ def IsIBAN(self, inputstr):
+ return self.ExecMethod(self.vbMethod, 'IsIBAN', inputstr)
+
+ def IsIPv4(self, inputstr):
+ return self.ExecMethod(self.vbMethod, 'IsIPv4', inputstr)
+
+ def IsLike(self, inputstr, pattern, casesensitive = False):
+ return self.ExecMethod(self.vbMethod, 'IsLike', inputstr, pattern, casesensitive)
+
+ def IsSheetName(self, inputstr):
+ return self.ExecMethod(self.vbMethod, 'IsSheetName', inputstr)
+
+ def IsUrl(self, inputstr):
+ return self.ExecMethod(self.vbMethod, 'IsUrl', inputstr)
+
+ def SplitNotQuoted(self, inputstr, delimiter = ' ', occurrences = 0, quotechar = '"'):
+ return self.ExecMethod(self.vbMethod, 'SplitNotQuoted', inputstr, delimiter, occurrences, quotechar)
+
+ def Wrap(self, inputstr, width = 70, tabsize = 8):
+ return self.ExecMethod(self.vbMethod, 'Wrap', inputstr, width, tabsize)
+
+ # #########################################################################
+ # SF_TextStream CLASS
+ # #########################################################################
+ class SF_TextStream(SFServices):
+ """
+ The TextStream service is used to sequentially read from and write to files opened or created
+ using the ScriptForge.FileSystem service..
+ """
+ # Mandatory class properties for service registration
+ serviceimplementation = 'basic'
+ servicename = 'ScriptForge.TextStream'
+ servicesynonyms = ()
+ serviceproperties = dict(AtEndOfStream = False, Encoding = False, FileName = False, IOMode = False,
+ Line = False, NewLine = True)
+
+ @property
+ def AtEndOfStream(self):
+ return self.GetProperty('AtEndOfStream')
+ atEndOfStream, atendofstream = AtEndOfStream, AtEndOfStream
+
+ @property
+ def Line(self):
+ return self.GetProperty('Line')
+ line = Line
+
+ def CloseFile(self):
+ return self.ExecMethod(self.vbMethod, 'CloseFile')
+
+ def ReadAll(self):
+ return self.ExecMethod(self.vbMethod, 'ReadAll')
+
+ def ReadLine(self):
+ return self.ExecMethod(self.vbMethod, 'ReadLine')
+
+ def SkipLine(self):
+ return self.ExecMethod(self.vbMethod, 'SkipLine')
+
+ def WriteBlankLines(self, lines):
+ return self.ExecMethod(self.vbMethod, 'WriteBlankLines', lines)
+
+ def WriteLine(self, line):
+ return self.ExecMethod(self.vbMethod, 'WriteLine', line)
+
+ # #########################################################################
+ # 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'
+ servicesynonyms = ('timer', 'scriptforge.timer')
+ serviceproperties = dict(Duration = False, IsStarted = False, IsSuspended = False,
+ SuspendDuration = False, TotalDuration = False)
+ # Force for each property to get its value from Basic
+ forceGetProperty = True
+
+ @classmethod
+ def ReviewServiceArgs(cls, start = False):
+ """
+ Transform positional and keyword arguments into positional only
+ """
+ return (start,)
+
+ def Continue(self):
+ return self.ExecMethod(self.vbMethod, 'Continue')
+
+ def Restart(self):
+ return self.ExecMethod(self.vbMethod, 'Restart')
+
+ def Start(self):
+ return self.ExecMethod(self.vbMethod, 'Start')
+
+ def Suspend(self):
+ return self.ExecMethod(self.vbMethod, 'Suspend')
+
+ def Terminate(self):
+ return self.ExecMethod(self.vbMethod, 'Terminate')
+
+ # #########################################################################
+ # SF_UI CLASS
+ # #########################################################################
+ class SF_UI(SFServices, metaclass = _Singleton):
+ """
+ Singleton class for the identification and the manipulation of the
+ different windows composing the whole LibreOffice application:
+ - Windows selection
+ - Windows moving and resizing
+ - Statusbar settings
+ - Creation of new windows
+ - Access to the underlying "documents"
+ """
+ # Mandatory class properties for service registration
+ serviceimplementation = 'basic'
+ servicename = 'ScriptForge.UI'
+ servicesynonyms = ('ui', 'scriptforge.ui')
+ serviceproperties = dict(ActiveWindow = False, Height = False, Width = False, X = False, Y = False)
+
+ # Class constants
+ MACROEXECALWAYS, MACROEXECNEVER, MACROEXECNORMAL = 2, 1, 0
+ BASEDOCUMENT, CALCDOCUMENT, DRAWDOCUMENT, IMPRESSDOCUMENT, MATHDOCUMENT, WRITERDOCUMENT = \
+ 'Base', 'Calc', 'Draw', 'Impress', 'Math', 'Writer'
+
+ @property
+ def ActiveWindow(self):
+ return self.ExecMethod(self.vbMethod, 'ActiveWindow')
+ activeWindow, activewindow = ActiveWindow, ActiveWindow
+
+ def Activate(self, windowname = ''):
+ return self.ExecMethod(self.vbMethod, 'Activate', windowname)
+
+ def CreateBaseDocument(self, filename, embeddeddatabase = 'HSQLDB', registrationname = '', calcfilename = ''):
+ return self.ExecMethod(self.vbMethod, 'CreateBaseDocument', filename, embeddeddatabase, registrationname,
+ calcfilename)
+
+ def CreateDocument(self, documenttype = '', templatefile = '', hidden = False):
+ return self.ExecMethod(self.vbMethod, 'CreateDocument', documenttype, templatefile, hidden)
+
+ def Documents(self):
+ return self.ExecMethod(self.vbMethod, 'Documents')
+
+ def GetDocument(self, windowname = ''):
+ return self.ExecMethod(self.vbMethod, 'GetDocument', windowname)
+
+ def Maximize(self, windowname = ''):
+ return self.ExecMethod(self.vbMethod, 'Maximize', windowname)
+
+ def Minimize(self, windowname = ''):
+ return self.ExecMethod(self.vbMethod, 'Minimize', windowname)
+
+ def OpenBaseDocument(self, filename = '', registrationname = '', macroexecution = MACROEXECNORMAL):
+ return self.ExecMethod(self.vbMethod, 'OpenBaseDocument', filename, registrationname, macroexecution)
+
+ def OpenDocument(self, filename, password = '', readonly = False, hidden = False,
+ macroexecution = MACROEXECNORMAL, filtername = '', filteroptions = ''):
+ return self.ExecMethod(self.vbMethod, 'OpenDocument', filename, password, readonly, hidden,
+ macroexecution, filtername, filteroptions)
+
+ def Resize(self, left = -1, top = -1, width = -1, height = -1):
+ return self.ExecMethod(self.vbMethod, 'Resize', left, top, width, height)
+
+ def RunCommand(self, command, *args, **kwargs):
+ params = tuple(list(args) + ScriptForge.unpack_args(kwargs))
+ if len(params) == 0:
+ params = (command,) + (None,)
+ else:
+ params = (command,) + params
+ return self.SIMPLEEXEC('@SF_UI.RunCommand', params)
+
+ def SetStatusbar(self, text = '', percentage = -1):
+ return self.ExecMethod(self.vbMethod, 'SetStatusbar', text, percentage)
+
+ def ShowProgressBar(self, title = '', text = '', percentage = -1):
+ # From Python, the current XComponentContext must be added as last argument
+ return self.ExecMethod(self.vbMethod, 'ShowProgressBar', title, text, percentage,
+ ScriptForge.componentcontext)
+
+ def WindowExists(self, windowname):
+ return self.ExecMethod(self.vbMethod, 'WindowExists', windowname)
+
+
+# #####################################################################################################################
+# SFDatabases CLASS (alias of SFDatabases Basic library) ###
+# #####################################################################################################################
+class SFDatabases:
+ """
+ The SFDatabases class manages databases embedded in or connected to Base documents
+ """
+ pass
+
+ # #########################################################################
+ # SF_Database CLASS
+ # #########################################################################
+ class SF_Database(SFServices):
+ """
+ Each instance of the current class represents a single database, with essentially its tables, queries
+ and data
+ The exchanges with the database are done in SQL only.
+ To make them more readable, use optionally square brackets to surround table/query/field names
+ instead of the (RDBMS-dependent) normal surrounding character.
+ SQL statements may be run in direct or indirect mode. In direct mode the statement is transferred literally
+ without syntax checking nor review to the database engine.
+ """
+ # Mandatory class properties for service registration
+ serviceimplementation = 'basic'
+ servicename = 'SFDatabases.Database'
+ servicesynonyms = ('database', 'sfdatabases.database')
+ serviceproperties = dict(Queries = False, Tables = False, XConnection = False, XMetaData = False)
+
+ @classmethod
+ def ReviewServiceArgs(cls, filename = '', registrationname = '', readonly = True, user = '', password = ''):
+ """
+ Transform positional and keyword arguments into positional only
+ """
+ return filename, registrationname, readonly, user, password
+
+ def CloseDatabase(self):
+ return self.ExecMethod(self.vbMethod, 'CloseDatabase')
+
+ def DAvg(self, expression, tablename, criteria = ''):
+ return self.ExecMethod(self.vbMethod, 'DAvg', expression, tablename, criteria)
+
+ def DCount(self, expression, tablename, criteria = ''):
+ return self.ExecMethod(self.vbMethod, 'DCount', expression, tablename, criteria)
+
+ def DLookup(self, expression, tablename, criteria = '', orderclause = ''):
+ return self.ExecMethod(self.vbMethod, 'DLookup', expression, tablename, criteria, orderclause)
+
+ def DMax(self, expression, tablename, criteria = ''):
+ return self.ExecMethod(self.vbMethod, 'DMax', expression, tablename, criteria)
+
+ def DMin(self, expression, tablename, criteria = ''):
+ return self.ExecMethod(self.vbMethod, 'DMin', expression, tablename, criteria)
+
+ def DSum(self, expression, tablename, criteria = ''):
+ return self.ExecMethod(self.vbMethod, 'DSum', expression, tablename, criteria)
+
+ def GetRows(self, sqlcommand, directsql = False, header = False, maxrows = 0):
+ return self.ExecMethod(self.vbMethod + self.flgArrayRet, 'GetRows', sqlcommand, directsql, header, maxrows)
+
+ def RunSql(self, sqlcommand, directsql = False):
+ return self.ExecMethod(self.vbMethod, 'RunSql', sqlcommand, directsql)
+
+
+# #####################################################################################################################
+# SFDialogs CLASS (alias of SFDialogs Basic library) ###
+# #####################################################################################################################
+class SFDialogs:
+ """
+ The SFDialogs class manages dialogs defined with the Basic IDE
+ """
+ pass
+
+ # #########################################################################
+ # SF_Dialog CLASS
+ # #########################################################################
+ class SF_Dialog(SFServices):
+ """
+ Each instance of the current class represents a single dialog box displayed to the user.
+ The dialog box must have been designed and defined with the Basic IDE previously.
+ From a Python script, a dialog box can be displayed in modal or in non-modal modes.
+
+ In modal mode, the box is displayed and the execution of the macro process is suspended
+ until one of the OK or Cancel buttons is pressed. In the meantime, other user actions
+ executed on the box can trigger specific actions.
+
+ In non-modal mode, the floating dialog remains displayed until the dialog is terminated
+ by code (Terminate()) or until the LibreOffice application stops.
+ """
+ # Mandatory class properties for service registration
+ serviceimplementation = 'basic'
+ servicename = 'SFDialogs.Dialog'
+ servicesynonyms = ('dialog', 'sfdialogs.dialog')
+ serviceproperties = dict(Caption = True, Height = True, Modal = False, Name = False,
+ OnFocusGained = False, OnFocusLost = False, OnKeyPressed = False,
+ OnKeyReleased = False, OnMouseDragged = False, OnMouseEntered = False,
+ OnMouseExited = False, OnMouseMoved = False, OnMousePressed = False,
+ OnMouseReleased = False,
+ Page = True, Visible = True, Width = True, XDialogModel = False, XDialogView = False)
+ # Class constants used together with the Execute() method
+ OKBUTTON, CANCELBUTTON = 1, 0
+
+ @classmethod
+ def ReviewServiceArgs(cls, container = '', library = 'Standard', dialogname = ''):
+ """
+ Transform positional and keyword arguments into positional only
+ Add the XComponentContext as last argument
+ """
+ return container, library, dialogname, ScriptForge.componentcontext
+
+ # Methods potentially executed while the dialog is in execution require the flgHardCode flag
+ def Activate(self):
+ return self.ExecMethod(self.vbMethod + self.flgHardCode, 'Activate')
+
+ def Center(self, parent = ScriptForge.cstSymMissing):
+ parentclasses = (SFDocuments.SF_Document, SFDocuments.SF_Base, SFDocuments.SF_Calc, SFDocuments.SF_Writer,
+ SFDialogs.SF_Dialog)
+ parentobj = parent.objectreference if isinstance(parent, parentclasses) else parent
+ return self.ExecMethod(self.vbMethod + self.flgObject + self.flgHardCode, 'Center', parentobj)
+
+ def Controls(self, controlname = ''):
+ return self.ExecMethod(self.vbMethod + self.flgArrayRet + self.flgHardCode, 'Controls', controlname)
+
+ def EndExecute(self, returnvalue):
+ return self.ExecMethod(self.vbMethod + self.flgHardCode, 'EndExecute', returnvalue)
+
+ def Execute(self, modal = True):
+ return self.ExecMethod(self.vbMethod + self.flgHardCode, 'Execute', modal)
+
+ def GetTextsFromL10N(self, l10n):
+ l10nobj = l10n.objectreference if isinstance(l10n, SFScriptForge.SF_L10N) else l10n
+ return self.ExecMethod(self.vbMethod + self.flgObject, 'GetTextsFromL10N', l10nobj)
+
+ def Resize(self, left = -1, top = -1, width = -1, height = -1):
+ return self.ExecMethod(self.vbMethod + self.flgHardCode, 'Resize', left, top, width, height)
+
+ def Terminate(self):
+ return self.ExecMethod(self.vbMethod, 'Terminate')
+
+ # #########################################################################
+ # SF_DialogControl CLASS
+ # #########################################################################
+ class SF_DialogControl(SFServices):
+ """
+ Each instance of the current class represents a single control within a dialog box.
+ The focus is clearly set on getting and setting the values displayed by the controls of the dialog box,
+ not on their formatting.
+ A special attention is given to controls with type TreeControl.
+ """
+ # Mandatory class properties for service registration
+ serviceimplementation = 'basic'
+ servicename = 'SFDialogs.DialogControl'
+ servicesynonyms = ()
+ serviceproperties = dict(Cancel = True, Caption = True, ControlType = False, CurrentNode = True,
+ Default = True, Enabled = True, Format = True, ListCount = False,
+ ListIndex = True, Locked = True, MultiSelect = True, Name = False,
+ OnActionPerformed = False, OnAdjustmentValueChanged = False, OnFocusGained = False,
+ OnFocusLost = False, OnItemStateChanged = False, OnKeyPressed = False,
+ OnKeyReleased = False, OnMouseDragged = False, OnMouseEntered = False,
+ OnMouseExited = False, OnMouseMoved = False, OnMousePressed = False,
+ OnMouseReleased = False, OnNodeExpanded = True, OnNodeSelected = True,
+ OnTextChanged = False, Page = True, Parent = False, Picture = True,
+ RootNode = False, RowSource = True, Text = False, TipText = True,
+ TripleState = True, Value = True, Visible = True,
+ XControlModel = False, XControlView = False, XGridColumnModel = False,
+ XGridDataModel = False, XTreeDataModel = False)
+
+ # Root related properties do not start with X and, nevertheless, return a UNO object
+ @property
+ def CurrentNode(self):
+ return self.EXEC(self.objectreference, self.vbGet + self.flgUno, 'CurrentNode')
+
+ @property
+ def RootNode(self):
+ return self.EXEC(self.objectreference, self.vbGet + self.flgUno, 'RootNode')
+
+ def AddSubNode(self, parentnode, displayvalue, datavalue = ScriptForge.cstSymEmpty):
+ return self.ExecMethod(self.vbMethod + self.flgUno, 'AddSubNode', parentnode, displayvalue, datavalue)
+
+ def AddSubTree(self, parentnode, flattree, withdatavalue = False):
+ return self.ExecMethod(self.vbMethod, 'AddSubTree', parentnode, flattree, withdatavalue)
+
+ def CreateRoot(self, displayvalue, datavalue = ScriptForge.cstSymEmpty):
+ return self.ExecMethod(self.vbMethod + self.flgUno, 'CreateRoot', displayvalue, datavalue)
+
+ def FindNode(self, displayvalue, datavalue = ScriptForge.cstSymEmpty, casesensitive = False):
+ return self.ExecMethod(self.vbMethod + self.flgUno, 'FindNode', displayvalue, datavalue, casesensitive)
+
+ def SetFocus(self):
+ return self.ExecMethod(self.vbMethod, 'SetFocus')
+
+ def SetTableData(self, dataarray, widths = (1,), alignments = ''):
+ return self.ExecMethod(self.vbMethod + self.flgArrayArg, 'SetTableData', dataarray, widths, alignments)
+
+ def WriteLine(self, line = ''):
+ return self.ExecMethod(self.vbMethod, 'WriteLine', line)
+
+
+# #####################################################################################################################
+# SFDocuments CLASS (alias of SFDocuments Basic library) ###
+# #####################################################################################################################
+class SFDocuments:
+ """
+ The SFDocuments class gathers a number of classes, methods and properties making easy
+ managing and manipulating LibreOffice documents
+ """
+ pass
+
+ # #########################################################################
+ # SF_Document CLASS
+ # #########################################################################
+ class SF_Document(SFServices):
+ """
+ The methods and properties are generic for all types of documents: they are combined in the
+ current SF_Document class
+ - saving, closing documents
+ - accessing their standard or custom properties
+ Specific properties and methods are implemented in the concerned subclass(es) SF_Calc, SF_Base, ...
+ """
+ # Mandatory class properties for service registration
+ serviceimplementation = 'basic'
+ servicename = 'SFDocuments.Document'
+ servicesynonyms = ('document', 'sfdocuments.document')
+ serviceproperties = dict(Description = True, DocumentType = False, ExportFilters = False, ImportFilters = False,
+ IsBase = False, IsCalc = False, IsDraw = False, IsImpress = False, IsMath = False,
+ IsWriter = False, Keywords = True, Readonly = False, Subject = True, Title = True,
+ XComponent = False)
+ # Force for each property to get its value from Basic - due to intense interactivity with user
+ forceGetProperty = True
+
+ @classmethod
+ def ReviewServiceArgs(cls, windowname = ''):
+ """
+ Transform positional and keyword arguments into positional only
+ """
+ return windowname,
+
+ def Activate(self):
+ return self.ExecMethod(self.vbMethod, 'Activate')
+
+ def CloseDocument(self, saveask = True):
+ return self.ExecMethod(self.vbMethod, 'CloseDocument', saveask)
+
+ def CreateMenu(self, menuheader, before = '', submenuchar = '>'):
+ return self.ExecMethod(self.vbMethod, 'CreateMenu', menuheader, before, submenuchar)
+
+ def ExportAsPDF(self, filename, overwrite = False, pages = '', password = '', watermark = ''):
+ return self.ExecMethod(self.vbMethod, 'ExportAsPDF', filename, overwrite, pages, password, watermark)
+
+ def PrintOut(self, pages = '', copies = 1):
+ return self.ExecMethod(self.vbMethod, 'PrintOut', pages, copies)
+
+ def RemoveMenu(self, menuheader):
+ return self.ExecMethod(self.vbMethod, 'RemoveMenu', menuheader)
+
+ def RunCommand(self, command, *args, **kwargs):
+ params = tuple([command] + list(args) + ScriptForge.unpack_args(kwargs))
+ return self.ExecMethod(self.vbMethod, 'RunCommand', *params)
+
+ def Save(self):
+ return self.ExecMethod(self.vbMethod, 'Save')
+
+ def SaveAs(self, filename, overwrite = False, password = '', filtername = '', filteroptions = ''):
+ return self.ExecMethod(self.vbMethod, 'SaveAs', filename, overwrite, password, filtername, filteroptions)
+
+ def SaveCopyAs(self, filename, overwrite = False, password = '', filtername = '', filteroptions = ''):
+ return self.ExecMethod(self.vbMethod, 'SaveCopyAs', filename, overwrite,
+ password, filtername, filteroptions)
+
+ def SetPrinter(self, printer = '', orientation = '', paperformat = ''):
+ return self.ExecMethod(self.vbMethod, 'SetPrinter', printer, orientation, paperformat)
+
+ # #########################################################################
+ # SF_Base CLASS
+ # #########################################################################
+ class SF_Base(SF_Document, SFServices):
+ """
+ The SF_Base module is provided mainly to block parent properties that are NOT applicable to Base documents
+ In addition, it provides methods to identify form documents and access their internal forms
+ (read more elsewhere (the "SFDocuments.Form" service) about this subject)
+ """
+ # Mandatory class properties for service registration
+ serviceimplementation = 'basic'
+ servicename = 'SFDocuments.Base'
+ servicesynonyms = ('base', 'scriptforge.base')
+ serviceproperties = dict(DocumentType = False, IsBase = False, IsCalc = False,
+ IsDraw = False, IsImpress = False, IsMath = False, IsWriter = False,
+ XComponent = False)
+
+ @classmethod
+ def ReviewServiceArgs(cls, windowname = ''):
+ """
+ Transform positional and keyword arguments into positional only
+ """
+ return windowname,
+
+ def CloseDocument(self, saveask = True):
+ return self.ExecMethod(self.vbMethod, 'CloseDocument', saveask)
+
+ def CloseFormDocument(self, formdocument):
+ return self.ExecMethod(self.vbMethod, 'CloseFormDocument', formdocument)
+
+ def FormDocuments(self):
+ return self.ExecMethod(self.vbMethod + self.flgArrayRet, 'FormDocuments')
+
+ def Forms(self, formdocument, form = ''):
+ return self.ExecMethod(self.vbMethod + self.flgArrayRet, 'Forms', formdocument, form)
+
+ def GetDatabase(self, user = '', password = ''):
+ return self.ExecMethod(self.vbMethod, 'GetDatabase', user, password)
+
+ def IsLoaded(self, formdocument):
+ return self.ExecMethod(self.vbMethod, 'IsLoaded', formdocument)
+
+ def OpenFormDocument(self, formdocument, designmode = False):
+ return self.ExecMethod(self.vbMethod, 'OpenFormDocument', formdocument, designmode)
+
+ def PrintOut(self, formdocument, pages = '', copies = 1):
+ return self.ExecMethod(self.vbMethod, 'PrintOut', formdocument, pages, copies)
+
+ def SetPrinter(self, formdocument = '', printer = '', orientation = '', paperformat = ''):
+ return self.ExecMethod(self.vbMethod, 'SetPrinter', formdocument, printer, orientation, paperformat)
+
+ # #########################################################################
+ # SF_Calc CLASS
+ # #########################################################################
+ class SF_Calc(SF_Document, SFServices):
+ """
+ The SF_Calc module is focused on :
+ - management (copy, insert, move, ...) of sheets within a Calc document
+ - exchange of data between Basic data structures and Calc ranges of values
+ """
+ # Mandatory class properties for service registration
+ serviceimplementation = 'basic'
+ servicename = 'SFDocuments.Calc'
+ servicesynonyms = ('calc', 'sfdocuments.calc')
+ serviceproperties = dict(CurrentSelection = True, Sheets = False,
+ Description = True, DocumentType = False, ExportFilters = False, ImportFilters = False,
+ IsBase = False, IsCalc = False, IsDraw = False, IsImpress = False, IsMath = False,
+ IsWriter = False, Keywords = True, Readonly = False, Subject = True, Title = True,
+ XComponent = False)
+ # Force for each property to get its value from Basic - due to intense interactivity with user
+ forceGetProperty = True
+
+ @classmethod
+ def ReviewServiceArgs(cls, windowname = ''):
+ """
+ Transform positional and keyword arguments into positional only
+ """
+ return windowname,
+
+ # Next functions are implemented in Basic as read-only properties with 1 argument
+ def FirstCell(self, rangename):
+ return self.GetProperty('FirstCell', rangename)
+
+ def FirstColumn(self, rangename):
+ return self.GetProperty('FirstColumn', rangename)
+
+ def FirstRow(self, rangename):
+ return self.GetProperty('FirstRow', rangename)
+
+ def Height(self, rangename):
+ return self.GetProperty('Height', rangename)
+
+ def LastCell(self, rangename):
+ return self.GetProperty('LastCell', rangename)
+
+ def LastColumn(self, rangename):
+ return self.GetProperty('LastColumn', rangename)
+
+ def LastRow(self, rangename):
+ return self.GetProperty('LastRow', rangename)
+
+ def Range(self, rangename):
+ return self.GetProperty('Range', rangename)
+
+ def Region(self, rangename):
+ return self.GetProperty('Region', rangename)
+
+ def Sheet(self, sheetname):
+ return self.GetProperty('Sheet', sheetname)
+
+ def SheetName(self, rangename):
+ return self.GetProperty('SheetName', rangename)
+
+ def Width(self, rangename):
+ return self.GetProperty('Width', rangename)
+
+ def XCellRange(self, rangename):
+ return self.ExecMethod(self.vbGet + self.flgUno, 'XCellRange', rangename)
+
+ def XSheetCellCursor(self, rangename):
+ return self.ExecMethod(self.vbGet + self.flgUno, 'XSheetCellCursor', rangename)
+
+ def XSpreadsheet(self, sheetname):
+ return self.ExecMethod(self.vbGet + self.flgUno, 'XSpreadsheet', sheetname)
+
+ # Usual methods
+ def A1Style(self, row1, column1, row2 = 0, column2 = 0, sheetname = '~'):
+ return self.ExecMethod(self.vbMethod, 'A1Style', row1, column1, row2, column2, sheetname)
+
+ def Activate(self, sheetname = ''):
+ return self.ExecMethod(self.vbMethod, 'Activate', sheetname)
+
+ def Charts(self, sheetname, chartname = ''):
+ return self.ExecMethod(self.vbMethod + self.flgArrayRet, 'Charts', sheetname, chartname)
+
+ def ClearAll(self, range):
+ return self.ExecMethod(self.vbMethod, 'ClearAll', range)
+
+ def ClearFormats(self, range):
+ return self.ExecMethod(self.vbMethod, 'ClearFormats', range)
+
+ def ClearValues(self, range):
+ return self.ExecMethod(self.vbMethod, 'ClearValues', range)
+
+ def CompactLeft(self, range, wholecolumn = False, filterformula = ''):
+ return self.ExecMethod(self.vbMethod, 'CompactLeft', range, wholecolumn, filterformula)
+
+ def CompactUp(self, range, wholerow = False, filterformula = ''):
+ return self.ExecMethod(self.vbMethod, 'CompactUp', range, wholerow, filterformula)
+
+ def CopySheet(self, sheetname, newname, beforesheet = 32768):
+ sheet = (sheetname.objectreference if isinstance(sheetname, SFDocuments.SF_CalcReference) else sheetname)
+ return self.ExecMethod(self.vbMethod + self.flgObject, 'CopySheet', sheet, newname, beforesheet)
+
+ def CopySheetFromFile(self, filename, sheetname, newname, beforesheet = 32768):
+ sheet = (sheetname.objectreference if isinstance(sheetname, SFDocuments.SF_CalcReference) else sheetname)
+ return self.ExecMethod(self.vbMethod + self.flgObject, 'CopySheetFromFile',
+ filename, sheet, newname, beforesheet)
+
+ def CopyToCell(self, sourcerange, destinationcell):
+ range = (sourcerange.objectreference if isinstance(sourcerange, SFDocuments.SF_CalcReference)
+ else sourcerange)
+ return self.ExecMethod(self.vbMethod + self.flgObject, 'CopyToCell', range, destinationcell)
+
+ def CopyToRange(self, sourcerange, destinationrange):
+ range = (sourcerange.objectreference if isinstance(sourcerange, SFDocuments.SF_CalcReference)
+ else sourcerange)
+ return self.ExecMethod(self.vbMethod + self.flgObject, 'CopyToRange', range, destinationrange)
+
+ def CreateChart(self, chartname, sheetname, range, columnheader = False, rowheader = False):
+ return self.ExecMethod(self.vbMethod, 'CreateChart', chartname, sheetname, range, columnheader, rowheader)
+
+ def CreatePivotTable(self, pivottablename, sourcerange, targetcell, datafields = ScriptForge.cstSymEmpty,
+ rowfields = ScriptForge.cstSymEmpty, columnfields = ScriptForge.cstSymEmpty,
+ filterbutton = True, rowtotals = True, columntotals = True):
+ return self.ExecMethod(self.vbMethod, 'CreatePivotTable', pivottablename, sourcerange, targetcell,
+ datafields, rowfields, columnfields, filterbutton, rowtotals, columntotals)
+
+ def DAvg(self, range):
+ return self.ExecMethod(self.vbMethod, 'DAvg', range)
+
+ def DCount(self, range):
+ return self.ExecMethod(self.vbMethod, 'DCount', range)
+
+ def DMax(self, range):
+ return self.ExecMethod(self.vbMethod, 'DMax', range)
+
+ def DMin(self, range):
+ return self.ExecMethod(self.vbMethod, 'DMin', range)
+
+ def DSum(self, range):
+ return self.ExecMethod(self.vbMethod, 'DSum', range)
+
+ def ExportRangeToFile(self, range, filename, imagetype = 'pdf', overwrite = False):
+ return self.ExecMethod(self.vbMethod, 'ExportRangeToFile', range, filename, imagetype, overwrite)
+
+ def Forms(self, sheetname, form = ''):
+ return self.ExecMethod(self.vbMethod + self.flgArrayRet, 'Forms', sheetname, form)
+
+ def GetColumnName(self, columnnumber):
+ return self.ExecMethod(self.vbMethod, 'GetColumnName', columnnumber)
+
+ def GetFormula(self, range):
+ return self.ExecMethod(self.vbMethod + self.flgArrayRet, 'GetFormula', range)
+
+ def GetValue(self, range):
+ return self.ExecMethod(self.vbMethod + self.flgArrayRet, 'GetValue', range)
+
+ def ImportFromCSVFile(self, filename, destinationcell, filteroptions = ScriptForge.cstSymEmpty):
+ return self.ExecMethod(self.vbMethod, 'ImportFromCSVFile', filename, destinationcell, filteroptions)
+
+ def ImportFromDatabase(self, filename = '', registrationname = '', destinationcell = '', sqlcommand = '',
+ directsql = False):
+ return self.ExecMethod(self.vbMethod, 'ImportFromDatabase', filename, registrationname,
+ destinationcell, sqlcommand, directsql)
+
+ def InsertSheet(self, sheetname, beforesheet = 32768):
+ return self.ExecMethod(self.vbMethod, 'InsertSheet', sheetname, beforesheet)
+
+ def MoveRange(self, source, destination):
+ return self.ExecMethod(self.vbMethod, 'MoveRange', source, destination)
+
+ def MoveSheet(self, sheetname, beforesheet = 32768):
+ return self.ExecMethod(self.vbMethod, 'MoveSheet', sheetname, beforesheet)
+
+ def Offset(self, range, rows = 0, columns = 0, height = ScriptForge.cstSymEmpty,
+ width = ScriptForge.cstSymEmpty):
+ return self.ExecMethod(self.vbMethod, 'Offset', range, rows, columns, height, width)
+
+ def OpenRangeSelector(self, title = '', selection = '~', singlecell = False, closeafterselect = True):
+ return self.ExecMethod(self.vbMethod, 'OpenRangeSelector', title, selection, singlecell, closeafterselect)
+
+ def Printf(self, inputstr, range, tokencharacter = '%'):
+ return self.ExecMethod(self.vbMethod, 'Printf', inputstr, range, tokencharacter)
+
+ def PrintOut(self, sheetname = '~', pages = '', copies = 1):
+ return self.ExecMethod(self.vbMethod, 'PrintOut', sheetname, pages, copies)
+
+ def RemoveSheet(self, sheetname):
+ return self.ExecMethod(self.vbMethod, 'RemoveSheet', sheetname)
+
+ def RenameSheet(self, sheetname, newname):
+ return self.ExecMethod(self.vbMethod, 'RenameSheet', sheetname, newname)
+
+ def SetArray(self, targetcell, value):
+ return self.ExecMethod(self.vbMethod + self.flgArrayArg, 'SetArray', targetcell, value)
+
+ def SetCellStyle(self, targetrange, style):
+ return self.ExecMethod(self.vbMethod, 'SetCellStyle', targetrange, style)
+
+ def SetFormula(self, targetrange, formula):
+ return self.ExecMethod(self.vbMethod + self.flgArrayArg, 'SetFormula', targetrange, formula)
+
+ def SetValue(self, targetrange, value):
+ return self.ExecMethod(self.vbMethod + self.flgArrayArg, 'SetValue', targetrange, value)
+
+ def ShiftDown(self, range, wholerow = False, rows = 0):
+ return self.ExecMethod(self.vbMethod, 'ShiftDown', range, wholerow, rows)
+
+ def ShiftLeft(self, range, wholecolumn = False, columns = 0):
+ return self.ExecMethod(self.vbMethod, 'ShiftLeft', range, wholecolumn, columns)
+
+ def ShiftRight(self, range, wholecolumn = False, columns = 0):
+ return self.ExecMethod(self.vbMethod, 'ShiftRight', range, wholecolumn, columns)
+
+ def ShiftUp(self, range, wholerow = False, rows = 0):
+ return self.ExecMethod(self.vbMethod, 'ShiftUp', range, wholerow, rows)
+
+ def SortRange(self, range, sortkeys, sortorder = 'ASC', destinationcell = ScriptForge.cstSymEmpty,
+ containsheader = False, casesensitive = False, sortcolumns = False):
+ return self.ExecMethod(self.vbMethod, 'SortRange', range, sortkeys, sortorder, destinationcell,
+ containsheader, casesensitive, sortcolumns)
+
+ # #########################################################################
+ # SF_CalcReference CLASS
+ # #########################################################################
+ class SF_CalcReference(SFServices):
+ """
+ The SF_CalcReference class has as unique role to hold sheet and range references.
+ They are implemented in Basic as Type ... End Type data structures
+ """
+ # Mandatory class properties for service registration
+ serviceimplementation = 'basic'
+ servicename = 'SFDocuments.CalcReference'
+ servicesynonyms = ()
+ serviceproperties = dict()
+
+ # #########################################################################
+ # SF_Chart CLASS
+ # #########################################################################
+ class SF_Chart(SFServices):
+ """
+ The SF_Chart module is focused on the description of chart documents
+ stored in Calc sheets.
+ With this service, many chart types and chart characteristics available
+ in the user interface can be read or modified.
+ """
+ # Mandatory class properties for service registration
+ serviceimplementation = 'basic'
+ servicename = 'SFDocuments.Chart'
+ servicesynonyms = ()
+ serviceproperties = dict(ChartType = True, Deep = True, Dim3D = True, Exploded = True, Filled = True,
+ Legend = True, Percent = True, Stacked = True, Title = True,
+ XChartObj = False, XDiagram = False, XShape = False, XTableChart = False,
+ XTitle = True, YTitle = True)
+
+ def Resize(self, xpos = -1, ypos = -1, width = -1, height = -1):
+ return self.ExecMethod(self.vbMethod, 'Resize', xpos, ypos, width, height)
+
+ def ExportToFile(self, filename, imagetype = 'png', overwrite = False):
+ return self.ExecMethod(self.vbMethod, 'ExportToFile', filename, imagetype, overwrite)
+
+ # #########################################################################
+ # SF_Form CLASS
+ # #########################################################################
+ class SF_Form(SFServices):
+ """
+ Management of forms defined in LibreOffice documents. Supported types are Base, Calc and Writer documents.
+ It includes the management of subforms
+ Each instance of the current class represents a single form or a single subform
+ A form may optionally be (understand "is often") linked to a data source manageable with
+ the SFDatabases.Database service. The current service offers a rapid access to that service.
+ """
+ # Mandatory class properties for service registration
+ serviceimplementation = 'basic'
+ servicename = 'SFDocuments.Form'
+ servicesynonyms = ()
+ serviceproperties = dict(AllowDeletes = True, AllowInserts = True, AllowUpdates = True, BaseForm = False,
+ Bookmark = True, CurrentRecord = True, Filter = True, LinkChildFields = False,
+ LinkParentFields = False, Name = False,
+ OnApproveCursorMove = True, OnApproveParameter = True, OnApproveReset = True,
+ OnApproveRowChange = True, OnApproveSubmit = True, OnConfirmDelete = True,
+ OnCursorMoved = True, OnErrorOccurred = True, OnLoaded = True, OnReloaded = True,
+ OnReloading = True, OnResetted = True, OnRowChanged = True, OnUnloaded = True,
+ OnUnloading = True,
+ OrderBy = True, Parent = False, RecordSource = True, XForm = False)
+
+ def Activate(self):
+ return self.ExecMethod(self.vbMethod, 'Activate')
+
+ def CloseFormDocument(self):
+ return self.ExecMethod(self.vbMethod, 'CloseFormDocument')
+
+ def Controls(self, controlname = ''):
+ return self.ExecMethod(self.vbMethod + self.flgArrayRet, 'Controls', controlname)
+
+ def GetDatabase(self, user = '', password = ''):
+ return self.ExecMethod(self.vbMethod, 'GetDatabase', user, password)
+
+ def MoveFirst(self):
+ return self.ExecMethod(self.vbMethod, 'MoveFirst')
+
+ def MoveLast(self):
+ return self.ExecMethod(self.vbMethod, 'MoveLast')
+
+ def MoveNew(self):
+ return self.ExecMethod(self.vbMethod, 'MoveNew')
+
+ def MoveNext(self, offset = 1):
+ return self.ExecMethod(self.vbMethod, 'MoveNext', offset)
+
+ def MovePrevious(self, offset = 1):
+ return self.ExecMethod(self.vbMethod, 'MovePrevious', offset)
+
+ def Requery(self):
+ return self.ExecMethod(self.vbMethod, 'Requery')
+
+ def Subforms(self, subform = ''):
+ return self.ExecMethod(self.vbMethod + self.flgArrayRet, 'Subforms', subform)
+
+ # #########################################################################
+ # SF_FormControl CLASS
+ # #########################################################################
+ class SF_FormControl(SFServices):
+ """
+ Manage the controls belonging to a form or subform stored in a document.
+ Each instance of the current class represents a single control within a form, a subform or a tablecontrol.
+ A prerequisite is that all controls within the same form, subform or tablecontrol must have
+ a unique name.
+ """
+ # Mandatory class properties for service registration
+ serviceimplementation = 'basic'
+ servicename = 'SFDocuments.FormControl'
+ servicesynonyms = ()
+ serviceproperties = dict(Action = True, Caption = True, ControlSource = False, ControlType = False,
+ Default = True, DefaultValue = True, Enabled = True, Format = True,
+ ListCount = False, ListIndex = True, ListSource = True, ListSourceType = True,
+ Locked = True, MultiSelect = True, Name = False,
+ OnActionPerformed = True, OnAdjustmentValueChanged = True,
+ OnApproveAction = True, OnApproveReset = True, OnApproveUpdate = True,
+ OnChanged = True, OnErrorOccurred = True, OnFocusGained = True, OnFocusLost = True,
+ OnItemStateChanged = True, OnKeyPressed = True, OnKeyReleased = True,
+ OnMouseDragged = True, OnMouseEntered = True, OnMouseExited = True,
+ OnMouseMoved = True, OnMousePressed = True, OnMouseReleased = True, OnResetted = True,
+ OnTextChanged = True, OnUpdated = True, Parent = False, Picture = True,
+ Required = True, Text = False, TipText = True, TripleState = True, Value = True,
+ Visible = True, XControlModel = False, XControlView = False)
+
+ def Controls(self, controlname = ''):
+ return self.ExecMethod(self.vbMethod + self.flgArrayRet, 'Controls', controlname)
+
+ def SetFocus(self):
+ return self.ExecMethod(self.vbMethod, 'SetFocus')
+
+ # #########################################################################
+ # SF_Writer CLASS
+ # #########################################################################
+ class SF_Writer(SF_Document, SFServices):
+ """
+ The SF_Writer module is focused on :
+ - TBD
+ """
+ # Mandatory class properties for service registration
+ serviceimplementation = 'basic'
+ servicename = 'SFDocuments.Writer'
+ servicesynonyms = ('writer', 'sfdocuments.writer')
+ serviceproperties = dict(Description = True, DocumentType = False, ExportFilters = False, ImportFilters = False,
+ IsBase = False, IsCalc = False, IsDraw = False, IsImpress = False, IsMath = False,
+ IsWriter = False, Keywords = True, Readonly = False, Subject = True, Title = True,
+ XComponent = False)
+ # Force for each property to get its value from Basic - due to intense interactivity with user
+ forceGetProperty = True
+
+ @classmethod
+ def ReviewServiceArgs(cls, windowname = ''):
+ """
+ Transform positional and keyword arguments into positional only
+ """
+ return windowname,
+
+ def Forms(self, form = ''):
+ return self.ExecMethod(self.vbMethod + self.flgArrayRet, 'Forms', form)
+
+ def PrintOut(self, pages = '', copies = 1, printbackground = True, printblankpages = False,
+ printevenpages = True, printoddpages = True, printimages = True):
+ return self.ExecMethod(self.vbMethod, 'PrintOut', pages, copies, printbackground, printblankpages,
+ printevenpages, printoddpages, printimages)
+
+
+# #####################################################################################################################
+# SFWidgets CLASS (alias of SFWidgets Basic library) ###
+# #####################################################################################################################
+class SFWidgets:
+ """
+ The SFWidgets class manages toolbars and popup menus
+ """
+ pass
+
+ # #########################################################################
+ # SF_Menu CLASS
+ # #########################################################################
+ class SF_Menu(SFServices):
+ """
+ Display a menu in the menubar of a document or a form document.
+ After use, the menu will not be saved neither in the application settings, nor in the document.
+ The menu will be displayed, as usual, when its header in the menubar is clicked.
+ When one of its items is selected, there are 3 alternative options:
+ - a UNO command (like ".uno:About") is triggered
+ - a user script is run receiving a standard argument defined in this service
+ - one of above combined with a toggle of the status of the item
+ The menu is described from top to bottom. Each menu item receives a numeric and a string identifier.
+ """
+ # Mandatory class properties for service registration
+ serviceimplementation = 'basic'
+ servicename = 'SFWidgets.Menu'
+ servicesynonyms = ('menu', 'sfwidgets.menu')
+ serviceproperties = dict(ShortcutCharacter = False, SubmenuCharacter = False)
+
+ def AddCheckBox(self, menuitem, name = '', status = False, icon = '', tooltip = '',
+ command = '', script = ''):
+ return self.ExecMethod(self.vbMethod, 'AddCheckBox', menuitem, name, status, icon, tooltip,
+ command, script)
+
+ def AddItem(self, menuitem, name = '', icon = '', tooltip = '', command = '', script = ''):
+ return self.ExecMethod(self.vbMethod, 'AddItem', menuitem, name, icon, tooltip, command, script)
+
+ def AddRadioButton(self, menuitem, name = '', status = False, icon = '', tooltip = '',
+ command = '', script = ''):
+ return self.ExecMethod(self.vbMethod, 'AddRadioButton', menuitem, name, status, icon, tooltip,
+ command, script)
+
+ # #########################################################################
+ # SF_PopupMenu CLASS
+ # #########################################################################
+ class SF_PopupMenu(SFServices):
+ """
+ Display a popup menu anywhere and any time.
+ A popup menu is usually triggered by a mouse action (typically a right-click) on a dialog, a form
+ or one of their controls. In this case the menu will be displayed below the clicked area.
+ When triggered by other events, including in the normal flow of a user script, the script should
+ provide the coordinates of the topleft edge of the menu versus the actual component.
+ The menu is described from top to bottom. Each menu item receives a numeric and a string identifier.
+ The execute() method returns the item selected by the user.
+ """
+ # Mandatory class properties for service registration
+ serviceimplementation = 'basic'
+ servicename = 'SFWidgets.PopupMenu'
+ servicesynonyms = ('popupmenu', 'sfwidgets.popupmenu')
+ serviceproperties = dict(ShortcutCharacter = False, SubmenuCharacter = False)
+
+ @classmethod
+ def ReviewServiceArgs(cls, event = None, x = 0, y = 0, submenuchar = ''):
+ """
+ Transform positional and keyword arguments into positional only
+ """
+ return event, x, y, submenuchar
+
+ def AddCheckBox(self, menuitem, name = '', status = False, icon = '', tooltip = ''):
+ return self.ExecMethod(self.vbMethod, 'AddCheckBox', menuitem, name, status, icon, tooltip)
+
+ def AddItem(self, menuitem, name = '', icon = '', tooltip = ''):
+ return self.ExecMethod(self.vbMethod, 'AddItem', menuitem, name, icon, tooltip)
+
+ def AddRadioButton(self, menuitem, name = '', status = False, icon = '', tooltip = ''):
+ return self.ExecMethod(self.vbMethod, 'AddRadioButton', menuitem, name, status, icon, tooltip)
+
+ def Execute(self, returnid = True):
+ return self.ExecMethod(self.vbMethod, 'Execute', returnid)
+
+
+# ##############################################False##################################################################
+# CreateScriptService() ###
+# #####################################################################################################################
+def CreateScriptService(service, *args, **kwargs):
+ """
+ A service being the name of a collection of properties and methods,
+ this method returns either
+ - the Python object mirror of the Basic object implementing the requested service
+ - the Python object implementing the service itself
+
+ A service may be designated by its official name, stored in its class.servicename
+ or by one of its synonyms stored in its class.servicesynonyms list
+ If the service is not identified, the service creation is delegated to Basic, that might raise an error
+ if still not identified there
+
+ :param service: the name of the service as a string 'library.service' - cased exactly
+ or one of its synonyms
+ :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
+ if ScriptForge.SCRIPTFORGEINITDONE is False:
+ ScriptForge()
+
+ def ResolveSynonyms(servicename):
+ """
+ Synonyms within service names implemented in Python or predefined are resolved here
+ :param servicename: The short name of the service
+ :return: The official service name if found, the argument otherwise
+ """
+ for cls in SFServices.__subclasses__():
+ if servicename.lower() in cls.servicesynonyms:
+ return cls.servicename
+ return servicename
+
+ #
+ # Check the list of available services
+ scriptservice = ResolveSynonyms(service)
+ if scriptservice in ScriptForge.serviceslist:
+ serv = ScriptForge.serviceslist[scriptservice]
+ # Check if the requested service is within the Python world
+ if serv.serviceimplementation == 'python':
+ return serv(*args)
+ # Check if the service is a predefined standard Basic service
+ elif scriptservice in ScriptForge.servicesmodules:
+ return serv(ScriptForge.servicesmodules[scriptservice], classmodule = SFServices.moduleStandard)
+ else:
+ serv = None
+ # The requested service is to be found in the Basic world
+ # Check if the service must review the arguments
+ if serv is not None:
+ if hasattr(serv, 'ReviewServiceArgs'):
+ # ReviewServiceArgs() must be a class method
+ args = serv.ReviewServiceArgs(*args, **kwargs)
+ # Get the service object back from Basic
+ 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
+
+
+createScriptService, createscriptservice = CreateScriptService, CreateScriptService
+
+
+# ######################################################################
+# Lists the scripts, that shall be visible inside the Basic/Python IDE
+# ######################################################################
+
+g_exportedScripts = ()
diff --git a/wizards/source/scriptforge/script.xlb b/wizards/source/scriptforge/script.xlb
new file mode 100644
index 000000000..dc625046f
--- /dev/null
+++ b/wizards/source/scriptforge/script.xlb
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE library:library PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "library.dtd">
+<library:library xmlns:library="http://openoffice.org/2000/library" library:name="ScriptForge" library:readonly="false" library:passwordprotected="false">
+ <library:element library:name="__License"/>
+ <library:element library:name="SF_String"/>
+ <library:element library:name="_CodingConventions"/>
+ <library:element library:name="SF_Timer"/>
+ <library:element library:name="_ModuleModel"/>
+ <library:element library:name="SF_Utils"/>
+ <library:element library:name="SF_Root"/>
+ <library:element library:name="SF_Array"/>
+ <library:element library:name="SF_Services"/>
+ <library:element library:name="SF_Dictionary"/>
+ <library:element library:name="SF_Session"/>
+ <library:element library:name="SF_FileSystem"/>
+ <library:element library:name="SF_TextStream"/>
+ <library:element library:name="SF_L10N"/>
+ <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:element library:name="SF_Region"/>
+</library:library> \ No newline at end of file