summaryrefslogtreecommitdiffstats
path: root/wizards/source/scriptforge/SF_Array.xba
diff options
context:
space:
mode:
Diffstat (limited to 'wizards/source/scriptforge/SF_Array.xba')
-rw-r--r--wizards/source/scriptforge/SF_Array.xba2608
1 files changed, 2608 insertions, 0 deletions
diff --git a/wizards/source/scriptforge/SF_Array.xba b/wizards/source/scriptforge/SF_Array.xba
new file mode 100644
index 000000000..49bdab147
--- /dev/null
+++ b/wizards/source/scriptforge/SF_Array.xba
@@ -0,0 +1,2608 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd">
+<script:module xmlns:script="http://openoffice.org/2000/script" script:name="SF_Array" script:language="StarBasic" script:moduleType="normal">REM =======================================================================================================================
+REM === The ScriptForge library and its associated libraries are part of the LibreOffice project. ===
+REM === Full documentation is available on https://help.libreoffice.org/ ===
+REM =======================================================================================================================
+
+Option Compatible
+Option Explicit
+
+&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;
+&apos;&apos;&apos; SF_Array
+&apos;&apos;&apos; ========
+&apos;&apos;&apos; Singleton class implementing the &quot;ScriptForge.Array&quot; service
+&apos;&apos;&apos; Implemented as a usual Basic module
+&apos;&apos;&apos; Only 1D or 2D arrays are considered. Arrays with more than 2 dimensions are rejected
+&apos;&apos;&apos; With the noticeable exception of the CountDims method (&gt;2 dims allowed)
+&apos;&apos;&apos; The first argument of almost every method is the array to consider
+&apos;&apos;&apos; It is always passed by reference and left unchanged
+&apos;&apos;&apos;
+&apos;&apos;&apos; Detailed user documentation:
+&apos;&apos;&apos; https://help.libreoffice.org/latest/en-US/text/sbasic/shared/03/sf_array.html?DbPAR=BASIC
+&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;
+
+REM ================================================================== EXCEPTIONS
+
+Const ARRAYSEQUENCEERROR = &quot;ARRAYSEQUENCEERROR&quot; &apos; Incoherent arguments
+Const ARRAYINSERTERROR = &quot;ARRAYINSERTERROR&quot; &apos; Matrix and vector have incompatible sizes
+Const ARRAYINDEX1ERROR = &quot;ARRAYINDEX1ERROR&quot; &apos; Given index does not fit in array bounds
+Const ARRAYINDEX2ERROR = &quot;ARRAYINDEX2ERROR&quot; &apos; Given indexes do not fit in array bounds
+Const CSVPARSINGERROR = &quot;CSVPARSINGERROR&quot; &apos; Parsing error detected while parsing a csv file
+Const CSVOVERFLOWWARNING = &quot;CSVOVERFLOWWARNING&quot; &apos; Array becoming too big, import process of csv file is interrupted
+
+REM ============================================================ MODULE CONSTANTS
+
+Const MAXREPR = 50 &apos; Maximum length to represent an array in the console
+
+REM ===================================================== CONSTRUCTOR/DESTRUCTOR
+
+REM -----------------------------------------------------------------------------
+Public Function Dispose() As Variant
+ Set Dispose = Nothing
+End Function &apos; ScriptForge.SF_Array Explicit destructor
+
+REM ================================================================== PROPERTIES
+
+REM -----------------------------------------------------------------------------
+Property Get ObjectType As String
+&apos;&apos;&apos; Only to enable object representation
+ ObjectType = &quot;SF_Array&quot;
+End Property &apos; ScriptForge.SF_Array.ObjectType
+
+REM -----------------------------------------------------------------------------
+Property Get ServiceName As String
+&apos;&apos;&apos; Internal use
+ ServiceName = &quot;ScriptForge.Array&quot;
+End Property &apos; ScriptForge.SF_Array.ServiceName
+
+REM ============================================================== PUBLIC METHODS
+
+REM -----------------------------------------------------------------------------
+Public Function Append(Optional ByRef Array_1D As Variant _
+ , ParamArray pvArgs() As Variant _
+ ) As Variant
+&apos;&apos;&apos; Append at the end of the input array the items listed as arguments
+&apos;&apos;&apos; Arguments are appended blindly
+&apos;&apos;&apos; each of them might be a scalar of any type or a subarray
+&apos;&apos;&apos; Args
+&apos;&apos;&apos; Array_1D: the pre-existing array, may be empty
+&apos;&apos;&apos; pvArgs: a list of items to append to Array_1D
+&apos;&apos;&apos; Return:
+&apos;&apos;&apos; the new extended array. Its LBound is identical to that of Array_1D
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Array.Append(Array(1, 2, 3), 4, 5) returns (1, 2, 3, 4, 5)
+
+Dim vAppend As Variant &apos; Return value
+Dim lNbArgs As Long &apos; Number of elements to append
+Dim lMax As Long &apos; UBound of input array
+Dim i As Long
+Const cstThisSub = &quot;Array.Append&quot;
+Const cstSubArgs = &quot;Array_1D, arg0[, arg1] ...&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vAppend = Array()
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
+ End If
+
+Try:
+ lMax = UBound(Array_1D)
+ lNbArgs = UBound(pvArgs) + 1 &apos; pvArgs is always zero-based
+ If lMax &lt; LBound(Array_1D) Then &apos; Initial array is empty
+ If lNbArgs &gt; 0 Then
+ ReDim vAppend(0 To lNbArgs - 1)
+ End If
+ Else
+ vAppend() = Array_1D()
+ If lNbArgs &gt; 0 Then
+ ReDim Preserve vAppend(LBound(Array_1D) To lMax + lNbArgs)
+ End If
+ End If
+ For i = 1 To lNbArgs
+ vAppend(lMax + i) = pvArgs(i - 1)
+ Next i
+
+Finally:
+ Append = vAppend()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.Append
+
+REM -----------------------------------------------------------------------------
+Public Function AppendColumn(Optional ByRef Array_2D As Variant _
+ , Optional ByRef Column As Variant _
+ ) As Variant
+&apos;&apos;&apos; AppendColumn appends to the right side of a 2D array a new Column
+&apos;&apos;&apos; Args
+&apos;&apos;&apos; Array_2D: the pre-existing array, may be empty
+&apos;&apos;&apos; If the array has 1 dimension, it is considered as the 1st Column of the resulting 2D array
+&apos;&apos;&apos; Column: a 1D array with as many items as there are rows in Array_2D
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; the new extended array. Its LBounds are identical to that of Array_2D
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; ARRAYINSERTERROR
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Array.AppendColumn(Array(1, 2, 3), Array(4, 5, 6)) returns ((1, 4), (2, 5), (3, 6))
+&apos;&apos;&apos; x = SF_Array.AppendColumn(Array(), Array(1, 2, 3)) =&gt; ∀ i ∈ {0 ≤ i ≤ 2} : x(0, i) ≡ i
+
+Dim vAppendColumn As Variant &apos; Return value
+Dim iDims As Integer &apos; Dimensions of Array_2D
+Dim lMin1 As Long &apos; LBound1 of input array
+Dim lMax1 As Long &apos; UBound1 of input array
+Dim lMin2 As Long &apos; LBound2 of input array
+Dim lMax2 As Long &apos; UBound2 of input array
+Dim lMin As Long &apos; LBound of Column array
+Dim lMax As Long &apos; UBound of Column array
+Dim i As Long
+Dim j As Long
+Const cstThisSub = &quot;Array.AppendColumn&quot;
+Const cstSubArgs = &quot;Array_2D, Column&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vAppendColumn = Array()
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;) Then GoTo Finally &apos;Initial check: not missing and array
+ If Not SF_Utils._ValidateArray(Column, &quot;Column&quot;, 1) Then GoTo Finally
+ End If
+ iDims = SF_Array.CountDims(Array_2D)
+ If iDims &gt; 2 Then
+ If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally &apos;2nd check to manage error
+ End If
+
+Try:
+ lMin = LBound(Column)
+ lMax = UBound(Column)
+
+ &apos; Compute future dimensions of output array
+ Select Case iDims
+ Case 0 : lMin1 = lMin : lMax1 = lMax
+ lMin2 = 0 : lMax2 = -1
+ Case 1 : lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
+ lMin2 = 0 : lMax2 = 0
+ Case 2 : lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
+ lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
+ End Select
+ If iDims &gt; 0 And lMax - lMin &lt;&gt; lMax1 - lMin1 Then GoTo CatchColumn
+ ReDim vAppendColumn(lMin1 To lMax1, lMin2 To lMax2 + 1)
+
+ &apos; Copy input array to output array
+ For i = lMin1 To lMax1
+ For j = lMin2 To lMax2
+ If iDims = 2 Then vAppendColumn(i, j) = Array_2D(i, j) Else vAppendColumn(i, j) = Array_2D(i)
+ Next j
+ Next i
+ &apos; Copy new Column
+ For i = lMin1 To lMax1
+ vAppendColumn(i, lMax2 + 1) = Column(i)
+ Next i
+
+Finally:
+ AppendColumn = vAppendColumn()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchColumn:
+ SF_Exception.RaiseFatal(ARRAYINSERTERROR, &quot;Column&quot;, SF_Array._Repr(Array_2D), SF_Utils._Repr(Column, MAXREPR))
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.AppendColumn
+
+REM -----------------------------------------------------------------------------
+Public Function AppendRow(Optional ByRef Array_2D As Variant _
+ , Optional ByRef Row As Variant _
+ ) As Variant
+&apos;&apos;&apos; AppendRow appends below a 2D array a new row
+&apos;&apos;&apos; Args
+&apos;&apos;&apos; Array_2D: the pre-existing array, may be empty
+&apos;&apos;&apos; If the array has 1 dimension, it is considered as the 1st row of the resulting 2D array
+&apos;&apos;&apos; Row: a 1D array with as many items as there are columns in Array_2D
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; the new extended array. Its LBounds are identical to that of Array_2D
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; ARRAYINSERTERROR
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Array.AppendRow(Array(1, 2, 3), Array(4, 5, 6)) returns ((1, 2, 3), (4, 5, 6))
+&apos;&apos;&apos; x = SF_Array.AppendRow(Array(), Array(1, 2, 3)) =&gt; ∀ i ∈ {0 ≤ i ≤ 2} : x(i, 0) ≡ i
+
+Dim vAppendRow As Variant &apos; Return value
+Dim iDims As Integer &apos; Dimensions of Array_2D
+Dim lMin1 As Long &apos; LBound1 of input array
+Dim lMax1 As Long &apos; UBound1 of input array
+Dim lMin2 As Long &apos; LBound2 of input array
+Dim lMax2 As Long &apos; UBound2 of input array
+Dim lMin As Long &apos; LBound of row array
+Dim lMax As Long &apos; UBound of row array
+Dim i As Long
+Dim j As Long
+Const cstThisSub = &quot;Array.AppendRow&quot;
+Const cstSubArgs = &quot;Array_2D, Row&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vAppendRow = Array()
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;) Then GoTo Finally &apos;Initial check: not missing and array
+ If Not SF_Utils._ValidateArray(Row, &quot;Row&quot;, 1) Then GoTo Finally
+ End If
+ iDims = SF_Array.CountDims(Array_2D)
+ If iDims &gt; 2 Then
+ If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally &apos;2nd check to manage error
+ End If
+
+Try:
+ lMin = LBound(Row)
+ lMax = UBound(Row)
+
+ &apos; Compute future dimensions of output array
+ Select Case iDims
+ Case 0 : lMin1 = 0 : lMax1 = -1
+ lMin2 = lMin : lMax2 = lMax
+ Case 1 : lMin1 = 0 : lMax1 = 0
+ lMin2 = LBound(Array_2D, 1) : lMax2 = UBound(Array_2D, 1)
+ Case 2 : lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
+ lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
+ End Select
+ If iDims &gt; 0 And lMax - lMin &lt;&gt; lMax2 - lMin2 Then GoTo CatchRow
+ ReDim vAppendRow(lMin1 To lMax1 + 1, lMin2 To lMax2)
+
+ &apos; Copy input array to output array
+ For i = lMin1 To lMax1
+ For j = lMin2 To lMax2
+ If iDims = 2 Then vAppendRow(i, j) = Array_2D(i, j) Else vAppendRow(i, j) = Array_2D(j)
+ Next j
+ Next i
+ &apos; Copy new row
+ For j = lMin2 To lMax2
+ vAppendRow(lMax1 + 1, j) = Row(j)
+ Next j
+
+Finally:
+ AppendRow = vAppendRow()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchRow:
+ SF_Exception.RaiseFatal(ARRAYINSERTERROR, &quot;Row&quot;, SF_Array._Repr(Array_2D), SF_Utils._Repr(Row, MAXREPR))
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.AppendRow
+
+REM -----------------------------------------------------------------------------
+Public Function Contains(Optional ByRef Array_1D As Variant _
+ , Optional ByVal ToFind As Variant _
+ , Optional ByVal CaseSensitive As Variant _
+ , Optional ByVal SortOrder As Variant _
+ ) As Boolean
+&apos;&apos;&apos; Check if a 1D array contains the ToFind number, string or date
+&apos;&apos;&apos; The comparison between strings can be done case-sensitive or not
+&apos;&apos;&apos; If the array is sorted then
+&apos;&apos;&apos; the array must be filled homogeneously, i.e. all items must be of the same type
+&apos;&apos;&apos; Empty and Null items are forbidden
+&apos;&apos;&apos; a binary search is done
+&apos;&apos;&apos; Otherwise the array is scanned from top. Null or Empty items are simply ignored
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array_1D: the array to scan
+&apos;&apos;&apos; ToFind: a number, a date or a string to find
+&apos;&apos;&apos; CaseSensitive: Only for string comparisons, default = False
+&apos;&apos;&apos; SortOrder: &quot;ASC&quot;, &quot;DESC&quot; or &quot;&quot; (= not sorted, default)
+&apos;&apos;&apos; Return: True when found
+&apos;&apos;&apos; Result is unpredictable when array is announced sorted and is in reality not
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Array.Contains(Array(&quot;A&quot;,&quot;B&quot;,&quot;c&quot;,&quot;D&quot;), &quot;C&quot;, SortOrder := &quot;ASC&quot;) returns True
+&apos;&apos;&apos; SF_Array.Contains(Array(&quot;A&quot;,&quot;B&quot;,&quot;c&quot;,&quot;D&quot;), &quot;C&quot;, CaseSensitive := True) returns False
+
+Dim bContains As Boolean &apos; Return value
+Dim iToFindType As Integer &apos; VarType of ToFind
+Const cstThisSub = &quot;Array.Contains&quot;
+Const cstSubArgs = &quot;Array_1D, ToFind, [CaseSensitive=False], [SortOrder=&quot;&quot;&quot;&quot;|&quot;&quot;ASC&quot;&quot;|&quot;&quot;DESC&quot;&quot;]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+
+ bContains = False
+
+Check:
+ If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False
+ If IsMissing(SortOrder) Or IsEmpty(SortOrder) Then SortOrder = &quot;&quot;
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(SortOrder, &quot;SortOrder&quot;, V_STRING, Array(&quot;ASC&quot;, &quot;DESC&quot;, &quot;&quot;)) Then GoTo Finally
+ If Not SF_Utils._Validate(ToFind, &quot;ToFind&quot;, Array(V_STRING, V_DATE, V_NUMERIC)) Then GoTo Finally
+ iToFindType = SF_Utils._VarTypeExt(ToFind)
+ If SortOrder &lt;&gt; &quot;&quot; Then
+ If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1, iToFindType) Then GoTo Finally
+ Else
+ If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
+ End If
+ If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
+ End If
+
+Try:
+ bContains = SF_Array._FindItem(Array_1D, ToFind, CaseSensitive, SortOrder)(0)
+
+Finally:
+ Contains = bContains
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.Contains
+
+REM -----------------------------------------------------------------------------
+Public Function ConvertToDictionary(Optional ByRef Array_2D As Variant) As Variant
+&apos;&apos;&apos; Store the content of a 2-columns array into a dictionary
+&apos;&apos;&apos; Key found in 1st column, Item found in 2nd
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array_2D: 1st column must contain exclusively non zero-length strings
+&apos;&apos;&apos; 1st column may not be sorted
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; a ScriptForge dictionary object
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos;
+
+Dim oDict As Variant &apos; Return value
+Dim i As Long
+Const cstThisSub = &quot;Dictionary.ConvertToArray&quot;
+Const cstSubArgs = &quot;Array_2D&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2, V_STRING, True) Then GoTo Finally
+ End If
+
+Try:
+ Set oDict = SF_Services.CreateScriptService(&quot;Dictionary&quot;)
+ For i = LBound(Array_2D, 1) To UBound(Array_2D, 1)
+ oDict.Add(Array_2D(i, 0), Array_2D(i, 1))
+ Next i
+
+ ConvertToDictionary = oDict
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.ConvertToDictionary
+
+REM -----------------------------------------------------------------------------
+Public Function Copy(Optional ByRef Array_ND As Variant) As Variant
+&apos;&apos;&apos; Duplicate a 1D or 2D array
+&apos;&apos;&apos; A usual assignment copies an array by reference, i.e. shares the same memory location
+&apos;&apos;&apos; Dim a, b
+&apos;&apos;&apos; a = Array(1, 2, 3)
+&apos;&apos;&apos; b = a
+&apos;&apos;&apos; a(2) = 30
+&apos;&apos;&apos; MsgBox b(2) &apos; 30
+&apos;&apos;&apos; Args
+&apos;&apos;&apos; Array_ND: the array to copy, may be empty
+&apos;&apos;&apos; Return:
+&apos;&apos;&apos; the copied array. Subarrays however still remain assigned by reference
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Array.Copy(Array(1, 2, 3)) returns (1, 2, 3)
+
+Dim vCopy As Variant &apos; Return value
+Dim iDims As Integer &apos; Number of dimensions of the input array
+Const cstThisSub = &quot;Array.Copy&quot;
+Const cstSubArgs = &quot;Array_ND&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vCopy = Array()
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_ND, &quot;Array_ND&quot;) Then GoTo Finally
+ iDims = SF_Array.CountDims(Array_ND)
+ If iDims &gt; 2 Then
+ If Not SF_Utils._ValidateArray(Array_ND, &quot;Array_ND&quot;, 2) Then GoTo Finally
+ End If
+ End If
+
+Try:
+ Select Case iDims
+ Case 0
+ Case 1
+ vCopy = Array_ND
+ ReDim Preserve vCopy(LBound(Array_ND) To UBound(Array_ND))
+ Case 2
+ vCopy = Array_ND
+ ReDim Preserve vCopy(LBound(Array_ND, 1) To UBound(Array_ND, 1), LBound(Array_ND, 2) To UBound(Array_ND, 2))
+ End Select
+
+Finally:
+ Copy = vCopy()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.Copy
+
+REM -----------------------------------------------------------------------------
+Public Function CountDims(Optional ByRef Array_ND As Variant) As Integer
+&apos;&apos;&apos; Count the number of dimensions of an array - may be &gt; 2
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array_ND: the array to be examined
+&apos;&apos;&apos; Return: the number of dimensions: -1 = not array, 0 = uninitialized array, else &gt;= 1
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; Dim a(1 To 10, -3 To 12, 5)
+&apos;&apos;&apos; CountDims(a) returns 3
+
+Dim iDims As Integer &apos; Return value
+Dim lMax As Long &apos; Storage for UBound of each dimension
+Const cstThisSub = &quot;Array.CountDims&quot;
+Const cstSubArgs = &quot;Array_ND&quot;
+
+Check:
+ iDims = -1
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If IsMissing(Array_ND) Then &apos; To have missing exception processed
+ If Not SF_Utils._ValidateArray(Array_ND, &quot;Array_ND&quot;) Then GoTo Finally
+ End If
+ End If
+
+Try:
+ On Local Error Goto ErrHandler
+ &apos; Loop, increasing the dimension index (i) until an error occurs.
+ &apos; An error will occur when i exceeds the number of dimensions in the array. Returns i - 1.
+ iDims = 0
+ If Not IsArray(Array_ND) Then
+ Else
+ Do
+ iDims = iDims + 1
+ lMax = UBound(Array_ND, iDims)
+ Loop Until (Err &lt;&gt; 0)
+ End If
+
+ ErrHandler:
+ On Local Error GoTo 0
+
+ iDims = iDims - 1
+ If iDims = 1 Then
+ If LBound(Array_ND, 1) &gt; UBound(Array_ND, 1) Then iDims = 0
+ End If
+
+Finally:
+ CountDims = iDims
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+End Function &apos; ScriptForge.SF_Array.CountDims
+
+REM -----------------------------------------------------------------------------
+Public Function Difference(Optional ByRef Array1_1D As Variant _
+ , Optional ByRef Array2_1D As Variant _
+ , Optional ByVal CaseSensitive As Variant _
+ ) As Variant
+&apos;&apos;&apos; Build a set being the Difference of the two input arrays, i.e. items are contained in 1st array and NOT in 2nd
+&apos;&apos;&apos; both input arrays must be filled homogeneously, i.e. all items must be of the same type
+&apos;&apos;&apos; Empty and Null items are forbidden
+&apos;&apos;&apos; The comparison between strings is case sensitive or not
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array1_1D: a 1st input array
+&apos;&apos;&apos; Array2_1D: a 2nd input array
+&apos;&apos;&apos; CaseSensitive: default = False
+&apos;&apos;&apos; Returns: a zero-based array containing unique items from the 1st array not present in the 2nd
+&apos;&apos;&apos; The output array is sorted in ascending order
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Array.Difference(Array(&quot;A&quot;, &quot;C&quot;, &quot;A&quot;, &quot;b&quot;, &quot;B&quot;), Array(&quot;C&quot;, &quot;Z&quot;, &quot;b&quot;), True) returns (&quot;A&quot;, &quot;B&quot;)
+
+Dim vDifference() As Variant &apos; Return value
+Dim vSorted() As Variant &apos; The 2nd input array after sort
+Dim iType As Integer &apos; VarType of elements in input arrays
+Dim lMin1 As Long &apos; LBound of 1st input array
+Dim lMax1 As Long &apos; UBound of 1st input array
+Dim lMin2 As Long &apos; LBound of 2nd input array
+Dim lMax2 As Long &apos; UBound of 2nd input array
+Dim lSize As Long &apos; Number of Difference items
+Dim vItem As Variant &apos; One single item in the array
+Dim i As Long
+Const cstThisSub = &quot;Array.Difference&quot;
+Const cstSubArgs = &quot;Array1_1D, Array2_1D, [CaseSensitive=False]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vDifference = Array()
+
+Check:
+ If IsMissing(CaseSensitive) Then CaseSensitive = False
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array1_1D, &quot;Array1_1D&quot;, 1, 0, True) Then GoTo Finally
+ iType = SF_Utils._VarTypeExt(Array1_1D(LBound(Array1_1D)))
+ If Not SF_Utils._ValidateArray(Array2_1D, &quot;Array2_1D&quot;, 1, iType, True) Then GoTo Finally
+ If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
+ End If
+
+Try:
+ lMin1 = LBound(Array1_1D) : lMax1 = UBound(Array1_1D)
+ lMin2 = LBound(Array2_1D) : lMax2 = UBound(Array2_1D)
+
+ &apos; If 1st array is empty, do nothing
+ If lMax1 &lt; lMin1 Then
+ ElseIf lMax2 &lt; lMin2 Then &apos; only 2nd array is empty
+ vUnion = SF_Array.Unique(Array1_1D, CaseSensitive)
+ Else
+
+ &apos; First sort the 2nd array
+ vSorted = SF_Array.Sort(Array2_1D, &quot;ASC&quot;, CaseSensitive)
+
+ &apos; Resize the output array to the size of the 1st array
+ ReDim vDifference(0 To (lMax1 - lMin1))
+ lSize = -1
+
+ &apos; Fill vDifference one by one with items present only in 1st set
+ For i = lMin1 To lMax1
+ vItem = Array1_1D(i)
+ If Not SF_Array.Contains(vSorted, vItem, CaseSensitive, &quot;ASC&quot;) Then
+ lSize = lSize + 1
+ vDifference(lSize) = vItem
+ End If
+ Next i
+
+ &apos; Remove unfilled entries and duplicates
+ If lSize &gt;= 0 Then
+ ReDim Preserve vDifference(0 To lSize)
+ vDifference() = SF_Array.Unique(vDifference, CaseSensitive)
+ Else
+ vDifference = Array()
+ End If
+ End If
+
+Finally:
+ Difference = vDifference()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.Difference
+
+REM -----------------------------------------------------------------------------
+Public Function ExportToTextFile(Optional ByRef Array_1D As Variant _
+ , Optional ByVal FileName As Variant _
+ , Optional ByVal Encoding As Variant _
+ ) As Boolean
+&apos;&apos;&apos; Write all items of the array sequentially to a text file
+&apos;&apos;&apos; If the file exists already, it will be overwritten without warning
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array_1D: the array to export
+&apos;&apos;&apos; FileName: the full name (path + file) in SF_FileSystem.FileNaming notation
+&apos;&apos;&apos; Encoding: The character set that should be used
+&apos;&apos;&apos; Use one of the Names listed in https://www.iana.org/assignments/character-sets/character-sets.xhtml
+&apos;&apos;&apos; Note that LibreOffice does not implement all existing sets
+&apos;&apos;&apos; Default = UTF-8
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True if successful
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Array.ExportToTextFile(Array(&quot;A&quot;,&quot;B&quot;,&quot;C&quot;,&quot;D&quot;), &quot;C:\Temp\A short file.txt&quot;)
+
+Dim bExport As Boolean &apos; Return value
+Dim oFile As Object &apos; Output file handler
+Dim sLine As String &apos; A single line
+Const cstThisSub = &quot;Array.ExportToTextFile&quot;
+Const cstSubArgs = &quot;Array_1D, FileName&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bExport = False
+
+Check:
+ If IsMissing(Encoding) Or IsEmpty(Encoding) Then Encoding = &quot;UTF-8&quot;
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1, V_STRING, True) Then GoTo Finally
+ If Not SF_Utils._ValidateFile(FileName, &quot;FileName&quot;) Then GoTo Finally
+ If Not SF_Utils._Validate(Encoding, &quot;Encoding&quot;, V_STRING) Then GoTo Finally
+ End If
+
+Try:
+ Set oFile = SF_FileSystem.CreateTextFile(FileName, Overwrite := True, Encoding := Encoding)
+ If Not IsNull(oFile) Then
+ With oFile
+ For Each sLine In Array_1D
+ .WriteLine(sLine)
+ Next sLine
+ .CloseFile()
+ End With
+ End If
+
+ bExport = True
+
+Finally:
+ If Not IsNull(oFile) Then Set oFile = oFile.Dispose()
+ ExportToTextFile = bExport
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.ExportToTextFile
+
+REM -----------------------------------------------------------------------------
+Public Function ExtractColumn(Optional ByRef Array_2D As Variant _
+ , Optional ByVal ColumnIndex As Variant _
+ ) As Variant
+&apos;&apos;&apos; ExtractColumn extracts from a 2D array a specific column
+&apos;&apos;&apos; Args
+&apos;&apos;&apos; Array_2D: the array from which to extract
+&apos;&apos;&apos; ColumnIndex: the column to extract - must be in the interval [LBound, UBound]
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; the extracted column. Its LBound and UBound are identical to that of the 1st dimension of Array_2D
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; ARRAYINDEX1ERROR
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; |1, 2, 3|
+&apos;&apos;&apos; SF_Array.ExtractColumn( |4, 5, 6|, 2) returns (3, 6, 9)
+&apos;&apos;&apos; |7, 8, 9|
+
+Dim vExtractColumn As Variant &apos; Return value
+Dim lMin1 As Long &apos; LBound1 of input array
+Dim lMax1 As Long &apos; UBound1 of input array
+Dim lMin2 As Long &apos; LBound1 of input array
+Dim lMax2 As Long &apos; UBound1 of input array
+Dim i As Long
+Const cstThisSub = &quot;Array.ExtractColumn&quot;
+Const cstSubArgs = &quot;Array_2D, ColumnIndex&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vExtractColumn = Array()
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally
+ If Not SF_Utils._Validate(ColumnIndex, &quot;ColumnIndex&quot;, V_NUMERIC) Then GoTo Finally
+ End If
+
+Try:
+ &apos; Compute future dimensions of output array
+ lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
+ If ColumnIndex &lt; lMin2 Or ColumnIndex &gt; lMax2 Then GoTo CatchIndex
+ lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
+ ReDim vExtractColumn(lMin1 To lMax1)
+
+ &apos; Copy Column of input array to output array
+ For i = lMin1 To lMax1
+ vExtractColumn(i) = Array_2D(i, ColumnIndex)
+ Next i
+
+Finally:
+ ExtractColumn = vExtractColumn()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchIndex:
+ SF_Exception.RaiseFatal(ARRAYINDEX1ERROR, &quot;ColumnIndex&quot;, SF_Array._Repr(Array_2D), ColumnIndex)
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.ExtractColumn
+
+REM -----------------------------------------------------------------------------
+Public Function ExtractRow(Optional ByRef Array_2D As Variant _
+ , Optional ByVal RowIndex As Variant _
+ ) As Variant
+&apos;&apos;&apos; ExtractRow extracts from a 2D array a specific row
+&apos;&apos;&apos; Args
+&apos;&apos;&apos; Array_2D: the array from which to extract
+&apos;&apos;&apos; RowIndex: the row to extract - must be in the interval [LBound, UBound]
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; the extracted row. Its LBound and UBound are identical to that of the 2nd dimension of Array_2D
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; ARRAYINDEX1ERROR
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; |1, 2, 3|
+&apos;&apos;&apos; SF_Array.ExtractRow(|4, 5, 6|, 2) returns (7, 8, 9)
+&apos;&apos;&apos; |7, 8, 9|
+
+Dim vExtractRow As Variant &apos; Return value
+Dim lMin1 As Long &apos; LBound1 of input array
+Dim lMax1 As Long &apos; UBound1 of input array
+Dim lMin2 As Long &apos; LBound1 of input array
+Dim lMax2 As Long &apos; UBound1 of input array
+Dim i As Long
+Const cstThisSub = &quot;Array.ExtractRow&quot;
+Const cstSubArgs = &quot;Array_2D, RowIndex&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vExtractRow = Array()
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally
+ If Not SF_Utils._Validate(RowIndex, &quot;RowIndex&quot;, V_NUMERIC) Then GoTo Finally
+ End If
+
+Try:
+ &apos; Compute future dimensions of output array
+ lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
+ If RowIndex &lt; lMin1 Or RowIndex &gt; lMax1 Then GoTo CatchIndex
+ lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
+ ReDim vExtractRow(lMin2 To lMax2)
+
+ &apos; Copy row of input array to output array
+ For i = lMin2 To lMax2
+ vExtractRow(i) = Array_2D(RowIndex, i)
+ Next i
+
+Finally:
+ ExtractRow = vExtractRow()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchIndex:
+ SF_Exception.RaiseFatal(ARRAYINDEX1ERROR, &quot;RowIndex&quot;, SF_Array._Repr(Array_2D), RowIndex)
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.ExtractRow
+
+REM -----------------------------------------------------------------------------
+Public Function Flatten(Optional ByRef Array_1D As Variant) As Variant
+&apos;&apos;&apos; Stack all items and all items in subarrays into one array without subarrays
+&apos;&apos;&apos; Args
+&apos;&apos;&apos; Array_1D: the pre-existing array, may be empty
+&apos;&apos;&apos; Return:
+&apos;&apos;&apos; The new flattened array. Its LBound is identical to that of Array_1D
+&apos;&apos;&apos; If one of the subarrays has a number of dimensions &gt; 1 Then that subarray is left unchanged
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Array.Flatten(Array(1, 2, Array(3, 4, 5)) returns (1, 2, 3, 4, 5)
+
+Dim vFlatten As Variant &apos; Return value
+Dim lMin As Long &apos; LBound of input array
+Dim lMax As Long &apos; UBound of input array
+Dim lIndex As Long &apos; Index in output array
+Dim vItem As Variant &apos; Array single item
+Dim iDims As Integer &apos; Array number of dimensions
+Dim lEmpty As Long &apos; Number of empty subarrays
+Dim i As Long
+Dim j As Long
+Const cstThisSub = &quot;Array.Flatten&quot;
+Const cstSubArgs = &quot;Array_1D&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vFlatten = Array()
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
+ End If
+
+Try:
+ If UBound(Array_1D) &gt;= LBound(Array_1D) Then
+ lMin = LBound(Array_1D) : lMax = UBound(Array_1D)
+ ReDim vFlatten(lMin To lMax) &apos; Initial minimal sizing
+ lEmpty = 0
+ lIndex = lMin - 1
+ For i = lMin To lMax
+ vItem = Array_1D(i)
+ If IsArray(vItem) Then
+ iDims = SF_Array.CountDims(vItem)
+ Select Case iDims
+ Case 0 &apos; Empty arrays are ignored
+ lEmpty = lEmpty + 1
+ Case 1 &apos; Only 1D subarrays are flattened
+ ReDim Preserve vFlatten(lMin To UBound(vFlatten) + UBound(vItem) - LBound(vItem))
+ For j = LBound(vItem) To UBound(vItem)
+ lIndex = lIndex + 1
+ vFlatten(lIndex) = vItem(j)
+ Next j
+ Case &gt; 1 &apos; Other arrays are left unchanged
+ lIndex = lIndex + 1
+ vFlatten(lIndex) = vItem
+ End Select
+ Else
+ lIndex = lIndex + 1
+ vFlatten(lIndex) = vItem
+ End If
+ Next i
+ End If
+ &apos; Reduce size of output if Array_1D is populated with some empty arrays
+ If lEmpty &gt; 0 Then
+ If lIndex - lEmpty &lt; lMin Then
+ vFlatten = Array()
+ Else
+ ReDim Preserve vFlatten(lMin To UBound(vFlatten) - lEmpty)
+ End If
+ End If
+
+Finally:
+ Flatten = vFlatten()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.Flatten
+
+REM -----------------------------------------------------------------------------
+Public Function GetProperty(Optional ByVal PropertyName As Variant) As Variant
+&apos;&apos;&apos; Return the actual value of the given property
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; PropertyName: the name of the property as a string
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The actual value of the property
+&apos;&apos;&apos; Exceptions
+&apos;&apos;&apos; ARGUMENTERROR The property does not exist
+
+Const cstThisSub = &quot;Array.GetProperty&quot;
+Const cstSubArgs = &quot;PropertyName&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ GetProperty = Null
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
+ End If
+
+Try:
+ Select Case UCase(PropertyName)
+ Case Else
+ End Select
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.GetProperty
+
+REM -----------------------------------------------------------------------------
+Public Function ImportFromCSVFile(Optional ByRef FileName As Variant _
+ , Optional ByVal Delimiter As Variant _
+ , Optional ByVal DateFormat As Variant _
+ , Optional ByVal _IsoDate As Variant _
+ ) As Variant
+&apos;&apos;&apos; Import the data contained in a comma-separated values (CSV) file
+&apos;&apos;&apos; The comma may be replaced by any character
+&apos;&apos;&apos; Each line in the file contains a full record
+&apos;&apos;&apos; Line splitting is not allowed)
+&apos;&apos;&apos; However sequences like \n, \t, ... are left unchanged. Use SF_String.Unescape() to manage them
+&apos;&apos;&apos; A special mechanism is implemented to load dates
+&apos;&apos;&apos; The applicable CSV format is described in https://tools.ietf.org/html/rfc4180
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; FileName: the name of the text file containing the data expressed as given by the current FileNaming
+&apos;&apos;&apos; property of the SF_FileSystem service. Default = both URL format or native format
+&apos;&apos;&apos; Delimiter: Default = &quot;,&quot;. Other usual options are &quot;;&quot; and the tab character
+&apos;&apos;&apos; DateFormat: either YYYY-MM-DD, DD-MM-YYYY or MM-DD-YYYY
+&apos;&apos;&apos; The dash (-) may be replaced by a dot (.), a slash (/) or a space
+&apos;&apos;&apos; Other date formats will be ignored
+&apos;&apos;&apos; If &quot;&quot; (default), dates will be considered as strings
+&apos;&apos;&apos; _IsoDate: when True, the execution is initiated from Python, do not convert dates to Date variables. Internal use only
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; A 2D-array with each row corresponding with a single record read in the file
+&apos;&apos;&apos; and each column corresponding with a field of the record
+&apos;&apos;&apos; No check is made about the coherence of the field types across columns
+&apos;&apos;&apos; A best guess will be made to identify numeric and date types
+&apos;&apos;&apos; If a line contains less or more fields than the first line in the file,
+&apos;&apos;&apos; an exception will be raised. Empty lines however are simply ignored
+&apos;&apos;&apos; If the size of the file exceeds the number of items limit, a warning is raised
+&apos;&apos;&apos; and the array is truncated
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; CSVPARSINGERROR Given file is not formatted as a csv file
+&apos;&apos;&apos; CSVOVERFLOWWARNING Maximum number of allowed items exceeded
+
+Dim vArray As Variant &apos; Returned array
+Dim lCol As Long &apos; Index of last column of vArray
+Dim lRow As Long &apos; Index of current row of vArray
+Dim lFileSize As Long &apos; Number of records found in the file
+Dim vCsv As Object &apos; CSV file handler
+Dim sLine As String &apos; Last read line
+Dim vLine As Variant &apos; Array of fields of last read line
+Dim sItem As String &apos; Individual item in the file
+Dim vItem As Variant &apos; Individual item in the output array
+Dim iPosition As Integer &apos; Date position in individual item
+Dim iYear As Integer, iMonth As Integer, iDay As Integer
+ &apos; Date components
+Dim i As Long
+Const cstItemsLimit = 250000 &apos; Maximum number of admitted items
+Const cstThisSub = &quot;Array.ImportFromCSVFile&quot;
+Const cstSubArgs = &quot;FileName, [Delimiter=&quot;&quot;,&quot;&quot;], [DateFormat=&quot;&quot;&quot;&quot;]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vArray = Array()
+
+Check:
+ If IsMissing(Delimiter) Or IsEmpty(Delimiter) Then Delimiter = &quot;,&quot;
+ If IsMissing(DateFormat) Or IsEmpty(DateFormat) Then DateFormat = &quot;&quot;
+ If IsMissing(_IsoDate) Or IsEmpty(_IsoDate) Then _IsoDate = False
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateFile(FileName, &quot;FileName&quot;) Then GoTo Finally
+ If Not SF_Utils._Validate(Delimiter, &quot;Delimiter&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(DateFormat, &quot;DateFormat&quot;, V_STRING) Then GoTo Finally
+ End If
+ If Len(Delimiter) = 0 Then Delimiter = &quot;,&quot;
+
+Try:
+ &apos; Counts the lines present in the file to size the final array
+ &apos; Very beneficial for large files, better than multiple ReDims
+ &apos; Small overhead for small files
+ lFileSize = SF_FileSystem._CountTextLines(FileName, False)
+ If lFileSize &lt;= 0 Then GoTo Finally
+
+ &apos; Reread file line by line
+ Set vCsv = SF_FileSystem.OpenTextFile(FileName, IOMode := SF_FileSystem.ForReading)
+ If IsNull(vCsv) Then GoTo Finally &apos; Open error
+ lRow = -1
+ With vCsv
+ Do While Not .AtEndOfStream
+ sLine = .ReadLine()
+ If Len(sLine) &gt; 0 Then &apos; Ignore empty lines
+ If InStr(sLine, &quot;&quot;&quot;&quot;) &gt; 0 Then vLine = SF_String.SplitNotQuoted(sLine, Delimiter) Else vLine = Split(sLine, Delimiter) &apos; Simple split when relevant
+ lRow = lRow + 1
+ If lRow = 0 Then &apos; Initial sizing of output array
+ lCol = UBound(vLine)
+ ReDim vArray(0 To lFileSize - 1, 0 To lCol)
+ ElseIf UBound(vLine) &lt;&gt; lCol Then
+ GoTo CatchCSVFormat
+ End If
+ &apos; Check type and copy all items of the line
+ For i = 0 To lCol
+ If Left(vLine(i), 1) = &quot;&quot;&quot;&quot; Then sItem = SF_String.Unquote(vLine(i)) Else sItem = vLine(i) &apos; Unquote only when useful
+ &apos; Interpret the individual line item
+ Select Case True
+ Case IsNumeric(sItem)
+ If InStr(sItem, &quot;.&quot;) + InStr(1, sItem, &quot;e&quot;, 1) &gt; 0 Then vItem = Val(sItem) Else vItem = CLng(sItem)
+ Case DateFormat &lt;&gt; &quot;&quot; And Len(sItem) = Len(DateFormat)
+ If SF_String.IsADate(sItem, DateFormat) Then
+ iPosition = InStr(DateFormat, &quot;YYYY&quot;) : iYear = CInt(Mid(sItem, iPosition, 4))
+ iPosition = InStr(DateFormat, &quot;MM&quot;) : iMonth = CInt(Mid(sItem, iPosition, 2))
+ iPosition = InStr(DateFormat, &quot;DD&quot;) : iDay = CInt(Mid(sItem, iPosition, 2))
+ vItem = DateSerial(iYear, iMonth, iDay)
+ If _IsoDate Then vItem = SF_Utils._CDateToIso(vItem) &apos; Called from Python
+ Else
+ vItem = sItem
+ End If
+ Case Else : vItem = sItem
+ End Select
+ vArray(lRow, i) = vItem
+ Next i
+ End If
+ &apos; Provision to avoid very large arrays and their sometimes erratic behaviour
+ If (lRow + 2) * (lCol + 1) &gt; cstItemsLimit Then
+ ReDim Preserve vArray(0 To lRow, 0 To lCol)
+ GoTo CatchOverflow
+ End If
+ Loop
+ End With
+
+Finally:
+ If Not IsNull(vCsv) Then
+ vCsv.CloseFile()
+ Set vCsv = vCsv.Dispose()
+ End If
+ ImportFromCSVFile = vArray
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchCSVFormat:
+ SF_Exception.RaiseFatal(CSVPARSINGERROR, FileName, vCsv.Line, sLine)
+ GoTo Finally
+CatchOverflow:
+ &apos;TODO SF_Exception.RaiseWarning(SF_Exception.CSVOVERFLOWWARNING, cstThisSub)
+ &apos;MsgBox &quot;TOO MUCH LINES !!&quot;
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.ImportFromCSVFile
+
+REM -----------------------------------------------------------------------------
+Public Function IndexOf(Optional ByRef Array_1D As Variant _
+ , Optional ByVal ToFind As Variant _
+ , Optional ByVal CaseSensitive As Variant _
+ , Optional ByVal SortOrder As Variant _
+ ) As Long
+&apos;&apos;&apos; Finds in a 1D array the ToFind number, string or date
+&apos;&apos;&apos; ToFind must exist within the array.
+&apos;&apos;&apos; The comparison between strings can be done case-sensitively or not
+&apos;&apos;&apos; If the array is sorted then
+&apos;&apos;&apos; the array must be filled homogeneously, i.e. all items must be of the same type
+&apos;&apos;&apos; Empty and Null items are forbidden
+&apos;&apos;&apos; a binary search is done
+&apos;&apos;&apos; Otherwise the array is scanned from top. Null or Empty items are simply ignored
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array_1D: the array to scan
+&apos;&apos;&apos; ToFind: a number, a date or a string to find
+&apos;&apos;&apos; CaseSensitive: Only for string comparisons, default = False
+&apos;&apos;&apos; SortOrder: &quot;ASC&quot;, &quot;DESC&quot; or &quot;&quot; (= not sorted, default)
+&apos;&apos;&apos; Return: the index of the found item, LBound - 1 if not found
+&apos;&apos;&apos; Result is unpredictable when array is announced sorted and is in reality not
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Array.IndexOf(Array(&quot;A&quot;,&quot;B&quot;,&quot;c&quot;,&quot;D&quot;), &quot;C&quot;, SortOrder := &quot;ASC&quot;) returns 2
+&apos;&apos;&apos; SF_Array.IndexOf(Array(&quot;A&quot;,&quot;B&quot;,&quot;c&quot;,&quot;D&quot;), &quot;C&quot;, CaseSensitive := True) returns -1
+
+Dim vFindItem As Variant &apos; 2-items array (0) = True if found, (1) = Index where found
+Dim lIndex As Long &apos; Return value
+Dim iToFindType As Integer &apos; VarType of ToFind
+Const cstThisSub = &quot;Array.IndexOf&quot;
+Const cstSubArgs = &quot;Array_1D, ToFind, [CaseSensitive=False], [SortOrder=&quot;&quot;&quot;&quot;|&quot;&quot;ASC&quot;&quot;|&quot;&quot;DESC&quot;&quot;]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+
+ lIndex = -1
+
+Check:
+ If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False
+ If IsMissing(SortOrder) Or IsEmpty(SortOrder) Then SortOrder = &quot;&quot;
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(SortOrder, &quot;SortOrder&quot;, V_STRING, Array(&quot;ASC&quot;, &quot;DESC&quot;, &quot;&quot;)) Then GoTo Finally
+ If Not SF_Utils._Validate(ToFind, &quot;ToFind&quot;, Array(V_STRING, V_DATE, V_NUMERIC)) Then GoTo Finally
+ iToFindType = SF_Utils._VarTypeExt(ToFind)
+ If SortOrder &lt;&gt; &quot;&quot; Then
+ If Not SF_Utils._ValidateArray(Array_1D, &quot;Array&quot;, 1, iToFindType) Then GoTo Finally
+ Else
+ If Not SF_Utils._ValidateArray(Array_1D, &quot;Array&quot;, 1) Then GoTo Finally
+ End If
+ If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
+ End If
+
+Try:
+ vFindItem = SF_Array._FindItem(Array_1D, ToFind, CaseSensitive, SortOrder)
+ If vFindItem(0) = True Then lIndex = vFindItem(1) Else lIndex = LBound(Array_1D) - 1
+
+Finally:
+ IndexOf = lIndex
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.IndexOf
+
+REM -----------------------------------------------------------------------------
+Public Function Insert(Optional ByRef Array_1D As Variant _
+ , Optional ByVal Before As Variant _
+ , ParamArray pvArgs() As Variant _
+ ) As Variant
+&apos;&apos;&apos; Insert before the index Before of the input array the items listed as arguments
+&apos;&apos;&apos; Arguments are inserted blindly
+&apos;&apos;&apos; each of them might be a scalar of any type or a subarray
+&apos;&apos;&apos; Args
+&apos;&apos;&apos; Array_1D: the pre-existing array, may be empty
+&apos;&apos;&apos; Before: the index before which to insert; must be in the interval [LBound, UBound + 1]
+&apos;&apos;&apos; pvArgs: a list of items to Insert inside Array_1D
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; the new rxtended array. Its LBound is identical to that of Array_1D
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; ARRAYINSERTERROR
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Array.Insert(Array(1, 2, 3), 2, 4, 5) returns (1, 2, 4, 5, 3)
+
+Dim vInsert As Variant &apos; Return value
+Dim lNbArgs As Long &apos; Number of elements to Insert
+Dim lMin As Long &apos; LBound of input array
+Dim lMax As Long &apos; UBound of input array
+Dim i As Long
+Const cstThisSub = &quot;Array.Insert&quot;
+Const cstSubArgs = &quot;Array_1D, Before, arg0[, arg1] ...&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vInsert = Array()
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
+ If Not SF_Utils._Validate(Before, &quot;Before&quot;, V_NUMERIC) Then GoTo Finally
+ If Before &lt; LBound(Array_1D) Or Before &gt; UBound(Array_1D) + 1 Then GoTo CatchArgument
+ End If
+
+Try:
+ lNbArgs = UBound(pvArgs) + 1 &apos; pvArgs is always zero-based
+ lMin = LBound(Array_1D) &apos; = LBound(vInsert)
+ lMax = UBound(Array_1D) &apos; &lt;&gt; UBound(vInsert)
+ If lNbArgs &gt; 0 Then
+ ReDim vInsert(lMin To lMax + lNbArgs)
+ For i = lMin To UBound(vInsert)
+ If i &lt; Before Then
+ vInsert(i) = Array_1D(i)
+ ElseIf i &lt; Before + lNbArgs Then
+ vInsert(i) = pvArgs(i - Before)
+ Else
+ vInsert(i) = Array_1D(i - lNbArgs)
+ End If
+ Next i
+ Else
+ vInsert() = Array_1D()
+ End If
+
+Finally:
+ Insert = vInsert()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchArgument:
+ &apos;TODO SF_Exception.RaiseFatal(ARRAYINSERTERROR, cstThisSub)
+ MsgBox &quot;INVALID ARGUMENT VALUE !!&quot;
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.Insert
+
+REM -----------------------------------------------------------------------------
+Public Function InsertSorted(Optional ByRef Array_1D As Variant _
+ , Optional ByVal Item As Variant _
+ , Optional ByVal SortOrder As Variant _
+ , Optional ByVal CaseSensitive As Variant _
+ ) As Variant
+&apos;&apos;&apos; Insert in a sorted array a new item on its place
+&apos;&apos;&apos; the array must be filled homogeneously, i.e. all items must be of the same type
+&apos;&apos;&apos; Empty and Null items are forbidden
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array_1D: the array to sort
+&apos;&apos;&apos; Item: the scalar value to insert, same type as the existing array items
+&apos;&apos;&apos; SortOrder: &quot;ASC&quot; (default) or &quot;DESC&quot;
+&apos;&apos;&apos; CaseSensitive: Default = False
+&apos;&apos;&apos; Returns: the extended sorted array with same LBound as input array
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; InsertSorted(Array(&quot;A&quot;, &quot;C&quot;, &quot;a&quot;, &quot;b&quot;), &quot;B&quot;, CaseSensitive := True) returns (&quot;A&quot;, &quot;B&quot;, &quot;C&quot;, &quot;a&quot;, &quot;b&quot;)
+
+Dim vSorted() As Variant &apos; Return value
+Dim iType As Integer &apos; VarType of elements in input array
+Dim lMin As Long &apos; LBound of input array
+Dim lMax As Long &apos; UBound of input array
+Dim lIndex As Long &apos; Place where to insert new item
+Const cstThisSub = &quot;Array.InsertSorted&quot;
+Const cstSubArgs = &quot;Array_1D, Item, [SortOrder=&quot;&quot;ASC&quot;&quot;|&quot;&quot;DESC&quot;&quot;], [CaseSensitive=False]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vSorted = Array()
+
+Check:
+ If IsMissing(SortOrder) Or IsEmpty(SortOrder) Then SortOrder = &quot;ASC&quot;
+ If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1, 0) Then GoTo Finally
+ If LBound(Array_1D) &lt;= UBound(Array_1D) Then
+ iType = SF_Utils._VarTypeExt(Array_1D(LBound(Array_1D)))
+ If Not SF_Utils._Validate(Item, &quot;Item&quot;, iType) Then GoTo Finally
+ Else
+ If Not SF_Utils._Validate(Item, &quot;Item&quot;, Array(V_STRING, V_DATE, V_NUMERIC)) Then GoTo Finally
+ End If
+ If Not SF_Utils._Validate(SortOrder, &quot;SortOrder&quot;, V_STRING, Array(&quot;ASC&quot;,&quot;DESC&quot;)) Then GoTo Finally
+ If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
+ End If
+
+Try:
+ lMin = LBound(Array_1D)
+ lMax = UBound(Array_1D)
+ lIndex = SF_Array._FindItem(Array_1D, Item, CaseSensitive, SortOrder)(1)
+ vSorted = SF_Array.Insert(Array_1D, lIndex, Item)
+
+Finally:
+ InsertSorted = vSorted()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.InsertSorted
+
+REM -----------------------------------------------------------------------------
+Public Function Intersection(Optional ByRef Array1_1D As Variant _
+ , Optional ByRef Array2_1D As Variant _
+ , Optional ByVal CaseSensitive As Variant _
+ ) As Variant
+&apos;&apos;&apos; Build a set being the intersection of the two input arrays, i.e. items are contained in both arrays
+&apos;&apos;&apos; both input arrays must be filled homogeneously, i.e. all items must be of the same type
+&apos;&apos;&apos; Empty and Null items are forbidden
+&apos;&apos;&apos; The comparison between strings is case sensitive or not
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array1_1D: a 1st input array
+&apos;&apos;&apos; Array2_1D: a 2nd input array
+&apos;&apos;&apos; CaseSensitive: default = False
+&apos;&apos;&apos; Returns: a zero-based array containing unique items stored in both input arrays
+&apos;&apos;&apos; The output array is sorted in ascending order
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; Intersection(Array(&quot;A&quot;, &quot;C&quot;, &quot;A&quot;, &quot;b&quot;, &quot;B&quot;), Array(&quot;C&quot;, &quot;Z&quot;, &quot;b&quot;), True) returns (&quot;C&quot;, &quot;b&quot;)
+
+Dim vIntersection() As Variant &apos; Return value
+Dim vSorted() As Variant &apos; The shortest input array after sort
+Dim iType As Integer &apos; VarType of elements in input arrays
+Dim lMin1 As Long &apos; LBound of 1st input array
+Dim lMax1 As Long &apos; UBound of 1st input array
+Dim lMin2 As Long &apos; LBound of 2nd input array
+Dim lMax2 As Long &apos; UBound of 2nd input array
+Dim lMin As Long &apos; LBound of unsorted array
+Dim lMax As Long &apos; UBound of unsorted array
+Dim iShortest As Integer &apos; 1 or 2 depending on shortest input array
+Dim lSize As Long &apos; Number of Intersection items
+Dim vItem As Variant &apos; One single item in the array
+Dim i As Long
+Const cstThisSub = &quot;Array.Intersection&quot;
+Const cstSubArgs = &quot;Array1_1D, Array2_1D, [CaseSensitive=False]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vIntersection = Array()
+
+Check:
+ If IsMissing(CaseSensitive) Then CaseSensitive = False
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array1_1D, &quot;Array1_1D&quot;, 1, 0, True) Then GoTo Finally
+ iType = SF_Utils._VarTypeExt(Array1_1D(LBound(Array1_1D)))
+ If Not SF_Utils._ValidateArray(Array2_1D, &quot;Array2_1D&quot;, 1, iType, True) Then GoTo Finally
+ If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
+ End If
+
+Try:
+ lMin1 = LBound(Array1_1D) : lMax1 = UBound(Array1_1D)
+ lMin2 = LBound(Array2_1D) : lMax2 = UBound(Array2_1D)
+
+ &apos; If one of both arrays is empty, do nothing
+ If lMax1 &gt;= lMin1 And lMax2 &gt;= lMin2 Then
+
+ &apos; First sort the shortest array
+ If lMax1 - lMin1 &lt;= lMax2 - lMin2 Then
+ iShortest = 1
+ vSorted = SF_Array.Sort(Array1_1D, &quot;ASC&quot;, CaseSensitive)
+ lMin = lMin2 : lMax = lMax2 &apos; Bounds of unsorted array
+ Else
+ iShortest = 2
+ vSorted = SF_Array.Sort(Array2_1D, &quot;ASC&quot;, CaseSensitive)
+ lMin = lMin1 : lMax = lMax1 &apos; Bounds of unsorted array
+ End If
+
+ &apos; Resize the output array to the size of the shortest array
+ ReDim vIntersection(0 To (lMax - lMin))
+ lSize = -1
+
+ &apos; Fill vIntersection one by one only with items present in both sets
+ For i = lMin To lMax
+ If iShortest = 1 Then vItem = Array2_1D(i) Else vItem = Array1_1D(i) &apos; Pick in unsorted array
+ If SF_Array.Contains(vSorted, vItem, CaseSensitive, &quot;ASC&quot;) Then
+ lSize = lSize + 1
+ vIntersection(lSize) = vItem
+ End If
+ Next i
+
+ &apos; Remove unfilled entries and duplicates
+ If lSize &gt;= 0 Then
+ ReDim Preserve vIntersection(0 To lSize)
+ vIntersection() = SF_Array.Unique(vIntersection, CaseSensitive)
+ Else
+ vIntersection = Array()
+ End If
+ End If
+
+Finally:
+ Intersection = vIntersection()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.Intersection
+
+REM -----------------------------------------------------------------------------
+Public Function Join2D(Optional ByRef Array_2D As Variant _
+ , Optional ByVal ColumnDelimiter As Variant _
+ , Optional ByVal RowDelimiter As Variant _
+ , Optional ByVal Quote As Variant _
+ ) As String
+&apos;&apos;&apos; Join a two-dimensional array with two delimiters, one for columns, one for rows
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array_2D: each item must be either a String, a number, a Date or a Boolean
+&apos;&apos;&apos; ColumnDelimiter: delimits each column (default = Tab/Chr(9))
+&apos;&apos;&apos; RowDelimiter: delimits each row (default = LineFeed/Chr(10))
+&apos;&apos;&apos; Quote: if True, protect strings with double quotes (default = False)
+&apos;&apos;&apos; Return:
+&apos;&apos;&apos; A string after conversion of numbers and dates
+&apos;&apos;&apos; Invalid items are replaced by a zero-length string
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; | 1, 2, &quot;A&quot;, [2020-02-29], 5 |
+&apos;&apos;&apos; SF_Array.Join_2D( | 6, 7, &quot;this is a string&quot;, 9, 10 | , &quot;,&quot;, &quot;/&quot;)
+&apos;&apos;&apos; &apos; &quot;1,2,A,2020-02-29 00:00:00,5/6,7,this is a string,9,10&quot;
+
+Dim sJoin As String &apos; The return value
+Dim sItem As String &apos; The string representation of a single item
+Dim vItem As Variant &apos; Single item
+Dim lMin1 As Long &apos; LBound1 of input array
+Dim lMax1 As Long &apos; UBound1 of input array
+Dim lMin2 As Long &apos; LBound2 of input array
+Dim lMax2 As Long &apos; UBound2 of input array
+Dim i As Long
+Dim j As Long
+Const cstThisSub = &quot;Array.Join2D&quot;
+Const cstSubArgs = &quot;Array_2D, [ColumnDelimiter=Chr(9)], [RowDelimiter=Chr(10)], [Quote=False]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ sJoin = &quot;&quot;
+
+Check:
+ If IsMissing(ColumnDelimiter) Or IsEmpty(ColumnDelimiter) Then ColumnDelimiter = Chr(9)
+ If IsMissing(RowDelimiter) Or IsEmpty(RowDelimiter) Then RowDelimiter = Chr(10)
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally
+ If Not SF_Utils._Validate(ColumnDelimiter, &quot;ColumnDelimiter&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(RowDelimiter, &quot;RowDelimiter&quot;, V_STRING) Then GoTo Finally
+ If Not SF_Utils._Validate(Quote, &quot;Quote&quot;, V_BOOLEAN) Then GoTo Finally
+ End If
+
+Try:
+ lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
+ lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
+ If lMin1 &lt;= lMax1 Then
+ For i = lMin1 To lMax1
+ For j = lMin2 To lMax2
+ vItem = Array_2D(i, j)
+ Select Case SF_Utils._VarTypeExt(vItem)
+ Case V_STRING : If Quote Then sItem = SF_String.Quote(vItem) Else sItem = vItem
+ Case V_NUMERIC, V_DATE : sItem = SF_Utils._Repr(vItem)
+ Case V_BOOLEAN : sItem = Iif(vItem, &quot;True&quot;, &quot;False&quot;) &apos;TODO: L10N
+ Case Else : sItem = &quot;&quot;
+ End Select
+ sJoin = sJoin &amp; sItem &amp; Iif(j &lt; lMax2, ColumnDelimiter, &quot;&quot;)
+ Next j
+ sJoin = sJoin &amp; Iif(i &lt; lMax1, RowDelimiter, &quot;&quot;)
+ Next i
+ End If
+
+Finally:
+ Join2D = sJoin
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.Join2D
+
+REM -----------------------------------------------------------------------------
+Public Function Methods() As Variant
+&apos;&apos;&apos; Return the list of public methods of the Array service as an array
+
+ Methods = Array( _
+ &quot;Append&quot; _
+ , &quot;AppendColumn&quot; _
+ , &quot;AppendRow&quot; _
+ , &quot;Contains&quot; _
+ , &quot;ConvertToDictionary&quot; _
+ , &quot;CountDims&quot; _
+ , &quot;Difference&quot; _
+ , &quot;ExportToTextFile&quot; _
+ , &quot;ExtractColumn&quot; _
+ , &quot;ExtractRow&quot; _
+ , &quot;Flatten&quot; _
+ , &quot;ImportFromCSVFile&quot; _
+ , &quot;IndexOf&quot; _
+ , &quot;Insert&quot; _
+ , &quot;InsertSorted&quot; _
+ , &quot;Intersection&quot; _
+ , &quot;Join2D&quot; _
+ , &quot;Prepend&quot; _
+ , &quot;PrependColumn&quot; _
+ , &quot;PrependRow&quot; _
+ , &quot;RangeInit&quot; _
+ , &quot;Reverse&quot; _
+ , &quot;Shuffle&quot; _
+ , &quot;Sort&quot; _
+ , &quot;SortColumns&quot; _
+ , &quot;SortRows&quot; _
+ , &quot;Transpose&quot; _
+ , &quot;TrimArray&quot; _
+ , &quot;Union&quot; _
+ , &quot;Unique&quot; _
+ )
+
+End Function &apos; ScriptForge.SF_Array.Methods
+
+REM -----------------------------------------------------------------------------
+Public Function Prepend(Optional ByRef Array_1D As Variant _
+ , ParamArray pvArgs() As Variant _
+ ) As Variant
+&apos;&apos;&apos; Prepend at the beginning of the input array the items listed as arguments
+&apos;&apos;&apos; Arguments are Prepended blindly
+&apos;&apos;&apos; each of them might be a scalar of any type or a subarray
+&apos;&apos;&apos; Args
+&apos;&apos;&apos; Array_1D: the pre-existing array, may be empty
+&apos;&apos;&apos; pvArgs: a list of items to Prepend to Array_1D
+&apos;&apos;&apos; Return: the new rxtended array. Its LBound is identical to that of Array_1D
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Array.Prepend(Array(1, 2, 3), 4, 5) returns (4, 5, 1, 2, 3)
+
+Dim vPrepend As Variant &apos; Return value
+Dim lNbArgs As Long &apos; Number of elements to Prepend
+Dim lMin As Long &apos; LBound of input array
+Dim lMax As Long &apos; UBound of input array
+Dim i As Long
+Const cstThisSub = &quot;Array.Prepend&quot;
+Const cstSubArgs = &quot;Array_1D, arg0[, arg1] ...&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vPrepend = Array()
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
+ End If
+
+Try:
+ lNbArgs = UBound(pvArgs) + 1 &apos; pvArgs is always zero-based
+ lMin = LBound(Array_1D) &apos; = LBound(vPrepend)
+ lMax = UBound(Array_1D) &apos; &lt;&gt; UBound(vPrepend)
+ If lMax &lt; LBound(Array_1D) And lNbArgs &gt; 0 Then &apos; Initial array is empty
+ ReDim vPrepend(0 To lNbArgs - 1)
+ Else
+ ReDim vPrepend(lMin To lMax + lNbArgs)
+ End If
+ For i = lMin To UBound(vPrepend)
+ If i &lt; lMin + lNbArgs Then vPrepend(i) = pvArgs(i - lMin) Else vPrepend(i) = Array_1D(i - lNbArgs)
+ Next i
+
+Finally:
+ Prepend = vPrepend
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.Prepend
+
+REM -----------------------------------------------------------------------------
+Public Function PrependColumn(Optional ByRef Array_2D As Variant _
+ , Optional ByRef Column As Variant _
+ ) As Variant
+&apos;&apos;&apos; PrependColumn prepends to the left side of a 2D array a new Column
+&apos;&apos;&apos; Args
+&apos;&apos;&apos; Array_2D: the pre-existing array, may be empty
+&apos;&apos;&apos; If the array has 1 dimension, it is considered as the last Column of the resulting 2D array
+&apos;&apos;&apos; Column: a 1D array with as many items as there are rows in Array_2D
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; the new rxtended array. Its LBounds are identical to that of Array_2D
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; ARRAYINSERTERROR
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Array.PrependColumn(Array(1, 2, 3), Array(4, 5, 6)) returns ((4, 1), (5, 2), (6, 3))
+&apos;&apos;&apos; x = SF_Array.PrependColumn(Array(), Array(1, 2, 3)) =&gt; ∀ i ∈ {0 ≤ i ≤ 2} : x(0, i) ≡ i
+
+Dim vPrependColumn As Variant &apos; Return value
+Dim iDims As Integer &apos; Dimensions of Array_2D
+Dim lMin1 As Long &apos; LBound1 of input array
+Dim lMax1 As Long &apos; UBound1 of input array
+Dim lMin2 As Long &apos; LBound2 of input array
+Dim lMax2 As Long &apos; UBound2 of input array
+Dim lMin As Long &apos; LBound of Column array
+Dim lMax As Long &apos; UBound of Column array
+Dim i As Long
+Dim j As Long
+Const cstThisSub = &quot;Array.PrependColumn&quot;
+Const cstSubArgs = &quot;Array_2D, Column&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vPrependColumn = Array()
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;) Then GoTo Finally &apos;Initial check: not missing and array
+ If Not SF_Utils._ValidateArray(Column, &quot;Column&quot;, 1) Then GoTo Finally
+ End If
+ iDims = SF_Array.CountDims(Array_2D)
+ If iDims &gt; 2 Then
+ If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally &apos;2nd check to manage error
+ End If
+
+Try:
+ lMin = LBound(Column)
+ lMax = UBound(Column)
+
+ &apos; Compute future dimensions of output array
+ Select Case iDims
+ Case 0 : lMin1 = lMin : lMax1 = lMax
+ lMin2 = 0 : lMax2 = -1
+ Case 1 : lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
+ lMin2 = 0 : lMax2 = 0
+ Case 2 : lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
+ lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
+ End Select
+ If iDims &gt; 0 And lMax - lMin &lt;&gt; lMax1 - lMin1 Then GoTo CatchColumn
+ ReDim vPrependColumn(lMin1 To lMax1, lMin2 To lMax2 + 1)
+
+ &apos; Copy input array to output array
+ For i = lMin1 To lMax1
+ For j = lMin2 + 1 To lMax2 + 1
+ If iDims = 2 Then vPrependColumn(i, j) = Array_2D(i, j - 1) Else vPrependColumn(i, j) = Array_2D(i)
+ Next j
+ Next i
+ &apos; Copy new Column
+ For i = lMin1 To lMax1
+ vPrependColumn(i, lMin2) = Column(i)
+ Next i
+
+Finally:
+ PrependColumn = vPrependColumn()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchColumn:
+ SF_Exception.RaiseFatal(ARRAYINSERTERROR, &quot;Column&quot;, SF_Array._Repr(Array_2D), SF_Utils._Repr(Column, MAXREPR))
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.PrependColumn
+
+REM -----------------------------------------------------------------------------
+Public Function PrependRow(Optional ByRef Array_2D As Variant _
+ , Optional ByRef Row As Variant _
+ ) As Variant
+&apos;&apos;&apos; PrependRow prepends on top of a 2D array a new row
+&apos;&apos;&apos; Args
+&apos;&apos;&apos; Array_2D: the pre-existing array, may be empty
+&apos;&apos;&apos; If the array has 1 dimension, it is considered as the last row of the resulting 2D array
+&apos;&apos;&apos; Row: a 1D array with as many items as there are columns in Array_2D
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; the new rxtended array. Its LBounds are identical to that of Array_2D
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; ARRAYINSERTERROR
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Array.PrependRow(Array(1, 2, 3), Array(4, 5, 6)) returns ((4, 5, 6), (1, 2, 3))
+&apos;&apos;&apos; x = SF_Array.PrependColumn(Array(), Array(1, 2, 3) =&gt; ∀ i ∈ {0 ≤ i ≤ 2} : x(i, 0) ≡ i
+
+Dim vPrependRow As Variant &apos; Return value
+Dim iDims As Integer &apos; Dimensions of Array_2D
+Dim lMin1 As Long &apos; LBound1 of input array
+Dim lMax1 As Long &apos; UBound1 of input array
+Dim lMin2 As Long &apos; LBound2 of input array
+Dim lMax2 As Long &apos; UBound2 of input array
+Dim lMin As Long &apos; LBound of row array
+Dim lMax As Long &apos; UBound of row array
+Dim i As Long
+Dim j As Long
+Const cstThisSub = &quot;Array.PrependRow&quot;
+Const cstSubArgs = &quot;Array_2D, Row&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vPrependRow = Array()
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;) Then GoTo Finally &apos;Initial check: not missing and array
+ If Not SF_Utils._ValidateArray(Row, &quot;Row&quot;, 1) Then GoTo Finally
+ End If
+ iDims = SF_Array.CountDims(Array_2D)
+ If iDims &gt; 2 Then
+ If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally &apos;2nd check to manage error
+ End If
+
+Try:
+ lMin = LBound(Row)
+ lMax = UBound(Row)
+
+ &apos; Compute future dimensions of output array
+ Select Case iDims
+ Case 0 : lMin1 = 0 : lMax1 = -1
+ lMin2 = lMin : lMax2 = lMax
+ Case 1 : lMin1 = 0 : lMax1 = 0
+ lMin2 = LBound(Array_2D, 1) : lMax2 = UBound(Array_2D, 1)
+ Case 2 : lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
+ lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
+ End Select
+ If iDims &gt; 0 And lMax - lMin &lt;&gt; lMax2 - lMin2 Then GoTo CatchRow
+ ReDim vPrependRow(lMin1 To lMax1 + 1, lMin2 To lMax2)
+
+ &apos; Copy input array to output array
+ For i = lMin1 + 1 To lMax1 + 1
+ For j = lMin2 To lMax2
+ If iDims = 2 Then vPrependRow(i, j) = Array_2D(i - 1, j) Else vPrependRow(i, j) = Array_2D(j)
+ Next j
+ Next i
+ &apos; Copy new row
+ For j = lMin2 To lMax2
+ vPrependRow(lMin1, j) = Row(j)
+ Next j
+
+Finally:
+ PrependRow = vPrependRow()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchRow:
+ SF_Exception.RaiseFatal(ARRAYINSERTERROR, &quot;Row&quot;, SF_Array._Repr(Array_2D), SF_Utils._Repr(Row, MAXREPR))
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.PrependRow
+
+REM -----------------------------------------------------------------------------
+Public Function Properties() As Variant
+&apos;&apos;&apos; Return the list or properties as an array
+
+ Properties = Array( _
+ )
+
+End Function &apos; ScriptForge.SF_Array.Properties
+
+REM -----------------------------------------------------------------------------
+Public Function RangeInit(Optional ByVal From As Variant _
+ , Optional ByVal UpTo As Variant _
+ , Optional ByVal ByStep As Variant _
+ ) As Variant
+&apos;&apos;&apos; Initialize a new zero-based array with numeric values
+&apos;&apos;&apos; Args: all numeric
+&apos;&apos;&apos; From: value of first item
+&apos;&apos;&apos; UpTo: last item should not exceed UpTo
+&apos;&apos;&apos; ByStep: difference between 2 successive items
+&apos;&apos;&apos; Return: the new array
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; ARRAYSEQUENCEERROR Wrong arguments, f.i. UpTo &lt; From with ByStep &gt; 0
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Array.RangeInit(10, 1, -1) returns (10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
+
+Dim lIndex As Long &apos; Index of array
+Dim lSize As Long &apos; UBound of resulting array
+Dim vCurrentItem As Variant &apos; Last stored item
+Dim vArray() &apos; The return value
+Const cstThisSub = &quot;Array.RangeInit&quot;
+Const cstSubArgs = &quot;From, UpTo, [ByStep = 1]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vArray = Array()
+
+Check:
+ If IsMissing(ByStep) Or IsEmpty(ByStep) Then ByStep = 1
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(From, &quot;From&quot;, V_NUMERIC) Then GoTo Finally
+ If Not SF_Utils._Validate(UpTo, &quot;UpTo&quot;, V_NUMERIC) Then GoTo Finally
+ If Not SF_Utils._Validate(ByStep, &quot;ByStep&quot;, V_NUMERIC) Then GoTo Finally
+ End If
+ If (From &lt; UpTo And ByStep &lt;= 0) Or (From &gt; UpTo And ByStep &gt;= 0) Then GoTo CatchSequence
+
+Try:
+ lSize = CLng(Abs((UpTo - From) / ByStep))
+ ReDim vArray(0 To lSize)
+ For lIndex = 0 To lSize
+ vArray(lIndex) = From + lIndex * ByStep
+ Next lIndex
+
+Finally:
+ RangeInit = vArray
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchSequence:
+ SF_Exception.RaiseFatal(ARRAYSEQUENCEERROR, From, UpTo, ByStep)
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.RangeInit
+
+REM -----------------------------------------------------------------------------
+Public Function Reverse(Optional ByRef Array_1D As Variant) As Variant
+&apos;&apos;&apos; Return the reversed 1D input array
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array_1D: the array to reverse
+&apos;&apos;&apos; Returns: the reversed array
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Array.Reverse(Array(1, 2, 3, 4)) returns (4, 3, 2, 1)
+
+Dim vReverse() As Variant &apos; Return value
+Dim lHalf As Long &apos; Middle of array
+Dim lMin As Long &apos; LBound of input array
+Dim lMax As Long &apos; UBound of input array
+Dim i As Long, j As Long
+Const cstThisSub = &quot;Array.Reverse&quot;
+Const cstSubArgs = &quot;Array_1D&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vReverse = Array()
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
+ End If
+
+Try:
+ lMin = LBound(Array_1D)
+ lMax = UBound(Array_1D)
+ ReDim vReverse(lMin To lMax)
+ lHalf = Int((lMax + lMin) / 2)
+ j = lMax
+ For i = lMin To lHalf
+ vReverse(i) = Array_1D(j)
+ vReverse(j) = Array_1D(i)
+ j = j - 1
+ Next i
+ &apos; Odd number of items
+ If IsEmpty(vReverse(lHalf + 1)) Then vReverse(lHalf + 1) = Array_1D(lHalf + 1)
+
+Finally:
+ Reverse = vReverse()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.Reverse
+
+REM -----------------------------------------------------------------------------
+Public Function SetProperty(Optional ByVal PropertyName As Variant _
+ , Optional ByRef Value As Variant _
+ ) As Boolean
+&apos;&apos;&apos; Set a new value to the given property
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; PropertyName: the name of the property as a string
+&apos;&apos;&apos; Value: its new value
+&apos;&apos;&apos; Exceptions
+&apos;&apos;&apos; ARGUMENTERROR The property does not exist
+
+Const cstThisSub = &quot;Array.SetProperty&quot;
+Const cstSubArgs = &quot;PropertyName, Value&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ SetProperty = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
+ End If
+
+Try:
+ Select Case UCase(PropertyName)
+ Case Else
+ End Select
+
+Finally:
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.SetProperty
+
+REM -----------------------------------------------------------------------------
+Public Function Shuffle(Optional ByRef Array_1D As Variant) As Variant
+&apos;&apos;&apos; Returns a random permutation of a 1D array
+&apos;&apos;&apos; https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array_1D: the array to shuffle
+&apos;&apos;&apos; Returns: the shuffled array
+
+Dim vShuffle() As Variant &apos; Return value
+Dim vSwapValue As Variant &apos; Intermediate value during swap
+Dim lMin As Long &apos; LBound of Array_1D
+Dim lCurrentIndex As Long &apos; Decremented from UBount to LBound
+Dim lRandomIndex As Long &apos; Random between LBound and lCurrentIndex
+Dim i As Long
+Const cstThisSub = &quot;Array.Shuffle&quot;
+Const cstSubArgs = &quot;Array_1D&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vShuffle = Array()
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
+ End If
+
+Try:
+ lMin = LBound(Array_1D)
+ lCurrentIndex = UBound(array_1D)
+ &apos; Initialize the output array
+ ReDim vShuffle(lMin To lCurrentIndex)
+ For i = lMin To lCurrentIndex
+ vShuffle(i) = Array_1D(i)
+ Next i
+ &apos; Now ... shuffle !
+ Do While lCurrentIndex &gt; lMin
+ lRandomIndex = Int(Rnd * (lCurrentIndex - lMin)) + lMin
+ vSwapValue = vShuffle(lCurrentIndex)
+ vShuffle(lCurrentIndex) = vShuffle(lRandomIndex)
+ vShuffle(lRandomIndex) = vSwapValue
+ lCurrentIndex = lCurrentIndex - 1
+ Loop
+
+Finally:
+ Shuffle = vShuffle()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.Shuffle
+
+REM -----------------------------------------------------------------------------
+Public Function Slice(Optional ByRef Array_1D As Variant _
+ , Optional ByVal From As Variant _
+ , Optional ByVal UpTo As Variant _
+ ) As Variant
+&apos;&apos;&apos; Returns a subset of a 1D array
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array_1D: the array to slice
+&apos;&apos;&apos; From: the lower index of the subarray to extract (included)
+&apos;&apos;&apos; UpTo: the upper index of the subarray to extract (included). Default = the last item of Array_1D
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The selected subarray with the same LBound as the input array.
+&apos;&apos;&apos; If UpTo &lt; From then the returned array is empty
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; ARRAYINDEX2ERROR Wrong values for From and/or UpTo
+&apos;&apos;&apos; Example:
+&apos;&apos;&apos; SF_Array.Slice(Array(1, 2, 3, 4, 5), 1, 3) returns (2, 3, 4)
+
+Dim vSlice() As Variant &apos; Return value
+Dim lMin As Long &apos; LBound of Array_1D
+Dim lIndex As Long &apos; Current index in output array
+Dim i As Long
+Const cstThisSub = &quot;Array.Slice&quot;
+Const cstSubArgs = &quot;Array_1D, From, [UpTo = UBound(Array_1D)]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vSlice = Array()
+
+Check:
+ If IsMissing(UpTo) Or IsEmpty(UpTo) Then UpTo = -1
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
+ If Not SF_Utils._Validate(From, &quot;From&quot;, V_NUMERIC) Then GoTo Finally
+ If Not SF_Utils._Validate(UpTo, &quot;UpTo&quot;, V_NUMERIC) Then GoTo Finally
+ End If
+ If UpTo = -1 Then UpTo = UBound(Array_1D)
+ If From &lt; LBound(Array_1D) Or From &gt; UBound(Array_1D) _
+ Or From &gt; UpTo Or UpTo &gt; UBound(Array_1D) Then GoTo CatchIndex
+
+Try:
+ If UpTo &gt;= From Then
+ lMin = LBound(Array_1D)
+ &apos; Initialize the output array
+ ReDim vSlice(lMin To lMin + UpTo - From)
+ lIndex = lMin - 1
+ For i = From To UpTo
+ lIndex = lIndex + 1
+ vSlice(lIndex) = Array_1D(i)
+ Next i
+ End If
+
+Finally:
+ Slice = vSlice()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchIndex:
+ SF_Exception.RaiseFatal(ARRAYINDEX2ERROR, SF_Array._Repr(Array_1D), From, UpTo)
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.Slice
+
+REM -----------------------------------------------------------------------------
+Public Function Sort(Optional ByRef Array_1D As Variant _
+ , Optional ByVal SortOrder As Variant _
+ , Optional ByVal CaseSensitive As Variant _
+ ) As Variant
+&apos;&apos;&apos; Sort a 1D array in ascending or descending order. String comparisons can be case-sensitive or not
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array_1D: the array to sort
+&apos;&apos;&apos; must be filled homogeneously by either strings, dates or numbers
+&apos;&apos;&apos; Null and Empty values are allowed
+&apos;&apos;&apos; SortOrder: &quot;ASC&quot; (default) or &quot;DESC&quot;
+&apos;&apos;&apos; CaseSensitive: Default = False
+&apos;&apos;&apos; Returns: the sorted array
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; Sort(Array(&quot;a&quot;, &quot;A&quot;, &quot;b&quot;, &quot;B&quot;, &quot;C&quot;), CaseSensitive := True) returns (&quot;A&quot;, &quot;B&quot;, &quot;C&quot;, &quot;a&quot;, &quot;b&quot;)
+
+Dim vSort() As Variant &apos; Return value
+Dim vIndexes() As Variant &apos; Indexes of sorted items
+Dim lMin As Long &apos; LBound of input array
+Dim lMax As Long &apos; UBound of input array
+Dim i As Long
+Const cstThisSub = &quot;Array.Sort&quot;
+Const cstSubArgs = &quot;Array_1D, [SortOrder=&quot;&quot;&quot;&quot;|&quot;&quot;ASC&quot;&quot;|&quot;&quot;DESC&quot;&quot;], [CaseSensitive=False]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vSort = Array()
+
+Check:
+ If IsMissing(SortOrder) Or IsEmpty(SortOrder) Then SortOrder = &quot;ASC&quot;
+ If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1, 0) Then GoTo Finally
+ If Not SF_Utils._Validate(SortOrder, &quot;SortOrder&quot;, V_STRING, Array(&quot;ASC&quot;,&quot;DESC&quot;)) Then GoTo Finally
+ If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
+ End If
+
+Try:
+ lMin = LBound(Array_1D)
+ lMax = UBound(Array_1D)
+ vIndexes() = SF_Array._HeapSort(Array_1D, ( SortOrder = &quot;ASC&quot; ), CaseSensitive)
+
+ &apos; Load output array
+ ReDim vSort(lMin To lMax)
+ For i = lMin To lMax
+ vSort(i) = Array_1D(vIndexes(i))
+ Next i
+
+Finally:
+ Sort = vSort()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.Sort
+
+REM -----------------------------------------------------------------------------
+Public Function SortColumns(Optional ByRef Array_2D As Variant _
+ , Optional ByVal RowIndex As Variant _
+ , Optional ByVal SortOrder As Variant _
+ , Optional ByVal CaseSensitive As Variant _
+ ) As Variant
+&apos;&apos;&apos; Returns a permutation of the columns of a 2D array, sorted on the values of a given row
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array_2D: the input array
+&apos;&apos;&apos; RowIndex: the index of the row to sort the columns on
+&apos;&apos;&apos; the row must be filled homogeneously by either strings, dates or numbers
+&apos;&apos;&apos; Null and Empty values are allowed
+&apos;&apos;&apos; SortOrder: &quot;ASC&quot; (default) or &quot;DESC&quot;
+&apos;&apos;&apos; CaseSensitive: Default = False
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; the array with permuted columns, LBounds and UBounds are unchanged
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; ARRAYINDEXERROR
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; | 5, 7, 3 | | 7, 5, 3 |
+&apos;&apos;&apos; SF_Array.SortColumns( | 1, 9, 5 |, 2, &quot;ASC&quot;) returns | 9, 1, 5 |
+&apos;&apos;&apos; | 6, 1, 8 | | 1, 6, 8 |
+
+Dim vSort() As Variant &apos; Return value
+Dim vRow() As Variant &apos; The row on which to sort the array
+Dim vIndexes() As Variant &apos; Indexes of sorted row
+Dim lMin1 As Long &apos; LBound1 of input array
+Dim lMax1 As Long &apos; UBound1 of input array
+Dim lMin2 As Long &apos; LBound2 of input array
+Dim lMax2 As Long &apos; UBound2 of input array
+Dim i As Long, j As Long
+Const cstThisSub = &quot;Array.SortColumn&quot;
+Const cstSubArgs = &quot;Array_2D, RowIndex, [SortOrder=&quot;&quot;&quot;&quot;|&quot;&quot;ASC&quot;&quot;|&quot;&quot;DESC&quot;&quot;], [CaseSensitive=False]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vSort = Array()
+
+Check:
+ If IsMissing(SortOrder) Or IsEmpty(SortOrder) Then SortOrder = &quot;ASC&quot;
+ If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally
+ If Not SF_Utils._Validate(RowIndex, &quot;RowIndex&quot;, V_NUMERIC) Then GoTo Finally
+ If Not SF_Utils._Validate(SortOrder, &quot;SortOrder&quot;, V_STRING, Array(&quot;ASC&quot;,&quot;DESC&quot;)) Then GoTo Finally
+ If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
+ End If
+
+Try:
+ lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
+ If RowIndex &lt; lMin1 Or RowIndex &gt; lMax1 Then GoTo CatchIndex
+ lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
+
+ &apos; Extract and sort the RowIndex-th row
+ vRow = SF_Array.ExtractRow(Array_2D, RowIndex)
+ If Not SF_Utils._ValidateArray(vRow, &quot;Row #&quot; &amp; CStr(RowIndex), 1, 0) Then GoTo Finally
+ vIndexes() = SF_Array._HeapSort(vRow, ( SortOrder = &quot;ASC&quot; ), CaseSensitive)
+
+ &apos; Load output array
+ ReDim vSort(lMin1 To lMax1, lMin2 To lMax2)
+ For i = lMin1 To lMax1
+ For j = lMin2 To lMax2
+ vSort(i, j) = Array_2D(i, vIndexes(j))
+ Next j
+ Next i
+
+Finally:
+ SortColumns = vSort()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchIndex:
+ &apos;TODO SF_Exception.RaiseFatal(ARRAYINDEXERROR, cstThisSub)
+ MsgBox &quot;INVALID INDEX VALUE !!&quot;
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.SortColumns
+
+REM -----------------------------------------------------------------------------
+Public Function SortRows(Optional ByRef Array_2D As Variant _
+ , Optional ByVal ColumnIndex As Variant _
+ , Optional ByVal SortOrder As Variant _
+ , Optional ByVal CaseSensitive As Variant _
+ ) As Variant
+&apos;&apos;&apos; Returns a permutation of the rows of a 2D array, sorted on the values of a given column
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array_2D: the input array
+&apos;&apos;&apos; ColumnIndex: the index of the column to sort the rows on
+&apos;&apos;&apos; the column must be filled homogeneously by either strings, dates or numbers
+&apos;&apos;&apos; Null and Empty values are allowed
+&apos;&apos;&apos; SortOrder: &quot;ASC&quot; (default) or &quot;DESC&quot;
+&apos;&apos;&apos; CaseSensitive: Default = False
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; the array with permuted Rows, LBounds and UBounds are unchanged
+&apos;&apos;&apos; Exceptions:
+&apos;&apos;&apos; ARRAYINDEXERROR
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; | 5, 7, 3 | | 1, 9, 5 |
+&apos;&apos;&apos; SF_Array.SortRows( | 1, 9, 5 |, 0, &quot;ASC&quot;) returns | 5, 7, 3 |
+&apos;&apos;&apos; | 6, 1, 8 | | 6, 1, 8 |
+
+Dim vSort() As Variant &apos; Return value
+Dim vCol() As Variant &apos; The column on which to sort the array
+Dim vIndexes() As Variant &apos; Indexes of sorted row
+Dim lMin1 As Long &apos; LBound1 of input array
+Dim lMax1 As Long &apos; UBound1 of input array
+Dim lMin2 As Long &apos; LBound2 of input array
+Dim lMax2 As Long &apos; UBound2 of input array
+Dim i As Long, j As Long
+Const cstThisSub = &quot;Array.SortRow&quot;
+Const cstSubArgs = &quot;Array_2D, ColumnIndex, [SortOrder=&quot;&quot;&quot;&quot;|&quot;&quot;ASC&quot;&quot;|&quot;&quot;DESC&quot;&quot;], [CaseSensitive=False]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vSort = Array()
+
+Check:
+ If IsMissing(SortOrder) Or IsEmpty(SortOrder) Then SortOrder = &quot;ASC&quot;
+ If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally
+ If Not SF_Utils._Validate(ColumnIndex, &quot;ColumnIndex&quot;, V_NUMERIC) Then GoTo Finally
+ If Not SF_Utils._Validate(SortOrder, &quot;SortOrder&quot;, V_STRING, Array(&quot;ASC&quot;,&quot;DESC&quot;)) Then GoTo Finally
+ If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
+ End If
+
+Try:
+ lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
+ If ColumnIndex &lt; lMin2 Or ColumnIndex &gt; lMax2 Then GoTo CatchIndex
+ lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
+
+ &apos; Extract and sort the ColumnIndex-th column
+ vCol = SF_Array.ExtractColumn(Array_2D, ColumnIndex)
+ If Not SF_Utils._ValidateArray(vCol, &quot;Column #&quot; &amp; CStr(ColumnIndex), 1, 0) Then GoTo Finally
+ vIndexes() = SF_Array._HeapSort(vCol, ( SortOrder = &quot;ASC&quot; ), CaseSensitive)
+
+ &apos; Load output array
+ ReDim vSort(lMin1 To lMax1, lMin2 To lMax2)
+ For i = lMin1 To lMax1
+ For j = lMin2 To lMax2
+ vSort(i, j) = Array_2D(vIndexes(i), j)
+ Next j
+ Next i
+
+Finally:
+ SortRows = vSort()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+CatchIndex:
+ &apos;TODO SF_Exception.RaiseFatal(ARRAYINDEXERROR, cstThisSub)
+ MsgBox &quot;INVALID INDEX VALUE !!&quot;
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.SortRows
+
+REM -----------------------------------------------------------------------------
+Public Function Transpose(Optional ByRef Array_2D As Variant) As Variant
+&apos;&apos;&apos; Swaps rows and columns in a 2D array
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array_2D: the array to transpose
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; The transposed array
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; | 1, 2 | | 1, 3, 5 |
+&apos;&apos;&apos; SF_Array.Transpose( | 3, 4 | ) returns | 2, 4, 6 |
+&apos;&apos;&apos; | 5, 6 |
+
+Dim vTranspose As Variant &apos; Return value
+Dim lIndex As Long &apos; vTranspose index
+Dim lMin1 As Long &apos; LBound1 of input array
+Dim lMax1 As Long &apos; UBound1 of input array
+Dim lMin2 As Long &apos; LBound2 of input array
+Dim lMax2 As Long &apos; UBound2 of input array
+Dim i As Long, j As Long
+Const cstThisSub = &quot;Array.Transpose&quot;
+Const cstSubArgs = &quot;Array_2D&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vTranspose = Array()
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally
+ End If
+
+Try:
+ &apos; Resize the output array
+ lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
+ lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
+ If lMin1 &lt;= lMax1 Then
+ ReDim vTranspose(lMin2 To lMax2, lMin1 To lMax1)
+ End If
+
+ &apos; Transpose items
+ For i = lMin1 To lMax1
+ For j = lMin2 To lMax2
+ vTranspose(j, i) = Array_2D(i, j)
+ Next j
+ Next i
+
+Finally:
+ Transpose = vTranspose
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.Transpose
+
+REM -----------------------------------------------------------------------------
+Public Function TrimArray(Optional ByRef Array_1D As Variant) As Variant
+&apos;&apos;&apos; Remove from a 1D array all Null, Empty and zero-length entries
+&apos;&apos;&apos; Strings are trimmed as well
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array_1D: the array to scan
+&apos;&apos;&apos; Return: The trimmed array
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Array.TrimArray(Array(&quot;A&quot;,&quot;B&quot;,Null,&quot; D &quot;)) returns (&quot;A&quot;,&quot;B&quot;,&quot;D&quot;)
+
+Dim vTrimArray As Variant &apos; Return value
+Dim lIndex As Long &apos; vTrimArray index
+Dim lMin As Long &apos; LBound of input array
+Dim lMax As Long &apos; UBound of input array
+Dim vItem As Variant &apos; Single array item
+Dim i As Long
+Const cstThisSub = &quot;Array.TrimArray&quot;
+Const cstSubArgs = &quot;Array_1D&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vTrimArray = Array()
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
+ End If
+
+Try:
+ lMin = LBound(Array_1D)
+ lMax = UBound(Array_1D)
+ If lMin &lt;= lMax Then
+ ReDim vTrimArray(lMin To lMax)
+ End If
+ lIndex = lMin - 1
+
+ &apos; Load only valid items from Array_1D to vTrimArray
+ For i = lMin To lMax
+ vItem = Array_1D(i)
+ Select Case VarType(vItem)
+ Case V_EMPTY
+ Case V_NULL : vItem = Empty
+ Case V_STRING
+ vItem = Trim(vItem)
+ If Len(vItem) = 0 Then vItem = Empty
+ Case Else
+ End Select
+ If Not IsEmpty(vItem) Then
+ lIndex = lIndex + 1
+ vTrimArray(lIndex) = vItem
+ End If
+ Next i
+
+ &apos;Keep valid entries
+ If lMin &lt;= lIndex Then
+ ReDim Preserve vTrimArray(lMin To lIndex)
+ Else
+ vTrimArray = Array()
+ End If
+
+Finally:
+ TrimArray = vTrimArray
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.TrimArray
+
+REM -----------------------------------------------------------------------------
+Public Function Union(Optional ByRef Array1_1D As Variant _
+ , Optional ByRef Array2_1D As Variant _
+ , Optional ByVal CaseSensitive As Variant _
+ ) As Variant
+&apos;&apos;&apos; Build a set being the Union of the two input arrays, i.e. items are contained in any of both arrays
+&apos;&apos;&apos; both input arrays must be filled homogeneously, i.e. all items must be of the same type
+&apos;&apos;&apos; Empty and Null items are forbidden
+&apos;&apos;&apos; The comparison between strings is case sensitive or not
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array1_1D: a 1st input array
+&apos;&apos;&apos; Array2_1D: a 2nd input array
+&apos;&apos;&apos; CaseSensitive: default = False
+&apos;&apos;&apos; Returns: a zero-based array containing unique items stored in any of both input arrays
+&apos;&apos;&apos; The output array is sorted in ascending order
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; SF_Array.Union(Array(&quot;A&quot;, &quot;C&quot;, &quot;A&quot;, &quot;b&quot;, &quot;B&quot;), Array(&quot;C&quot;, &quot;Z&quot;, &quot;b&quot;), True) returns (&quot;A&quot;, &quot;B&quot;, &quot;C&quot;, &quot;Z&quot;, &quot;b&quot;)
+
+Dim vUnion() As Variant &apos; Return value
+Dim iType As Integer &apos; VarType of elements in input arrays
+Dim lMin1 As Long &apos; LBound of 1st input array
+Dim lMax1 As Long &apos; UBound of 1st input array
+Dim lMin2 As Long &apos; LBound of 2nd input array
+Dim lMax2 As Long &apos; UBound of 2nd input array
+Dim lSize As Long &apos; Number of Union items
+Dim i As Long
+Const cstThisSub = &quot;Array.Union&quot;
+Const cstSubArgs = &quot;Array1_1D, Array2_1D, [CaseSensitive=False]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vUnion = Array()
+
+Check:
+ If IsMissing(CaseSensitive) Then CaseSensitive = False
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array1_1D, &quot;Array1_1D&quot;, 1, 0, True) Then GoTo Finally
+ iType = SF_Utils._VarTypeExt(Array1_1D(LBound(Array1_1D)))
+ If Not SF_Utils._ValidateArray(Array2_1D, &quot;Array2_1D&quot;, 1, iType, True) Then GoTo Finally
+ If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
+ End If
+
+Try:
+ lMin1 = LBound(Array1_1D) : lMax1 = UBound(Array1_1D)
+ lMin2 = LBound(Array2_1D) : lMax2 = UBound(Array2_1D)
+
+ &apos; If both arrays are empty, do nothing
+ If lMax1 &lt; lMin1 And lMax2 &lt; lMin2 Then
+ ElseIf lMax1 &lt; lMin1 Then &apos; only 1st array is empty
+ vUnion = SF_Array.Unique(Array2_1D, CaseSensitive)
+ ElseIf lMax2 &lt; lMin2 Then &apos; only 2nd array is empty
+ vUnion = SF_Array.Unique(Array1_1D, CaseSensitive)
+ Else
+
+ &apos; Build union of both arrays
+ ReDim vUnion(0 To (lMax1 - lMin1) + (lMax2 - lMin2) + 1)
+ lSize = -1
+
+ &apos; Fill vUnion one by one only with items present in any set
+ For i = lMin1 To lMax1
+ lSize = lSize + 1
+ vUnion(lSize) = Array1_1D(i)
+ Next i
+ For i = lMin2 To lMax2
+ lSize = lSize + 1
+ vUnion(lSize) = Array2_1D(i)
+ Next i
+
+ &apos; Remove duplicates
+ vUnion() = SF_Array.Unique(vUnion, CaseSensitive)
+ End If
+
+Finally:
+ Union = vUnion()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.Union
+
+REM -----------------------------------------------------------------------------
+Public Function Unique(Optional ByRef Array_1D As Variant _
+ , Optional ByVal CaseSensitive As Variant _
+ ) As Variant
+&apos;&apos;&apos; Build a set of unique values derived from the input array
+&apos;&apos;&apos; the input array must be filled homogeneously, i.e. all items must be of the same type
+&apos;&apos;&apos; Empty and Null items are forbidden
+&apos;&apos;&apos; The comparison between strings is case sensitive or not
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Array_1D: the input array with potential duplicates
+&apos;&apos;&apos; CaseSensitive: default = False
+&apos;&apos;&apos; Returns: the array without duplicates with same LBound as input array
+&apos;&apos;&apos; The output array is sorted in ascending order
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; Unique(Array(&quot;A&quot;, &quot;C&quot;, &quot;A&quot;, &quot;b&quot;, &quot;B&quot;), True) returns (&quot;A&quot;, &quot;B&quot;, &quot;C&quot;, &quot;b&quot;)
+
+Dim vUnique() As Variant &apos; Return value
+Dim vSorted() As Variant &apos; The input array after sort
+Dim lMin As Long &apos; LBound of input array
+Dim lMax As Long &apos; UBound of input array
+Dim lUnique As Long &apos; Number of unique items
+Dim vIndex As Variant &apos; Output of _FindItem() method
+Dim vItem As Variant &apos; One single item in the array
+Dim i As Long
+Const cstThisSub = &quot;Array.Unique&quot;
+Const cstSubArgs = &quot;Array_1D, [CaseSensitive=False]&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ vUnique = Array()
+
+Check:
+ If IsMissing(CaseSensitive) Then CaseSensitive = False
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1, 0, True) Then GoTo Finally
+ If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
+ End If
+
+Try:
+ lMin = LBound(Array_1D)
+ lMax = UBound(Array_1D)
+ If lMax &gt;= lMin Then
+ &apos; First sort the array
+ vSorted = SF_Array.Sort(Array_1D, &quot;ASC&quot;, CaseSensitive)
+ ReDim vUnique(lMin To lMax)
+ lUnique = lMin
+ &apos; Fill vUnique one by one ignoring duplicates
+ For i = lMin To lMax
+ vItem = vSorted(i)
+ If i = lMin Then
+ vUnique(i) = vItem
+ Else
+ If SF_Array._ValCompare(vItem, vSorted(i - 1), CaseSensitive) = 0 Then &apos; Ignore item
+ Else
+ lUnique = lUnique + 1
+ vUnique(lUnique) = vItem
+ End If
+ End If
+ Next i
+ &apos; Remove unfilled entries
+ ReDim Preserve vUnique(lMin To lUnique)
+ End If
+
+Finally:
+ Unique = vUnique()
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_Array.Unique
+
+REM ============================================================= PRIVATE METHODS
+
+REM -----------------------------------------------------------------------------
+Public Function _FindItem(ByRef pvArray_1D As Variant _
+ , ByVal pvToFind As Variant _
+ , ByVal pbCaseSensitive As Boolean _
+ , ByVal psSortOrder As String _
+ ) As Variant
+&apos;&apos;&apos; Check if a 1D array contains the ToFind number, string or date and return its index
+&apos;&apos;&apos; The comparison between strings can be done case-sensitively or not
+&apos;&apos;&apos; If the array is sorted then a binary search is done
+&apos;&apos;&apos; Otherwise the array is scanned from top. Null or Empty items are simply ignored
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; pvArray_1D: the array to scan
+&apos;&apos;&apos; pvToFind: a number, a date or a string to find
+&apos;&apos;&apos; pbCaseSensitive: Only for string comparisons, default = False
+&apos;&apos;&apos; psSortOrder: &quot;ASC&quot;, &quot;DESC&quot; or &quot;&quot; (= not sorted, default)
+&apos;&apos;&apos; Return: a (0:1) array
+&apos;&apos;&apos; (0) = True when found
+&apos;&apos;&apos; (1) = if found: index of item
+&apos;&apos;&apos; if not found: if sorted, index of next item in the array (might be = UBound + 1)
+&apos;&apos;&apos; if not sorted, meaningless
+&apos;&apos;&apos; Result is unpredictable when array is announced sorted and is in reality not
+&apos;&apos;&apos; Called by Contains, IndexOf and InsertSorted. Also called by SF_Dictionary
+
+Dim bContains As Boolean &apos; True if match found
+Dim iToFindType As Integer &apos; VarType of pvToFind
+Dim lTop As Long, lBottom As Long &apos; Interval in scope of binary search
+Dim lIndex As Long &apos; Index used in search
+Dim iCompare As Integer &apos; Output of _ValCompare function
+Dim lLoops As Long &apos; Count binary searches
+Dim lMaxLoops As Long &apos; Max number of loops during binary search: to avoid infinite loops if array not sorted
+Dim vFound(1) As Variant &apos; Returned array (Contains, Index)
+
+ bContains = False
+
+ If LBound(pvArray_1D) &gt; UBound(pvArray_1D) Then &apos; Empty array, do nothing
+ Else
+ &apos; Search sequentially
+ If Len(psSortOrder) = 0 Then
+ For lIndex = LBound(pvArray_1D) To UBound(pvArray_1D)
+ bContains = ( SF_Array._ValCompare(pvToFind, pvArray_1D(lIndex), pbCaseSensitive) = 0 )
+ If bContains Then Exit For
+ Next lIndex
+ Else
+ &apos; Binary search
+ If psSortOrder = &quot;ASC&quot; Then
+ lTop = UBound(pvArray_1D)
+ lBottom = lBound(pvArray_1D)
+ Else
+ lBottom = UBound(pvArray_1D)
+ lTop = lBound(pvArray_1D)
+ End If
+ lLoops = 0
+ lMaxLoops = CLng((Log(UBound(pvArray_1D) - LBound(pvArray_1D) + 1.0) / Log(2.0))) + 1
+ Do
+ lLoops = lLoops + 1
+ lIndex = (lTop + lBottom) / 2
+ iCompare = SF_Array._ValCompare(pvToFind, pvArray_1D(lIndex), pbCaseSensitive)
+ Select Case True
+ Case iCompare = 0 : bContains = True
+ Case iCompare &lt; 0 And psSortOrder = &quot;ASC&quot;
+ lTop = lIndex - 1
+ Case iCompare &gt; 0 And psSortOrder = &quot;DESC&quot;
+ lBottom = lIndex - 1
+ Case iCompare &gt; 0 And psSortOrder = &quot;ASC&quot;
+ lBottom = lIndex + 1
+ Case iCompare &lt; 0 And psSortOrder = &quot;DESC&quot;
+ lTop = lIndex + 1
+ End Select
+ Loop Until ( bContains ) Or ( lBottom &gt; lTop And psSortOrder = &quot;ASC&quot; ) Or (lBottom &lt; lTop And psSortOrder = &quot;DESC&quot; ) Or lLoops &gt; lMaxLoops
+ &apos; Flag first next non-matching element
+ If Not bContains Then lIndex = Iif(psSortOrder = &quot;ASC&quot;, lBottom, lTop)
+ End If
+ End If
+
+ &apos; Build output array
+ vFound(0) = bContains
+ vFound(1) = lIndex
+ _FindItem = vFound
+
+End Function &apos; ScriptForge.SF_Array._FindItem
+
+REM -----------------------------------------------------------------------------
+Private Function _HeapSort(ByRef pvArray As Variant _
+ , Optional ByVal pbAscending As Boolean _
+ , Optional ByVal pbCaseSensitive As Boolean _
+ ) As Variant
+&apos;&apos;&apos; Sort an array: items are presumed all strings, all dates or all numeric
+&apos;&apos;&apos; Null or Empty are allowed and are considered smaller than other items
+&apos;&apos;&apos; https://en.wikipedia.org/wiki/Heapsort
+&apos;&apos;&apos; http://www.vbforums.com/showthread.php?473677-VB6-Sorting-algorithms-(sort-array-sorting-arrays)&amp;p=2909250#post2909250
+&apos;&apos;&apos; HeapSort preferred to QuickSort because not recursive (this routine returns an array of indexes !!)
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; pvArray: a 1D array
+&apos;&apos;&apos; pbAscending: default = True
+&apos;&apos;&apos; pbCaseSensitive: default = False
+&apos;&apos;&apos; Returns
+&apos;&apos;&apos; An array of Longs of same dimensions as the input array listing the indexes of the sorted items
+&apos;&apos;&apos; An empty array if the sort failed
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; _HeapSort(Array(4, 2, 6, 1) returns (3, 1, 0, 2)
+
+Dim vIndexes As Variant &apos; Return value
+Dim i As Long
+Dim lMin As Long, lMax As Long &apos; Array bounds
+Dim lSwap As Long &apos; For index swaps
+
+ If IsMissing(pbAscending) Then pbAscending = True
+ If IsMissing(pbCaseSensitive) Then pbCaseSensitive = False
+ vIndexes = Array()
+ lMin = LBound(pvArray, 1)
+ lMax = UBound(pvArray, 1)
+
+ &apos; Initialize output array
+ ReDim vIndexes(lMin To lMax)
+ For i = lMin To lMax
+ vIndexes(i) = i
+ Next i
+
+ &apos; Initial heapify
+ For i = (lMax + lMin) \ 2 To lMin Step -1
+ SF_Array._HeapSort1(pvArray, vIndexes, i, lMin, lMax, pbCaseSensitive)
+ Next i
+ &apos; Next heapify
+ For i = lMax To lMin + 1 Step -1
+ &apos; Only indexes as swapped, not the array items themselves
+ lSwap = vIndexes(i)
+ vIndexes(i) = vIndexes(lMin)
+ vIndexes(lMin) = lSwap
+ SF_Array._HeapSort1(pvArray, vIndexes, lMin, lMin, i - 1, pbCaseSensitive)
+ Next i
+
+ If pbAscending Then _HeapSort = vIndexes() Else _HeapSort = SF_Array.Reverse(vIndexes())
+
+End Function &apos; ScriptForge.SF_Array._HeapSort
+
+REM -----------------------------------------------------------------------------
+Private Sub _HeapSort1(ByRef pvArray As Variant _
+ , ByRef pvIndexes As Variant _
+ , ByVal plIndex As Long _
+ , ByVal plMin As Long _
+ , ByVal plMax As Long _
+ , ByVal pbCaseSensitive As Boolean _
+ )
+&apos;&apos;&apos; Sub called by _HeapSort only
+
+ Dim lLeaf As Long
+ Dim lSwap As Long
+
+ Do
+ lLeaf = plIndex + plIndex - (plMin - 1)
+ Select Case lLeaf
+ Case Is &gt; plMax: Exit Do
+ Case Is &lt; plMax
+ If SF_Array._ValCompare(pvArray(pvIndexes(lLeaf + 1)), pvArray(pvIndexes(lLeaf)), pbCaseSensitive) &gt; 0 Then lLeaf = lLeaf + 1
+ End Select
+ If SF_Array._ValCompare(pvArray(pvIndexes(plIndex)), pvArray(pvIndexes(lLeaf)), pbCaseSensitive) &gt; 0 Then Exit Do
+ &apos; Only indexes as swapped, not the array items themselves
+ lSwap = pvIndexes(plIndex)
+ pvIndexes(plIndex) = pvIndexes(lLeaf)
+ pvIndexes(lLeaf) = lSwap
+ plIndex = lLeaf
+ Loop
+
+End Sub &apos; ScriptForge.SF_Array._HeapSort1
+
+REM -----------------------------------------------------------------------------
+Private Function _Repr(ByRef pvArray As Variant) As String
+&apos;&apos;&apos; Convert array to a readable string, typically for debugging purposes (DebugPrint ...)
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; pvArray: the array to convert, individual items may be of any type, including arrays
+&apos;&apos;&apos; Return:
+&apos;&apos;&apos; &quot;[ARRAY] (L:U[, L:U]...)&quot; if # of Dims &gt; 1
+&apos;&apos;&apos; &quot;[ARRAY] (L:U) (item1,item2, ...)&quot; if 1D array
+
+Dim iDims As Integer &apos; Number of dimensions of the array
+Dim sArray As String &apos; Return value
+Dim i As Long
+Const cstArrayEmpty = &quot;[ARRAY] ()&quot;
+Const cstArray = &quot;[ARRAY]&quot;
+Const cstMaxLength = 50 &apos; Maximum length for items
+Const cstSeparator = &quot;, &quot;
+
+ _Repr = &quot;&quot;
+ iDims = SF_Array.CountDims(pvArray)
+
+ Select Case iDims
+ Case -1 : Exit Function &apos; Not an array
+ Case 0 : sArray = cstArrayEmpty
+ Case Else
+ sArray = cstArray
+ For i = 1 To iDims
+ sArray = sArray &amp; Iif(i = 1, &quot; (&quot;, &quot;, &quot;) &amp; CStr(LBound(pvArray, i)) &amp; &quot;:&quot; &amp; CStr(UBound(pvArray, i))
+ Next i
+ sArray = sArray &amp; &quot;)&quot;
+ &apos; List individual items of 1D arrays
+ If iDims = 1 Then
+ sArray = sArray &amp; &quot; (&quot;
+ For i = LBound(pvArray) To UBound(pvArray)
+ sArray = sArray &amp; SF_Utils._Repr(pvArray(i), cstMaxLength) &amp; cstSeparator &apos; Recursive call
+ Next i
+ sArray = Left(sArray, Len(sArray) - Len(cstSeparator)) &apos; Suppress last comma
+ sArray = sArray &amp; &quot;)&quot;
+ End If
+ End Select
+
+ _Repr = sArray
+
+End Function &apos; ScriptForge.SF_Array._Repr
+
+REM -----------------------------------------------------------------------------
+Public Function _StaticType(ByRef pvArray As Variant) As Integer
+&apos;&apos;&apos; If array is static, return its type
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; pvArray: array to examine
+&apos;&apos;&apos; Return:
+&apos;&apos;&apos; array type, -1 if not identified
+&apos;&apos;&apos; All numeric types are aggregated into V_NUMERIC
+
+Dim iArrayType As Integer &apos; VarType of array
+Dim iType As Integer &apos; VarType of items
+
+ iArrayType = VarType(pvArray)
+ iType = iArrayType - V_ARRAY
+ Select Case iType
+ Case V_INTEGER, V_LONG, V_SINGLE, V_DOUBLE, V_CURRENCY, V_BIGINT, V_DECIMAL, V_BOOLEAN
+ _StaticType = V_NUMERIC
+ Case V_STRING, V_DATE
+ _StaticType = iType
+ Case Else
+ _StaticType = -1
+ End Select
+
+End Function &apos; ScriptForge.SF_Utils._StaticType
+
+REM -----------------------------------------------------------------------------
+Private Function _ValCompare(ByVal pvValue1 As Variant _
+ , pvValue2 As Variant _
+ , Optional ByVal pbCaseSensitive As Boolean _
+ ) As Integer
+&apos;&apos;&apos; Compare 2 values : equality, greater than or smaller than
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; pvValue1 and pvValue2: values to compare. pvValues must be String, Number, Date, Empty or Null
+&apos;&apos;&apos; By convention: Empty &lt; Null &lt; string, number or date
+&apos;&apos;&apos; pbCaseSensitive: ignored when not String comparison
+&apos;&apos;&apos; Return: -1 when pvValue1 &lt; pvValue2
+&apos;&apos;&apos; +1 when pvValue1 &gt; pvValue2
+&apos;&apos;&apos; 0 when pvValue1 = pvValue2
+&apos;&apos;&apos; -2 when comparison is nonsense
+
+Dim iCompare As Integer, iVarType1 As Integer, iVarType2 As Integer
+
+ If IsMissing(pbCaseSensitive) Then pbCaseSensitive = False
+ iVarType1 = SF_Utils._VarTypeExt(pvValue1)
+ iVarType2 = SF_Utils._VarTypeExt(pvValue2)
+
+ iCompare = -2
+ If iVarType1 = V_OBJECT Or iVarType1 = V_BYTE Or iVarType1 &gt;= V_ARRAY Then &apos; Nonsense
+ ElseIf iVarType2 = V_OBJECT Or iVarType2 = V_BYTE Or iVarType2 &gt;= V_ARRAY Then &apos; Nonsense
+ ElseIf iVarType1 = V_STRING And iVarType2 = V_STRING Then
+ iCompare = StrComp(pvValue1, pvValue2, Iif(pbCaseSensitive, 1, 0))
+ ElseIf iVarType1 = V_NULL Or iVarType1 = V_EMPTY Or iVarType2 = V_NULL Or iVarType2 = V_EMPTY Then
+ Select Case True
+ Case pvValue1 = pvValue2 : iCompare = 0
+ Case iVarType1 = V_NULL And iVarType2 = V_EMPTY : iCompare = +1
+ Case iVarType1 = V_EMPTY And iVarType2 = V_NULL : iCompare = -1
+ Case iVarType1 = V_NULL Or iVarType1 = V_EMPTY : iCompare = -1
+ Case iVarType2 = V_NULL Or iVarType2 = V_EMPTY : iCompare = +1
+ End Select
+ ElseIf iVarType1 = iVarType2 Then
+ Select Case True
+ Case pvValue1 &lt; pvValue2 : iCompare = -1
+ Case pvValue1 = pvValue2 : iCompare = 0
+ Case pvValue1 &gt; pvValue2 : iCompare = +1
+ End Select
+ End If
+
+ _ValCompare = iCompare
+
+End Function &apos; ScriptForge.SF_Array._ValCompare
+
+REM ================================================= END OF SCRIPTFORGE.SF_ARRAY
+</script:module> \ No newline at end of file