file-online-preview/server/libreoffice/share/basic/ScriptForge/SF_Array.xba

2550 lines
104 KiB
Java

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd">
<script:module xmlns:script="http://openoffice.org/2000/script" script:name="SF_Array" script:language="StarBasic" script:moduleType="normal">REM =======================================================================================================================
REM === The ScriptForge library and its associated libraries are part of the LibreOffice project. ===
REM === Full documentation is available on https://help.libreoffice.org/ ===
REM =======================================================================================================================
Option Compatible
Option Explicit
&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;
&apos;&apos;&apos; SF_Array
&apos;&apos;&apos; ========
&apos;&apos;&apos; Singleton class implementing the &quot;ScriptForge.Array&quot; service
&apos;&apos;&apos; Implemented as a usual Basic module
&apos;&apos;&apos; Only 1D or 2D arrays are considered. Arrays with more than 2 dimensions are rejected
&apos;&apos;&apos; With the noticeable exception of the CountDims method (&gt;2 dims allowed)
&apos;&apos;&apos; The first argument of almost every method is the array to consider
&apos;&apos;&apos; It is always passed by reference and left unchanged
&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;
REM ================================================================== EXCEPTIONS
Const ARRAYSEQUENCEERROR = &quot;ARRAYSEQUENCEERROR&quot; &apos; Incoherent arguments
Const ARRAYINSERTERROR = &quot;ARRAYINSERTERROR&quot; &apos; Matrix and vector have incompatible sizes
Const ARRAYINDEX1ERROR = &quot;ARRAYINDEX1ERROR&quot; &apos; Given index does not fit in array bounds
Const ARRAYINDEX2ERROR = &quot;ARRAYINDEX2ERROR&quot; &apos; Given indexes do not fit in array bounds
Const CSVPARSINGERROR = &quot;CSVPARSINGERROR&quot; &apos; Parsing error detected while parsing a csv file
Const CSVOVERFLOWWARNING = &quot;CSVOVERFLOWWARNING&quot; &apos; Array becoming too big, import process of csv file is interrupted
REM ============================================================ MODULE CONSTANTS
Const MAXREPR = 50 &apos; Maximum length to represent an array in the console
REM ===================================================== CONSTRUCTOR/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 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 _
) As Variant
&apos;&apos;&apos; Import the data contained in a comma-separated values (CSV) file
&apos;&apos;&apos; The comma may be replaced by any character
&apos;&apos;&apos; Each line in the file contains a full record
&apos;&apos;&apos; Line splitting is not allowed)
&apos;&apos;&apos; However sequences like \n, \t, ... are left unchanged. Use SF_String.Unescape() to manage them
&apos;&apos;&apos; A special mechanism is implemented to load dates
&apos;&apos;&apos; The applicable CSV format is described in https://tools.ietf.org/html/rfc4180
&apos;&apos;&apos; Args:
&apos;&apos;&apos; FileName: the name of the text file containing the data expressed as given by the current FileNaming
&apos;&apos;&apos; property of the SF_FileSystem service. Default = both URL format or native format
&apos;&apos;&apos; Delimiter: Default = &quot;,&quot;. Other usual options are &quot;;&quot; and the tab character
&apos;&apos;&apos; DateFormat: either YYYY-MM-DD, DD-MM-YYYY or MM-DD-YYYY
&apos;&apos;&apos; The dash (-) may be replaced by a dot (.), a slash (/) or a space
&apos;&apos;&apos; Other date formats will be ignored
&apos;&apos;&apos; If &quot;&quot; (default), dates will be considered as strings
&apos;&apos;&apos; Returns:
&apos;&apos;&apos; A 2D-array with each row corresponding with a single record read in the file
&apos;&apos;&apos; and each column corresponding with a field of the record
&apos;&apos;&apos; No check is made about the coherence of the field types across columns
&apos;&apos;&apos; A best guess will be made to identify numeric and date types
&apos;&apos;&apos; If a line contains less or more fields than the first line in the file,
&apos;&apos;&apos; an exception will be raised. Empty lines however are simply ignored
&apos;&apos;&apos; If the size of the file exceeds the number of items limit, a warning is raised
&apos;&apos;&apos; and the array is truncated
&apos;&apos;&apos; Exceptions:
&apos;&apos;&apos; CSVPARSINGERROR Given file is not formatted as a csv file
&apos;&apos;&apos; CSVOVERFLOWWARNING Maximum number of allowed items exceeded
Dim vArray As Variant &apos; Returned array
Dim lCol As Long &apos; Index of last column of vArray
Dim lRow As Long &apos; Index of current row of vArray
Dim lFileSize As Long &apos; Number of records found in the file
Dim vCsv As Object &apos; CSV file handler
Dim sLine As String &apos; Last read line
Dim vLine As Variant &apos; Array of fields of last read line
Dim sItem As String &apos; Individual item in the file
Dim vItem As Variant &apos; Individual item in the output array
Dim iPosition As Integer &apos; Date position in individual item
Dim iYear As Integer, iMonth As Integer, iDay As Integer
&apos; Date components
Dim i As Long
Const cstItemsLimit = 250000 &apos; Maximum number of admitted items
Const cstThisSub = &quot;Array.ImportFromCSVFile&quot;
Const cstSubArgs = &quot;FileName, [Delimiter=&quot;&quot;,&quot;&quot;], [DateFormat=&quot;&quot;&quot;&quot;]&quot;
If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
vArray = Array()
Check:
If IsMissing(Delimiter) Or IsEmpty(Delimiter) Then Delimiter = &quot;,&quot;
If IsMissing(DateFormat) Or IsEmpty(DateFormat) Then DateFormat = &quot;&quot;
If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
If Not SF_Utils._ValidateFile(FileName, &quot;FileName&quot;) Then GoTo Finally
If Not SF_Utils._Validate(Delimiter, &quot;Delimiter&quot;, V_STRING) Then GoTo Finally
If Not SF_Utils._Validate(DateFormat, &quot;DateFormat&quot;, V_STRING) Then GoTo Finally
End If
If Len(Delimiter) = 0 Then Delimiter = &quot;,&quot;
Try:
&apos; Counts the lines present in the file to size the final array
&apos; Very beneficial for large files, better than multiple ReDims
&apos; Small overhead for small files
lFileSize = SF_FileSystem._CountTextLines(FileName, False)
If lFileSize &lt;= 0 Then GoTo Finally
&apos; Reread file line by line
Set vCsv = SF_FileSystem.OpenTextFile(FileName, IOMode := SF_FileSystem.ForReading)
If IsNull(vCsv) Then GoTo Finally &apos; Open error
lRow = -1
With vCsv
Do While Not .AtEndOfStream
sLine = .ReadLine()
If Len(sLine) &gt; 0 Then &apos; Ignore empty lines
If InStr(sLine, &quot;&quot;&quot;&quot;) &gt; 0 Then vLine = SF_String.SplitNotQuoted(sLine, Delimiter) Else vLine = Split(sLine, Delimiter) &apos; Simple split when relevant
lRow = lRow + 1
If lRow = 0 Then &apos; Initial sizing of output array
lCol = UBound(vLine)
ReDim vArray(0 To lFileSize - 1, 0 To lCol)
ElseIf UBound(vLine) &lt;&gt; lCol Then
GoTo CatchCSVFormat
End If
&apos; Check type and copy all items of the line
For i = 0 To lCol
If Left(vLine(i), 1) = &quot;&quot;&quot;&quot; Then sItem = SF_String.Unquote(vLine(i)) Else sItem = vLine(i) &apos; Unquote only when useful
&apos; 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)
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>