wizards/source/scriptforge/SF_Array.xba           | 2549 +++++++++++++++++++++
 wizards/source/scriptforge/SF_Dictionary.xba      |  952 +++++++
 wizards/source/scriptforge/SF_Exception.xba       | 1107 +++++++++
 wizards/source/scriptforge/SF_FileSystem.xba      | 2084 +++++++++++++++++
 wizards/source/scriptforge/SF_L10N.xba            |  696 +++++
 wizards/source/scriptforge/SF_Platform.xba        |  281 ++
 wizards/source/scriptforge/SF_Root.xba            |  822 ++++++
 wizards/source/scriptforge/SF_Services.xba        |  607 +++++
 wizards/source/scriptforge/SF_Session.xba         |  918 +++++++
 wizards/source/scriptforge/SF_String.xba          | 2642 ++++++++++++++++++++++
 wizards/source/scriptforge/SF_TextStream.xba      |  701 +++++
 wizards/source/scriptforge/SF_Timer.xba           |  463 +++
 wizards/source/scriptforge/SF_UI.xba              | 1175 +++++++++
 wizards/source/scriptforge/SF_Utils.xba           |  967 ++++++++
 wizards/source/scriptforge/_CodingConventions.xba |  100 
 wizards/source/scriptforge/_ModuleModel.xba       |  221 +
 wizards/source/scriptforge/__License.xba          |   25 
 wizards/source/scriptforge/dialog.xlb             |    6 
 wizards/source/scriptforge/dlgConsole.xdl         |   14 
 wizards/source/scriptforge/dlgProgress.xdl        |   11 
 wizards/source/scriptforge/script.xlb             |   21 
 21 files changed, 16362 insertions(+)

New commits:
commit 09c1bee1f91315fd7901af1804e028f6574228a6
Author:     Jean-Pierre Ledure <j...@ledure.be>
AuthorDate: Thu Nov 5 15:55:39 2020 +0100
Commit:     Jean-Pierre Ledure <j...@ledure.be>
CommitDate: Thu Nov 5 15:55:39 2020 +0100

    ScriptForge - core library
    
    Additional "LibreOffice Macros & Dialogs" library
    
    Change-Id: I7380cf3f9ee56b73cfcf7b9e33d0cf50ecb40429

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

... etc. - the rest is truncated
_______________________________________________
Libreoffice-commits mailing list
libreoffice-comm...@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits

Reply via email to