summaryrefslogtreecommitdiff
path: root/wizards
diff options
context:
space:
mode:
authorJean-Pierre Ledure <jp@ledure.be>2020-11-05 15:55:39 +0100
committerJean-Pierre Ledure <jp@ledure.be>2020-11-05 15:55:39 +0100
commit09c1bee1f91315fd7901af1804e028f6574228a6 (patch)
tree1ed2b93132dffc8cd73c57943874e59164e67233 /wizards
parente2f66f5ba5d813af97bc4fb5f28cea9d737e25e9 (diff)
ScriptForge - core library
Additional "LibreOffice Macros & Dialogs" library Change-Id: I7380cf3f9ee56b73cfcf7b9e33d0cf50ecb40429
Diffstat (limited to 'wizards')
-rw-r--r--wizards/source/scriptforge/SF_Array.xba2549
-rw-r--r--wizards/source/scriptforge/SF_Dictionary.xba952
-rw-r--r--wizards/source/scriptforge/SF_Exception.xba1107
-rw-r--r--wizards/source/scriptforge/SF_FileSystem.xba2084
-rw-r--r--wizards/source/scriptforge/SF_L10N.xba696
-rw-r--r--wizards/source/scriptforge/SF_Platform.xba281
-rw-r--r--wizards/source/scriptforge/SF_Root.xba822
-rw-r--r--wizards/source/scriptforge/SF_Services.xba607
-rw-r--r--wizards/source/scriptforge/SF_Session.xba918
-rw-r--r--wizards/source/scriptforge/SF_String.xba2642
-rw-r--r--wizards/source/scriptforge/SF_TextStream.xba701
-rw-r--r--wizards/source/scriptforge/SF_Timer.xba463
-rw-r--r--wizards/source/scriptforge/SF_UI.xba1175
-rw-r--r--wizards/source/scriptforge/SF_Utils.xba967
-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/script.xlb21
21 files changed, 16362 insertions, 0 deletions
diff --git a/wizards/source/scriptforge/SF_Array.xba b/wizards/source/scriptforge/SF_Array.xba
new file mode 100644
index 000000000000..914f42269867
--- /dev/null
+++ b/wizards/source/scriptforge/SF_Array.xba
@@ -0,0 +1,2549 @@
+<?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;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&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/DESCTRUCTOR
+
+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 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 = unitialized 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 _
+ ) 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; 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 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; Interprete 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)
+ 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 heapifies
+ 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 000000000000..e84db342fd5b
--- /dev/null
+++ b/wizards/source/scriptforge/SF_Dictionary.xba
@@ -0,0 +1,952 @@
+<?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 Dictionay 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 instanciation 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;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&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/DESCTRUCTOR
+
+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;adress&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 Not SF_Utils._Validate(Value, &quot;Value&quot;) Then GoTo Catch
+ 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 000000000000..09e930a9ba90
--- /dev/null
+++ b/wizards/source/scriptforge/SF_Exception.xba
@@ -0,0 +1,1107 @@
+<?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;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&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;
+
+&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;
+
+&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;
+
+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/DESCTRUCTOR
+
+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)
+&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; Example:
+&apos;&apos;&apos; SF_Exception.Console()
+
+Dim bConsoleActive As Boolean &apos; When True, dialog is active
+Dim sClose As String &apos; Caption of the close buttons
+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 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 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;)
+ &apos; Setup labels and visibility
+ sClose = .Interface.GetText(&quot;CLOSEBUTTON&quot;)
+ Set oModalBtn = .ConsoleDialog.Controls(&quot;CloseModalButton&quot;)
+ Set oNonModalBtn = .ConsoleDialog.Controls(&quot;CloseNonModalButton&quot;)
+ If Modal Then oModalBtn.Caption = sClose Else oNonModalBtn.Caption = sClose
+ 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 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. It it exists, it will be 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 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 sMainSub As String &apos; Temporary storage for main function
+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)
+ 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 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 Interface
+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_.Interface
+ 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; Splitted array (library, module, method)
+Dim sMessage As String &apos; Error message to log and to display
+Dim L10N As Object &apos; Alias to Interface
+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_.Interface
+ 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
+ Set L10N = _SF_.Interface
+ 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 vLocation As Variant &apos; Splitted array (library, module, method)
+Dim sMessage As String &apos; Message to log and display
+Dim L10N As Object &apos; Alias of Interface
+Dim sAlt As String &apos; Alternative error messages
+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_.Interface
+ &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;)
+ sLocation = L10N.GetText(&quot;VALIDATESOURCE&quot;, vLocation(0), vLocation(1), vLocation(2)) _
+ &amp; &quot;\n&quot; &amp; L10N.GetText(&quot;VALIDATEARGS&quot;, _SF_.MainFunctionArgs)
+ Else
+ sLocation = &quot;&quot;
+ End If
+
+ With L10N
+ Select Case UCase(ErrorCode)
+ Case MISSINGARGERROR &apos; SF_Utils._Validate(Name)
+ 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)
+ 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)
+ 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)
+ 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)
+ 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)
+ 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.CopyFiler/MoveFile/DeleteFile(ArgName, Filename)
+ 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)
+ 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)
+ 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)
+ 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)
+ 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)
+ 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)
+ 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)
+ 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;, &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)
+ sMessage = sLocation _
+ &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; &quot;\n&quot; &amp; .GetText(&quot;VALIDATEERROR&quot;, &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)
+ 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)
+ 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 DOCUMENTERROR &apos; SF_UI.GetDocument(ArgName, WindowName)
+ 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)
+ 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)
+ 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)
+ 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)
+ 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)
+ 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)
+ 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)
+ 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)
+ 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)
+ 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)
+ 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 DIALOGNOTFOUNDERROR &apos; SF_Dialog._NewDialog(Service, DialogName, WindowName)
+ 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 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;)
+ MsgBox SF_String.ExpandTabs(SF_String.Unescape(sMessage), cstTabSize) _
+ , MB_OK + MB_ICONEXCLAMATION _
+ , L10N.GetText(&quot;ERRORNUMBER&quot;, ErrorCode)
+ 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 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 ============================================ 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 000000000000..a0d124da3b5b
--- /dev/null
+++ b/wizards/source/scriptforge/SF_FileSystem.xba
@@ -0,0 +1,2084 @@
+<?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;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&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/DESCTRUCTOR
+
+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 installed extensions
+
+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))
+ 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)
+ 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 solders are deleted, filesin 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 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 Date
+&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 &quot;ConfigFolder&quot; : GetProperty = ConfigFolder
+ Case &quot;ExtensionsFolder&quot; : GetProperty = ExtensionsFolder
+ Case &quot;FileNaming&quot; : GetProperty = FileNaming
+ Case &quot;HomeFolder&quot; : GetProperty = HomeFolder
+ Case &quot;InstallFolder&quot; : GetProperty = InstallFolder
+ Case &quot;TemplatesFolder&quot; : GetProperty = TemplatesFolder
+ Case &quot;TemporaryFolder&quot; : GetProperty = TemporaryFolder
+ Case &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;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.FineNaming = &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.FineNaming = &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 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 desination 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 000000000000..6ff222543a00
--- /dev/null
+++ b/wizards/source/scriptforge/SF_L10N.xba
@@ -0,0 +1,696 @@
+<?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 3 mechanisms:
+&apos;&apos;&apos; - AddText: for the programmer to build a set of words or sentences
+&apos;&apos;&apos; meant for being translated later
+&apos;&apos;&apos; - ExportToPOTFile: All the above texts are exported into a pristine POT-file
+&apos;&apos;&apos; - 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; Service invocation examples:
+&apos;&apos;&apos; Dim myPO As Variant
+&apos;&apos;&apos; myPO = CreateScriptService(&quot;L10N&quot;) &apos; AddText 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;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&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 distorsion 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/DESCTRUCTOR
+
+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 forbedden 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)
+
+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 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. It it exists, it will be overwritten without warning
+&apos;&apos;&apos; Header: Comments that will appear on top of the generated file. Do not include any leadung &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 IsMissing(Header) Then Header = &quot;&quot;
+ If IsMissing(Encoding) Or IsMissing(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;_&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 _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 000000000000..c1ac6c8e0b3c
--- /dev/null
+++ b/wizards/source/scriptforge/SF_Platform.xba
@@ -0,0 +1,281 @@
+<?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;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&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/DESCTRUCTOR
+
+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 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 ServiceName As String
+&apos;&apos;&apos; Internal use
+ ServiceName = &quot;ScriptForge.Platform&quot;
+End Property &apos; ScriptForge.SF_Platform.ServiceName
+
+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 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 ===================================================================== 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;Machine&quot; _
+ , &quot;OfficeVersion&quot; _
+ , &quot;OSName&quot; _
+ , &quot;OSPlatform&quot; _
+ , &quot;OSRelease&quot; _
+ , &quot;OSVersion&quot; _
+ , &quot;Processor&quot; _
+ )
+
+End Function &apos; ScriptForge.SF_Platform.Properties
+
+REM =========================================================== PRIVATE FUNCTIONS
+
+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
+
+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;
+ With ScriptForge.SF_Session
+ _PropertyGet = .ExecutePythonScript(.SCRIPTISSHARED, _SF_.PythonHelper &amp; cstPyHelper, psProperty)
+ End With
+ 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 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_Root.xba b/wizards/source/scriptforge/SF_Root.xba
new file mode 100644
index 000000000000..47e855421332
--- /dev/null
+++ b/wizards/source/scriptforge/SF_Root.xba
@@ -0,0 +1,822 @@
+<?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 DebugMode As Boolean &apos; When True, log enter/exit each official Sub
+
+&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 Locale As Object &apos; com.sun.star.lang.Locale
+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 MailService As Object &apos; com.sun.star.system.SimpleCommandMail or com.sun.star.system.SimpleSystemMail
+
+&apos; Specific psersistent 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 Interface 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
+
+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
+ DebugMode = False
+ 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 Locale = 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;
+ Set Interface = Nothing
+ Set BrowseNodeFactory = Nothing
+ Set DatabaseContext = Nothing
+ Set ConfigurationProvider = Nothing
+ Set MailService = Nothing
+ OSName = &quot;&quot;
+ SFDialogs = Empty
+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) = Mid(SF_Utils._Repr(Now()), 12) &amp; &quot; -&gt; &quot; &amp; psLine
+
+ &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 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 ScriptForge invocation by a user script
+&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;Locale&quot;).Language
+ sPOFolder = .BuildPath(sInstallFolder, &quot;po&quot;)
+ sPOFile = .BuildPath(sPOFolder, sLocale &amp; &quot;.po&quot;)
+ If Not .FileExists(sPOFile) Then &apos; File not found =&gt; load texts from code below
+ psMode = &quot;ADDTEXT&quot;
+ Else
+ Set Interface = 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 Interface = CreateScriptService(&quot;L10N&quot;)
+ With Interface
+ &apos; SF_Exception.Raise
+ .AddText( Context := &quot;CLOSEBUTTON&quot; _
+ , MsgId := &quot;Close&quot; _
+ , Comment := &quot;Text in close buttons of progress and console dialog boxes&quot; _
+ )
+ .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;SF_Utils._Validate error message&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_2D » = %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_2D&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;%2: An identifier\n&quot; _
+ &amp; &quot;%3: 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_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._IsStllAlive
+ .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_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 &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 cannot interprete SQL statement\n&quot; _
+ &amp; &quot;%1: The statement&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
+
+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 000000000000..be62730838b9
--- /dev/null
+++ b/wizards/source/scriptforge/SF_Services.xba
@@ -0,0 +1,607 @@
+<?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; - ReisterService
+&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;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&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 sService
+ Case &quot;Document&quot;, &quot;Calc&quot;, &quot;Base&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 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 finction 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 reistered 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;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-lengt 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; 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 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
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+
+Check:
+ If IsMissing(pvArgs) Then pvArgs = Array()
+ If UBound(pvArgs) &lt; 0 Then
+ sPOFile = &quot;&quot;
+ sEncoding = &quot;&quot;
+ Else
+ If Not SF_Utils._ValidateFile(pvArgs(0), &quot;Folder (Arg0)&quot;) Then GoTo Catch
+ sFolderName = pvArgs(0)
+ 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)
+ Else
+ Set oLocale = SF_Utils._GetUNOService(&quot;Locale&quot;)
+ sLocale = oLocale.Language &amp; &quot;-&quot; &amp; oLocale.Country
+ End If
+ If UBound(pvArgs) &gt;= 2 Then
+ 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
+ sPOFile = SF_FileSystem.BuildPath(sFolderName, sLocale &amp; &quot;.po&quot;)
+ If Not SF_FileSystem.FileExists(sPOFile) Then GoTo CatchNotExists
+ 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 000000000000..19117bc5796e
--- /dev/null
+++ b/wizards/source/scriptforge/SF_Session.xba
@@ -0,0 +1,918 @@
+<?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;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&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.myLib.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)
+
+REM ===================================================== CONSTRUCTOR/DESCTRUCTOR
+
+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.openoffice.org/wiki/Documentation/DevGuide/Scripting/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;)
+ 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.openoffice.org/wiki/Documentation/DevGuide/Scripting/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; 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 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.DEFAULTS)
+ 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 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 oService As Object &apos; com.sun.star.reflection.CoreReflection
+Dim vClass as Variant &apos; com.sun.star.reflection.XIdlClass
+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:
+ On Local Error Resume Next
+ &apos; Try usual ImplementationName method
+ sObjectType = UnoObject.getImplementationName()
+ If sObjectType = &quot;&quot; Then
+ &apos; Now try CoreReflection trick
+ Set oService = SF_Utils._GetUNOService(&quot;CoreReflection&quot;)
+ vClass = oService.getType(UnoObject)
+ If vClass.TypeClass &gt;= com.sun.star.uno.TypeClass.STRUCT Then sObjectType = vClass.Name
+ End If
+
+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 _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.openoffice.org/wiki/Documentation/DevGuide/Scripting/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
+Const cstScript1 = &quot;vnd.sun.star.script:&quot;
+Const cstScript2 = &quot;?language=&quot;
+Const cstScript3 = &quot;&amp;location=&quot;
+
+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 000000000000..24acd984ad16
--- /dev/null
+++ b/wizards/source/scriptforge/SF_String.xba
@@ -0,0 +1,2642 @@
+<?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 chararacter 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;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&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/DESCTRUCTOR
+
+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;Locale&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 exprsession 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 numer 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;Locale&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 sRegex As String &apos; The regex to check against the input string
+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
+ sRegex = ReplaceStr(sFormat, Array(&quot;YYYY&quot;, &quot;MM&quot;, &quot;DD&quot;) _
+ , Array(REGEXDATEYEAR, REGEXDATEMONTH, REGEXDATEDAY) _
+ , CaseSensitive := False)
+ bADate = SF_String.IsRegex(InputStr, sRegex, CaseSensitive := False)
+ End If
+
+Finally:
+ IsADate = bADate
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ 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;Locale&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;Locale&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 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; Substiture 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;Locale&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 methof 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 boundariess 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 preceede 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 chararacter 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;Locale&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; 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; &quot;&quot;&quot;&quot; Then &apos; No need to parse further
+ sUnquote = InputStr
+ Else
+ Set oCharacterClass = SF_Utils._GetUNOService(&quot;CharacterClass&quot;)
+ Set oLocale = SF_Utils._GetUNOService(&quot;Locale&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 whitepaces
+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; Interprete 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 chararacter
+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 000000000000..6b042f92e435
--- /dev/null
+++ b/wizards/source/scriptforge/SF_TextStream.xba
@@ -0,0 +1,701 @@
+<?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;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&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
+
+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/DESCTRUCTOR
+
+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 aleardy 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:
+ &apos;TODO: SF_Exception.RaiseFatal(FILEWRITEMODEERROR, cstThisSub)
+ MsgBox &quot;END OF FILE ERROR !!&quot;
+ 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 aleardy 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:
+ &apos;TODO: SF_Exception.RaiseFatal(FILEWRITEMODEERROR, cstThisSub)
+ MsgBox &quot;END OF FILE ERROR !!&quot;
+ 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 aleardy 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:
+ &apos;TODO: SF_Exception.RaiseFatal(FILEWRITEMODEERROR, cstThisSub)
+ MsfBox &quot;END OF FILE ERROR !!&quot;
+ 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 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 = _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 000000000000..5ed3a7d9546d
--- /dev/null
+++ b/wizards/source/scriptforge/SF_Timer.xba
@@ -0,0 +1,463 @@
+<?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;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&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/DESCTRUCTOR
+
+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 Calss_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 = ( _TimerStatus = STATUSSTARTED Or _TimerStatus = STATUSSUSPENDED )
+ Case UCase(&quot;IsSuspended&quot;)
+ _PropertyGet = ( _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 000000000000..5cbd6b8aae1f
--- /dev/null
+++ b/wizards/source/scriptforge/SF_UI.xba
@@ -0,0 +1,1175 @@
+<?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;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&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
+
+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/DESCTRUCTOR
+
+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 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 ===================================================================== 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 _
+ ) 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 altready exists, it is overwritten without warning
+&apos;&apos;&apos; EmbeddedDatabase: either &quot;HSQLDB&quot; (default) or &quot;FIREBIRD&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; Returns:
+&apos;&apos;&apos; A SFDocuments.SF_Document object or one of its subclasses
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; Dim myBase As Object
+&apos;&apos;&apos; Set myBase = ui.CreateBaseDocument(&quot;C:\Databases\MyBaseFile.odb&quot;, &quot;FIREBIRD&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;], [RegistrationName=&quot;&quot;&quot;&quot;]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ Set oCreate = Nothing
+
+Check:
+ If IsMissing(EmbeddedDatabase) Or IsEmpty(EmbeddedDatabase) Then EmbeddedDatabase = &quot;HSQLDB&quot;
+ If IsMissing(RegistrationName) Or IsEmpty(RegistrationName) Then RegistrationName = &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;HSQLDB&quot;, &quot;FIREBIRD&quot;)) Then GoTo Finally
+ If Not SF_Utils._Validate(RegistrationName, &quot;RegistrationName&quot;, V_STRING) Then GoTo Finally
+ End If
+
+Try:
+ Set oDBContext = SF_Utils._GetUNOService(&quot;DatabaseContext&quot;)
+ With oDBContext
+ Set oDatabase = .createInstance()
+ oDatabase.URL = &quot;sdbc:embedded:&quot; &amp; LCase(EmbeddedDatabase)
+ &apos; Create empty Base document
+ Set FSO = CreateScriptService(&quot;FileSystem&quot;)
+ 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
+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: see definitions. If absent the active window is considered
+&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;&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._ValidateFile(WindowName, &quot;WindowName&quot;, , True) Then GoTo Finally
+ 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 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;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; _
+ )
+
+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 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
+Static 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:
+ 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()
+ 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:
+ 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 _
+ )
+&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; 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
+Static oDialog As Object &apos; SFDialogs.Dialog object
+Static oFixedText As Object &apos; SFDialogs.DialogControl object
+Static 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 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 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;)
+
+ 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;)
+ .Controls(&quot;CloseButton&quot;).Caption = _SF_.Interface.GetText(&quot;CLOSEBUTTON&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:
+ 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 -----------------------------------------------------------------------------
+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 000000000000..eed61f074e53
--- /dev/null
+++ b/wizards/source/scriptforge/SF_Utils.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_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.1&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
+Global Const V_NUMERIC = 99 &apos; Fictive VarType synonym of any numeric value
+
+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. It it exists, it 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_
+ .Interface.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; Retuns:
+&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 -----------------------------------------------------------------------------
+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 oConfigProvider As Object
+Dim oDefaultContext As Object
+Dim vNodePath As Variant
+
+ 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;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;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;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;Locale&quot;
+ If IsEmpty(.Locale) Or IsNull(.Locale) Then
+ .Locale = CreateUnoStruct(&quot;com.sun.star.lang.Locale&quot;)
+ &apos; Derived from the Tools library
+ Set oConfigProvider = createUnoService(&quot;com.sun.star.configuration.ConfigurationProvider&quot;)
+ vNodePath = Array() : ReDim vNodePath(0)
+ vNodePath(0) = New com.sun.star.beans.PropertyValue
+ vNodePath(0).Name = &quot;nodepath&quot; : vNodePath(0).Value = &quot;org.openoffice.Setup/L10N&quot;
+ sLocale = oConfigProvider.createInstanceWithArguments(&quot;com.sun.star.configuration.ConfigurationAccess&quot;, vNodePath()).getByName(&quot;ooLocale&quot;)
+ .Locale.Language = Left(sLocale, 2)
+ .Locale.Country = Right(sLocale, 2)
+ End If
+ Set _GetUNOService = .Locale
+ 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;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;ScriptProvider&quot;
+ If IsMissing(pvArg) Then pvArg = SF_Session.SCRIPTISAPPLICATION
+ Select Case 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;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;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_
+ &apos; Localization
+ _SF_._LoadLocalizedInterface()
+ 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 sObject As String &apos; Object representation
+Dim sObjectType As String &apos; ObjectType attribute of Basic objects
+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) Or 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
+ If IsNull(pvArg) Then
+ sArg = &quot;[NULL]&quot;
+ Else
+ sObject = SF_Session.UnoObjectType(pvArg)
+ If sObject = &quot;&quot; Or sObject = cstBasicObject Then &apos; Not a UNO object
+ &apos; Test if argument is a ScriptForge object
+ sObjectType = &quot;&quot;
+ On Local Error Resume Next
+ Set oObject = pvArg
+ sObjectType = oObject.ObjectType
+ On Error GoTo 0
+ If sObjectType = &quot;&quot; Then
+ sArg = &quot;[OBJECT]&quot;
+ ElseIf Left(sObjectType, 3) = &quot;SF_&quot; Then
+ sArg = &quot;[&quot; &amp; sObjectType &amp; &quot;]&quot;
+ Else
+ sArg = oObject._Repr()
+ End If
+ Else
+ sArg = &quot;[&quot; &amp; sObject &amp; &quot;]&quot;
+ End If
+ End If
+ 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 Sub _SetPropertyValue(ByRef pvPropertyValue As Variant _
+ , ByVal psName As String _
+ , ByRef pvValue As Variant _
+ )
+&apos;&apos;&apos; Update 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 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 i As Long
+
+ lIndex = -1
+ For i = 0 To UBound(pvPropertyValue)
+ If pvPropertyValue(i).Name = psName Then
+ lIndex = i
+ Exit For
+ End If
+ Next i
+ If lIndex &lt; 0 Then &apos; Not found
+ lIndex = UBound(pvPropertyValue) + 1
+ ReDim Preserve pvPropertyValue(0 To lIndex)
+ Set oPropertyValue = SF_Utils._MakePropertyValue(psName, pvValue)
+ pvPropertyValue(lIndex) = oPropertyValue
+ Else &apos; psName exists already in array of property values
+ pvPropertyValue(lIndex).Value = SF_Utils._CPropertyValue(pvValue)
+ End If
+
+End Sub &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-sepatrated 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 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
+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
+ Set oArgument = pvArgument
+ bValid = ( pvObjectType = oArgument.ObjectType )
+ 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; Chech 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 ================================================= 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 000000000000..b0443e0a1efa
--- /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 undercore 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 000000000000..30f1aa8f170d
--- /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 desribed 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 000000000000..37bb6a75f29d
--- /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-2020 Jean-Pierre LEDURE, Jean-François NIFENECKER, 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 000000000000..7b54d071c4f9
--- /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 000000000000..626be565d8b7
--- /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:value="Close">
+ <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:value="Close" dlg:button-type="ok"/>
+ </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 000000000000..cdb8f313214b
--- /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:value="Close">
+ <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/script.xlb b/wizards/source/scriptforge/script.xlb
new file mode 100644
index 000000000000..d4c21b652ebe
--- /dev/null
+++ b/wizards/source/scriptforge/script.xlb
@@ -0,0 +1,21 @@
+<?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:library> \ No newline at end of file