calc: on editing invalidation of view with different zoom is wrong
[LibreOffice.git] / wizards / source / scriptforge / SF_Array.xba
blob49bdab14770a189da94d1f819c42f5bee8cd8766
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd">
3 <script:module xmlns:script="http://openoffice.org/2000/script" script:name="SF_Array" script:language="StarBasic" script:moduleType="normal">REM =======================================================================================================================
4 REM === The ScriptForge library and its associated libraries are part of the LibreOffice project. ===
5 REM === Full documentation is available on https://help.libreoffice.org/ ===
6 REM =======================================================================================================================
8 Option Compatible
9 Option Explicit
11 &apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;
12 &apos;&apos;&apos; SF_Array
13 &apos;&apos;&apos; ========
14 &apos;&apos;&apos; Singleton class implementing the &quot;ScriptForge.Array&quot; service
15 &apos;&apos;&apos; Implemented as a usual Basic module
16 &apos;&apos;&apos; Only 1D or 2D arrays are considered. Arrays with more than 2 dimensions are rejected
17 &apos;&apos;&apos; With the noticeable exception of the CountDims method (&gt;2 dims allowed)
18 &apos;&apos;&apos; The first argument of almost every method is the array to consider
19 &apos;&apos;&apos; It is always passed by reference and left unchanged
20 &apos;&apos;&apos;
21 &apos;&apos;&apos; Detailed user documentation:
22 &apos;&apos;&apos; https://help.libreoffice.org/latest/en-US/text/sbasic/shared/03/sf_array.html?DbPAR=BASIC
23 &apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;
25 REM ================================================================== EXCEPTIONS
27 Const ARRAYSEQUENCEERROR = &quot;ARRAYSEQUENCEERROR&quot; &apos; Incoherent arguments
28 Const ARRAYINSERTERROR = &quot;ARRAYINSERTERROR&quot; &apos; Matrix and vector have incompatible sizes
29 Const ARRAYINDEX1ERROR = &quot;ARRAYINDEX1ERROR&quot; &apos; Given index does not fit in array bounds
30 Const ARRAYINDEX2ERROR = &quot;ARRAYINDEX2ERROR&quot; &apos; Given indexes do not fit in array bounds
31 Const CSVPARSINGERROR = &quot;CSVPARSINGERROR&quot; &apos; Parsing error detected while parsing a csv file
32 Const CSVOVERFLOWWARNING = &quot;CSVOVERFLOWWARNING&quot; &apos; Array becoming too big, import process of csv file is interrupted
34 REM ============================================================ MODULE CONSTANTS
36 Const MAXREPR = 50 &apos; Maximum length to represent an array in the console
38 REM ===================================================== CONSTRUCTOR/DESTRUCTOR
40 REM -----------------------------------------------------------------------------
41 Public Function Dispose() As Variant
42 Set Dispose = Nothing
43 End Function &apos; ScriptForge.SF_Array Explicit destructor
45 REM ================================================================== PROPERTIES
47 REM -----------------------------------------------------------------------------
48 Property Get ObjectType As String
49 &apos;&apos;&apos; Only to enable object representation
50 ObjectType = &quot;SF_Array&quot;
51 End Property &apos; ScriptForge.SF_Array.ObjectType
53 REM -----------------------------------------------------------------------------
54 Property Get ServiceName As String
55 &apos;&apos;&apos; Internal use
56 ServiceName = &quot;ScriptForge.Array&quot;
57 End Property &apos; ScriptForge.SF_Array.ServiceName
59 REM ============================================================== PUBLIC METHODS
61 REM -----------------------------------------------------------------------------
62 Public Function Append(Optional ByRef Array_1D As Variant _
63 , ParamArray pvArgs() As Variant _
64 ) As Variant
65 &apos;&apos;&apos; Append at the end of the input array the items listed as arguments
66 &apos;&apos;&apos; Arguments are appended blindly
67 &apos;&apos;&apos; each of them might be a scalar of any type or a subarray
68 &apos;&apos;&apos; Args
69 &apos;&apos;&apos; Array_1D: the pre-existing array, may be empty
70 &apos;&apos;&apos; pvArgs: a list of items to append to Array_1D
71 &apos;&apos;&apos; Return:
72 &apos;&apos;&apos; the new extended array. Its LBound is identical to that of Array_1D
73 &apos;&apos;&apos; Examples:
74 &apos;&apos;&apos; SF_Array.Append(Array(1, 2, 3), 4, 5) returns (1, 2, 3, 4, 5)
76 Dim vAppend As Variant &apos; Return value
77 Dim lNbArgs As Long &apos; Number of elements to append
78 Dim lMax As Long &apos; UBound of input array
79 Dim i As Long
80 Const cstThisSub = &quot;Array.Append&quot;
81 Const cstSubArgs = &quot;Array_1D, arg0[, arg1] ...&quot;
83 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
84 vAppend = Array()
86 Check:
87 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
88 If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
89 End If
91 Try:
92 lMax = UBound(Array_1D)
93 lNbArgs = UBound(pvArgs) + 1 &apos; pvArgs is always zero-based
94 If lMax &lt; LBound(Array_1D) Then &apos; Initial array is empty
95 If lNbArgs &gt; 0 Then
96 ReDim vAppend(0 To lNbArgs - 1)
97 End If
98 Else
99 vAppend() = Array_1D()
100 If lNbArgs &gt; 0 Then
101 ReDim Preserve vAppend(LBound(Array_1D) To lMax + lNbArgs)
102 End If
103 End If
104 For i = 1 To lNbArgs
105 vAppend(lMax + i) = pvArgs(i - 1)
106 Next i
108 Finally:
109 Append = vAppend()
110 SF_Utils._ExitFunction(cstThisSub)
111 Exit Function
112 Catch:
113 GoTo Finally
114 End Function &apos; ScriptForge.SF_Array.Append
116 REM -----------------------------------------------------------------------------
117 Public Function AppendColumn(Optional ByRef Array_2D As Variant _
118 , Optional ByRef Column As Variant _
119 ) As Variant
120 &apos;&apos;&apos; AppendColumn appends to the right side of a 2D array a new Column
121 &apos;&apos;&apos; Args
122 &apos;&apos;&apos; Array_2D: the pre-existing array, may be empty
123 &apos;&apos;&apos; If the array has 1 dimension, it is considered as the 1st Column of the resulting 2D array
124 &apos;&apos;&apos; Column: a 1D array with as many items as there are rows in Array_2D
125 &apos;&apos;&apos; Returns:
126 &apos;&apos;&apos; the new extended array. Its LBounds are identical to that of Array_2D
127 &apos;&apos;&apos; Exceptions:
128 &apos;&apos;&apos; ARRAYINSERTERROR
129 &apos;&apos;&apos; Examples:
130 &apos;&apos;&apos; SF_Array.AppendColumn(Array(1, 2, 3), Array(4, 5, 6)) returns ((1, 4), (2, 5), (3, 6))
131 &apos;&apos;&apos; x = SF_Array.AppendColumn(Array(), Array(1, 2, 3)) =&gt; ∀ i ∈ {0 ≤ i ≤ 2} : x(0, i) ≡ i
133 Dim vAppendColumn As Variant &apos; Return value
134 Dim iDims As Integer &apos; Dimensions of Array_2D
135 Dim lMin1 As Long &apos; LBound1 of input array
136 Dim lMax1 As Long &apos; UBound1 of input array
137 Dim lMin2 As Long &apos; LBound2 of input array
138 Dim lMax2 As Long &apos; UBound2 of input array
139 Dim lMin As Long &apos; LBound of Column array
140 Dim lMax As Long &apos; UBound of Column array
141 Dim i As Long
142 Dim j As Long
143 Const cstThisSub = &quot;Array.AppendColumn&quot;
144 Const cstSubArgs = &quot;Array_2D, Column&quot;
146 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
147 vAppendColumn = Array()
149 Check:
150 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
151 If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;) Then GoTo Finally &apos;Initial check: not missing and array
152 If Not SF_Utils._ValidateArray(Column, &quot;Column&quot;, 1) Then GoTo Finally
153 End If
154 iDims = SF_Array.CountDims(Array_2D)
155 If iDims &gt; 2 Then
156 If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally &apos;2nd check to manage error
157 End If
159 Try:
160 lMin = LBound(Column)
161 lMax = UBound(Column)
163 &apos; Compute future dimensions of output array
164 Select Case iDims
165 Case 0 : lMin1 = lMin : lMax1 = lMax
166 lMin2 = 0 : lMax2 = -1
167 Case 1 : lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
168 lMin2 = 0 : lMax2 = 0
169 Case 2 : lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
170 lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
171 End Select
172 If iDims &gt; 0 And lMax - lMin &lt;&gt; lMax1 - lMin1 Then GoTo CatchColumn
173 ReDim vAppendColumn(lMin1 To lMax1, lMin2 To lMax2 + 1)
175 &apos; Copy input array to output array
176 For i = lMin1 To lMax1
177 For j = lMin2 To lMax2
178 If iDims = 2 Then vAppendColumn(i, j) = Array_2D(i, j) Else vAppendColumn(i, j) = Array_2D(i)
179 Next j
180 Next i
181 &apos; Copy new Column
182 For i = lMin1 To lMax1
183 vAppendColumn(i, lMax2 + 1) = Column(i)
184 Next i
186 Finally:
187 AppendColumn = vAppendColumn()
188 SF_Utils._ExitFunction(cstThisSub)
189 Exit Function
190 Catch:
191 GoTo Finally
192 CatchColumn:
193 SF_Exception.RaiseFatal(ARRAYINSERTERROR, &quot;Column&quot;, SF_Array._Repr(Array_2D), SF_Utils._Repr(Column, MAXREPR))
194 GoTo Finally
195 End Function &apos; ScriptForge.SF_Array.AppendColumn
197 REM -----------------------------------------------------------------------------
198 Public Function AppendRow(Optional ByRef Array_2D As Variant _
199 , Optional ByRef Row As Variant _
200 ) As Variant
201 &apos;&apos;&apos; AppendRow appends below a 2D array a new row
202 &apos;&apos;&apos; Args
203 &apos;&apos;&apos; Array_2D: the pre-existing array, may be empty
204 &apos;&apos;&apos; If the array has 1 dimension, it is considered as the 1st row of the resulting 2D array
205 &apos;&apos;&apos; Row: a 1D array with as many items as there are columns in Array_2D
206 &apos;&apos;&apos; Returns:
207 &apos;&apos;&apos; the new extended array. Its LBounds are identical to that of Array_2D
208 &apos;&apos;&apos; Exceptions:
209 &apos;&apos;&apos; ARRAYINSERTERROR
210 &apos;&apos;&apos; Examples:
211 &apos;&apos;&apos; SF_Array.AppendRow(Array(1, 2, 3), Array(4, 5, 6)) returns ((1, 2, 3), (4, 5, 6))
212 &apos;&apos;&apos; x = SF_Array.AppendRow(Array(), Array(1, 2, 3)) =&gt; ∀ i ∈ {0 ≤ i ≤ 2} : x(i, 0) ≡ i
214 Dim vAppendRow As Variant &apos; Return value
215 Dim iDims As Integer &apos; Dimensions of Array_2D
216 Dim lMin1 As Long &apos; LBound1 of input array
217 Dim lMax1 As Long &apos; UBound1 of input array
218 Dim lMin2 As Long &apos; LBound2 of input array
219 Dim lMax2 As Long &apos; UBound2 of input array
220 Dim lMin As Long &apos; LBound of row array
221 Dim lMax As Long &apos; UBound of row array
222 Dim i As Long
223 Dim j As Long
224 Const cstThisSub = &quot;Array.AppendRow&quot;
225 Const cstSubArgs = &quot;Array_2D, Row&quot;
227 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
228 vAppendRow = Array()
230 Check:
231 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
232 If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;) Then GoTo Finally &apos;Initial check: not missing and array
233 If Not SF_Utils._ValidateArray(Row, &quot;Row&quot;, 1) Then GoTo Finally
234 End If
235 iDims = SF_Array.CountDims(Array_2D)
236 If iDims &gt; 2 Then
237 If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally &apos;2nd check to manage error
238 End If
240 Try:
241 lMin = LBound(Row)
242 lMax = UBound(Row)
244 &apos; Compute future dimensions of output array
245 Select Case iDims
246 Case 0 : lMin1 = 0 : lMax1 = -1
247 lMin2 = lMin : lMax2 = lMax
248 Case 1 : lMin1 = 0 : lMax1 = 0
249 lMin2 = LBound(Array_2D, 1) : lMax2 = UBound(Array_2D, 1)
250 Case 2 : lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
251 lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
252 End Select
253 If iDims &gt; 0 And lMax - lMin &lt;&gt; lMax2 - lMin2 Then GoTo CatchRow
254 ReDim vAppendRow(lMin1 To lMax1 + 1, lMin2 To lMax2)
256 &apos; Copy input array to output array
257 For i = lMin1 To lMax1
258 For j = lMin2 To lMax2
259 If iDims = 2 Then vAppendRow(i, j) = Array_2D(i, j) Else vAppendRow(i, j) = Array_2D(j)
260 Next j
261 Next i
262 &apos; Copy new row
263 For j = lMin2 To lMax2
264 vAppendRow(lMax1 + 1, j) = Row(j)
265 Next j
267 Finally:
268 AppendRow = vAppendRow()
269 SF_Utils._ExitFunction(cstThisSub)
270 Exit Function
271 Catch:
272 GoTo Finally
273 CatchRow:
274 SF_Exception.RaiseFatal(ARRAYINSERTERROR, &quot;Row&quot;, SF_Array._Repr(Array_2D), SF_Utils._Repr(Row, MAXREPR))
275 GoTo Finally
276 End Function &apos; ScriptForge.SF_Array.AppendRow
278 REM -----------------------------------------------------------------------------
279 Public Function Contains(Optional ByRef Array_1D As Variant _
280 , Optional ByVal ToFind As Variant _
281 , Optional ByVal CaseSensitive As Variant _
282 , Optional ByVal SortOrder As Variant _
283 ) As Boolean
284 &apos;&apos;&apos; Check if a 1D array contains the ToFind number, string or date
285 &apos;&apos;&apos; The comparison between strings can be done case-sensitive or not
286 &apos;&apos;&apos; If the array is sorted then
287 &apos;&apos;&apos; the array must be filled homogeneously, i.e. all items must be of the same type
288 &apos;&apos;&apos; Empty and Null items are forbidden
289 &apos;&apos;&apos; a binary search is done
290 &apos;&apos;&apos; Otherwise the array is scanned from top. Null or Empty items are simply ignored
291 &apos;&apos;&apos; Args:
292 &apos;&apos;&apos; Array_1D: the array to scan
293 &apos;&apos;&apos; ToFind: a number, a date or a string to find
294 &apos;&apos;&apos; CaseSensitive: Only for string comparisons, default = False
295 &apos;&apos;&apos; SortOrder: &quot;ASC&quot;, &quot;DESC&quot; or &quot;&quot; (= not sorted, default)
296 &apos;&apos;&apos; Return: True when found
297 &apos;&apos;&apos; Result is unpredictable when array is announced sorted and is in reality not
298 &apos;&apos;&apos; Examples:
299 &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
300 &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
302 Dim bContains As Boolean &apos; Return value
303 Dim iToFindType As Integer &apos; VarType of ToFind
304 Const cstThisSub = &quot;Array.Contains&quot;
305 Const cstSubArgs = &quot;Array_1D, ToFind, [CaseSensitive=False], [SortOrder=&quot;&quot;&quot;&quot;|&quot;&quot;ASC&quot;&quot;|&quot;&quot;DESC&quot;&quot;]&quot;
307 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
309 bContains = False
311 Check:
312 If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False
313 If IsMissing(SortOrder) Or IsEmpty(SortOrder) Then SortOrder = &quot;&quot;
314 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
315 If Not SF_Utils._Validate(SortOrder, &quot;SortOrder&quot;, V_STRING, Array(&quot;ASC&quot;, &quot;DESC&quot;, &quot;&quot;)) Then GoTo Finally
316 If Not SF_Utils._Validate(ToFind, &quot;ToFind&quot;, Array(V_STRING, V_DATE, V_NUMERIC)) Then GoTo Finally
317 iToFindType = SF_Utils._VarTypeExt(ToFind)
318 If SortOrder &lt;&gt; &quot;&quot; Then
319 If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1, iToFindType) Then GoTo Finally
320 Else
321 If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
322 End If
323 If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
324 End If
326 Try:
327 bContains = SF_Array._FindItem(Array_1D, ToFind, CaseSensitive, SortOrder)(0)
329 Finally:
330 Contains = bContains
331 SF_Utils._ExitFunction(cstThisSub)
332 Exit Function
333 Catch:
334 GoTo Finally
335 End Function &apos; ScriptForge.SF_Array.Contains
337 REM -----------------------------------------------------------------------------
338 Public Function ConvertToDictionary(Optional ByRef Array_2D As Variant) As Variant
339 &apos;&apos;&apos; Store the content of a 2-columns array into a dictionary
340 &apos;&apos;&apos; Key found in 1st column, Item found in 2nd
341 &apos;&apos;&apos; Args:
342 &apos;&apos;&apos; Array_2D: 1st column must contain exclusively non zero-length strings
343 &apos;&apos;&apos; 1st column may not be sorted
344 &apos;&apos;&apos; Returns:
345 &apos;&apos;&apos; a ScriptForge dictionary object
346 &apos;&apos;&apos; Examples:
347 &apos;&apos;&apos;
349 Dim oDict As Variant &apos; Return value
350 Dim i As Long
351 Const cstThisSub = &quot;Dictionary.ConvertToArray&quot;
352 Const cstSubArgs = &quot;Array_2D&quot;
354 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
356 Check:
357 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
358 If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2, V_STRING, True) Then GoTo Finally
359 End If
361 Try:
362 Set oDict = SF_Services.CreateScriptService(&quot;Dictionary&quot;)
363 For i = LBound(Array_2D, 1) To UBound(Array_2D, 1)
364 oDict.Add(Array_2D(i, 0), Array_2D(i, 1))
365 Next i
367 ConvertToDictionary = oDict
369 Finally:
370 SF_Utils._ExitFunction(cstThisSub)
371 Exit Function
372 Catch:
373 GoTo Finally
374 End Function &apos; ScriptForge.SF_Array.ConvertToDictionary
376 REM -----------------------------------------------------------------------------
377 Public Function Copy(Optional ByRef Array_ND As Variant) As Variant
378 &apos;&apos;&apos; Duplicate a 1D or 2D array
379 &apos;&apos;&apos; A usual assignment copies an array by reference, i.e. shares the same memory location
380 &apos;&apos;&apos; Dim a, b
381 &apos;&apos;&apos; a = Array(1, 2, 3)
382 &apos;&apos;&apos; b = a
383 &apos;&apos;&apos; a(2) = 30
384 &apos;&apos;&apos; MsgBox b(2) &apos; 30
385 &apos;&apos;&apos; Args
386 &apos;&apos;&apos; Array_ND: the array to copy, may be empty
387 &apos;&apos;&apos; Return:
388 &apos;&apos;&apos; the copied array. Subarrays however still remain assigned by reference
389 &apos;&apos;&apos; Examples:
390 &apos;&apos;&apos; SF_Array.Copy(Array(1, 2, 3)) returns (1, 2, 3)
392 Dim vCopy As Variant &apos; Return value
393 Dim iDims As Integer &apos; Number of dimensions of the input array
394 Const cstThisSub = &quot;Array.Copy&quot;
395 Const cstSubArgs = &quot;Array_ND&quot;
397 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
398 vCopy = Array()
400 Check:
401 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
402 If Not SF_Utils._ValidateArray(Array_ND, &quot;Array_ND&quot;) Then GoTo Finally
403 iDims = SF_Array.CountDims(Array_ND)
404 If iDims &gt; 2 Then
405 If Not SF_Utils._ValidateArray(Array_ND, &quot;Array_ND&quot;, 2) Then GoTo Finally
406 End If
407 End If
409 Try:
410 Select Case iDims
411 Case 0
412 Case 1
413 vCopy = Array_ND
414 ReDim Preserve vCopy(LBound(Array_ND) To UBound(Array_ND))
415 Case 2
416 vCopy = Array_ND
417 ReDim Preserve vCopy(LBound(Array_ND, 1) To UBound(Array_ND, 1), LBound(Array_ND, 2) To UBound(Array_ND, 2))
418 End Select
420 Finally:
421 Copy = vCopy()
422 SF_Utils._ExitFunction(cstThisSub)
423 Exit Function
424 Catch:
425 GoTo Finally
426 End Function &apos; ScriptForge.SF_Array.Copy
428 REM -----------------------------------------------------------------------------
429 Public Function CountDims(Optional ByRef Array_ND As Variant) As Integer
430 &apos;&apos;&apos; Count the number of dimensions of an array - may be &gt; 2
431 &apos;&apos;&apos; Args:
432 &apos;&apos;&apos; Array_ND: the array to be examined
433 &apos;&apos;&apos; Return: the number of dimensions: -1 = not array, 0 = uninitialized array, else &gt;= 1
434 &apos;&apos;&apos; Examples:
435 &apos;&apos;&apos; Dim a(1 To 10, -3 To 12, 5)
436 &apos;&apos;&apos; CountDims(a) returns 3
438 Dim iDims As Integer &apos; Return value
439 Dim lMax As Long &apos; Storage for UBound of each dimension
440 Const cstThisSub = &quot;Array.CountDims&quot;
441 Const cstSubArgs = &quot;Array_ND&quot;
443 Check:
444 iDims = -1
445 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
446 If IsMissing(Array_ND) Then &apos; To have missing exception processed
447 If Not SF_Utils._ValidateArray(Array_ND, &quot;Array_ND&quot;) Then GoTo Finally
448 End If
449 End If
451 Try:
452 On Local Error Goto ErrHandler
453 &apos; Loop, increasing the dimension index (i) until an error occurs.
454 &apos; An error will occur when i exceeds the number of dimensions in the array. Returns i - 1.
455 iDims = 0
456 If Not IsArray(Array_ND) Then
457 Else
459 iDims = iDims + 1
460 lMax = UBound(Array_ND, iDims)
461 Loop Until (Err &lt;&gt; 0)
462 End If
464 ErrHandler:
465 On Local Error GoTo 0
467 iDims = iDims - 1
468 If iDims = 1 Then
469 If LBound(Array_ND, 1) &gt; UBound(Array_ND, 1) Then iDims = 0
470 End If
472 Finally:
473 CountDims = iDims
474 SF_Utils._ExitFunction(cstThisSub)
475 Exit Function
476 End Function &apos; ScriptForge.SF_Array.CountDims
478 REM -----------------------------------------------------------------------------
479 Public Function Difference(Optional ByRef Array1_1D As Variant _
480 , Optional ByRef Array2_1D As Variant _
481 , Optional ByVal CaseSensitive As Variant _
482 ) As Variant
483 &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
484 &apos;&apos;&apos; both input arrays must be filled homogeneously, i.e. all items must be of the same type
485 &apos;&apos;&apos; Empty and Null items are forbidden
486 &apos;&apos;&apos; The comparison between strings is case sensitive or not
487 &apos;&apos;&apos; Args:
488 &apos;&apos;&apos; Array1_1D: a 1st input array
489 &apos;&apos;&apos; Array2_1D: a 2nd input array
490 &apos;&apos;&apos; CaseSensitive: default = False
491 &apos;&apos;&apos; Returns: a zero-based array containing unique items from the 1st array not present in the 2nd
492 &apos;&apos;&apos; The output array is sorted in ascending order
493 &apos;&apos;&apos; Examples:
494 &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;)
496 Dim vDifference() As Variant &apos; Return value
497 Dim vSorted() As Variant &apos; The 2nd input array after sort
498 Dim iType As Integer &apos; VarType of elements in input arrays
499 Dim lMin1 As Long &apos; LBound of 1st input array
500 Dim lMax1 As Long &apos; UBound of 1st input array
501 Dim lMin2 As Long &apos; LBound of 2nd input array
502 Dim lMax2 As Long &apos; UBound of 2nd input array
503 Dim lSize As Long &apos; Number of Difference items
504 Dim vItem As Variant &apos; One single item in the array
505 Dim i As Long
506 Const cstThisSub = &quot;Array.Difference&quot;
507 Const cstSubArgs = &quot;Array1_1D, Array2_1D, [CaseSensitive=False]&quot;
509 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
510 vDifference = Array()
512 Check:
513 If IsMissing(CaseSensitive) Then CaseSensitive = False
514 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
515 If Not SF_Utils._ValidateArray(Array1_1D, &quot;Array1_1D&quot;, 1, 0, True) Then GoTo Finally
516 iType = SF_Utils._VarTypeExt(Array1_1D(LBound(Array1_1D)))
517 If Not SF_Utils._ValidateArray(Array2_1D, &quot;Array2_1D&quot;, 1, iType, True) Then GoTo Finally
518 If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
519 End If
521 Try:
522 lMin1 = LBound(Array1_1D) : lMax1 = UBound(Array1_1D)
523 lMin2 = LBound(Array2_1D) : lMax2 = UBound(Array2_1D)
525 &apos; If 1st array is empty, do nothing
526 If lMax1 &lt; lMin1 Then
527 ElseIf lMax2 &lt; lMin2 Then &apos; only 2nd array is empty
528 vUnion = SF_Array.Unique(Array1_1D, CaseSensitive)
529 Else
531 &apos; First sort the 2nd array
532 vSorted = SF_Array.Sort(Array2_1D, &quot;ASC&quot;, CaseSensitive)
534 &apos; Resize the output array to the size of the 1st array
535 ReDim vDifference(0 To (lMax1 - lMin1))
536 lSize = -1
538 &apos; Fill vDifference one by one with items present only in 1st set
539 For i = lMin1 To lMax1
540 vItem = Array1_1D(i)
541 If Not SF_Array.Contains(vSorted, vItem, CaseSensitive, &quot;ASC&quot;) Then
542 lSize = lSize + 1
543 vDifference(lSize) = vItem
544 End If
545 Next i
547 &apos; Remove unfilled entries and duplicates
548 If lSize &gt;= 0 Then
549 ReDim Preserve vDifference(0 To lSize)
550 vDifference() = SF_Array.Unique(vDifference, CaseSensitive)
551 Else
552 vDifference = Array()
553 End If
554 End If
556 Finally:
557 Difference = vDifference()
558 SF_Utils._ExitFunction(cstThisSub)
559 Exit Function
560 Catch:
561 GoTo Finally
562 End Function &apos; ScriptForge.SF_Array.Difference
564 REM -----------------------------------------------------------------------------
565 Public Function ExportToTextFile(Optional ByRef Array_1D As Variant _
566 , Optional ByVal FileName As Variant _
567 , Optional ByVal Encoding As Variant _
568 ) As Boolean
569 &apos;&apos;&apos; Write all items of the array sequentially to a text file
570 &apos;&apos;&apos; If the file exists already, it will be overwritten without warning
571 &apos;&apos;&apos; Args:
572 &apos;&apos;&apos; Array_1D: the array to export
573 &apos;&apos;&apos; FileName: the full name (path + file) in SF_FileSystem.FileNaming notation
574 &apos;&apos;&apos; Encoding: The character set that should be used
575 &apos;&apos;&apos; Use one of the Names listed in https://www.iana.org/assignments/character-sets/character-sets.xhtml
576 &apos;&apos;&apos; Note that LibreOffice does not implement all existing sets
577 &apos;&apos;&apos; Default = UTF-8
578 &apos;&apos;&apos; Returns:
579 &apos;&apos;&apos; True if successful
580 &apos;&apos;&apos; Examples:
581 &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;)
583 Dim bExport As Boolean &apos; Return value
584 Dim oFile As Object &apos; Output file handler
585 Dim sLine As String &apos; A single line
586 Const cstThisSub = &quot;Array.ExportToTextFile&quot;
587 Const cstSubArgs = &quot;Array_1D, FileName&quot;
589 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
590 bExport = False
592 Check:
593 If IsMissing(Encoding) Or IsEmpty(Encoding) Then Encoding = &quot;UTF-8&quot;
594 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
595 If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1, V_STRING, True) Then GoTo Finally
596 If Not SF_Utils._ValidateFile(FileName, &quot;FileName&quot;) Then GoTo Finally
597 If Not SF_Utils._Validate(Encoding, &quot;Encoding&quot;, V_STRING) Then GoTo Finally
598 End If
600 Try:
601 Set oFile = SF_FileSystem.CreateTextFile(FileName, Overwrite := True, Encoding := Encoding)
602 If Not IsNull(oFile) Then
603 With oFile
604 For Each sLine In Array_1D
605 .WriteLine(sLine)
606 Next sLine
607 .CloseFile()
608 End With
609 End If
611 bExport = True
613 Finally:
614 If Not IsNull(oFile) Then Set oFile = oFile.Dispose()
615 ExportToTextFile = bExport
616 SF_Utils._ExitFunction(cstThisSub)
617 Exit Function
618 Catch:
619 GoTo Finally
620 End Function &apos; ScriptForge.SF_Array.ExportToTextFile
622 REM -----------------------------------------------------------------------------
623 Public Function ExtractColumn(Optional ByRef Array_2D As Variant _
624 , Optional ByVal ColumnIndex As Variant _
625 ) As Variant
626 &apos;&apos;&apos; ExtractColumn extracts from a 2D array a specific column
627 &apos;&apos;&apos; Args
628 &apos;&apos;&apos; Array_2D: the array from which to extract
629 &apos;&apos;&apos; ColumnIndex: the column to extract - must be in the interval [LBound, UBound]
630 &apos;&apos;&apos; Returns:
631 &apos;&apos;&apos; the extracted column. Its LBound and UBound are identical to that of the 1st dimension of Array_2D
632 &apos;&apos;&apos; Exceptions:
633 &apos;&apos;&apos; ARRAYINDEX1ERROR
634 &apos;&apos;&apos; Examples:
635 &apos;&apos;&apos; |1, 2, 3|
636 &apos;&apos;&apos; SF_Array.ExtractColumn( |4, 5, 6|, 2) returns (3, 6, 9)
637 &apos;&apos;&apos; |7, 8, 9|
639 Dim vExtractColumn As Variant &apos; Return value
640 Dim lMin1 As Long &apos; LBound1 of input array
641 Dim lMax1 As Long &apos; UBound1 of input array
642 Dim lMin2 As Long &apos; LBound1 of input array
643 Dim lMax2 As Long &apos; UBound1 of input array
644 Dim i As Long
645 Const cstThisSub = &quot;Array.ExtractColumn&quot;
646 Const cstSubArgs = &quot;Array_2D, ColumnIndex&quot;
648 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
649 vExtractColumn = Array()
651 Check:
652 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
653 If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally
654 If Not SF_Utils._Validate(ColumnIndex, &quot;ColumnIndex&quot;, V_NUMERIC) Then GoTo Finally
655 End If
657 Try:
658 &apos; Compute future dimensions of output array
659 lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
660 If ColumnIndex &lt; lMin2 Or ColumnIndex &gt; lMax2 Then GoTo CatchIndex
661 lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
662 ReDim vExtractColumn(lMin1 To lMax1)
664 &apos; Copy Column of input array to output array
665 For i = lMin1 To lMax1
666 vExtractColumn(i) = Array_2D(i, ColumnIndex)
667 Next i
669 Finally:
670 ExtractColumn = vExtractColumn()
671 SF_Utils._ExitFunction(cstThisSub)
672 Exit Function
673 Catch:
674 GoTo Finally
675 CatchIndex:
676 SF_Exception.RaiseFatal(ARRAYINDEX1ERROR, &quot;ColumnIndex&quot;, SF_Array._Repr(Array_2D), ColumnIndex)
677 GoTo Finally
678 End Function &apos; ScriptForge.SF_Array.ExtractColumn
680 REM -----------------------------------------------------------------------------
681 Public Function ExtractRow(Optional ByRef Array_2D As Variant _
682 , Optional ByVal RowIndex As Variant _
683 ) As Variant
684 &apos;&apos;&apos; ExtractRow extracts from a 2D array a specific row
685 &apos;&apos;&apos; Args
686 &apos;&apos;&apos; Array_2D: the array from which to extract
687 &apos;&apos;&apos; RowIndex: the row to extract - must be in the interval [LBound, UBound]
688 &apos;&apos;&apos; Returns:
689 &apos;&apos;&apos; the extracted row. Its LBound and UBound are identical to that of the 2nd dimension of Array_2D
690 &apos;&apos;&apos; Exceptions:
691 &apos;&apos;&apos; ARRAYINDEX1ERROR
692 &apos;&apos;&apos; Examples:
693 &apos;&apos;&apos; |1, 2, 3|
694 &apos;&apos;&apos; SF_Array.ExtractRow(|4, 5, 6|, 2) returns (7, 8, 9)
695 &apos;&apos;&apos; |7, 8, 9|
697 Dim vExtractRow As Variant &apos; Return value
698 Dim lMin1 As Long &apos; LBound1 of input array
699 Dim lMax1 As Long &apos; UBound1 of input array
700 Dim lMin2 As Long &apos; LBound1 of input array
701 Dim lMax2 As Long &apos; UBound1 of input array
702 Dim i As Long
703 Const cstThisSub = &quot;Array.ExtractRow&quot;
704 Const cstSubArgs = &quot;Array_2D, RowIndex&quot;
706 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
707 vExtractRow = Array()
709 Check:
710 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
711 If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally
712 If Not SF_Utils._Validate(RowIndex, &quot;RowIndex&quot;, V_NUMERIC) Then GoTo Finally
713 End If
715 Try:
716 &apos; Compute future dimensions of output array
717 lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
718 If RowIndex &lt; lMin1 Or RowIndex &gt; lMax1 Then GoTo CatchIndex
719 lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
720 ReDim vExtractRow(lMin2 To lMax2)
722 &apos; Copy row of input array to output array
723 For i = lMin2 To lMax2
724 vExtractRow(i) = Array_2D(RowIndex, i)
725 Next i
727 Finally:
728 ExtractRow = vExtractRow()
729 SF_Utils._ExitFunction(cstThisSub)
730 Exit Function
731 Catch:
732 GoTo Finally
733 CatchIndex:
734 SF_Exception.RaiseFatal(ARRAYINDEX1ERROR, &quot;RowIndex&quot;, SF_Array._Repr(Array_2D), RowIndex)
735 GoTo Finally
736 End Function &apos; ScriptForge.SF_Array.ExtractRow
738 REM -----------------------------------------------------------------------------
739 Public Function Flatten(Optional ByRef Array_1D As Variant) As Variant
740 &apos;&apos;&apos; Stack all items and all items in subarrays into one array without subarrays
741 &apos;&apos;&apos; Args
742 &apos;&apos;&apos; Array_1D: the pre-existing array, may be empty
743 &apos;&apos;&apos; Return:
744 &apos;&apos;&apos; The new flattened array. Its LBound is identical to that of Array_1D
745 &apos;&apos;&apos; If one of the subarrays has a number of dimensions &gt; 1 Then that subarray is left unchanged
746 &apos;&apos;&apos; Examples:
747 &apos;&apos;&apos; SF_Array.Flatten(Array(1, 2, Array(3, 4, 5)) returns (1, 2, 3, 4, 5)
749 Dim vFlatten As Variant &apos; Return value
750 Dim lMin As Long &apos; LBound of input array
751 Dim lMax As Long &apos; UBound of input array
752 Dim lIndex As Long &apos; Index in output array
753 Dim vItem As Variant &apos; Array single item
754 Dim iDims As Integer &apos; Array number of dimensions
755 Dim lEmpty As Long &apos; Number of empty subarrays
756 Dim i As Long
757 Dim j As Long
758 Const cstThisSub = &quot;Array.Flatten&quot;
759 Const cstSubArgs = &quot;Array_1D&quot;
761 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
762 vFlatten = Array()
764 Check:
765 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
766 If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
767 End If
769 Try:
770 If UBound(Array_1D) &gt;= LBound(Array_1D) Then
771 lMin = LBound(Array_1D) : lMax = UBound(Array_1D)
772 ReDim vFlatten(lMin To lMax) &apos; Initial minimal sizing
773 lEmpty = 0
774 lIndex = lMin - 1
775 For i = lMin To lMax
776 vItem = Array_1D(i)
777 If IsArray(vItem) Then
778 iDims = SF_Array.CountDims(vItem)
779 Select Case iDims
780 Case 0 &apos; Empty arrays are ignored
781 lEmpty = lEmpty + 1
782 Case 1 &apos; Only 1D subarrays are flattened
783 ReDim Preserve vFlatten(lMin To UBound(vFlatten) + UBound(vItem) - LBound(vItem))
784 For j = LBound(vItem) To UBound(vItem)
785 lIndex = lIndex + 1
786 vFlatten(lIndex) = vItem(j)
787 Next j
788 Case &gt; 1 &apos; Other arrays are left unchanged
789 lIndex = lIndex + 1
790 vFlatten(lIndex) = vItem
791 End Select
792 Else
793 lIndex = lIndex + 1
794 vFlatten(lIndex) = vItem
795 End If
796 Next i
797 End If
798 &apos; Reduce size of output if Array_1D is populated with some empty arrays
799 If lEmpty &gt; 0 Then
800 If lIndex - lEmpty &lt; lMin Then
801 vFlatten = Array()
802 Else
803 ReDim Preserve vFlatten(lMin To UBound(vFlatten) - lEmpty)
804 End If
805 End If
807 Finally:
808 Flatten = vFlatten()
809 SF_Utils._ExitFunction(cstThisSub)
810 Exit Function
811 Catch:
812 GoTo Finally
813 End Function &apos; ScriptForge.SF_Array.Flatten
815 REM -----------------------------------------------------------------------------
816 Public Function GetProperty(Optional ByVal PropertyName As Variant) As Variant
817 &apos;&apos;&apos; Return the actual value of the given property
818 &apos;&apos;&apos; Args:
819 &apos;&apos;&apos; PropertyName: the name of the property as a string
820 &apos;&apos;&apos; Returns:
821 &apos;&apos;&apos; The actual value of the property
822 &apos;&apos;&apos; Exceptions
823 &apos;&apos;&apos; ARGUMENTERROR The property does not exist
825 Const cstThisSub = &quot;Array.GetProperty&quot;
826 Const cstSubArgs = &quot;PropertyName&quot;
828 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
829 GetProperty = Null
831 Check:
832 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
833 If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
834 End If
836 Try:
837 Select Case UCase(PropertyName)
838 Case Else
839 End Select
841 Finally:
842 SF_Utils._ExitFunction(cstThisSub)
843 Exit Function
844 Catch:
845 GoTo Finally
846 End Function &apos; ScriptForge.SF_Array.GetProperty
848 REM -----------------------------------------------------------------------------
849 Public Function ImportFromCSVFile(Optional ByRef FileName As Variant _
850 , Optional ByVal Delimiter As Variant _
851 , Optional ByVal DateFormat As Variant _
852 , Optional ByVal _IsoDate As Variant _
853 ) As Variant
854 &apos;&apos;&apos; Import the data contained in a comma-separated values (CSV) file
855 &apos;&apos;&apos; The comma may be replaced by any character
856 &apos;&apos;&apos; Each line in the file contains a full record
857 &apos;&apos;&apos; Line splitting is not allowed)
858 &apos;&apos;&apos; However sequences like \n, \t, ... are left unchanged. Use SF_String.Unescape() to manage them
859 &apos;&apos;&apos; A special mechanism is implemented to load dates
860 &apos;&apos;&apos; The applicable CSV format is described in https://tools.ietf.org/html/rfc4180
861 &apos;&apos;&apos; Args:
862 &apos;&apos;&apos; FileName: the name of the text file containing the data expressed as given by the current FileNaming
863 &apos;&apos;&apos; property of the SF_FileSystem service. Default = both URL format or native format
864 &apos;&apos;&apos; Delimiter: Default = &quot;,&quot;. Other usual options are &quot;;&quot; and the tab character
865 &apos;&apos;&apos; DateFormat: either YYYY-MM-DD, DD-MM-YYYY or MM-DD-YYYY
866 &apos;&apos;&apos; The dash (-) may be replaced by a dot (.), a slash (/) or a space
867 &apos;&apos;&apos; Other date formats will be ignored
868 &apos;&apos;&apos; If &quot;&quot; (default), dates will be considered as strings
869 &apos;&apos;&apos; _IsoDate: when True, the execution is initiated from Python, do not convert dates to Date variables. Internal use only
870 &apos;&apos;&apos; Returns:
871 &apos;&apos;&apos; A 2D-array with each row corresponding with a single record read in the file
872 &apos;&apos;&apos; and each column corresponding with a field of the record
873 &apos;&apos;&apos; No check is made about the coherence of the field types across columns
874 &apos;&apos;&apos; A best guess will be made to identify numeric and date types
875 &apos;&apos;&apos; If a line contains less or more fields than the first line in the file,
876 &apos;&apos;&apos; an exception will be raised. Empty lines however are simply ignored
877 &apos;&apos;&apos; If the size of the file exceeds the number of items limit, a warning is raised
878 &apos;&apos;&apos; and the array is truncated
879 &apos;&apos;&apos; Exceptions:
880 &apos;&apos;&apos; CSVPARSINGERROR Given file is not formatted as a csv file
881 &apos;&apos;&apos; CSVOVERFLOWWARNING Maximum number of allowed items exceeded
883 Dim vArray As Variant &apos; Returned array
884 Dim lCol As Long &apos; Index of last column of vArray
885 Dim lRow As Long &apos; Index of current row of vArray
886 Dim lFileSize As Long &apos; Number of records found in the file
887 Dim vCsv As Object &apos; CSV file handler
888 Dim sLine As String &apos; Last read line
889 Dim vLine As Variant &apos; Array of fields of last read line
890 Dim sItem As String &apos; Individual item in the file
891 Dim vItem As Variant &apos; Individual item in the output array
892 Dim iPosition As Integer &apos; Date position in individual item
893 Dim iYear As Integer, iMonth As Integer, iDay As Integer
894 &apos; Date components
895 Dim i As Long
896 Const cstItemsLimit = 250000 &apos; Maximum number of admitted items
897 Const cstThisSub = &quot;Array.ImportFromCSVFile&quot;
898 Const cstSubArgs = &quot;FileName, [Delimiter=&quot;&quot;,&quot;&quot;], [DateFormat=&quot;&quot;&quot;&quot;]&quot;
900 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
901 vArray = Array()
903 Check:
904 If IsMissing(Delimiter) Or IsEmpty(Delimiter) Then Delimiter = &quot;,&quot;
905 If IsMissing(DateFormat) Or IsEmpty(DateFormat) Then DateFormat = &quot;&quot;
906 If IsMissing(_IsoDate) Or IsEmpty(_IsoDate) Then _IsoDate = False
907 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
908 If Not SF_Utils._ValidateFile(FileName, &quot;FileName&quot;) Then GoTo Finally
909 If Not SF_Utils._Validate(Delimiter, &quot;Delimiter&quot;, V_STRING) Then GoTo Finally
910 If Not SF_Utils._Validate(DateFormat, &quot;DateFormat&quot;, V_STRING) Then GoTo Finally
911 End If
912 If Len(Delimiter) = 0 Then Delimiter = &quot;,&quot;
914 Try:
915 &apos; Counts the lines present in the file to size the final array
916 &apos; Very beneficial for large files, better than multiple ReDims
917 &apos; Small overhead for small files
918 lFileSize = SF_FileSystem._CountTextLines(FileName, False)
919 If lFileSize &lt;= 0 Then GoTo Finally
921 &apos; Reread file line by line
922 Set vCsv = SF_FileSystem.OpenTextFile(FileName, IOMode := SF_FileSystem.ForReading)
923 If IsNull(vCsv) Then GoTo Finally &apos; Open error
924 lRow = -1
925 With vCsv
926 Do While Not .AtEndOfStream
927 sLine = .ReadLine()
928 If Len(sLine) &gt; 0 Then &apos; Ignore empty lines
929 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
930 lRow = lRow + 1
931 If lRow = 0 Then &apos; Initial sizing of output array
932 lCol = UBound(vLine)
933 ReDim vArray(0 To lFileSize - 1, 0 To lCol)
934 ElseIf UBound(vLine) &lt;&gt; lCol Then
935 GoTo CatchCSVFormat
936 End If
937 &apos; Check type and copy all items of the line
938 For i = 0 To lCol
939 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
940 &apos; Interpret the individual line item
941 Select Case True
942 Case IsNumeric(sItem)
943 If InStr(sItem, &quot;.&quot;) + InStr(1, sItem, &quot;e&quot;, 1) &gt; 0 Then vItem = Val(sItem) Else vItem = CLng(sItem)
944 Case DateFormat &lt;&gt; &quot;&quot; And Len(sItem) = Len(DateFormat)
945 If SF_String.IsADate(sItem, DateFormat) Then
946 iPosition = InStr(DateFormat, &quot;YYYY&quot;) : iYear = CInt(Mid(sItem, iPosition, 4))
947 iPosition = InStr(DateFormat, &quot;MM&quot;) : iMonth = CInt(Mid(sItem, iPosition, 2))
948 iPosition = InStr(DateFormat, &quot;DD&quot;) : iDay = CInt(Mid(sItem, iPosition, 2))
949 vItem = DateSerial(iYear, iMonth, iDay)
950 If _IsoDate Then vItem = SF_Utils._CDateToIso(vItem) &apos; Called from Python
951 Else
952 vItem = sItem
953 End If
954 Case Else : vItem = sItem
955 End Select
956 vArray(lRow, i) = vItem
957 Next i
958 End If
959 &apos; Provision to avoid very large arrays and their sometimes erratic behaviour
960 If (lRow + 2) * (lCol + 1) &gt; cstItemsLimit Then
961 ReDim Preserve vArray(0 To lRow, 0 To lCol)
962 GoTo CatchOverflow
963 End If
964 Loop
965 End With
967 Finally:
968 If Not IsNull(vCsv) Then
969 vCsv.CloseFile()
970 Set vCsv = vCsv.Dispose()
971 End If
972 ImportFromCSVFile = vArray
973 SF_Utils._ExitFunction(cstThisSub)
974 Exit Function
975 Catch:
976 GoTo Finally
977 CatchCSVFormat:
978 SF_Exception.RaiseFatal(CSVPARSINGERROR, FileName, vCsv.Line, sLine)
979 GoTo Finally
980 CatchOverflow:
981 &apos;TODO SF_Exception.RaiseWarning(SF_Exception.CSVOVERFLOWWARNING, cstThisSub)
982 &apos;MsgBox &quot;TOO MUCH LINES !!&quot;
983 GoTo Finally
984 End Function &apos; ScriptForge.SF_Array.ImportFromCSVFile
986 REM -----------------------------------------------------------------------------
987 Public Function IndexOf(Optional ByRef Array_1D As Variant _
988 , Optional ByVal ToFind As Variant _
989 , Optional ByVal CaseSensitive As Variant _
990 , Optional ByVal SortOrder As Variant _
991 ) As Long
992 &apos;&apos;&apos; Finds in a 1D array the ToFind number, string or date
993 &apos;&apos;&apos; ToFind must exist within the array.
994 &apos;&apos;&apos; The comparison between strings can be done case-sensitively or not
995 &apos;&apos;&apos; If the array is sorted then
996 &apos;&apos;&apos; the array must be filled homogeneously, i.e. all items must be of the same type
997 &apos;&apos;&apos; Empty and Null items are forbidden
998 &apos;&apos;&apos; a binary search is done
999 &apos;&apos;&apos; Otherwise the array is scanned from top. Null or Empty items are simply ignored
1000 &apos;&apos;&apos; Args:
1001 &apos;&apos;&apos; Array_1D: the array to scan
1002 &apos;&apos;&apos; ToFind: a number, a date or a string to find
1003 &apos;&apos;&apos; CaseSensitive: Only for string comparisons, default = False
1004 &apos;&apos;&apos; SortOrder: &quot;ASC&quot;, &quot;DESC&quot; or &quot;&quot; (= not sorted, default)
1005 &apos;&apos;&apos; Return: the index of the found item, LBound - 1 if not found
1006 &apos;&apos;&apos; Result is unpredictable when array is announced sorted and is in reality not
1007 &apos;&apos;&apos; Examples:
1008 &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
1009 &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
1011 Dim vFindItem As Variant &apos; 2-items array (0) = True if found, (1) = Index where found
1012 Dim lIndex As Long &apos; Return value
1013 Dim iToFindType As Integer &apos; VarType of ToFind
1014 Const cstThisSub = &quot;Array.IndexOf&quot;
1015 Const cstSubArgs = &quot;Array_1D, ToFind, [CaseSensitive=False], [SortOrder=&quot;&quot;&quot;&quot;|&quot;&quot;ASC&quot;&quot;|&quot;&quot;DESC&quot;&quot;]&quot;
1017 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
1019 lIndex = -1
1021 Check:
1022 If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False
1023 If IsMissing(SortOrder) Or IsEmpty(SortOrder) Then SortOrder = &quot;&quot;
1024 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
1025 If Not SF_Utils._Validate(SortOrder, &quot;SortOrder&quot;, V_STRING, Array(&quot;ASC&quot;, &quot;DESC&quot;, &quot;&quot;)) Then GoTo Finally
1026 If Not SF_Utils._Validate(ToFind, &quot;ToFind&quot;, Array(V_STRING, V_DATE, V_NUMERIC)) Then GoTo Finally
1027 iToFindType = SF_Utils._VarTypeExt(ToFind)
1028 If SortOrder &lt;&gt; &quot;&quot; Then
1029 If Not SF_Utils._ValidateArray(Array_1D, &quot;Array&quot;, 1, iToFindType) Then GoTo Finally
1030 Else
1031 If Not SF_Utils._ValidateArray(Array_1D, &quot;Array&quot;, 1) Then GoTo Finally
1032 End If
1033 If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
1034 End If
1036 Try:
1037 vFindItem = SF_Array._FindItem(Array_1D, ToFind, CaseSensitive, SortOrder)
1038 If vFindItem(0) = True Then lIndex = vFindItem(1) Else lIndex = LBound(Array_1D) - 1
1040 Finally:
1041 IndexOf = lIndex
1042 SF_Utils._ExitFunction(cstThisSub)
1043 Exit Function
1044 Catch:
1045 GoTo Finally
1046 End Function &apos; ScriptForge.SF_Array.IndexOf
1048 REM -----------------------------------------------------------------------------
1049 Public Function Insert(Optional ByRef Array_1D As Variant _
1050 , Optional ByVal Before As Variant _
1051 , ParamArray pvArgs() As Variant _
1052 ) As Variant
1053 &apos;&apos;&apos; Insert before the index Before of the input array the items listed as arguments
1054 &apos;&apos;&apos; Arguments are inserted blindly
1055 &apos;&apos;&apos; each of them might be a scalar of any type or a subarray
1056 &apos;&apos;&apos; Args
1057 &apos;&apos;&apos; Array_1D: the pre-existing array, may be empty
1058 &apos;&apos;&apos; Before: the index before which to insert; must be in the interval [LBound, UBound + 1]
1059 &apos;&apos;&apos; pvArgs: a list of items to Insert inside Array_1D
1060 &apos;&apos;&apos; Returns:
1061 &apos;&apos;&apos; the new rxtended array. Its LBound is identical to that of Array_1D
1062 &apos;&apos;&apos; Exceptions:
1063 &apos;&apos;&apos; ARRAYINSERTERROR
1064 &apos;&apos;&apos; Examples:
1065 &apos;&apos;&apos; SF_Array.Insert(Array(1, 2, 3), 2, 4, 5) returns (1, 2, 4, 5, 3)
1067 Dim vInsert As Variant &apos; Return value
1068 Dim lNbArgs As Long &apos; Number of elements to Insert
1069 Dim lMin As Long &apos; LBound of input array
1070 Dim lMax As Long &apos; UBound of input array
1071 Dim i As Long
1072 Const cstThisSub = &quot;Array.Insert&quot;
1073 Const cstSubArgs = &quot;Array_1D, Before, arg0[, arg1] ...&quot;
1075 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
1076 vInsert = Array()
1078 Check:
1079 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
1080 If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
1081 If Not SF_Utils._Validate(Before, &quot;Before&quot;, V_NUMERIC) Then GoTo Finally
1082 If Before &lt; LBound(Array_1D) Or Before &gt; UBound(Array_1D) + 1 Then GoTo CatchArgument
1083 End If
1085 Try:
1086 lNbArgs = UBound(pvArgs) + 1 &apos; pvArgs is always zero-based
1087 lMin = LBound(Array_1D) &apos; = LBound(vInsert)
1088 lMax = UBound(Array_1D) &apos; &lt;&gt; UBound(vInsert)
1089 If lNbArgs &gt; 0 Then
1090 ReDim vInsert(lMin To lMax + lNbArgs)
1091 For i = lMin To UBound(vInsert)
1092 If i &lt; Before Then
1093 vInsert(i) = Array_1D(i)
1094 ElseIf i &lt; Before + lNbArgs Then
1095 vInsert(i) = pvArgs(i - Before)
1096 Else
1097 vInsert(i) = Array_1D(i - lNbArgs)
1098 End If
1099 Next i
1100 Else
1101 vInsert() = Array_1D()
1102 End If
1104 Finally:
1105 Insert = vInsert()
1106 SF_Utils._ExitFunction(cstThisSub)
1107 Exit Function
1108 Catch:
1109 GoTo Finally
1110 CatchArgument:
1111 &apos;TODO SF_Exception.RaiseFatal(ARRAYINSERTERROR, cstThisSub)
1112 MsgBox &quot;INVALID ARGUMENT VALUE !!&quot;
1113 GoTo Finally
1114 End Function &apos; ScriptForge.SF_Array.Insert
1116 REM -----------------------------------------------------------------------------
1117 Public Function InsertSorted(Optional ByRef Array_1D As Variant _
1118 , Optional ByVal Item As Variant _
1119 , Optional ByVal SortOrder As Variant _
1120 , Optional ByVal CaseSensitive As Variant _
1121 ) As Variant
1122 &apos;&apos;&apos; Insert in a sorted array a new item on its place
1123 &apos;&apos;&apos; the array must be filled homogeneously, i.e. all items must be of the same type
1124 &apos;&apos;&apos; Empty and Null items are forbidden
1125 &apos;&apos;&apos; Args:
1126 &apos;&apos;&apos; Array_1D: the array to sort
1127 &apos;&apos;&apos; Item: the scalar value to insert, same type as the existing array items
1128 &apos;&apos;&apos; SortOrder: &quot;ASC&quot; (default) or &quot;DESC&quot;
1129 &apos;&apos;&apos; CaseSensitive: Default = False
1130 &apos;&apos;&apos; Returns: the extended sorted array with same LBound as input array
1131 &apos;&apos;&apos; Examples:
1132 &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;)
1134 Dim vSorted() As Variant &apos; Return value
1135 Dim iType As Integer &apos; VarType of elements in input array
1136 Dim lMin As Long &apos; LBound of input array
1137 Dim lMax As Long &apos; UBound of input array
1138 Dim lIndex As Long &apos; Place where to insert new item
1139 Const cstThisSub = &quot;Array.InsertSorted&quot;
1140 Const cstSubArgs = &quot;Array_1D, Item, [SortOrder=&quot;&quot;ASC&quot;&quot;|&quot;&quot;DESC&quot;&quot;], [CaseSensitive=False]&quot;
1142 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
1143 vSorted = Array()
1145 Check:
1146 If IsMissing(SortOrder) Or IsEmpty(SortOrder) Then SortOrder = &quot;ASC&quot;
1147 If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False
1148 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
1149 If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1, 0) Then GoTo Finally
1150 If LBound(Array_1D) &lt;= UBound(Array_1D) Then
1151 iType = SF_Utils._VarTypeExt(Array_1D(LBound(Array_1D)))
1152 If Not SF_Utils._Validate(Item, &quot;Item&quot;, iType) Then GoTo Finally
1153 Else
1154 If Not SF_Utils._Validate(Item, &quot;Item&quot;, Array(V_STRING, V_DATE, V_NUMERIC)) Then GoTo Finally
1155 End If
1156 If Not SF_Utils._Validate(SortOrder, &quot;SortOrder&quot;, V_STRING, Array(&quot;ASC&quot;,&quot;DESC&quot;)) Then GoTo Finally
1157 If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
1158 End If
1160 Try:
1161 lMin = LBound(Array_1D)
1162 lMax = UBound(Array_1D)
1163 lIndex = SF_Array._FindItem(Array_1D, Item, CaseSensitive, SortOrder)(1)
1164 vSorted = SF_Array.Insert(Array_1D, lIndex, Item)
1166 Finally:
1167 InsertSorted = vSorted()
1168 SF_Utils._ExitFunction(cstThisSub)
1169 Exit Function
1170 Catch:
1171 GoTo Finally
1172 End Function &apos; ScriptForge.SF_Array.InsertSorted
1174 REM -----------------------------------------------------------------------------
1175 Public Function Intersection(Optional ByRef Array1_1D As Variant _
1176 , Optional ByRef Array2_1D As Variant _
1177 , Optional ByVal CaseSensitive As Variant _
1178 ) As Variant
1179 &apos;&apos;&apos; Build a set being the intersection of the two input arrays, i.e. items are contained in both arrays
1180 &apos;&apos;&apos; both input arrays must be filled homogeneously, i.e. all items must be of the same type
1181 &apos;&apos;&apos; Empty and Null items are forbidden
1182 &apos;&apos;&apos; The comparison between strings is case sensitive or not
1183 &apos;&apos;&apos; Args:
1184 &apos;&apos;&apos; Array1_1D: a 1st input array
1185 &apos;&apos;&apos; Array2_1D: a 2nd input array
1186 &apos;&apos;&apos; CaseSensitive: default = False
1187 &apos;&apos;&apos; Returns: a zero-based array containing unique items stored in both input arrays
1188 &apos;&apos;&apos; The output array is sorted in ascending order
1189 &apos;&apos;&apos; Examples:
1190 &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;)
1192 Dim vIntersection() As Variant &apos; Return value
1193 Dim vSorted() As Variant &apos; The shortest input array after sort
1194 Dim iType As Integer &apos; VarType of elements in input arrays
1195 Dim lMin1 As Long &apos; LBound of 1st input array
1196 Dim lMax1 As Long &apos; UBound of 1st input array
1197 Dim lMin2 As Long &apos; LBound of 2nd input array
1198 Dim lMax2 As Long &apos; UBound of 2nd input array
1199 Dim lMin As Long &apos; LBound of unsorted array
1200 Dim lMax As Long &apos; UBound of unsorted array
1201 Dim iShortest As Integer &apos; 1 or 2 depending on shortest input array
1202 Dim lSize As Long &apos; Number of Intersection items
1203 Dim vItem As Variant &apos; One single item in the array
1204 Dim i As Long
1205 Const cstThisSub = &quot;Array.Intersection&quot;
1206 Const cstSubArgs = &quot;Array1_1D, Array2_1D, [CaseSensitive=False]&quot;
1208 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
1209 vIntersection = Array()
1211 Check:
1212 If IsMissing(CaseSensitive) Then CaseSensitive = False
1213 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
1214 If Not SF_Utils._ValidateArray(Array1_1D, &quot;Array1_1D&quot;, 1, 0, True) Then GoTo Finally
1215 iType = SF_Utils._VarTypeExt(Array1_1D(LBound(Array1_1D)))
1216 If Not SF_Utils._ValidateArray(Array2_1D, &quot;Array2_1D&quot;, 1, iType, True) Then GoTo Finally
1217 If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
1218 End If
1220 Try:
1221 lMin1 = LBound(Array1_1D) : lMax1 = UBound(Array1_1D)
1222 lMin2 = LBound(Array2_1D) : lMax2 = UBound(Array2_1D)
1224 &apos; If one of both arrays is empty, do nothing
1225 If lMax1 &gt;= lMin1 And lMax2 &gt;= lMin2 Then
1227 &apos; First sort the shortest array
1228 If lMax1 - lMin1 &lt;= lMax2 - lMin2 Then
1229 iShortest = 1
1230 vSorted = SF_Array.Sort(Array1_1D, &quot;ASC&quot;, CaseSensitive)
1231 lMin = lMin2 : lMax = lMax2 &apos; Bounds of unsorted array
1232 Else
1233 iShortest = 2
1234 vSorted = SF_Array.Sort(Array2_1D, &quot;ASC&quot;, CaseSensitive)
1235 lMin = lMin1 : lMax = lMax1 &apos; Bounds of unsorted array
1236 End If
1238 &apos; Resize the output array to the size of the shortest array
1239 ReDim vIntersection(0 To (lMax - lMin))
1240 lSize = -1
1242 &apos; Fill vIntersection one by one only with items present in both sets
1243 For i = lMin To lMax
1244 If iShortest = 1 Then vItem = Array2_1D(i) Else vItem = Array1_1D(i) &apos; Pick in unsorted array
1245 If SF_Array.Contains(vSorted, vItem, CaseSensitive, &quot;ASC&quot;) Then
1246 lSize = lSize + 1
1247 vIntersection(lSize) = vItem
1248 End If
1249 Next i
1251 &apos; Remove unfilled entries and duplicates
1252 If lSize &gt;= 0 Then
1253 ReDim Preserve vIntersection(0 To lSize)
1254 vIntersection() = SF_Array.Unique(vIntersection, CaseSensitive)
1255 Else
1256 vIntersection = Array()
1257 End If
1258 End If
1260 Finally:
1261 Intersection = vIntersection()
1262 SF_Utils._ExitFunction(cstThisSub)
1263 Exit Function
1264 Catch:
1265 GoTo Finally
1266 End Function &apos; ScriptForge.SF_Array.Intersection
1268 REM -----------------------------------------------------------------------------
1269 Public Function Join2D(Optional ByRef Array_2D As Variant _
1270 , Optional ByVal ColumnDelimiter As Variant _
1271 , Optional ByVal RowDelimiter As Variant _
1272 , Optional ByVal Quote As Variant _
1273 ) As String
1274 &apos;&apos;&apos; Join a two-dimensional array with two delimiters, one for columns, one for rows
1275 &apos;&apos;&apos; Args:
1276 &apos;&apos;&apos; Array_2D: each item must be either a String, a number, a Date or a Boolean
1277 &apos;&apos;&apos; ColumnDelimiter: delimits each column (default = Tab/Chr(9))
1278 &apos;&apos;&apos; RowDelimiter: delimits each row (default = LineFeed/Chr(10))
1279 &apos;&apos;&apos; Quote: if True, protect strings with double quotes (default = False)
1280 &apos;&apos;&apos; Return:
1281 &apos;&apos;&apos; A string after conversion of numbers and dates
1282 &apos;&apos;&apos; Invalid items are replaced by a zero-length string
1283 &apos;&apos;&apos; Examples:
1284 &apos;&apos;&apos; | 1, 2, &quot;A&quot;, [2020-02-29], 5 |
1285 &apos;&apos;&apos; SF_Array.Join_2D( | 6, 7, &quot;this is a string&quot;, 9, 10 | , &quot;,&quot;, &quot;/&quot;)
1286 &apos;&apos;&apos; &apos; &quot;1,2,A,2020-02-29 00:00:00,5/6,7,this is a string,9,10&quot;
1288 Dim sJoin As String &apos; The return value
1289 Dim sItem As String &apos; The string representation of a single item
1290 Dim vItem As Variant &apos; Single item
1291 Dim lMin1 As Long &apos; LBound1 of input array
1292 Dim lMax1 As Long &apos; UBound1 of input array
1293 Dim lMin2 As Long &apos; LBound2 of input array
1294 Dim lMax2 As Long &apos; UBound2 of input array
1295 Dim i As Long
1296 Dim j As Long
1297 Const cstThisSub = &quot;Array.Join2D&quot;
1298 Const cstSubArgs = &quot;Array_2D, [ColumnDelimiter=Chr(9)], [RowDelimiter=Chr(10)], [Quote=False]&quot;
1300 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
1301 sJoin = &quot;&quot;
1303 Check:
1304 If IsMissing(ColumnDelimiter) Or IsEmpty(ColumnDelimiter) Then ColumnDelimiter = Chr(9)
1305 If IsMissing(RowDelimiter) Or IsEmpty(RowDelimiter) Then RowDelimiter = Chr(10)
1306 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
1307 If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally
1308 If Not SF_Utils._Validate(ColumnDelimiter, &quot;ColumnDelimiter&quot;, V_STRING) Then GoTo Finally
1309 If Not SF_Utils._Validate(RowDelimiter, &quot;RowDelimiter&quot;, V_STRING) Then GoTo Finally
1310 If Not SF_Utils._Validate(Quote, &quot;Quote&quot;, V_BOOLEAN) Then GoTo Finally
1311 End If
1313 Try:
1314 lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
1315 lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
1316 If lMin1 &lt;= lMax1 Then
1317 For i = lMin1 To lMax1
1318 For j = lMin2 To lMax2
1319 vItem = Array_2D(i, j)
1320 Select Case SF_Utils._VarTypeExt(vItem)
1321 Case V_STRING : If Quote Then sItem = SF_String.Quote(vItem) Else sItem = vItem
1322 Case V_NUMERIC, V_DATE : sItem = SF_Utils._Repr(vItem)
1323 Case V_BOOLEAN : sItem = Iif(vItem, &quot;True&quot;, &quot;False&quot;) &apos;TODO: L10N
1324 Case Else : sItem = &quot;&quot;
1325 End Select
1326 sJoin = sJoin &amp; sItem &amp; Iif(j &lt; lMax2, ColumnDelimiter, &quot;&quot;)
1327 Next j
1328 sJoin = sJoin &amp; Iif(i &lt; lMax1, RowDelimiter, &quot;&quot;)
1329 Next i
1330 End If
1332 Finally:
1333 Join2D = sJoin
1334 SF_Utils._ExitFunction(cstThisSub)
1335 Exit Function
1336 Catch:
1337 GoTo Finally
1338 End Function &apos; ScriptForge.SF_Array.Join2D
1340 REM -----------------------------------------------------------------------------
1341 Public Function Methods() As Variant
1342 &apos;&apos;&apos; Return the list of public methods of the Array service as an array
1344 Methods = Array( _
1345 &quot;Append&quot; _
1346 , &quot;AppendColumn&quot; _
1347 , &quot;AppendRow&quot; _
1348 , &quot;Contains&quot; _
1349 , &quot;ConvertToDictionary&quot; _
1350 , &quot;CountDims&quot; _
1351 , &quot;Difference&quot; _
1352 , &quot;ExportToTextFile&quot; _
1353 , &quot;ExtractColumn&quot; _
1354 , &quot;ExtractRow&quot; _
1355 , &quot;Flatten&quot; _
1356 , &quot;ImportFromCSVFile&quot; _
1357 , &quot;IndexOf&quot; _
1358 , &quot;Insert&quot; _
1359 , &quot;InsertSorted&quot; _
1360 , &quot;Intersection&quot; _
1361 , &quot;Join2D&quot; _
1362 , &quot;Prepend&quot; _
1363 , &quot;PrependColumn&quot; _
1364 , &quot;PrependRow&quot; _
1365 , &quot;RangeInit&quot; _
1366 , &quot;Reverse&quot; _
1367 , &quot;Shuffle&quot; _
1368 , &quot;Sort&quot; _
1369 , &quot;SortColumns&quot; _
1370 , &quot;SortRows&quot; _
1371 , &quot;Transpose&quot; _
1372 , &quot;TrimArray&quot; _
1373 , &quot;Union&quot; _
1374 , &quot;Unique&quot; _
1377 End Function &apos; ScriptForge.SF_Array.Methods
1379 REM -----------------------------------------------------------------------------
1380 Public Function Prepend(Optional ByRef Array_1D As Variant _
1381 , ParamArray pvArgs() As Variant _
1382 ) As Variant
1383 &apos;&apos;&apos; Prepend at the beginning of the input array the items listed as arguments
1384 &apos;&apos;&apos; Arguments are Prepended blindly
1385 &apos;&apos;&apos; each of them might be a scalar of any type or a subarray
1386 &apos;&apos;&apos; Args
1387 &apos;&apos;&apos; Array_1D: the pre-existing array, may be empty
1388 &apos;&apos;&apos; pvArgs: a list of items to Prepend to Array_1D
1389 &apos;&apos;&apos; Return: the new rxtended array. Its LBound is identical to that of Array_1D
1390 &apos;&apos;&apos; Examples:
1391 &apos;&apos;&apos; SF_Array.Prepend(Array(1, 2, 3), 4, 5) returns (4, 5, 1, 2, 3)
1393 Dim vPrepend As Variant &apos; Return value
1394 Dim lNbArgs As Long &apos; Number of elements to Prepend
1395 Dim lMin As Long &apos; LBound of input array
1396 Dim lMax As Long &apos; UBound of input array
1397 Dim i As Long
1398 Const cstThisSub = &quot;Array.Prepend&quot;
1399 Const cstSubArgs = &quot;Array_1D, arg0[, arg1] ...&quot;
1401 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
1402 vPrepend = Array()
1404 Check:
1405 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
1406 If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
1407 End If
1409 Try:
1410 lNbArgs = UBound(pvArgs) + 1 &apos; pvArgs is always zero-based
1411 lMin = LBound(Array_1D) &apos; = LBound(vPrepend)
1412 lMax = UBound(Array_1D) &apos; &lt;&gt; UBound(vPrepend)
1413 If lMax &lt; LBound(Array_1D) And lNbArgs &gt; 0 Then &apos; Initial array is empty
1414 ReDim vPrepend(0 To lNbArgs - 1)
1415 Else
1416 ReDim vPrepend(lMin To lMax + lNbArgs)
1417 End If
1418 For i = lMin To UBound(vPrepend)
1419 If i &lt; lMin + lNbArgs Then vPrepend(i) = pvArgs(i - lMin) Else vPrepend(i) = Array_1D(i - lNbArgs)
1420 Next i
1422 Finally:
1423 Prepend = vPrepend
1424 SF_Utils._ExitFunction(cstThisSub)
1425 Exit Function
1426 Catch:
1427 GoTo Finally
1428 End Function &apos; ScriptForge.SF_Array.Prepend
1430 REM -----------------------------------------------------------------------------
1431 Public Function PrependColumn(Optional ByRef Array_2D As Variant _
1432 , Optional ByRef Column As Variant _
1433 ) As Variant
1434 &apos;&apos;&apos; PrependColumn prepends to the left side of a 2D array a new Column
1435 &apos;&apos;&apos; Args
1436 &apos;&apos;&apos; Array_2D: the pre-existing array, may be empty
1437 &apos;&apos;&apos; If the array has 1 dimension, it is considered as the last Column of the resulting 2D array
1438 &apos;&apos;&apos; Column: a 1D array with as many items as there are rows in Array_2D
1439 &apos;&apos;&apos; Returns:
1440 &apos;&apos;&apos; the new rxtended array. Its LBounds are identical to that of Array_2D
1441 &apos;&apos;&apos; Exceptions:
1442 &apos;&apos;&apos; ARRAYINSERTERROR
1443 &apos;&apos;&apos; Examples:
1444 &apos;&apos;&apos; SF_Array.PrependColumn(Array(1, 2, 3), Array(4, 5, 6)) returns ((4, 1), (5, 2), (6, 3))
1445 &apos;&apos;&apos; x = SF_Array.PrependColumn(Array(), Array(1, 2, 3)) =&gt; ∀ i ∈ {0 ≤ i ≤ 2} : x(0, i) ≡ i
1447 Dim vPrependColumn As Variant &apos; Return value
1448 Dim iDims As Integer &apos; Dimensions of Array_2D
1449 Dim lMin1 As Long &apos; LBound1 of input array
1450 Dim lMax1 As Long &apos; UBound1 of input array
1451 Dim lMin2 As Long &apos; LBound2 of input array
1452 Dim lMax2 As Long &apos; UBound2 of input array
1453 Dim lMin As Long &apos; LBound of Column array
1454 Dim lMax As Long &apos; UBound of Column array
1455 Dim i As Long
1456 Dim j As Long
1457 Const cstThisSub = &quot;Array.PrependColumn&quot;
1458 Const cstSubArgs = &quot;Array_2D, Column&quot;
1460 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
1461 vPrependColumn = Array()
1463 Check:
1464 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
1465 If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;) Then GoTo Finally &apos;Initial check: not missing and array
1466 If Not SF_Utils._ValidateArray(Column, &quot;Column&quot;, 1) Then GoTo Finally
1467 End If
1468 iDims = SF_Array.CountDims(Array_2D)
1469 If iDims &gt; 2 Then
1470 If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally &apos;2nd check to manage error
1471 End If
1473 Try:
1474 lMin = LBound(Column)
1475 lMax = UBound(Column)
1477 &apos; Compute future dimensions of output array
1478 Select Case iDims
1479 Case 0 : lMin1 = lMin : lMax1 = lMax
1480 lMin2 = 0 : lMax2 = -1
1481 Case 1 : lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
1482 lMin2 = 0 : lMax2 = 0
1483 Case 2 : lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
1484 lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
1485 End Select
1486 If iDims &gt; 0 And lMax - lMin &lt;&gt; lMax1 - lMin1 Then GoTo CatchColumn
1487 ReDim vPrependColumn(lMin1 To lMax1, lMin2 To lMax2 + 1)
1489 &apos; Copy input array to output array
1490 For i = lMin1 To lMax1
1491 For j = lMin2 + 1 To lMax2 + 1
1492 If iDims = 2 Then vPrependColumn(i, j) = Array_2D(i, j - 1) Else vPrependColumn(i, j) = Array_2D(i)
1493 Next j
1494 Next i
1495 &apos; Copy new Column
1496 For i = lMin1 To lMax1
1497 vPrependColumn(i, lMin2) = Column(i)
1498 Next i
1500 Finally:
1501 PrependColumn = vPrependColumn()
1502 SF_Utils._ExitFunction(cstThisSub)
1503 Exit Function
1504 Catch:
1505 GoTo Finally
1506 CatchColumn:
1507 SF_Exception.RaiseFatal(ARRAYINSERTERROR, &quot;Column&quot;, SF_Array._Repr(Array_2D), SF_Utils._Repr(Column, MAXREPR))
1508 GoTo Finally
1509 End Function &apos; ScriptForge.SF_Array.PrependColumn
1511 REM -----------------------------------------------------------------------------
1512 Public Function PrependRow(Optional ByRef Array_2D As Variant _
1513 , Optional ByRef Row As Variant _
1514 ) As Variant
1515 &apos;&apos;&apos; PrependRow prepends on top of a 2D array a new row
1516 &apos;&apos;&apos; Args
1517 &apos;&apos;&apos; Array_2D: the pre-existing array, may be empty
1518 &apos;&apos;&apos; If the array has 1 dimension, it is considered as the last row of the resulting 2D array
1519 &apos;&apos;&apos; Row: a 1D array with as many items as there are columns in Array_2D
1520 &apos;&apos;&apos; Returns:
1521 &apos;&apos;&apos; the new rxtended array. Its LBounds are identical to that of Array_2D
1522 &apos;&apos;&apos; Exceptions:
1523 &apos;&apos;&apos; ARRAYINSERTERROR
1524 &apos;&apos;&apos; Examples:
1525 &apos;&apos;&apos; SF_Array.PrependRow(Array(1, 2, 3), Array(4, 5, 6)) returns ((4, 5, 6), (1, 2, 3))
1526 &apos;&apos;&apos; x = SF_Array.PrependColumn(Array(), Array(1, 2, 3) =&gt; ∀ i ∈ {0 ≤ i ≤ 2} : x(i, 0) ≡ i
1528 Dim vPrependRow As Variant &apos; Return value
1529 Dim iDims As Integer &apos; Dimensions of Array_2D
1530 Dim lMin1 As Long &apos; LBound1 of input array
1531 Dim lMax1 As Long &apos; UBound1 of input array
1532 Dim lMin2 As Long &apos; LBound2 of input array
1533 Dim lMax2 As Long &apos; UBound2 of input array
1534 Dim lMin As Long &apos; LBound of row array
1535 Dim lMax As Long &apos; UBound of row array
1536 Dim i As Long
1537 Dim j As Long
1538 Const cstThisSub = &quot;Array.PrependRow&quot;
1539 Const cstSubArgs = &quot;Array_2D, Row&quot;
1541 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
1542 vPrependRow = Array()
1544 Check:
1545 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
1546 If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;) Then GoTo Finally &apos;Initial check: not missing and array
1547 If Not SF_Utils._ValidateArray(Row, &quot;Row&quot;, 1) Then GoTo Finally
1548 End If
1549 iDims = SF_Array.CountDims(Array_2D)
1550 If iDims &gt; 2 Then
1551 If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally &apos;2nd check to manage error
1552 End If
1554 Try:
1555 lMin = LBound(Row)
1556 lMax = UBound(Row)
1558 &apos; Compute future dimensions of output array
1559 Select Case iDims
1560 Case 0 : lMin1 = 0 : lMax1 = -1
1561 lMin2 = lMin : lMax2 = lMax
1562 Case 1 : lMin1 = 0 : lMax1 = 0
1563 lMin2 = LBound(Array_2D, 1) : lMax2 = UBound(Array_2D, 1)
1564 Case 2 : lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
1565 lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
1566 End Select
1567 If iDims &gt; 0 And lMax - lMin &lt;&gt; lMax2 - lMin2 Then GoTo CatchRow
1568 ReDim vPrependRow(lMin1 To lMax1 + 1, lMin2 To lMax2)
1570 &apos; Copy input array to output array
1571 For i = lMin1 + 1 To lMax1 + 1
1572 For j = lMin2 To lMax2
1573 If iDims = 2 Then vPrependRow(i, j) = Array_2D(i - 1, j) Else vPrependRow(i, j) = Array_2D(j)
1574 Next j
1575 Next i
1576 &apos; Copy new row
1577 For j = lMin2 To lMax2
1578 vPrependRow(lMin1, j) = Row(j)
1579 Next j
1581 Finally:
1582 PrependRow = vPrependRow()
1583 SF_Utils._ExitFunction(cstThisSub)
1584 Exit Function
1585 Catch:
1586 GoTo Finally
1587 CatchRow:
1588 SF_Exception.RaiseFatal(ARRAYINSERTERROR, &quot;Row&quot;, SF_Array._Repr(Array_2D), SF_Utils._Repr(Row, MAXREPR))
1589 GoTo Finally
1590 End Function &apos; ScriptForge.SF_Array.PrependRow
1592 REM -----------------------------------------------------------------------------
1593 Public Function Properties() As Variant
1594 &apos;&apos;&apos; Return the list or properties as an array
1596 Properties = Array( _
1599 End Function &apos; ScriptForge.SF_Array.Properties
1601 REM -----------------------------------------------------------------------------
1602 Public Function RangeInit(Optional ByVal From As Variant _
1603 , Optional ByVal UpTo As Variant _
1604 , Optional ByVal ByStep As Variant _
1605 ) As Variant
1606 &apos;&apos;&apos; Initialize a new zero-based array with numeric values
1607 &apos;&apos;&apos; Args: all numeric
1608 &apos;&apos;&apos; From: value of first item
1609 &apos;&apos;&apos; UpTo: last item should not exceed UpTo
1610 &apos;&apos;&apos; ByStep: difference between 2 successive items
1611 &apos;&apos;&apos; Return: the new array
1612 &apos;&apos;&apos; Exceptions:
1613 &apos;&apos;&apos; ARRAYSEQUENCEERROR Wrong arguments, f.i. UpTo &lt; From with ByStep &gt; 0
1614 &apos;&apos;&apos; Examples:
1615 &apos;&apos;&apos; SF_Array.RangeInit(10, 1, -1) returns (10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
1617 Dim lIndex As Long &apos; Index of array
1618 Dim lSize As Long &apos; UBound of resulting array
1619 Dim vCurrentItem As Variant &apos; Last stored item
1620 Dim vArray() &apos; The return value
1621 Const cstThisSub = &quot;Array.RangeInit&quot;
1622 Const cstSubArgs = &quot;From, UpTo, [ByStep = 1]&quot;
1624 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
1625 vArray = Array()
1627 Check:
1628 If IsMissing(ByStep) Or IsEmpty(ByStep) Then ByStep = 1
1629 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
1630 If Not SF_Utils._Validate(From, &quot;From&quot;, V_NUMERIC) Then GoTo Finally
1631 If Not SF_Utils._Validate(UpTo, &quot;UpTo&quot;, V_NUMERIC) Then GoTo Finally
1632 If Not SF_Utils._Validate(ByStep, &quot;ByStep&quot;, V_NUMERIC) Then GoTo Finally
1633 End If
1634 If (From &lt; UpTo And ByStep &lt;= 0) Or (From &gt; UpTo And ByStep &gt;= 0) Then GoTo CatchSequence
1636 Try:
1637 lSize = CLng(Abs((UpTo - From) / ByStep))
1638 ReDim vArray(0 To lSize)
1639 For lIndex = 0 To lSize
1640 vArray(lIndex) = From + lIndex * ByStep
1641 Next lIndex
1643 Finally:
1644 RangeInit = vArray
1645 SF_Utils._ExitFunction(cstThisSub)
1646 Exit Function
1647 Catch:
1648 GoTo Finally
1649 CatchSequence:
1650 SF_Exception.RaiseFatal(ARRAYSEQUENCEERROR, From, UpTo, ByStep)
1651 GoTo Finally
1652 End Function &apos; ScriptForge.SF_Array.RangeInit
1654 REM -----------------------------------------------------------------------------
1655 Public Function Reverse(Optional ByRef Array_1D As Variant) As Variant
1656 &apos;&apos;&apos; Return the reversed 1D input array
1657 &apos;&apos;&apos; Args:
1658 &apos;&apos;&apos; Array_1D: the array to reverse
1659 &apos;&apos;&apos; Returns: the reversed array
1660 &apos;&apos;&apos; Examples:
1661 &apos;&apos;&apos; SF_Array.Reverse(Array(1, 2, 3, 4)) returns (4, 3, 2, 1)
1663 Dim vReverse() As Variant &apos; Return value
1664 Dim lHalf As Long &apos; Middle of array
1665 Dim lMin As Long &apos; LBound of input array
1666 Dim lMax As Long &apos; UBound of input array
1667 Dim i As Long, j As Long
1668 Const cstThisSub = &quot;Array.Reverse&quot;
1669 Const cstSubArgs = &quot;Array_1D&quot;
1671 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
1672 vReverse = Array()
1674 Check:
1675 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
1676 If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
1677 End If
1679 Try:
1680 lMin = LBound(Array_1D)
1681 lMax = UBound(Array_1D)
1682 ReDim vReverse(lMin To lMax)
1683 lHalf = Int((lMax + lMin) / 2)
1684 j = lMax
1685 For i = lMin To lHalf
1686 vReverse(i) = Array_1D(j)
1687 vReverse(j) = Array_1D(i)
1688 j = j - 1
1689 Next i
1690 &apos; Odd number of items
1691 If IsEmpty(vReverse(lHalf + 1)) Then vReverse(lHalf + 1) = Array_1D(lHalf + 1)
1693 Finally:
1694 Reverse = vReverse()
1695 SF_Utils._ExitFunction(cstThisSub)
1696 Exit Function
1697 Catch:
1698 GoTo Finally
1699 End Function &apos; ScriptForge.SF_Array.Reverse
1701 REM -----------------------------------------------------------------------------
1702 Public Function SetProperty(Optional ByVal PropertyName As Variant _
1703 , Optional ByRef Value As Variant _
1704 ) As Boolean
1705 &apos;&apos;&apos; Set a new value to the given property
1706 &apos;&apos;&apos; Args:
1707 &apos;&apos;&apos; PropertyName: the name of the property as a string
1708 &apos;&apos;&apos; Value: its new value
1709 &apos;&apos;&apos; Exceptions
1710 &apos;&apos;&apos; ARGUMENTERROR The property does not exist
1712 Const cstThisSub = &quot;Array.SetProperty&quot;
1713 Const cstSubArgs = &quot;PropertyName, Value&quot;
1715 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
1716 SetProperty = False
1718 Check:
1719 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
1720 If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
1721 End If
1723 Try:
1724 Select Case UCase(PropertyName)
1725 Case Else
1726 End Select
1728 Finally:
1729 SF_Utils._ExitFunction(cstThisSub)
1730 Exit Function
1731 Catch:
1732 GoTo Finally
1733 End Function &apos; ScriptForge.SF_Array.SetProperty
1735 REM -----------------------------------------------------------------------------
1736 Public Function Shuffle(Optional ByRef Array_1D As Variant) As Variant
1737 &apos;&apos;&apos; Returns a random permutation of a 1D array
1738 &apos;&apos;&apos; https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
1739 &apos;&apos;&apos; Args:
1740 &apos;&apos;&apos; Array_1D: the array to shuffle
1741 &apos;&apos;&apos; Returns: the shuffled array
1743 Dim vShuffle() As Variant &apos; Return value
1744 Dim vSwapValue As Variant &apos; Intermediate value during swap
1745 Dim lMin As Long &apos; LBound of Array_1D
1746 Dim lCurrentIndex As Long &apos; Decremented from UBount to LBound
1747 Dim lRandomIndex As Long &apos; Random between LBound and lCurrentIndex
1748 Dim i As Long
1749 Const cstThisSub = &quot;Array.Shuffle&quot;
1750 Const cstSubArgs = &quot;Array_1D&quot;
1752 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
1753 vShuffle = Array()
1755 Check:
1756 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
1757 If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
1758 End If
1760 Try:
1761 lMin = LBound(Array_1D)
1762 lCurrentIndex = UBound(array_1D)
1763 &apos; Initialize the output array
1764 ReDim vShuffle(lMin To lCurrentIndex)
1765 For i = lMin To lCurrentIndex
1766 vShuffle(i) = Array_1D(i)
1767 Next i
1768 &apos; Now ... shuffle !
1769 Do While lCurrentIndex &gt; lMin
1770 lRandomIndex = Int(Rnd * (lCurrentIndex - lMin)) + lMin
1771 vSwapValue = vShuffle(lCurrentIndex)
1772 vShuffle(lCurrentIndex) = vShuffle(lRandomIndex)
1773 vShuffle(lRandomIndex) = vSwapValue
1774 lCurrentIndex = lCurrentIndex - 1
1775 Loop
1777 Finally:
1778 Shuffle = vShuffle()
1779 SF_Utils._ExitFunction(cstThisSub)
1780 Exit Function
1781 Catch:
1782 GoTo Finally
1783 End Function &apos; ScriptForge.SF_Array.Shuffle
1785 REM -----------------------------------------------------------------------------
1786 Public Function Slice(Optional ByRef Array_1D As Variant _
1787 , Optional ByVal From As Variant _
1788 , Optional ByVal UpTo As Variant _
1789 ) As Variant
1790 &apos;&apos;&apos; Returns a subset of a 1D array
1791 &apos;&apos;&apos; Args:
1792 &apos;&apos;&apos; Array_1D: the array to slice
1793 &apos;&apos;&apos; From: the lower index of the subarray to extract (included)
1794 &apos;&apos;&apos; UpTo: the upper index of the subarray to extract (included). Default = the last item of Array_1D
1795 &apos;&apos;&apos; Returns:
1796 &apos;&apos;&apos; The selected subarray with the same LBound as the input array.
1797 &apos;&apos;&apos; If UpTo &lt; From then the returned array is empty
1798 &apos;&apos;&apos; Exceptions:
1799 &apos;&apos;&apos; ARRAYINDEX2ERROR Wrong values for From and/or UpTo
1800 &apos;&apos;&apos; Example:
1801 &apos;&apos;&apos; SF_Array.Slice(Array(1, 2, 3, 4, 5), 1, 3) returns (2, 3, 4)
1803 Dim vSlice() As Variant &apos; Return value
1804 Dim lMin As Long &apos; LBound of Array_1D
1805 Dim lIndex As Long &apos; Current index in output array
1806 Dim i As Long
1807 Const cstThisSub = &quot;Array.Slice&quot;
1808 Const cstSubArgs = &quot;Array_1D, From, [UpTo = UBound(Array_1D)]&quot;
1810 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
1811 vSlice = Array()
1813 Check:
1814 If IsMissing(UpTo) Or IsEmpty(UpTo) Then UpTo = -1
1815 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
1816 If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
1817 If Not SF_Utils._Validate(From, &quot;From&quot;, V_NUMERIC) Then GoTo Finally
1818 If Not SF_Utils._Validate(UpTo, &quot;UpTo&quot;, V_NUMERIC) Then GoTo Finally
1819 End If
1820 If UpTo = -1 Then UpTo = UBound(Array_1D)
1821 If From &lt; LBound(Array_1D) Or From &gt; UBound(Array_1D) _
1822 Or From &gt; UpTo Or UpTo &gt; UBound(Array_1D) Then GoTo CatchIndex
1824 Try:
1825 If UpTo &gt;= From Then
1826 lMin = LBound(Array_1D)
1827 &apos; Initialize the output array
1828 ReDim vSlice(lMin To lMin + UpTo - From)
1829 lIndex = lMin - 1
1830 For i = From To UpTo
1831 lIndex = lIndex + 1
1832 vSlice(lIndex) = Array_1D(i)
1833 Next i
1834 End If
1836 Finally:
1837 Slice = vSlice()
1838 SF_Utils._ExitFunction(cstThisSub)
1839 Exit Function
1840 Catch:
1841 GoTo Finally
1842 CatchIndex:
1843 SF_Exception.RaiseFatal(ARRAYINDEX2ERROR, SF_Array._Repr(Array_1D), From, UpTo)
1844 GoTo Finally
1845 End Function &apos; ScriptForge.SF_Array.Slice
1847 REM -----------------------------------------------------------------------------
1848 Public Function Sort(Optional ByRef Array_1D As Variant _
1849 , Optional ByVal SortOrder As Variant _
1850 , Optional ByVal CaseSensitive As Variant _
1851 ) As Variant
1852 &apos;&apos;&apos; Sort a 1D array in ascending or descending order. String comparisons can be case-sensitive or not
1853 &apos;&apos;&apos; Args:
1854 &apos;&apos;&apos; Array_1D: the array to sort
1855 &apos;&apos;&apos; must be filled homogeneously by either strings, dates or numbers
1856 &apos;&apos;&apos; Null and Empty values are allowed
1857 &apos;&apos;&apos; SortOrder: &quot;ASC&quot; (default) or &quot;DESC&quot;
1858 &apos;&apos;&apos; CaseSensitive: Default = False
1859 &apos;&apos;&apos; Returns: the sorted array
1860 &apos;&apos;&apos; Examples:
1861 &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;)
1863 Dim vSort() As Variant &apos; Return value
1864 Dim vIndexes() As Variant &apos; Indexes of sorted items
1865 Dim lMin As Long &apos; LBound of input array
1866 Dim lMax As Long &apos; UBound of input array
1867 Dim i As Long
1868 Const cstThisSub = &quot;Array.Sort&quot;
1869 Const cstSubArgs = &quot;Array_1D, [SortOrder=&quot;&quot;&quot;&quot;|&quot;&quot;ASC&quot;&quot;|&quot;&quot;DESC&quot;&quot;], [CaseSensitive=False]&quot;
1871 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
1872 vSort = Array()
1874 Check:
1875 If IsMissing(SortOrder) Or IsEmpty(SortOrder) Then SortOrder = &quot;ASC&quot;
1876 If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False
1877 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
1878 If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1, 0) Then GoTo Finally
1879 If Not SF_Utils._Validate(SortOrder, &quot;SortOrder&quot;, V_STRING, Array(&quot;ASC&quot;,&quot;DESC&quot;)) Then GoTo Finally
1880 If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
1881 End If
1883 Try:
1884 lMin = LBound(Array_1D)
1885 lMax = UBound(Array_1D)
1886 vIndexes() = SF_Array._HeapSort(Array_1D, ( SortOrder = &quot;ASC&quot; ), CaseSensitive)
1888 &apos; Load output array
1889 ReDim vSort(lMin To lMax)
1890 For i = lMin To lMax
1891 vSort(i) = Array_1D(vIndexes(i))
1892 Next i
1894 Finally:
1895 Sort = vSort()
1896 SF_Utils._ExitFunction(cstThisSub)
1897 Exit Function
1898 Catch:
1899 GoTo Finally
1900 End Function &apos; ScriptForge.SF_Array.Sort
1902 REM -----------------------------------------------------------------------------
1903 Public Function SortColumns(Optional ByRef Array_2D As Variant _
1904 , Optional ByVal RowIndex As Variant _
1905 , Optional ByVal SortOrder As Variant _
1906 , Optional ByVal CaseSensitive As Variant _
1907 ) As Variant
1908 &apos;&apos;&apos; Returns a permutation of the columns of a 2D array, sorted on the values of a given row
1909 &apos;&apos;&apos; Args:
1910 &apos;&apos;&apos; Array_2D: the input array
1911 &apos;&apos;&apos; RowIndex: the index of the row to sort the columns on
1912 &apos;&apos;&apos; the row must be filled homogeneously by either strings, dates or numbers
1913 &apos;&apos;&apos; Null and Empty values are allowed
1914 &apos;&apos;&apos; SortOrder: &quot;ASC&quot; (default) or &quot;DESC&quot;
1915 &apos;&apos;&apos; CaseSensitive: Default = False
1916 &apos;&apos;&apos; Returns:
1917 &apos;&apos;&apos; the array with permuted columns, LBounds and UBounds are unchanged
1918 &apos;&apos;&apos; Exceptions:
1919 &apos;&apos;&apos; ARRAYINDEXERROR
1920 &apos;&apos;&apos; Examples:
1921 &apos;&apos;&apos; | 5, 7, 3 | | 7, 5, 3 |
1922 &apos;&apos;&apos; SF_Array.SortColumns( | 1, 9, 5 |, 2, &quot;ASC&quot;) returns | 9, 1, 5 |
1923 &apos;&apos;&apos; | 6, 1, 8 | | 1, 6, 8 |
1925 Dim vSort() As Variant &apos; Return value
1926 Dim vRow() As Variant &apos; The row on which to sort the array
1927 Dim vIndexes() As Variant &apos; Indexes of sorted row
1928 Dim lMin1 As Long &apos; LBound1 of input array
1929 Dim lMax1 As Long &apos; UBound1 of input array
1930 Dim lMin2 As Long &apos; LBound2 of input array
1931 Dim lMax2 As Long &apos; UBound2 of input array
1932 Dim i As Long, j As Long
1933 Const cstThisSub = &quot;Array.SortColumn&quot;
1934 Const cstSubArgs = &quot;Array_2D, RowIndex, [SortOrder=&quot;&quot;&quot;&quot;|&quot;&quot;ASC&quot;&quot;|&quot;&quot;DESC&quot;&quot;], [CaseSensitive=False]&quot;
1936 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
1937 vSort = Array()
1939 Check:
1940 If IsMissing(SortOrder) Or IsEmpty(SortOrder) Then SortOrder = &quot;ASC&quot;
1941 If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False
1942 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
1943 If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally
1944 If Not SF_Utils._Validate(RowIndex, &quot;RowIndex&quot;, V_NUMERIC) Then GoTo Finally
1945 If Not SF_Utils._Validate(SortOrder, &quot;SortOrder&quot;, V_STRING, Array(&quot;ASC&quot;,&quot;DESC&quot;)) Then GoTo Finally
1946 If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
1947 End If
1949 Try:
1950 lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
1951 If RowIndex &lt; lMin1 Or RowIndex &gt; lMax1 Then GoTo CatchIndex
1952 lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
1954 &apos; Extract and sort the RowIndex-th row
1955 vRow = SF_Array.ExtractRow(Array_2D, RowIndex)
1956 If Not SF_Utils._ValidateArray(vRow, &quot;Row #&quot; &amp; CStr(RowIndex), 1, 0) Then GoTo Finally
1957 vIndexes() = SF_Array._HeapSort(vRow, ( SortOrder = &quot;ASC&quot; ), CaseSensitive)
1959 &apos; Load output array
1960 ReDim vSort(lMin1 To lMax1, lMin2 To lMax2)
1961 For i = lMin1 To lMax1
1962 For j = lMin2 To lMax2
1963 vSort(i, j) = Array_2D(i, vIndexes(j))
1964 Next j
1965 Next i
1967 Finally:
1968 SortColumns = vSort()
1969 SF_Utils._ExitFunction(cstThisSub)
1970 Exit Function
1971 Catch:
1972 GoTo Finally
1973 CatchIndex:
1974 &apos;TODO SF_Exception.RaiseFatal(ARRAYINDEXERROR, cstThisSub)
1975 MsgBox &quot;INVALID INDEX VALUE !!&quot;
1976 GoTo Finally
1977 End Function &apos; ScriptForge.SF_Array.SortColumns
1979 REM -----------------------------------------------------------------------------
1980 Public Function SortRows(Optional ByRef Array_2D As Variant _
1981 , Optional ByVal ColumnIndex As Variant _
1982 , Optional ByVal SortOrder As Variant _
1983 , Optional ByVal CaseSensitive As Variant _
1984 ) As Variant
1985 &apos;&apos;&apos; Returns a permutation of the rows of a 2D array, sorted on the values of a given column
1986 &apos;&apos;&apos; Args:
1987 &apos;&apos;&apos; Array_2D: the input array
1988 &apos;&apos;&apos; ColumnIndex: the index of the column to sort the rows on
1989 &apos;&apos;&apos; the column must be filled homogeneously by either strings, dates or numbers
1990 &apos;&apos;&apos; Null and Empty values are allowed
1991 &apos;&apos;&apos; SortOrder: &quot;ASC&quot; (default) or &quot;DESC&quot;
1992 &apos;&apos;&apos; CaseSensitive: Default = False
1993 &apos;&apos;&apos; Returns:
1994 &apos;&apos;&apos; the array with permuted Rows, LBounds and UBounds are unchanged
1995 &apos;&apos;&apos; Exceptions:
1996 &apos;&apos;&apos; ARRAYINDEXERROR
1997 &apos;&apos;&apos; Examples:
1998 &apos;&apos;&apos; | 5, 7, 3 | | 1, 9, 5 |
1999 &apos;&apos;&apos; SF_Array.SortRows( | 1, 9, 5 |, 0, &quot;ASC&quot;) returns | 5, 7, 3 |
2000 &apos;&apos;&apos; | 6, 1, 8 | | 6, 1, 8 |
2002 Dim vSort() As Variant &apos; Return value
2003 Dim vCol() As Variant &apos; The column on which to sort the array
2004 Dim vIndexes() As Variant &apos; Indexes of sorted row
2005 Dim lMin1 As Long &apos; LBound1 of input array
2006 Dim lMax1 As Long &apos; UBound1 of input array
2007 Dim lMin2 As Long &apos; LBound2 of input array
2008 Dim lMax2 As Long &apos; UBound2 of input array
2009 Dim i As Long, j As Long
2010 Const cstThisSub = &quot;Array.SortRow&quot;
2011 Const cstSubArgs = &quot;Array_2D, ColumnIndex, [SortOrder=&quot;&quot;&quot;&quot;|&quot;&quot;ASC&quot;&quot;|&quot;&quot;DESC&quot;&quot;], [CaseSensitive=False]&quot;
2013 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
2014 vSort = Array()
2016 Check:
2017 If IsMissing(SortOrder) Or IsEmpty(SortOrder) Then SortOrder = &quot;ASC&quot;
2018 If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False
2019 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
2020 If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally
2021 If Not SF_Utils._Validate(ColumnIndex, &quot;ColumnIndex&quot;, V_NUMERIC) Then GoTo Finally
2022 If Not SF_Utils._Validate(SortOrder, &quot;SortOrder&quot;, V_STRING, Array(&quot;ASC&quot;,&quot;DESC&quot;)) Then GoTo Finally
2023 If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
2024 End If
2026 Try:
2027 lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
2028 If ColumnIndex &lt; lMin2 Or ColumnIndex &gt; lMax2 Then GoTo CatchIndex
2029 lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
2031 &apos; Extract and sort the ColumnIndex-th column
2032 vCol = SF_Array.ExtractColumn(Array_2D, ColumnIndex)
2033 If Not SF_Utils._ValidateArray(vCol, &quot;Column #&quot; &amp; CStr(ColumnIndex), 1, 0) Then GoTo Finally
2034 vIndexes() = SF_Array._HeapSort(vCol, ( SortOrder = &quot;ASC&quot; ), CaseSensitive)
2036 &apos; Load output array
2037 ReDim vSort(lMin1 To lMax1, lMin2 To lMax2)
2038 For i = lMin1 To lMax1
2039 For j = lMin2 To lMax2
2040 vSort(i, j) = Array_2D(vIndexes(i), j)
2041 Next j
2042 Next i
2044 Finally:
2045 SortRows = vSort()
2046 SF_Utils._ExitFunction(cstThisSub)
2047 Exit Function
2048 Catch:
2049 GoTo Finally
2050 CatchIndex:
2051 &apos;TODO SF_Exception.RaiseFatal(ARRAYINDEXERROR, cstThisSub)
2052 MsgBox &quot;INVALID INDEX VALUE !!&quot;
2053 GoTo Finally
2054 End Function &apos; ScriptForge.SF_Array.SortRows
2056 REM -----------------------------------------------------------------------------
2057 Public Function Transpose(Optional ByRef Array_2D As Variant) As Variant
2058 &apos;&apos;&apos; Swaps rows and columns in a 2D array
2059 &apos;&apos;&apos; Args:
2060 &apos;&apos;&apos; Array_2D: the array to transpose
2061 &apos;&apos;&apos; Returns:
2062 &apos;&apos;&apos; The transposed array
2063 &apos;&apos;&apos; Examples:
2064 &apos;&apos;&apos; | 1, 2 | | 1, 3, 5 |
2065 &apos;&apos;&apos; SF_Array.Transpose( | 3, 4 | ) returns | 2, 4, 6 |
2066 &apos;&apos;&apos; | 5, 6 |
2068 Dim vTranspose As Variant &apos; Return value
2069 Dim lIndex As Long &apos; vTranspose index
2070 Dim lMin1 As Long &apos; LBound1 of input array
2071 Dim lMax1 As Long &apos; UBound1 of input array
2072 Dim lMin2 As Long &apos; LBound2 of input array
2073 Dim lMax2 As Long &apos; UBound2 of input array
2074 Dim i As Long, j As Long
2075 Const cstThisSub = &quot;Array.Transpose&quot;
2076 Const cstSubArgs = &quot;Array_2D&quot;
2078 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
2079 vTranspose = Array()
2081 Check:
2082 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
2083 If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally
2084 End If
2086 Try:
2087 &apos; Resize the output array
2088 lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
2089 lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
2090 If lMin1 &lt;= lMax1 Then
2091 ReDim vTranspose(lMin2 To lMax2, lMin1 To lMax1)
2092 End If
2094 &apos; Transpose items
2095 For i = lMin1 To lMax1
2096 For j = lMin2 To lMax2
2097 vTranspose(j, i) = Array_2D(i, j)
2098 Next j
2099 Next i
2101 Finally:
2102 Transpose = vTranspose
2103 SF_Utils._ExitFunction(cstThisSub)
2104 Exit Function
2105 Catch:
2106 GoTo Finally
2107 End Function &apos; ScriptForge.SF_Array.Transpose
2109 REM -----------------------------------------------------------------------------
2110 Public Function TrimArray(Optional ByRef Array_1D As Variant) As Variant
2111 &apos;&apos;&apos; Remove from a 1D array all Null, Empty and zero-length entries
2112 &apos;&apos;&apos; Strings are trimmed as well
2113 &apos;&apos;&apos; Args:
2114 &apos;&apos;&apos; Array_1D: the array to scan
2115 &apos;&apos;&apos; Return: The trimmed array
2116 &apos;&apos;&apos; Examples:
2117 &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;)
2119 Dim vTrimArray As Variant &apos; Return value
2120 Dim lIndex As Long &apos; vTrimArray index
2121 Dim lMin As Long &apos; LBound of input array
2122 Dim lMax As Long &apos; UBound of input array
2123 Dim vItem As Variant &apos; Single array item
2124 Dim i As Long
2125 Const cstThisSub = &quot;Array.TrimArray&quot;
2126 Const cstSubArgs = &quot;Array_1D&quot;
2128 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
2129 vTrimArray = Array()
2131 Check:
2132 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
2133 If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
2134 End If
2136 Try:
2137 lMin = LBound(Array_1D)
2138 lMax = UBound(Array_1D)
2139 If lMin &lt;= lMax Then
2140 ReDim vTrimArray(lMin To lMax)
2141 End If
2142 lIndex = lMin - 1
2144 &apos; Load only valid items from Array_1D to vTrimArray
2145 For i = lMin To lMax
2146 vItem = Array_1D(i)
2147 Select Case VarType(vItem)
2148 Case V_EMPTY
2149 Case V_NULL : vItem = Empty
2150 Case V_STRING
2151 vItem = Trim(vItem)
2152 If Len(vItem) = 0 Then vItem = Empty
2153 Case Else
2154 End Select
2155 If Not IsEmpty(vItem) Then
2156 lIndex = lIndex + 1
2157 vTrimArray(lIndex) = vItem
2158 End If
2159 Next i
2161 &apos;Keep valid entries
2162 If lMin &lt;= lIndex Then
2163 ReDim Preserve vTrimArray(lMin To lIndex)
2164 Else
2165 vTrimArray = Array()
2166 End If
2168 Finally:
2169 TrimArray = vTrimArray
2170 SF_Utils._ExitFunction(cstThisSub)
2171 Exit Function
2172 Catch:
2173 GoTo Finally
2174 End Function &apos; ScriptForge.SF_Array.TrimArray
2176 REM -----------------------------------------------------------------------------
2177 Public Function Union(Optional ByRef Array1_1D As Variant _
2178 , Optional ByRef Array2_1D As Variant _
2179 , Optional ByVal CaseSensitive As Variant _
2180 ) As Variant
2181 &apos;&apos;&apos; Build a set being the Union of the two input arrays, i.e. items are contained in any of both arrays
2182 &apos;&apos;&apos; both input arrays must be filled homogeneously, i.e. all items must be of the same type
2183 &apos;&apos;&apos; Empty and Null items are forbidden
2184 &apos;&apos;&apos; The comparison between strings is case sensitive or not
2185 &apos;&apos;&apos; Args:
2186 &apos;&apos;&apos; Array1_1D: a 1st input array
2187 &apos;&apos;&apos; Array2_1D: a 2nd input array
2188 &apos;&apos;&apos; CaseSensitive: default = False
2189 &apos;&apos;&apos; Returns: a zero-based array containing unique items stored in any of both input arrays
2190 &apos;&apos;&apos; The output array is sorted in ascending order
2191 &apos;&apos;&apos; Examples:
2192 &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;)
2194 Dim vUnion() As Variant &apos; Return value
2195 Dim iType As Integer &apos; VarType of elements in input arrays
2196 Dim lMin1 As Long &apos; LBound of 1st input array
2197 Dim lMax1 As Long &apos; UBound of 1st input array
2198 Dim lMin2 As Long &apos; LBound of 2nd input array
2199 Dim lMax2 As Long &apos; UBound of 2nd input array
2200 Dim lSize As Long &apos; Number of Union items
2201 Dim i As Long
2202 Const cstThisSub = &quot;Array.Union&quot;
2203 Const cstSubArgs = &quot;Array1_1D, Array2_1D, [CaseSensitive=False]&quot;
2205 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
2206 vUnion = Array()
2208 Check:
2209 If IsMissing(CaseSensitive) Then CaseSensitive = False
2210 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
2211 If Not SF_Utils._ValidateArray(Array1_1D, &quot;Array1_1D&quot;, 1, 0, True) Then GoTo Finally
2212 iType = SF_Utils._VarTypeExt(Array1_1D(LBound(Array1_1D)))
2213 If Not SF_Utils._ValidateArray(Array2_1D, &quot;Array2_1D&quot;, 1, iType, True) Then GoTo Finally
2214 If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
2215 End If
2217 Try:
2218 lMin1 = LBound(Array1_1D) : lMax1 = UBound(Array1_1D)
2219 lMin2 = LBound(Array2_1D) : lMax2 = UBound(Array2_1D)
2221 &apos; If both arrays are empty, do nothing
2222 If lMax1 &lt; lMin1 And lMax2 &lt; lMin2 Then
2223 ElseIf lMax1 &lt; lMin1 Then &apos; only 1st array is empty
2224 vUnion = SF_Array.Unique(Array2_1D, CaseSensitive)
2225 ElseIf lMax2 &lt; lMin2 Then &apos; only 2nd array is empty
2226 vUnion = SF_Array.Unique(Array1_1D, CaseSensitive)
2227 Else
2229 &apos; Build union of both arrays
2230 ReDim vUnion(0 To (lMax1 - lMin1) + (lMax2 - lMin2) + 1)
2231 lSize = -1
2233 &apos; Fill vUnion one by one only with items present in any set
2234 For i = lMin1 To lMax1
2235 lSize = lSize + 1
2236 vUnion(lSize) = Array1_1D(i)
2237 Next i
2238 For i = lMin2 To lMax2
2239 lSize = lSize + 1
2240 vUnion(lSize) = Array2_1D(i)
2241 Next i
2243 &apos; Remove duplicates
2244 vUnion() = SF_Array.Unique(vUnion, CaseSensitive)
2245 End If
2247 Finally:
2248 Union = vUnion()
2249 SF_Utils._ExitFunction(cstThisSub)
2250 Exit Function
2251 Catch:
2252 GoTo Finally
2253 End Function &apos; ScriptForge.SF_Array.Union
2255 REM -----------------------------------------------------------------------------
2256 Public Function Unique(Optional ByRef Array_1D As Variant _
2257 , Optional ByVal CaseSensitive As Variant _
2258 ) As Variant
2259 &apos;&apos;&apos; Build a set of unique values derived from the input array
2260 &apos;&apos;&apos; the input array must be filled homogeneously, i.e. all items must be of the same type
2261 &apos;&apos;&apos; Empty and Null items are forbidden
2262 &apos;&apos;&apos; The comparison between strings is case sensitive or not
2263 &apos;&apos;&apos; Args:
2264 &apos;&apos;&apos; Array_1D: the input array with potential duplicates
2265 &apos;&apos;&apos; CaseSensitive: default = False
2266 &apos;&apos;&apos; Returns: the array without duplicates with same LBound as input array
2267 &apos;&apos;&apos; The output array is sorted in ascending order
2268 &apos;&apos;&apos; Examples:
2269 &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;)
2271 Dim vUnique() As Variant &apos; Return value
2272 Dim vSorted() As Variant &apos; The input array after sort
2273 Dim lMin As Long &apos; LBound of input array
2274 Dim lMax As Long &apos; UBound of input array
2275 Dim lUnique As Long &apos; Number of unique items
2276 Dim vIndex As Variant &apos; Output of _FindItem() method
2277 Dim vItem As Variant &apos; One single item in the array
2278 Dim i As Long
2279 Const cstThisSub = &quot;Array.Unique&quot;
2280 Const cstSubArgs = &quot;Array_1D, [CaseSensitive=False]&quot;
2282 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
2283 vUnique = Array()
2285 Check:
2286 If IsMissing(CaseSensitive) Then CaseSensitive = False
2287 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
2288 If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1, 0, True) Then GoTo Finally
2289 If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
2290 End If
2292 Try:
2293 lMin = LBound(Array_1D)
2294 lMax = UBound(Array_1D)
2295 If lMax &gt;= lMin Then
2296 &apos; First sort the array
2297 vSorted = SF_Array.Sort(Array_1D, &quot;ASC&quot;, CaseSensitive)
2298 ReDim vUnique(lMin To lMax)
2299 lUnique = lMin
2300 &apos; Fill vUnique one by one ignoring duplicates
2301 For i = lMin To lMax
2302 vItem = vSorted(i)
2303 If i = lMin Then
2304 vUnique(i) = vItem
2305 Else
2306 If SF_Array._ValCompare(vItem, vSorted(i - 1), CaseSensitive) = 0 Then &apos; Ignore item
2307 Else
2308 lUnique = lUnique + 1
2309 vUnique(lUnique) = vItem
2310 End If
2311 End If
2312 Next i
2313 &apos; Remove unfilled entries
2314 ReDim Preserve vUnique(lMin To lUnique)
2315 End If
2317 Finally:
2318 Unique = vUnique()
2319 SF_Utils._ExitFunction(cstThisSub)
2320 Exit Function
2321 Catch:
2322 GoTo Finally
2323 End Function &apos; ScriptForge.SF_Array.Unique
2325 REM ============================================================= PRIVATE METHODS
2327 REM -----------------------------------------------------------------------------
2328 Public Function _FindItem(ByRef pvArray_1D As Variant _
2329 , ByVal pvToFind As Variant _
2330 , ByVal pbCaseSensitive As Boolean _
2331 , ByVal psSortOrder As String _
2332 ) As Variant
2333 &apos;&apos;&apos; Check if a 1D array contains the ToFind number, string or date and return its index
2334 &apos;&apos;&apos; The comparison between strings can be done case-sensitively or not
2335 &apos;&apos;&apos; If the array is sorted then a binary search is done
2336 &apos;&apos;&apos; Otherwise the array is scanned from top. Null or Empty items are simply ignored
2337 &apos;&apos;&apos; Args:
2338 &apos;&apos;&apos; pvArray_1D: the array to scan
2339 &apos;&apos;&apos; pvToFind: a number, a date or a string to find
2340 &apos;&apos;&apos; pbCaseSensitive: Only for string comparisons, default = False
2341 &apos;&apos;&apos; psSortOrder: &quot;ASC&quot;, &quot;DESC&quot; or &quot;&quot; (= not sorted, default)
2342 &apos;&apos;&apos; Return: a (0:1) array
2343 &apos;&apos;&apos; (0) = True when found
2344 &apos;&apos;&apos; (1) = if found: index of item
2345 &apos;&apos;&apos; if not found: if sorted, index of next item in the array (might be = UBound + 1)
2346 &apos;&apos;&apos; if not sorted, meaningless
2347 &apos;&apos;&apos; Result is unpredictable when array is announced sorted and is in reality not
2348 &apos;&apos;&apos; Called by Contains, IndexOf and InsertSorted. Also called by SF_Dictionary
2350 Dim bContains As Boolean &apos; True if match found
2351 Dim iToFindType As Integer &apos; VarType of pvToFind
2352 Dim lTop As Long, lBottom As Long &apos; Interval in scope of binary search
2353 Dim lIndex As Long &apos; Index used in search
2354 Dim iCompare As Integer &apos; Output of _ValCompare function
2355 Dim lLoops As Long &apos; Count binary searches
2356 Dim lMaxLoops As Long &apos; Max number of loops during binary search: to avoid infinite loops if array not sorted
2357 Dim vFound(1) As Variant &apos; Returned array (Contains, Index)
2359 bContains = False
2361 If LBound(pvArray_1D) &gt; UBound(pvArray_1D) Then &apos; Empty array, do nothing
2362 Else
2363 &apos; Search sequentially
2364 If Len(psSortOrder) = 0 Then
2365 For lIndex = LBound(pvArray_1D) To UBound(pvArray_1D)
2366 bContains = ( SF_Array._ValCompare(pvToFind, pvArray_1D(lIndex), pbCaseSensitive) = 0 )
2367 If bContains Then Exit For
2368 Next lIndex
2369 Else
2370 &apos; Binary search
2371 If psSortOrder = &quot;ASC&quot; Then
2372 lTop = UBound(pvArray_1D)
2373 lBottom = lBound(pvArray_1D)
2374 Else
2375 lBottom = UBound(pvArray_1D)
2376 lTop = lBound(pvArray_1D)
2377 End If
2378 lLoops = 0
2379 lMaxLoops = CLng((Log(UBound(pvArray_1D) - LBound(pvArray_1D) + 1.0) / Log(2.0))) + 1
2381 lLoops = lLoops + 1
2382 lIndex = (lTop + lBottom) / 2
2383 iCompare = SF_Array._ValCompare(pvToFind, pvArray_1D(lIndex), pbCaseSensitive)
2384 Select Case True
2385 Case iCompare = 0 : bContains = True
2386 Case iCompare &lt; 0 And psSortOrder = &quot;ASC&quot;
2387 lTop = lIndex - 1
2388 Case iCompare &gt; 0 And psSortOrder = &quot;DESC&quot;
2389 lBottom = lIndex - 1
2390 Case iCompare &gt; 0 And psSortOrder = &quot;ASC&quot;
2391 lBottom = lIndex + 1
2392 Case iCompare &lt; 0 And psSortOrder = &quot;DESC&quot;
2393 lTop = lIndex + 1
2394 End Select
2395 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
2396 &apos; Flag first next non-matching element
2397 If Not bContains Then lIndex = Iif(psSortOrder = &quot;ASC&quot;, lBottom, lTop)
2398 End If
2399 End If
2401 &apos; Build output array
2402 vFound(0) = bContains
2403 vFound(1) = lIndex
2404 _FindItem = vFound
2406 End Function &apos; ScriptForge.SF_Array._FindItem
2408 REM -----------------------------------------------------------------------------
2409 Private Function _HeapSort(ByRef pvArray As Variant _
2410 , Optional ByVal pbAscending As Boolean _
2411 , Optional ByVal pbCaseSensitive As Boolean _
2412 ) As Variant
2413 &apos;&apos;&apos; Sort an array: items are presumed all strings, all dates or all numeric
2414 &apos;&apos;&apos; Null or Empty are allowed and are considered smaller than other items
2415 &apos;&apos;&apos; https://en.wikipedia.org/wiki/Heapsort
2416 &apos;&apos;&apos; http://www.vbforums.com/showthread.php?473677-VB6-Sorting-algorithms-(sort-array-sorting-arrays)&amp;p=2909250#post2909250
2417 &apos;&apos;&apos; HeapSort preferred to QuickSort because not recursive (this routine returns an array of indexes !!)
2418 &apos;&apos;&apos; Args:
2419 &apos;&apos;&apos; pvArray: a 1D array
2420 &apos;&apos;&apos; pbAscending: default = True
2421 &apos;&apos;&apos; pbCaseSensitive: default = False
2422 &apos;&apos;&apos; Returns
2423 &apos;&apos;&apos; An array of Longs of same dimensions as the input array listing the indexes of the sorted items
2424 &apos;&apos;&apos; An empty array if the sort failed
2425 &apos;&apos;&apos; Examples:
2426 &apos;&apos;&apos; _HeapSort(Array(4, 2, 6, 1) returns (3, 1, 0, 2)
2428 Dim vIndexes As Variant &apos; Return value
2429 Dim i As Long
2430 Dim lMin As Long, lMax As Long &apos; Array bounds
2431 Dim lSwap As Long &apos; For index swaps
2433 If IsMissing(pbAscending) Then pbAscending = True
2434 If IsMissing(pbCaseSensitive) Then pbCaseSensitive = False
2435 vIndexes = Array()
2436 lMin = LBound(pvArray, 1)
2437 lMax = UBound(pvArray, 1)
2439 &apos; Initialize output array
2440 ReDim vIndexes(lMin To lMax)
2441 For i = lMin To lMax
2442 vIndexes(i) = i
2443 Next i
2445 &apos; Initial heapify
2446 For i = (lMax + lMin) \ 2 To lMin Step -1
2447 SF_Array._HeapSort1(pvArray, vIndexes, i, lMin, lMax, pbCaseSensitive)
2448 Next i
2449 &apos; Next heapify
2450 For i = lMax To lMin + 1 Step -1
2451 &apos; Only indexes as swapped, not the array items themselves
2452 lSwap = vIndexes(i)
2453 vIndexes(i) = vIndexes(lMin)
2454 vIndexes(lMin) = lSwap
2455 SF_Array._HeapSort1(pvArray, vIndexes, lMin, lMin, i - 1, pbCaseSensitive)
2456 Next i
2458 If pbAscending Then _HeapSort = vIndexes() Else _HeapSort = SF_Array.Reverse(vIndexes())
2460 End Function &apos; ScriptForge.SF_Array._HeapSort
2462 REM -----------------------------------------------------------------------------
2463 Private Sub _HeapSort1(ByRef pvArray As Variant _
2464 , ByRef pvIndexes As Variant _
2465 , ByVal plIndex As Long _
2466 , ByVal plMin As Long _
2467 , ByVal plMax As Long _
2468 , ByVal pbCaseSensitive As Boolean _
2470 &apos;&apos;&apos; Sub called by _HeapSort only
2472 Dim lLeaf As Long
2473 Dim lSwap As Long
2476 lLeaf = plIndex + plIndex - (plMin - 1)
2477 Select Case lLeaf
2478 Case Is &gt; plMax: Exit Do
2479 Case Is &lt; plMax
2480 If SF_Array._ValCompare(pvArray(pvIndexes(lLeaf + 1)), pvArray(pvIndexes(lLeaf)), pbCaseSensitive) &gt; 0 Then lLeaf = lLeaf + 1
2481 End Select
2482 If SF_Array._ValCompare(pvArray(pvIndexes(plIndex)), pvArray(pvIndexes(lLeaf)), pbCaseSensitive) &gt; 0 Then Exit Do
2483 &apos; Only indexes as swapped, not the array items themselves
2484 lSwap = pvIndexes(plIndex)
2485 pvIndexes(plIndex) = pvIndexes(lLeaf)
2486 pvIndexes(lLeaf) = lSwap
2487 plIndex = lLeaf
2488 Loop
2490 End Sub &apos; ScriptForge.SF_Array._HeapSort1
2492 REM -----------------------------------------------------------------------------
2493 Private Function _Repr(ByRef pvArray As Variant) As String
2494 &apos;&apos;&apos; Convert array to a readable string, typically for debugging purposes (DebugPrint ...)
2495 &apos;&apos;&apos; Args:
2496 &apos;&apos;&apos; pvArray: the array to convert, individual items may be of any type, including arrays
2497 &apos;&apos;&apos; Return:
2498 &apos;&apos;&apos; &quot;[ARRAY] (L:U[, L:U]...)&quot; if # of Dims &gt; 1
2499 &apos;&apos;&apos; &quot;[ARRAY] (L:U) (item1,item2, ...)&quot; if 1D array
2501 Dim iDims As Integer &apos; Number of dimensions of the array
2502 Dim sArray As String &apos; Return value
2503 Dim i As Long
2504 Const cstArrayEmpty = &quot;[ARRAY] ()&quot;
2505 Const cstArray = &quot;[ARRAY]&quot;
2506 Const cstMaxLength = 50 &apos; Maximum length for items
2507 Const cstSeparator = &quot;, &quot;
2509 _Repr = &quot;&quot;
2510 iDims = SF_Array.CountDims(pvArray)
2512 Select Case iDims
2513 Case -1 : Exit Function &apos; Not an array
2514 Case 0 : sArray = cstArrayEmpty
2515 Case Else
2516 sArray = cstArray
2517 For i = 1 To iDims
2518 sArray = sArray &amp; Iif(i = 1, &quot; (&quot;, &quot;, &quot;) &amp; CStr(LBound(pvArray, i)) &amp; &quot;:&quot; &amp; CStr(UBound(pvArray, i))
2519 Next i
2520 sArray = sArray &amp; &quot;)&quot;
2521 &apos; List individual items of 1D arrays
2522 If iDims = 1 Then
2523 sArray = sArray &amp; &quot; (&quot;
2524 For i = LBound(pvArray) To UBound(pvArray)
2525 sArray = sArray &amp; SF_Utils._Repr(pvArray(i), cstMaxLength) &amp; cstSeparator &apos; Recursive call
2526 Next i
2527 sArray = Left(sArray, Len(sArray) - Len(cstSeparator)) &apos; Suppress last comma
2528 sArray = sArray &amp; &quot;)&quot;
2529 End If
2530 End Select
2532 _Repr = sArray
2534 End Function &apos; ScriptForge.SF_Array._Repr
2536 REM -----------------------------------------------------------------------------
2537 Public Function _StaticType(ByRef pvArray As Variant) As Integer
2538 &apos;&apos;&apos; If array is static, return its type
2539 &apos;&apos;&apos; Args:
2540 &apos;&apos;&apos; pvArray: array to examine
2541 &apos;&apos;&apos; Return:
2542 &apos;&apos;&apos; array type, -1 if not identified
2543 &apos;&apos;&apos; All numeric types are aggregated into V_NUMERIC
2545 Dim iArrayType As Integer &apos; VarType of array
2546 Dim iType As Integer &apos; VarType of items
2548 iArrayType = VarType(pvArray)
2549 iType = iArrayType - V_ARRAY
2550 Select Case iType
2551 Case V_INTEGER, V_LONG, V_SINGLE, V_DOUBLE, V_CURRENCY, V_BIGINT, V_DECIMAL, V_BOOLEAN
2552 _StaticType = V_NUMERIC
2553 Case V_STRING, V_DATE
2554 _StaticType = iType
2555 Case Else
2556 _StaticType = -1
2557 End Select
2559 End Function &apos; ScriptForge.SF_Utils._StaticType
2561 REM -----------------------------------------------------------------------------
2562 Private Function _ValCompare(ByVal pvValue1 As Variant _
2563 , pvValue2 As Variant _
2564 , Optional ByVal pbCaseSensitive As Boolean _
2565 ) As Integer
2566 &apos;&apos;&apos; Compare 2 values : equality, greater than or smaller than
2567 &apos;&apos;&apos; Args:
2568 &apos;&apos;&apos; pvValue1 and pvValue2: values to compare. pvValues must be String, Number, Date, Empty or Null
2569 &apos;&apos;&apos; By convention: Empty &lt; Null &lt; string, number or date
2570 &apos;&apos;&apos; pbCaseSensitive: ignored when not String comparison
2571 &apos;&apos;&apos; Return: -1 when pvValue1 &lt; pvValue2
2572 &apos;&apos;&apos; +1 when pvValue1 &gt; pvValue2
2573 &apos;&apos;&apos; 0 when pvValue1 = pvValue2
2574 &apos;&apos;&apos; -2 when comparison is nonsense
2576 Dim iCompare As Integer, iVarType1 As Integer, iVarType2 As Integer
2578 If IsMissing(pbCaseSensitive) Then pbCaseSensitive = False
2579 iVarType1 = SF_Utils._VarTypeExt(pvValue1)
2580 iVarType2 = SF_Utils._VarTypeExt(pvValue2)
2582 iCompare = -2
2583 If iVarType1 = V_OBJECT Or iVarType1 = V_BYTE Or iVarType1 &gt;= V_ARRAY Then &apos; Nonsense
2584 ElseIf iVarType2 = V_OBJECT Or iVarType2 = V_BYTE Or iVarType2 &gt;= V_ARRAY Then &apos; Nonsense
2585 ElseIf iVarType1 = V_STRING And iVarType2 = V_STRING Then
2586 iCompare = StrComp(pvValue1, pvValue2, Iif(pbCaseSensitive, 1, 0))
2587 ElseIf iVarType1 = V_NULL Or iVarType1 = V_EMPTY Or iVarType2 = V_NULL Or iVarType2 = V_EMPTY Then
2588 Select Case True
2589 Case pvValue1 = pvValue2 : iCompare = 0
2590 Case iVarType1 = V_NULL And iVarType2 = V_EMPTY : iCompare = +1
2591 Case iVarType1 = V_EMPTY And iVarType2 = V_NULL : iCompare = -1
2592 Case iVarType1 = V_NULL Or iVarType1 = V_EMPTY : iCompare = -1
2593 Case iVarType2 = V_NULL Or iVarType2 = V_EMPTY : iCompare = +1
2594 End Select
2595 ElseIf iVarType1 = iVarType2 Then
2596 Select Case True
2597 Case pvValue1 &lt; pvValue2 : iCompare = -1
2598 Case pvValue1 = pvValue2 : iCompare = 0
2599 Case pvValue1 &gt; pvValue2 : iCompare = +1
2600 End Select
2601 End If
2603 _ValCompare = iCompare
2605 End Function &apos; ScriptForge.SF_Array._ValCompare
2607 REM ================================================= END OF SCRIPTFORGE.SF_ARRAY
2608 </script:module>