1
0
Fork 0
libreoffice/wizards/source/scriptforge/SF_Array.xba
Daniel Baumann 8e63e14cf6
Adding upstream version 4:25.2.3.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-22 16:20:04 +02:00

2992 lines
No EOL
122 KiB
XML

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd">
<script:module xmlns:script="http://openoffice.org/2000/script" script:name="SF_Array" script:language="StarBasic" script:moduleType="normal">REM =======================================================================================================================
REM === The ScriptForge library and its associated libraries are part of the LibreOffice project. ===
REM === Full documentation is available on https://help.libreoffice.org/ ===
REM =======================================================================================================================
Option Compatible
Option Explicit
&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;
&apos;&apos;&apos; SF_Array
&apos;&apos;&apos; ========
&apos;&apos;&apos; Singleton class implementing the &quot;ScriptForge.Array&quot; service
&apos;&apos;&apos; Implemented as a usual Basic module
&apos;&apos;&apos; Only 1D or 2D arrays are considered. Arrays with more than 2 dimensions are rejected
&apos;&apos;&apos; With the noticeable exception of the CountDims method (&gt;2 dims allowed)
&apos;&apos;&apos; The first argument of almost every method is the array to consider
&apos;&apos;&apos; It is always passed by reference and left unchanged
&apos;&apos;&apos;
&apos;&apos;&apos; Detailed user documentation:
&apos;&apos;&apos; https://help.libreoffice.org/latest/en-US/text/sbasic/shared/03/sf_array.html?DbPAR=BASIC
&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;
REM ================================================================== EXCEPTIONS
Const ARRAYSEQUENCEERROR = &quot;ARRAYSEQUENCEERROR&quot; &apos; Incoherent arguments
Const ARRAYINSERTERROR = &quot;ARRAYINSERTERROR&quot; &apos; Matrix and vector have incompatible sizes
Const ARRAYINDEX1ERROR = &quot;ARRAYINDEX1ERROR&quot; &apos; Given index does not fit in array bounds
Const ARRAYINDEX2ERROR = &quot;ARRAYINDEX2ERROR&quot; &apos; Given indexes do not fit in array bounds
Const CSVPARSINGERROR = &quot;CSVPARSINGERROR&quot; &apos; Parsing error detected while parsing a csv file
Const CSVOVERFLOWWARNING = &quot;CSVOVERFLOWWARNING&quot; &apos; Array becoming too big, import process of csv file is interrupted
REM ============================================================ MODULE CONSTANTS
Const MAXREPR = 50 &apos; Maximum length to represent an array in the console
REM ===================================================== CONSTRUCTOR/DESTRUCTOR
REM -----------------------------------------------------------------------------
Public Function Dispose() As Variant
Set Dispose = Nothing
End Function &apos; ScriptForge.SF_Array Explicit destructor
REM ================================================================== PROPERTIES
REM -----------------------------------------------------------------------------
Property Get ObjectType As String
&apos;&apos;&apos; Only to enable object representation
ObjectType = &quot;SF_Array&quot;
End Property &apos; ScriptForge.SF_Array.ObjectType
REM -----------------------------------------------------------------------------
Property Get ServiceName As String
&apos;&apos;&apos; Internal use
ServiceName = &quot;ScriptForge.Array&quot;
End Property &apos; ScriptForge.SF_Array.ServiceName
REM ============================================================== PUBLIC METHODS
REM -----------------------------------------------------------------------------
Public Function Append(Optional ByRef Array_1D As Variant _
, ParamArray pvArgs() As Variant _
) As Variant
&apos;&apos;&apos; Append at the end of the input array the items listed as arguments
&apos;&apos;&apos; Arguments are appended blindly
&apos;&apos;&apos; each of them might be a scalar of any type or a subarray
&apos;&apos;&apos; Args
&apos;&apos;&apos; Array_1D: the pre-existing array, may be empty
&apos;&apos;&apos; pvArgs: a list of items to append to Array_1D
&apos;&apos;&apos; Return:
&apos;&apos;&apos; the new extended array. Its LBound is identical to that of Array_1D
&apos;&apos;&apos; Examples:
&apos;&apos;&apos; SF_Array.Append(Array(1, 2, 3), 4, 5) returns (1, 2, 3, 4, 5)
Dim vAppend As Variant &apos; Return value
Dim lNbArgs As Long &apos; Number of elements to append
Dim lMax As Long &apos; UBound of input array
Dim i As Long
Const cstThisSub = &quot;Array.Append&quot;
Const cstSubArgs = &quot;Array_1D, arg0[, arg1] ...&quot;
If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
vAppend = Array()
Check:
If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
End If
Try:
lMax = UBound(Array_1D)
lNbArgs = UBound(pvArgs) + 1 &apos; pvArgs is always zero-based
If lMax &lt; LBound(Array_1D) Then &apos; Initial array is empty
If lNbArgs &gt; 0 Then
ReDim vAppend(0 To lNbArgs - 1)
End If
Else
vAppend() = Array_1D()
If lNbArgs &gt; 0 Then
ReDim Preserve vAppend(LBound(Array_1D) To lMax + lNbArgs)
End If
End If
For i = 1 To lNbArgs
vAppend(lMax + i) = pvArgs(i - 1)
Next i
Finally:
Append = vAppend()
SF_Utils._ExitFunction(cstThisSub)
Exit Function
Catch:
GoTo Finally
End Function &apos; ScriptForge.SF_Array.Append
REM -----------------------------------------------------------------------------
Public Function AppendColumn(Optional ByRef Array_2D As Variant _
, Optional ByRef Column As Variant _
) As Variant
&apos;&apos;&apos; AppendColumn appends to the right side of a 2D array a new Column
&apos;&apos;&apos; Args
&apos;&apos;&apos; Array_2D: the pre-existing array, may be empty
&apos;&apos;&apos; If the array has 1 dimension, it is considered as the 1st Column of the resulting 2D array
&apos;&apos;&apos; Column: a 1D array with as many items as there are rows in Array_2D
&apos;&apos;&apos; Returns:
&apos;&apos;&apos; the new extended array. Its LBounds are identical to that of Array_2D
&apos;&apos;&apos; Exceptions:
&apos;&apos;&apos; ARRAYINSERTERROR
&apos;&apos;&apos; Examples:
&apos;&apos;&apos; SF_Array.AppendColumn(Array(1, 2, 3), Array(4, 5, 6)) returns ((1, 4), (2, 5), (3, 6))
&apos;&apos;&apos; x = SF_Array.AppendColumn(Array(), Array(1, 2, 3)) =&gt; ∀ i ∈ {0 ≤ i ≤ 2} : x(0, i) ≡ i
Dim vAppendColumn As Variant &apos; Return value
Dim iDims As Integer &apos; Dimensions of Array_2D
Dim lMin1 As Long &apos; LBound1 of input array
Dim lMax1 As Long &apos; UBound1 of input array
Dim lMin2 As Long &apos; LBound2 of input array
Dim lMax2 As Long &apos; UBound2 of input array
Dim lMin As Long &apos; LBound of Column array
Dim lMax As Long &apos; UBound of Column array
Dim i As Long
Dim j As Long
Const cstThisSub = &quot;Array.AppendColumn&quot;
Const cstSubArgs = &quot;Array_2D, Column&quot;
If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
vAppendColumn = Array()
Check:
If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;) Then GoTo Finally &apos;Initial check: not missing and array
If Not SF_Utils._ValidateArray(Column, &quot;Column&quot;, 1) Then GoTo Finally
End If
iDims = SF_Array.CountDims(Array_2D)
If iDims &gt; 2 Then
If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally &apos;2nd check to manage error
End If
Try:
lMin = LBound(Column)
lMax = UBound(Column)
&apos; Compute future dimensions of output array
Select Case iDims
Case 0 : lMin1 = lMin : lMax1 = lMax
lMin2 = 0 : lMax2 = -1
Case 1 : lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
lMin2 = 0 : lMax2 = 0
Case 2 : lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
End Select
If iDims &gt; 0 And lMax - lMin &lt;&gt; lMax1 - lMin1 Then GoTo CatchColumn
ReDim vAppendColumn(lMin1 To lMax1, lMin2 To lMax2 + 1)
&apos; Copy input array to output array
For i = lMin1 To lMax1
For j = lMin2 To lMax2
If iDims = 2 Then vAppendColumn(i, j) = Array_2D(i, j) Else vAppendColumn(i, j) = Array_2D(i)
Next j
Next i
&apos; Copy new Column
For i = lMin1 To lMax1
vAppendColumn(i, lMax2 + 1) = Column(i)
Next i
Finally:
AppendColumn = vAppendColumn()
SF_Utils._ExitFunction(cstThisSub)
Exit Function
Catch:
GoTo Finally
CatchColumn:
SF_Exception.RaiseFatal(ARRAYINSERTERROR, &quot;Column&quot;, SF_Array._Repr(Array_2D), SF_Utils._Repr(Column, MAXREPR))
GoTo Finally
End Function &apos; ScriptForge.SF_Array.AppendColumn
REM -----------------------------------------------------------------------------
Public Function AppendRow(Optional ByRef Array_2D As Variant _
, Optional ByRef Row As Variant _
) As Variant
&apos;&apos;&apos; AppendRow appends below a 2D array a new row
&apos;&apos;&apos; Args
&apos;&apos;&apos; Array_2D: the pre-existing array, may be empty
&apos;&apos;&apos; If the array has 1 dimension, it is considered as the 1st row of the resulting 2D array
&apos;&apos;&apos; Row: a 1D array with as many items as there are columns in Array_2D
&apos;&apos;&apos; Returns:
&apos;&apos;&apos; the new extended array. Its LBounds are identical to that of Array_2D
&apos;&apos;&apos; Exceptions:
&apos;&apos;&apos; ARRAYINSERTERROR
&apos;&apos;&apos; Examples:
&apos;&apos;&apos; SF_Array.AppendRow(Array(1, 2, 3), Array(4, 5, 6)) returns ((1, 2, 3), (4, 5, 6))
&apos;&apos;&apos; x = SF_Array.AppendRow(Array(), Array(1, 2, 3)) =&gt; ∀ i ∈ {0 ≤ i ≤ 2} : x(i, 0) ≡ i
Dim vAppendRow As Variant &apos; Return value
Dim iDims As Integer &apos; Dimensions of Array_2D
Dim lMin1 As Long &apos; LBound1 of input array
Dim lMax1 As Long &apos; UBound1 of input array
Dim lMin2 As Long &apos; LBound2 of input array
Dim lMax2 As Long &apos; UBound2 of input array
Dim lMin As Long &apos; LBound of row array
Dim lMax As Long &apos; UBound of row array
Dim i As Long
Dim j As Long
Const cstThisSub = &quot;Array.AppendRow&quot;
Const cstSubArgs = &quot;Array_2D, Row&quot;
If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
vAppendRow = Array()
Check:
If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;) Then GoTo Finally &apos;Initial check: not missing and array
If Not SF_Utils._ValidateArray(Row, &quot;Row&quot;, 1) Then GoTo Finally
End If
iDims = SF_Array.CountDims(Array_2D)
If iDims &gt; 2 Then
If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally &apos;2nd check to manage error
End If
Try:
lMin = LBound(Row)
lMax = UBound(Row)
&apos; Compute future dimensions of output array
Select Case iDims
Case 0 : lMin1 = 0 : lMax1 = -1
lMin2 = lMin : lMax2 = lMax
Case 1 : lMin1 = 0 : lMax1 = 0
lMin2 = LBound(Array_2D, 1) : lMax2 = UBound(Array_2D, 1)
Case 2 : lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
End Select
If iDims &gt; 0 And lMax - lMin &lt;&gt; lMax2 - lMin2 Then GoTo CatchRow
ReDim vAppendRow(lMin1 To lMax1 + 1, lMin2 To lMax2)
&apos; Copy input array to output array
For i = lMin1 To lMax1
For j = lMin2 To lMax2
If iDims = 2 Then vAppendRow(i, j) = Array_2D(i, j) Else vAppendRow(i, j) = Array_2D(j)
Next j
Next i
&apos; Copy new row
For j = lMin2 To lMax2
vAppendRow(lMax1 + 1, j) = Row(j)
Next j
Finally:
AppendRow = vAppendRow()
SF_Utils._ExitFunction(cstThisSub)
Exit Function
Catch:
GoTo Finally
CatchRow:
SF_Exception.RaiseFatal(ARRAYINSERTERROR, &quot;Row&quot;, SF_Array._Repr(Array_2D), SF_Utils._Repr(Row, MAXREPR))
GoTo Finally
End Function &apos; ScriptForge.SF_Array.AppendRow
REM -----------------------------------------------------------------------------
Public Function Contains(Optional ByRef Array_1D As Variant _
, Optional ByVal ToFind As Variant _
, Optional ByVal CaseSensitive As Variant _
, Optional ByVal SortOrder As Variant _
) As Boolean
&apos;&apos;&apos; Check if a 1D array contains the ToFind number, string or date
&apos;&apos;&apos; The comparison between strings can be done case-sensitive or not
&apos;&apos;&apos; If the array is sorted then
&apos;&apos;&apos; the array must be filled homogeneously, i.e. all items must be of the same type
&apos;&apos;&apos; Empty and Null items are forbidden
&apos;&apos;&apos; a binary search is done
&apos;&apos;&apos; Otherwise the array is scanned from top. Null or Empty items are simply ignored
&apos;&apos;&apos; Args:
&apos;&apos;&apos; Array_1D: the array to scan
&apos;&apos;&apos; ToFind: a number, a date or a string to find
&apos;&apos;&apos; CaseSensitive: Only for string comparisons, default = False
&apos;&apos;&apos; SortOrder: &quot;ASC&quot;, &quot;DESC&quot; or &quot;&quot; (= not sorted, default)
&apos;&apos;&apos; Return: True when found
&apos;&apos;&apos; Result is unpredictable when array is announced sorted and is in reality not
&apos;&apos;&apos; Examples:
&apos;&apos;&apos; SF_Array.Contains(Array(&quot;A&quot;,&quot;B&quot;,&quot;c&quot;,&quot;D&quot;), &quot;C&quot;, SortOrder := &quot;ASC&quot;) returns True
&apos;&apos;&apos; SF_Array.Contains(Array(&quot;A&quot;,&quot;B&quot;,&quot;c&quot;,&quot;D&quot;), &quot;C&quot;, CaseSensitive := True) returns False
Dim bContains As Boolean &apos; Return value
Dim iToFindType As Integer &apos; VarType of ToFind
Const cstThisSub = &quot;Array.Contains&quot;
Const cstSubArgs = &quot;Array_1D, ToFind, [CaseSensitive=False], [SortOrder=&quot;&quot;&quot;&quot;|&quot;&quot;ASC&quot;&quot;|&quot;&quot;DESC&quot;&quot;]&quot;
If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
bContains = False
Check:
If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False
If IsMissing(SortOrder) Or IsEmpty(SortOrder) Then SortOrder = &quot;&quot;
If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
If Not SF_Utils._Validate(SortOrder, &quot;SortOrder&quot;, V_STRING, Array(&quot;ASC&quot;, &quot;DESC&quot;, &quot;&quot;)) Then GoTo Finally
If Not SF_Utils._Validate(ToFind, &quot;ToFind&quot;, Array(V_STRING, V_DATE, V_NUMERIC)) Then GoTo Finally
iToFindType = SF_Utils._VarTypeExt(ToFind)
If SortOrder &lt;&gt; &quot;&quot; Then
If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1, iToFindType) Then GoTo Finally
Else
If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
End If
If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
End If
Try:
bContains = SF_Array._FindItem(Array_1D, ToFind, CaseSensitive, UCase(SortOrder))(0)
Finally:
Contains = bContains
SF_Utils._ExitFunction(cstThisSub)
Exit Function
Catch:
GoTo Finally
End Function &apos; ScriptForge.SF_Array.Contains
REM -----------------------------------------------------------------------------
Public Function ConvertFromDataArray(Optional ByRef DataArray As Variant _
, Optional ByVal IsRange As Variant _
, Optional ByVal FillValue As Variant _
) As Variant
&apos;&apos;&apos; Convert a data array to a scalar, a vector or a 2D array
&apos;&apos;&apos; A &quot;data array&quot; is an array of subarrays. The array and the subarrays must be zero-based.
&apos;&apos;&apos; The individual items may be of any type.
&apos;&apos;&apos; On request, the individual items are reduced to strings or doubles only.
&apos;&apos;&apos; Typically it represents
&apos;&apos;&apos; - the content of a range of Calc cells returned by the UNO XCellRange.getDataArray()
&apos;&apos;&apos; or XCellRange.getFormulaArray() methods
&apos;&apos;&apos; - the output of the SF_Session.ExecuteCalcFunction() method
&apos;&apos;&apos; - a tuple of (sub)tuples returned by a Python script.
&apos;&apos;&apos; Args:
&apos;&apos;&apos; DataArray: an array of subarrays. Array and subarrays must be zero-based.
&apos;&apos;&apos; IsRange: When True (default = False), the items are converted to strings or doubles.
&apos;&apos;&apos; FillValue: Default value of missing items. Default = the empty string
&apos;&apos;&apos; Returns:
&apos;&apos;&apos; A scalar, a zero-based 1D array or a zero-based 2D array.
&apos;&apos;&apos; The number of columns in the resulting 2D array = the number of items in the 1st subarray.
&apos;&apos;&apos; If next subarrays have a different size, next rows can be complemented with FillValue or truncated.
&apos;&apos;&apos; Example:
&apos;&apos;&apos; vDataArray = Array(Array(&quot;Col1&quot;, &quot;Col2&quot;, &quot;Col3&quot;), Array(10, 20, 30))
&apos;&apos;&apos; Array2D = SF_Array.ConvertFromDataArray(vDataArray)
&apos;&apos;&apos; MsgBox Array2D(1, 2) &apos; 30
Dim vArray As Variant &apos; Return value
Dim lMax1 As Long &apos; UBound of DataArray
Dim lMax2 As Long &apos; UBound of the 1st row of DataArray
Dim lMax As Long &apos; UBound of a subarray
Dim i As Long
Dim j As Long
Const cstThisSub = &quot;Array.ConvertFromDataArray&quot;
Const cstSubArgs = &quot;DataArray, [IsRange=False], [FillValue=&quot;&quot;&quot;&quot;]&quot;
If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
vArray = Array()
Check:
If IsMissing(IsRange) Or IsEmpty(IsRange) Then IsRange = False
If IsMissing(FillValue) Or IsEmpty(FillValue) Then FillValue = &quot;&quot;
If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
If Not SF_Utils._ValidateArray(DataArray, &quot;DataArray&quot;, 1) Then GoTo Finally
For i = 0 To UBound(DataArray)
If Not SF_Utils._ValidateArray(DataArray(i), &quot;DataArray(&quot; &amp; i &amp; &quot;)&quot;, 1) Then GoTo Finally
Next i
End If
Try:
&apos; Convert the data array to scalar, vector or array
lMax1 = UBound(DataArray)
If lMax1 &gt;= 0 Then
lMax2 = UBound(DataArray(0))
If lMax2 &gt;= 0 Then
If lMax1 + lMax2 &gt; 0 Then vArray = Array()
Select Case True
Case lMax1 = 0 And lMax2 = 0 &apos; Scalar
vArray = _ConvertToCellValue(DataArray(0)(0), pbIsCell := IsRange)
Case lMax1 &gt; 0 And lMax2 = 0 &apos; Vertical vector
ReDim vArray(0 To lMax1)
For i = 0 To lMax1
vArray(i) = _ConvertToCellValue(DataArray(i)(0), pbIsCell := IsRange)
Next i
Case lMax1 = 0 And lMax2 &gt; 0 &apos; Horizontal vector
ReDim vArray(0 To lMax2)
For j = 0 To lMax2
vArray(j) = _ConvertToCellValue(DataArray(0)(j), pbIsCell := IsRange)
Next j
Case Else &apos; Array
ReDim vArray(0 To lMax1, 0 To lMax2)
For i = 0 To lMax1
lMax = UBound(DataArray(i))
For j = 0 To lMax2
If j &lt;= lMax Then vArray(i, j) = _ConvertToCellValue(DataArray(i)(j), pbIsCell := IsRange) Else vArray(i, j) = FillValue
Next j
Next i
End Select
End If
End If
Finally:
ConvertFromDataArray = vArray
SF_Utils._ExitFunction(cstThisSub)
Exit Function
Catch:
GoTo Finally
End Function &apos; ScriptForge.SF_Array.ConvertFromDataArray
REM -----------------------------------------------------------------------------
Public Function ConvertToDataArray(Optional ByRef Data As Variant _
, Optional ByVal IsRange As Variant _
, Optional ByVal Rows As Variant _
, Optional ByVal Columns As Variant _
) As Variant
&apos;&apos;&apos; Create a data array from a scalar, a 1D array or a 2D array
&apos;&apos;&apos; The returned &quot;data array&quot; is an array of subarrays.
&apos;&apos;&apos; On request, the individual items are reduced to strings or doubles only.
&apos;&apos;&apos; Typically it represents
&apos;&apos;&apos; - the content of a range of Calc cells used to apply the UNO XCellRange.setDataArray()
&apos;&apos;&apos; or XCellRange.setFormulaArray() methods
&apos;&apos;&apos; - a tuple of (sub)tuples passed to a Python script.
&apos;&apos;&apos; Input argument may already be a data array, typically when call is issued by a Python script.
&apos;&apos;&apos; When IsRange = True then the individual items are converted to (possibly empty) strings or doubles.
&apos;&apos;&apos; Args:
&apos;&apos;&apos; Data: the input scalar or array. If array, must be 1D or 2D otherwise it is ignored.
&apos;&apos;&apos; IsRange: When True (default = False), the items are converted to strings or doubles.
&apos;&apos;&apos; Rows, Columns: the number of rows or columns of the returned data array.
&apos;&apos;&apos; If bigger than Data, fill with zero-length strings
&apos;&apos;&apos; If smaller than Data, truncate
&apos;&apos;&apos; If Rows = 1 and the input array is a vector, the data array is aligned horizontally
&apos;&apos;&apos; By default, vectors are aligned vertically.
&apos;&apos;&apos; When absent, the size of the output is determined by the input array.
&apos;&apos;&apos; Returns:
&apos;&apos;&apos; The output is always an array of nested arrays. Array and nested arrays are zero-based.
&apos;&apos;&apos; It is compatible with the XCellRange.DataArray property when IsRange = True.
Dim vDataArray() As Variant &apos; Return value
Dim vVector() As Variant &apos; A temporary 1D array
Dim vItem As Variant &apos; A single input item
Dim iDims As Integer &apos; Number of dimensions of the input argument
Dim lMin1 As Long &apos; Lower bound (1) of input array
Dim lMax1 As Long &apos; Upper bound (1)
Dim lMin2 As Long &apos; Lower bound (2)
Dim lMax2 As Long &apos; Upper bound (2)
Dim lRows As Long &apos; Upper bound of vDataArray
Dim lCols As Long &apos; Upper bound of vVector
Dim bHorizontal As Boolean &apos; Horizontal vectoriDims &lt;&gt; 0 =&gt; Data has at least 1 item
Dim bDataArray As Boolean &apos; Input array is already an array of arrays
Dim i As Long
Dim j As Long
Const cstEmpty = &quot;&quot; &apos; Empty cell
Const cstThisSub = &quot;Array.ConvertToDataArray&quot;
Const cstSubArgs = &quot;Data, [IsRange=False], [Rows=0], [Columns=0]&quot;
If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
vDataArray = Array()
Check:
If IsMissing(IsRange) Or IsEmpty(IsRange) Then IsRange = False
If IsMissing(Rows) Or IsEmpty(Rows) Then Rows = 0
If IsMissing(Columns) Or IsEmpty(Columns) Then Columns = 0
If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
If Not SF_Utils._Validate(IsRange, &quot;IsRange&quot;, V_BOOLEAN) Then GoTo Finally
If Not SF_Utils._Validate(Rows, &quot;Rows&quot;, V_NUMERIC) Then GoTo Finally
If Not SF_Utils._Validate(Columns, &quot;Columns&quot;, V_NUMERIC) Then GoTo Finally
End If
Try:
&apos; Examine the input argument and determine its boundaries
&apos; Set the output dimensions accordingly
iDims = SF_Array.CountDims(Data)
If iDims = 0 Or iDims &gt; 2 Then Exit Function
lMin1 = 0 : lMax1 = 0 &apos; Default values
lMin2 = 0 : lMax2 = 0
lRows = 0 : lCols = 0
Select Case iDims
Case -1 &apos; Scalar value
Case 1 &apos; 1D Array
bHorizontal = ( Rows = 1 )
bDataArray = IsArray(Data(LBound(Data)))
If Not bDataArray Then
If Not bHorizontal Then
lMin1 = LBound(Data) : lMax1 = UBound(Data)
lRows = lMax1 - lMin1
Else
lMin2 = LBound(Data) : lMax2 = UBound(Data)
lCols = lMax2 - lMin2
End If
Else
iDims = 2
lMin1 = LBound(Data) : lMax1 = UBound(Data)
lMin2 = LBound(Data(0)) : lMax2 = UBound(Data(0))
lRows = lMax1 - lMin1
lCols = lMax2 - lMin2
End If
Case 2 &apos; 2D Array
lMin1 = LBound(Data, 1) : lMax1 = UBound(Data, 1)
lMin2 = LBound(Data, 2) : lMax2 = UBound(Data, 2)
lRows = lMax1 - lMin1
lCols = lMax2 - lMin2
End Select
&apos; Size the future data array to the output dimensions
&apos; Are the dimensions imposed ?
If Rows &gt;= 1 Then lRows = Rows - 1
If Columns &gt;= 1 Then lCols = Columns - 1
ReDim vDataArray(0 To lRows)
&apos; Feed the output array row by row, each row being a vector
For i = 0 To lRows
ReDim vVector(0 To lCols)
For j = 0 To lCols
If i &gt; lMax1 - lMin1 Then
vVector(j) = cstEmpty
ElseIf j &gt; lMax2 - lMin2 Then
vVector(j) = cstEmpty
Else
Select Case iDims
Case -1 : vItem = _ConvertToCellValue(Data, IsRange)
Case 1
If bHorizontal Then
vItem = _ConvertToCellValue(Data(j + lMin2), IsRange)
Else
vItem = _ConvertToCellValue(Data(i + lMin1), IsRange)
End If
Case 2
If bDataArray Then
vItem = _ConvertToCellValue(Data(i + lMin1)(j + lMin2), IsRange)
Else
vItem = _ConvertToCellValue(Data(i + lMin1, j + lMin2), IsRange)
End If
End Select
vVector(j) = vItem
End If
vDataArray(i) = vVector
Next j
Next i
Finally:
ConvertToDataArray = vDataArray
SF_Utils._ExitFunction(cstThisSub)
Exit Function
Catch:
GoTo Finally
End Function &apos; ScriptForge.SF_Array.ConvertToDataArray
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 with case-sensitive comparison of keys
&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;Array.ConvertToDictionary&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;, True)
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 ConvertToRange(Optional ByRef Data As Variant _
, Optional ByVal Direction As Variant _
) As Variant
&apos;&apos;&apos; Create a valid cells range content from a scalar, a 1D array, a 2D array or a data array
&apos;&apos;&apos; (a &quot;data array&quot; is an array of subarrays). Lower bounds are preserved.
&apos;&apos;&apos; The individual items are always reduced to strings or doubles. Including booleans or dates.
&apos;&apos;&apos; The returned scalar or array is a valid argument of the SF_Session.ExecuteCalcFunction() method.
&apos;&apos;&apos; Input argument may be a data array, typically when returned by a Python script.
&apos;&apos;&apos; Args:
&apos;&apos;&apos; Data: the input scalar, array or data array. If array, must be 1D or 2D, otherwise 0 is returned.
&apos;&apos;&apos; Direction: the alignment of the range when it is a vector, either &quot;V&quot;[ertical, default] or &quot;H&quot;[orizontal].
&apos;&apos;&apos; Returns:
&apos;&apos;&apos; A scalar or a 2D array compatible with the format of the arguments expected by
&apos;&apos;&apos; - the SF_Session.ExecuteCalcFunction() method, in the context of [complex] array functions
&apos;&apos;&apos; - a Calc user-defined function when called by a macro
&apos;&apos;&apos; Example:
&apos;&apos;&apos; arr = CreateScriptService(&quot;Array&quot;)
&apos;&apos;&apos; sess = CreateScriptService(&quot;Session&quot;)
&apos;&apos;&apos; matrix = Array(Array(-1, 2, 3), Array(4, -5, 6), Array(7, 8, -9)) &apos; Input = a data array
&apos;&apos;&apos; result = sess.ExecuteCalcFunction(&quot;ABS&quot;, arr.ConvertToRange(matrix))
&apos;&apos;&apos; &apos; result is a data array; result(2)(2) = 9
Dim vRange As Variant &apos; Return value
Dim iDims As Integer &apos; Number of dimensions of Data
Dim vDataArray As Variant &apos; External array of a data array
Dim vVector As Variant &apos; Internal array of a data array
Dim lMin1 As Long &apos; Lower bound #1
Dim lMax1 As Long &apos; Upper bound #1
Dim lMin2 As Long &apos; Lower bound #2
Dim lMax2 As Long &apos; Upper bound #2
Dim lMin As Long &apos; Lower bound
Dim lMax As Long &apos; Upper bound
Dim i As Long
Dim j As Long
Const cstThisSub = &quot;Array.ConvertToRange&quot;
Const cstSubArgs = &quot;Data, [Direction=&quot;&quot;&quot;&quot;|&quot;&quot;V&quot;&quot;|&quot;&quot;H&quot;&quot;]&quot;
If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
vRange = 0
Check:
If IsMissing(Direction) Or IsEmpty(Direction) Then Direction = &quot;V&quot;
If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
If IsMissing(Data) Or IsEmpty(Data) Then
If Not SF_Utils._Validate(Data, &quot;Data&quot;) Then GoTo Finally
End If
If Not SF_Utils._Validate(Direction, &quot;Direction&quot;, V_STRING, Array(&quot;&quot;, &quot;V&quot;, &quot;H&quot;)) Then GoTo Finally
End If
Try:
iDims = SF_Array.CountDims(Data)
If iDims = 0 Or iDims &gt; 2 Then Exit Function
Select Case iDims
&apos; All input items are converted on-the-fly to an acceptable Calc cell value
Case -1 &apos; Scalar
vRange = _ConvertToCellValue(Data, pbIsCell := True)
Case 1 &apos; Vector or data array
&apos; iDims &lt;&gt; 0 =&gt; Data has at least 1 item
lMin1 = LBound(Data) : lMax1 = UBound(Data)
If IsArray(Data(lMin1)) Then &apos; Data array
&apos; Design a 2D array of the appropriate size
lMin2 = LBound(Data(lMin1)) : lMax2 = UBound(Data(lMin1))
vRange = Array()
ReDim vRange(lMin1 To lMax1, lMin2 To lMax2)
For i = lMin1 To lMax1
If Not IsArray(Data(i)) Then
lMin = -1 : lMax = -1
Else
lMin = LBound(Data(i))
lMax = UBound(Data(i))
End If
For j = lMin2 To lMax2
If j &lt; lMin Or j &gt; lMax Then vRange(i, j) = &quot;&quot; Else vRange(i, j) = _ConvertToCellValue(Data(i)(j), pbIsCell := True)
Next j
Next i
Else &apos; Vector
vRange = Array()
If UCase(Direction) = &quot;V&quot; Then &apos; Vertical
ReDim vRange(lMin1 To lMax1, 0 To 0)
For i = lMin1 To lMax1
vRange(i, 0) = _ConvertToCellValue(Data(i), pbIsCell := True)
Next i
Else &apos; Horizontal
ReDim vRange(0 To 0, lMin1 To lMax1)
For j = lMin1 To lMax1
vRange(0, j) = _ConvertToCellValue(Data(j), pbIsCell := True)
Next j
End If
End If
Case 2
&apos; Copy all array items
vRange = Array()
lMin1 = LBound(Data, 1) : lMax1 = UBound(Data, 1)
lMin2 = LBound(Data, 2) : lMax2 = UBound(Data, 2)
ReDim vRange(lMin1 To lMax1, lMin2 To lMax2)
For i = lMin1 To lMax1
For j = lMin2 To lMax2
vRange(i, j) = _ConvertToCellValue(Data(i, j), pbIsCell := True)
Next j
Next i
Case Else
End Select
Finally:
ConvertToRange = vRange
SF_Utils._ExitFunction(cstThisSub)
Exit Function
Catch:
GoTo Finally
End Function &apos; ScriptForge.SF_Array.ConvertToRange
REM -----------------------------------------------------------------------------
Public Function Copy(Optional ByRef Array_ND As Variant) As Variant
&apos;&apos;&apos; Duplicate a 1D or 2D array
&apos;&apos;&apos; A usual assignment copies an array by reference, i.e. shares the same memory location
&apos;&apos;&apos; Dim a, b
&apos;&apos;&apos; a = Array(1, 2, 3)
&apos;&apos;&apos; b = a
&apos;&apos;&apos; a(2) = 30
&apos;&apos;&apos; MsgBox b(2) &apos; 30
&apos;&apos;&apos; Args
&apos;&apos;&apos; Array_ND: the array to copy, may be empty
&apos;&apos;&apos; Return:
&apos;&apos;&apos; the copied array. Subarrays however still remain assigned by reference
&apos;&apos;&apos; Examples:
&apos;&apos;&apos; SF_Array.Copy(Array(1, 2, 3)) returns (1, 2, 3)
Dim vCopy As Variant &apos; Return value
Dim iDims As Integer &apos; Number of dimensions of the input array
Const cstThisSub = &quot;Array.Copy&quot;
Const cstSubArgs = &quot;Array_ND&quot;
If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
vCopy = Array()
Check:
If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
If Not SF_Utils._ValidateArray(Array_ND, &quot;Array_ND&quot;) Then GoTo Finally
iDims = SF_Array.CountDims(Array_ND)
If iDims &gt; 2 Then
If Not SF_Utils._ValidateArray(Array_ND, &quot;Array_ND&quot;, 2) Then GoTo Finally
End If
End If
Try:
Select Case iDims
Case 0
Case 1
vCopy = Array_ND
ReDim Preserve vCopy(LBound(Array_ND) To UBound(Array_ND))
Case 2
vCopy = Array_ND
ReDim Preserve vCopy(LBound(Array_ND, 1) To UBound(Array_ND, 1), LBound(Array_ND, 2) To UBound(Array_ND, 2))
End Select
Finally:
Copy = vCopy()
SF_Utils._ExitFunction(cstThisSub)
Exit Function
Catch:
GoTo Finally
End Function &apos; ScriptForge.SF_Array.Copy
REM -----------------------------------------------------------------------------
Public Function CountDims(Optional ByRef Array_ND As Variant) As Integer
&apos;&apos;&apos; Count the number of dimensions of an array - may be &gt; 2
&apos;&apos;&apos; Args:
&apos;&apos;&apos; Array_ND: the array to be examined
&apos;&apos;&apos; Return: the number of dimensions: -1 = not array, 0 = uninitialized array, else &gt;= 1
&apos;&apos;&apos; Examples:
&apos;&apos;&apos; Dim a(1 To 10, -3 To 12, 5)
&apos;&apos;&apos; CountDims(a) returns 3
Dim iDims As Integer &apos; Return value
Dim lMax As Long &apos; Storage for UBound of each dimension
Const cstThisSub = &quot;Array.CountDims&quot;
Const cstSubArgs = &quot;Array_ND&quot;
Check:
iDims = -1
If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
If IsMissing(Array_ND) Then &apos; To have missing exception processed
If Not SF_Utils._ValidateArray(Array_ND, &quot;Array_ND&quot;) Then GoTo Finally
End If
End If
Try:
On Local Error Goto ErrHandler
&apos; Loop, increasing the dimension index (i) until an error occurs.
&apos; An error will occur when i exceeds the number of dimensions in the array. Returns i - 1.
iDims = 0
If Not IsArray(Array_ND) Then
Else
Do
iDims = iDims + 1
lMax = UBound(Array_ND, iDims)
Loop Until (Err &lt;&gt; 0)
End If
ErrHandler:
On Local Error GoTo 0
iDims = iDims - 1
If iDims = 1 Then
If LBound(Array_ND, 1) &gt; UBound(Array_ND, 1) Then iDims = 0
End If
Finally:
CountDims = iDims
SF_Utils._ExitFunction(cstThisSub)
Exit Function
End Function &apos; ScriptForge.SF_Array.CountDims
REM -----------------------------------------------------------------------------
Public Function Difference(Optional ByRef Array1_1D As Variant _
, Optional ByRef Array2_1D As Variant _
, Optional ByVal CaseSensitive As Variant _
) As Variant
&apos;&apos;&apos; Build a set being the Difference of the two input arrays, i.e. items are contained in 1st array and NOT in 2nd
&apos;&apos;&apos; both input arrays must be filled homogeneously, i.e. all items must be of the same type
&apos;&apos;&apos; Empty and Null items are forbidden
&apos;&apos;&apos; The comparison between strings is case sensitive or not
&apos;&apos;&apos; Args:
&apos;&apos;&apos; Array1_1D: a 1st input array
&apos;&apos;&apos; Array2_1D: a 2nd input array
&apos;&apos;&apos; CaseSensitive: default = False
&apos;&apos;&apos; Returns: a zero-based array containing unique items from the 1st array not present in the 2nd
&apos;&apos;&apos; The output array is sorted in ascending order
&apos;&apos;&apos; Examples:
&apos;&apos;&apos; SF_Array.Difference(Array(&quot;A&quot;, &quot;C&quot;, &quot;A&quot;, &quot;b&quot;, &quot;B&quot;), Array(&quot;C&quot;, &quot;Z&quot;, &quot;b&quot;), True) returns (&quot;A&quot;, &quot;B&quot;)
Dim vDifference() As Variant &apos; Return value
Dim vSorted() As Variant &apos; The 2nd input array after sort
Dim iType As Integer &apos; VarType of elements in input arrays
Dim lMin1 As Long &apos; LBound of 1st input array
Dim lMax1 As Long &apos; UBound of 1st input array
Dim lMin2 As Long &apos; LBound of 2nd input array
Dim lMax2 As Long &apos; UBound of 2nd input array
Dim lSize As Long &apos; Number of Difference items
Dim vItem As Variant &apos; One single item in the array
Dim i As Long
Const cstThisSub = &quot;Array.Difference&quot;
Const cstSubArgs = &quot;Array1_1D, Array2_1D, [CaseSensitive=False]&quot;
If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
vDifference = Array()
Check:
If IsMissing(CaseSensitive) Then CaseSensitive = False
If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
If Not SF_Utils._ValidateArray(Array1_1D, &quot;Array1_1D&quot;, 1, 0, True) Then GoTo Finally
iType = SF_Utils._VarTypeExt(Array1_1D(LBound(Array1_1D)))
If Not SF_Utils._ValidateArray(Array2_1D, &quot;Array2_1D&quot;, 1, iType, True) Then GoTo Finally
If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
End If
Try:
lMin1 = LBound(Array1_1D) : lMax1 = UBound(Array1_1D)
lMin2 = LBound(Array2_1D) : lMax2 = UBound(Array2_1D)
&apos; If 1st array is empty, do nothing
If lMax1 &lt; lMin1 Then
ElseIf lMax2 &lt; lMin2 Then &apos; only 2nd array is empty
vUnion = SF_Array.Unique(Array1_1D, CaseSensitive)
Else
&apos; First sort the 2nd array
vSorted = SF_Array.Sort(Array2_1D, &quot;ASC&quot;, CaseSensitive)
&apos; Resize the output array to the size of the 1st array
ReDim vDifference(0 To (lMax1 - lMin1))
lSize = -1
&apos; Fill vDifference one by one with items present only in 1st set
For i = lMin1 To lMax1
vItem = Array1_1D(i)
If Not SF_Array.Contains(vSorted, vItem, CaseSensitive, &quot;ASC&quot;) Then
lSize = lSize + 1
vDifference(lSize) = vItem
End If
Next i
&apos; Remove unfilled entries and duplicates
If lSize &gt;= 0 Then
ReDim Preserve vDifference(0 To lSize)
vDifference() = SF_Array.Unique(vDifference, CaseSensitive)
Else
vDifference = Array()
End If
End If
Finally:
Difference = vDifference()
SF_Utils._ExitFunction(cstThisSub)
Exit Function
Catch:
GoTo Finally
End Function &apos; ScriptForge.SF_Array.Difference
REM -----------------------------------------------------------------------------
Public Function ExportToTextFile(Optional ByRef Array_1D As Variant _
, Optional ByVal FileName As Variant _
, Optional ByVal Encoding As Variant _
) As Boolean
&apos;&apos;&apos; Write all items of the array sequentially to a text file
&apos;&apos;&apos; If the file exists already, it will be overwritten without warning
&apos;&apos;&apos; Args:
&apos;&apos;&apos; Array_1D: the array to export
&apos;&apos;&apos; FileName: the full name (path + file) in SF_FileSystem.FileNaming notation
&apos;&apos;&apos; Encoding: The character set that should be used
&apos;&apos;&apos; Use one of the Names listed in https://www.iana.org/assignments/character-sets/character-sets.xhtml
&apos;&apos;&apos; Note that LibreOffice does not implement all existing sets
&apos;&apos;&apos; Default = UTF-8
&apos;&apos;&apos; Returns:
&apos;&apos;&apos; True if successful
&apos;&apos;&apos; Examples:
&apos;&apos;&apos; SF_Array.ExportToTextFile(Array(&quot;A&quot;,&quot;B&quot;,&quot;C&quot;,&quot;D&quot;), &quot;C:\Temp\A short file.txt&quot;)
Dim bExport As Boolean &apos; Return value
Dim oFile As Object &apos; Output file handler
Dim sLine As String &apos; A single line
Const cstThisSub = &quot;Array.ExportToTextFile&quot;
Const cstSubArgs = &quot;Array_1D, FileName&quot;
If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
bExport = False
Check:
If IsMissing(Encoding) Or IsEmpty(Encoding) Then Encoding = &quot;UTF-8&quot;
If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1, V_STRING, True) Then GoTo Finally
If Not SF_Utils._ValidateFile(FileName, &quot;FileName&quot;) Then GoTo Finally
If Not SF_Utils._Validate(Encoding, &quot;Encoding&quot;, V_STRING) Then GoTo Finally
End If
Try:
Set oFile = SF_FileSystem.CreateTextFile(FileName, Overwrite := True, Encoding := Encoding)
If Not IsNull(oFile) Then
With oFile
For Each sLine In Array_1D
.WriteLine(sLine)
Next sLine
.CloseFile()
End With
End If
bExport = True
Finally:
If Not IsNull(oFile) Then Set oFile = oFile.Dispose()
ExportToTextFile = bExport
SF_Utils._ExitFunction(cstThisSub)
Exit Function
Catch:
GoTo Finally
End Function &apos; ScriptForge.SF_Array.ExportToTextFile
REM -----------------------------------------------------------------------------
Public Function ExtractColumn(Optional ByRef Array_2D As Variant _
, Optional ByVal ColumnIndex As Variant _
) As Variant
&apos;&apos;&apos; ExtractColumn extracts from a 2D array a specific column
&apos;&apos;&apos; Args
&apos;&apos;&apos; Array_2D: the array from which to extract
&apos;&apos;&apos; ColumnIndex: the column to extract - must be in the interval [LBound, UBound]
&apos;&apos;&apos; Returns:
&apos;&apos;&apos; the extracted column. Its LBound and UBound are identical to that of the 1st dimension of Array_2D
&apos;&apos;&apos; Exceptions:
&apos;&apos;&apos; ARRAYINDEX1ERROR
&apos;&apos;&apos; Examples:
&apos;&apos;&apos; |1, 2, 3|
&apos;&apos;&apos; SF_Array.ExtractColumn( |4, 5, 6|, 2) returns (3, 6, 9)
&apos;&apos;&apos; |7, 8, 9|
Dim vExtractColumn As Variant &apos; Return value
Dim lMin1 As Long &apos; LBound1 of input array
Dim lMax1 As Long &apos; UBound1 of input array
Dim lMin2 As Long &apos; LBound1 of input array
Dim lMax2 As Long &apos; UBound1 of input array
Dim i As Long
Const cstThisSub = &quot;Array.ExtractColumn&quot;
Const cstSubArgs = &quot;Array_2D, ColumnIndex&quot;
If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
vExtractColumn = Array()
Check:
If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally
If Not SF_Utils._Validate(ColumnIndex, &quot;ColumnIndex&quot;, V_NUMERIC) Then GoTo Finally
End If
Try:
&apos; Compute future dimensions of output array
lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
If ColumnIndex &lt; lMin2 Or ColumnIndex &gt; lMax2 Then GoTo CatchIndex
lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
ReDim vExtractColumn(lMin1 To lMax1)
&apos; Copy Column of input array to output array
For i = lMin1 To lMax1
vExtractColumn(i) = Array_2D(i, ColumnIndex)
Next i
Finally:
ExtractColumn = vExtractColumn()
SF_Utils._ExitFunction(cstThisSub)
Exit Function
Catch:
GoTo Finally
CatchIndex:
SF_Exception.RaiseFatal(ARRAYINDEX1ERROR, &quot;ColumnIndex&quot;, SF_Array._Repr(Array_2D), ColumnIndex)
GoTo Finally
End Function &apos; ScriptForge.SF_Array.ExtractColumn
REM -----------------------------------------------------------------------------
Public Function ExtractRow(Optional ByRef Array_2D As Variant _
, Optional ByVal RowIndex As Variant _
) As Variant
&apos;&apos;&apos; ExtractRow extracts from a 2D array a specific row
&apos;&apos;&apos; Args
&apos;&apos;&apos; Array_2D: the array from which to extract
&apos;&apos;&apos; RowIndex: the row to extract - must be in the interval [LBound, UBound]
&apos;&apos;&apos; Returns:
&apos;&apos;&apos; the extracted row. Its LBound and UBound are identical to that of the 2nd dimension of Array_2D
&apos;&apos;&apos; Exceptions:
&apos;&apos;&apos; ARRAYINDEX1ERROR
&apos;&apos;&apos; Examples:
&apos;&apos;&apos; |1, 2, 3|
&apos;&apos;&apos; SF_Array.ExtractRow(|4, 5, 6|, 2) returns (7, 8, 9)
&apos;&apos;&apos; |7, 8, 9|
Dim vExtractRow As Variant &apos; Return value
Dim lMin1 As Long &apos; LBound1 of input array
Dim lMax1 As Long &apos; UBound1 of input array
Dim lMin2 As Long &apos; LBound1 of input array
Dim lMax2 As Long &apos; UBound1 of input array
Dim i As Long
Const cstThisSub = &quot;Array.ExtractRow&quot;
Const cstSubArgs = &quot;Array_2D, RowIndex&quot;
If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
vExtractRow = Array()
Check:
If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally
If Not SF_Utils._Validate(RowIndex, &quot;RowIndex&quot;, V_NUMERIC) Then GoTo Finally
End If
Try:
&apos; Compute future dimensions of output array
lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
If RowIndex &lt; lMin1 Or RowIndex &gt; lMax1 Then GoTo CatchIndex
lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
ReDim vExtractRow(lMin2 To lMax2)
&apos; Copy row of input array to output array
For i = lMin2 To lMax2
vExtractRow(i) = Array_2D(RowIndex, i)
Next i
Finally:
ExtractRow = vExtractRow()
SF_Utils._ExitFunction(cstThisSub)
Exit Function
Catch:
GoTo Finally
CatchIndex:
SF_Exception.RaiseFatal(ARRAYINDEX1ERROR, &quot;RowIndex&quot;, SF_Array._Repr(Array_2D), RowIndex)
GoTo Finally
End Function &apos; ScriptForge.SF_Array.ExtractRow
REM -----------------------------------------------------------------------------
Public Function Flatten(Optional ByRef Array_1D As Variant) As Variant
&apos;&apos;&apos; Stack all items and all items in subarrays into one array without subarrays
&apos;&apos;&apos; Args
&apos;&apos;&apos; Array_1D: the pre-existing array, may be empty
&apos;&apos;&apos; Return:
&apos;&apos;&apos; The new flattened array. Its LBound is identical to that of Array_1D
&apos;&apos;&apos; If one of the subarrays has a number of dimensions &gt; 1 Then that subarray is left unchanged
&apos;&apos;&apos; Examples:
&apos;&apos;&apos; SF_Array.Flatten(Array(1, 2, Array(3, 4, 5)) returns (1, 2, 3, 4, 5)
Dim vFlatten As Variant &apos; Return value
Dim lMin As Long &apos; LBound of input array
Dim lMax As Long &apos; UBound of input array
Dim lIndex As Long &apos; Index in output array
Dim vItem As Variant &apos; Array single item
Dim iDims As Integer &apos; Array number of dimensions
Dim lEmpty As Long &apos; Number of empty subarrays
Dim i As Long
Dim j As Long
Const cstThisSub = &quot;Array.Flatten&quot;
Const cstSubArgs = &quot;Array_1D&quot;
If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
vFlatten = Array()
Check:
If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
End If
Try:
If UBound(Array_1D) &gt;= LBound(Array_1D) Then
lMin = LBound(Array_1D) : lMax = UBound(Array_1D)
ReDim vFlatten(lMin To lMax) &apos; Initial minimal sizing
lEmpty = 0
lIndex = lMin - 1
For i = lMin To lMax
vItem = Array_1D(i)
If IsArray(vItem) Then
iDims = SF_Array.CountDims(vItem)
Select Case iDims
Case 0 &apos; Empty arrays are ignored
lEmpty = lEmpty + 1
Case 1 &apos; Only 1D subarrays are flattened
ReDim Preserve vFlatten(lMin To UBound(vFlatten) + UBound(vItem) - LBound(vItem))
For j = LBound(vItem) To UBound(vItem)
lIndex = lIndex + 1
vFlatten(lIndex) = vItem(j)
Next j
Case &gt; 1 &apos; Other arrays are left unchanged
lIndex = lIndex + 1
vFlatten(lIndex) = vItem
End Select
Else
lIndex = lIndex + 1
vFlatten(lIndex) = vItem
End If
Next i
End If
&apos; Reduce size of output if Array_1D is populated with some empty arrays
If lEmpty &gt; 0 Then
If lIndex - lEmpty &lt; lMin Then
vFlatten = Array()
Else
ReDim Preserve vFlatten(lMin To UBound(vFlatten) - lEmpty)
End If
End If
Finally:
Flatten = vFlatten()
SF_Utils._ExitFunction(cstThisSub)
Exit Function
Catch:
GoTo Finally
End Function &apos; ScriptForge.SF_Array.Flatten
REM -----------------------------------------------------------------------------
Public Function GetProperty(Optional ByVal PropertyName As Variant) As Variant
&apos;&apos;&apos; Return the actual value of the given property
&apos;&apos;&apos; Args:
&apos;&apos;&apos; PropertyName: the name of the property as a string
&apos;&apos;&apos; Returns:
&apos;&apos;&apos; The actual value of the property
&apos;&apos;&apos; Exceptions
&apos;&apos;&apos; ARGUMENTERROR The property does not exist
Const cstThisSub = &quot;Array.GetProperty&quot;
Const cstSubArgs = &quot;PropertyName&quot;
If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
GetProperty = Null
Check:
If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
End If
Try:
Select Case UCase(PropertyName)
Case Else
End Select
Finally:
SF_Utils._ExitFunction(cstThisSub)
Exit Function
Catch:
GoTo Finally
End Function &apos; ScriptForge.SF_Array.GetProperty
REM -----------------------------------------------------------------------------
Public Function ImportFromCSVFile(Optional ByRef FileName As Variant _
, Optional ByVal Delimiter As Variant _
, Optional ByVal DateFormat As Variant _
) 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 bIsoDate As Boolean &apos; When True, do not convert dates to Date variables
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:
bIsoDate = _SF_.TriggeredByPython &apos; Dates are not converted
&apos; Counts the lines present in the file to size the final array
&apos; Very beneficial for large files, better than multiple ReDims
&apos; Small overhead for small files
lFileSize = SF_FileSystem._CountTextLines(FileName, False)
If lFileSize &lt;= 0 Then GoTo Finally
&apos; Reread file line by line
Set vCsv = SF_FileSystem.OpenTextFile(FileName, IOMode := SF_FileSystem.ForReading)
If IsNull(vCsv) Then GoTo Finally &apos; Open error
lRow = -1
With vCsv
Do While Not .AtEndOfStream
sLine = .ReadLine()
If Len(sLine) &gt; 0 Then &apos; Ignore empty lines
If InStr(sLine, &quot;&quot;&quot;&quot;) &gt; 0 Then vLine = SF_String.SplitNotQuoted(sLine, Delimiter) Else vLine = Split(sLine, Delimiter) &apos; Simple split when relevant
lRow = lRow + 1
If lRow = 0 Then &apos; Initial sizing of output array
lCol = UBound(vLine)
ReDim vArray(0 To lFileSize - 1, 0 To lCol)
ElseIf UBound(vLine) &lt;&gt; lCol Then
GoTo CatchCSVFormat
End If
&apos; Check type and copy all items of the line
For i = 0 To lCol
If Left(vLine(i), 1) = &quot;&quot;&quot;&quot; Then sItem = SF_String.Unquote(vLine(i)) Else sItem = vLine(i) &apos; Unquote only when useful
&apos; Interpret the individual line item
Select Case True
Case IsNumeric(sItem)
If InStr(sItem, &quot;.&quot;) + InStr(1, sItem, &quot;e&quot;, 1) &gt; 0 Then vItem = Val(sItem) Else vItem = CLng(sItem)
Case DateFormat &lt;&gt; &quot;&quot; And Len(sItem) = Len(DateFormat)
If SF_String.IsADate(sItem, DateFormat) Then
iPosition = InStr(DateFormat, &quot;YYYY&quot;) : iYear = CInt(Mid(sItem, iPosition, 4))
iPosition = InStr(DateFormat, &quot;MM&quot;) : iMonth = CInt(Mid(sItem, iPosition, 2))
iPosition = InStr(DateFormat, &quot;DD&quot;) : iDay = CInt(Mid(sItem, iPosition, 2))
vItem = DateSerial(iYear, iMonth, iDay)
If bIsoDate Then vItem = SF_Utils._CDateToIso(vItem) &apos; Called from Python
Else
vItem = sItem
End If
Case Else : vItem = sItem
End Select
vArray(lRow, i) = vItem
Next i
End If
&apos; Provision to avoid very large arrays and their sometimes erratic behaviour
If (lRow + 2) * (lCol + 1) &gt; cstItemsLimit Then
ReDim Preserve vArray(0 To lRow, 0 To lCol)
GoTo CatchOverflow
End If
Loop
End With
Finally:
If Not IsNull(vCsv) Then
vCsv.CloseFile()
Set vCsv = vCsv.Dispose()
End If
ImportFromCSVFile = vArray
SF_Utils._ExitFunction(cstThisSub)
Exit Function
Catch:
GoTo Finally
CatchCSVFormat:
SF_Exception.RaiseFatal(CSVPARSINGERROR, FileName, vCsv.Line, sLine)
GoTo Finally
CatchOverflow:
&apos;TODO SF_Exception.RaiseWarning(SF_Exception.CSVOVERFLOWWARNING, cstThisSub)
&apos;MsgBox &quot;TOO MUCH LINES !!&quot;
GoTo Finally
End Function &apos; ScriptForge.SF_Array.ImportFromCSVFile
REM -----------------------------------------------------------------------------
Public Function IndexOf(Optional ByRef Array_1D As Variant _
, Optional ByVal ToFind As Variant _
, Optional ByVal CaseSensitive As Variant _
, Optional ByVal SortOrder As Variant _
) As Long
&apos;&apos;&apos; Finds in a 1D array the ToFind number, string or date
&apos;&apos;&apos; ToFind must exist within the array.
&apos;&apos;&apos; The comparison between strings can be done case-sensitively or not
&apos;&apos;&apos; If the array is sorted then
&apos;&apos;&apos; the array must be filled homogeneously, i.e. all items must be of the same type
&apos;&apos;&apos; Empty and Null items are forbidden
&apos;&apos;&apos; a binary search is done
&apos;&apos;&apos; Otherwise the array is scanned from top. Null or Empty items are simply ignored
&apos;&apos;&apos; Args:
&apos;&apos;&apos; Array_1D: the array to scan
&apos;&apos;&apos; ToFind: a number, a date or a string to find
&apos;&apos;&apos; CaseSensitive: Only for string comparisons, default = False
&apos;&apos;&apos; SortOrder: &quot;ASC&quot;, &quot;DESC&quot; or &quot;&quot; (= not sorted, default)
&apos;&apos;&apos; Return: the index of the found item, LBound - 1 if not found
&apos;&apos;&apos; Result is unpredictable when array is announced sorted and is in reality not
&apos;&apos;&apos; Examples:
&apos;&apos;&apos; SF_Array.IndexOf(Array(&quot;A&quot;,&quot;B&quot;,&quot;c&quot;,&quot;D&quot;), &quot;C&quot;, SortOrder := &quot;ASC&quot;) returns 2
&apos;&apos;&apos; SF_Array.IndexOf(Array(&quot;A&quot;,&quot;B&quot;,&quot;c&quot;,&quot;D&quot;), &quot;C&quot;, CaseSensitive := True) returns -1
Dim vFindItem As Variant &apos; 2-items array (0) = True if found, (1) = Index where found
Dim lIndex As Long &apos; Return value
Dim iToFindType As Integer &apos; VarType of ToFind
Const cstThisSub = &quot;Array.IndexOf&quot;
Const cstSubArgs = &quot;Array_1D, ToFind, [CaseSensitive=False], [SortOrder=&quot;&quot;&quot;&quot;|&quot;&quot;ASC&quot;&quot;|&quot;&quot;DESC&quot;&quot;]&quot;
If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
lIndex = -1
Check:
If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False
If IsMissing(SortOrder) Or IsEmpty(SortOrder) Then SortOrder = &quot;&quot;
If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
If Not SF_Utils._Validate(SortOrder, &quot;SortOrder&quot;, V_STRING, Array(&quot;ASC&quot;, &quot;DESC&quot;, &quot;&quot;)) Then GoTo Finally
If Not SF_Utils._Validate(ToFind, &quot;ToFind&quot;, Array(V_STRING, V_DATE, V_NUMERIC)) Then GoTo Finally
iToFindType = SF_Utils._VarTypeExt(ToFind)
If SortOrder &lt;&gt; &quot;&quot; Then
If Not SF_Utils._ValidateArray(Array_1D, &quot;Array&quot;, 1, iToFindType) Then GoTo Finally
Else
If Not SF_Utils._ValidateArray(Array_1D, &quot;Array&quot;, 1) Then GoTo Finally
End If
If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
End If
Try:
vFindItem = SF_Array._FindItem(Array_1D, ToFind, CaseSensitive, UCase(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, UCase(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 + 1)) + 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)
If lMax &lt; lMin Then GoTo Finally &apos; Do nothing if the input array is empty
vIndexes() = SF_Array._HeapSort(Array_1D, ( UCase(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, ( UCase(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, ( UCase(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 -----------------------------------------------------------------------------
Private Function _ConvertToCellValue(ByVal pvItem As Variant _
, ByVal pbIsCell As Boolean _
) As Variant
&apos;&apos;&apos; Convert the argument to a valid Calc cell content, i.e. a string or a double.
&apos;&apos;&apos; When the argument is not immediately convertible, either
&apos;&apos;&apos; - the cell range is returned unchanged if the argument is a UNO com.sun.star.table.XCellRange object
&apos;&apos;&apos; - the date is converted to a double when the argument is a UNO com.sun.star.util.DateTime object
&apos;&apos;&apos; - the zero-length string is returned (pbIsCell = True)
&apos;&apos;&apos; - the argument is returned unchanged (pbIsCell = False)
Dim vCell As Variant &apos; Return value
Dim oObjectDescriptor As Object &apos; Object descriptor (see SF_Utils)
Try:
&apos; Conversion takes place only when pbIsCell = True
If pbIsCell Then
Select Case SF_Utils._VarTypeExt(pvItem)
Case V_STRING : vCell = pvItem
Case V_DATE : vCell = CDbl(pvItem)
Case V_NUMERIC : vCell = CDbl(pvItem)
Case V_BOOLEAN : vCell = CDbl(Iif(pvItem, 1, 0))
Case V_OBJECT
Set oObjectDescriptor = SF_Utils._VarTypeObj(pvItem)
Select Case oObjectDescriptor.sObjectType
Case &quot;ScCellRangeObj&quot; : vCell = pvItem
Case &quot;com.sun.star.util.DateTime&quot; : vCell = CDbl(CDateFromUnoDateTime(pvItem)) &apos; Python date
Case Else : vCell = &quot;&quot;
End Select
Case Else : vCell = &quot;&quot;
End Select
Else &apos; Return the input item unchanged
vCell = pvItem
End If
Finally:
_ConvertToCellValue = vCell
Exit Function
End Function &apos; ScriptForge.SF_Array._ConvertToCellValue
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>