SF_Array.xba 104 KB

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