;; 
;;=====================================================================================----- 
;; 
;;FUNCTION       IniArray() / ReadIniArray() / WriteIniArray() / EnumIniArray() 
;;                  ReadIniArray()  - subfunction for reading array 
;;                  WriteIniArray() - subfunction for writing array 
;;                  EnumIniArray()  - subfunction to enumerate an array or section 
;; 
;;ACTION         Uses an array image of an INI file to allow large files and fast operation 
;;               Reads INI file into an array; 
;;               Writes array to INI format file & reloads the array with fresh data 
;;                 ReadIniArray() reads a Section:Key:Data set from the array 
;;                 WriteIniArray() writes a Section:Key:Data set to the array 
;;                 EnumIniArray() returns a list of sections or values within a section 
;; 
;;AUTHOR         Glenn Barnas 
;; 
;;VERSION        1.5  - 2020/07/23 
;; 
;;HISTORY        1.0  - 2011/08/02 
;;               1.1  - 2013/04/17 - Improve file load time on large files. 
;;               1.2  - 2013/04/20 - Read empty section or file, write empty section,  
;;                                   WriteIniArray() fix error if duplicate write of null 
;;                                   data Added Options to IniArray for Write to add blank 
;;                                   lines between sections. 
;;               1.3  - 2014/06/23 - Fixed issue when data contained equal signs. 
;;               1.4  - 2019/08/08 - Added the ability to pass an array of INI file 
;;                                   data instead of the filename 
;;               1.5  - 2020/07/23 - Added EnumIniArray() support function 
;; 
;;SYNTAX         IniArray(filename [,array] [,options]) 
;; 
;;PARAMETERS     Filename - REQUIRED - String 
;;               - The name of the INI file to read/write. If an array is detected, it will be assumed that  
;;                 the array contains the contents of an INI File, possibly read from FileIO(). 
;; 
;;               Array - OPTIONAL - Array 
;;               - The properly formatted array to write 
;; 
;;               Options - OPTIONAL - Integer 
;;               - A bitwise settings to control output formatting 
;;               Value   Mode    Description 
;;               1       Write   Adds blank lines between Sections when true 
;;               2       Write   Suppress the array reload on write, useful when no further INI manipulation is planned. 
;; 
;;REMARKS        Reads an entire INI file into an array for processing. There is no 
;;               limit on the size of the INI file, other than any Kix limits on 
;;               general file sizes. On read, blank lines and comments (lines that 
;;               begin with ";" or "#") are ignored. On write, only sections that 
;;               contain data/value pairs are written, mimicing the action of 
;;               WriteProfileString(). Similarly, only Keys that have non-null 
;;               values are written. 
;; 
;;               The secondary functions ReadIniArray() and WriteIniArray() make 
;;               using the IniArray() UDF as easy to use as ReadProfileString() 
;;               and WriteProfileString(), simply requiring calls to IniArray to 
;;               load and then save the INI file. The Read and Write sub-functions 
;;               use the same syntax as the ProfileString functions but reference 
;;               the array instead of the INI file. 
;; 
;;               NOTE: The WriteIniArray function returns the updated array. This 
;;               is the only significant deviation from the WriteProfileString operation, 
;;               which returns status of the write. 
;; 
;;               NOTE: During array manipulation, deleted records are set to null 
;;               and not reused. Similarly, when a new key/data pair is added, deleted 
;;               array items are not reused. When the array is written to disk, the 
;;               null records are skipped. When the file is again read, the array will 
;;               only contain valid data with no empty records. 
;;               WRITING an array causes a RE-READ operation, cleaning the empty records. 
;; 
;;               NOTE: IMPORTANT - When using WriteIniArray() to create a new Ini-array, 
;;               you MUST first declare the array via Dim $array[1, 0]. This is not 
;;               necessary if you read an INI file first. 
;;  
;;RETURNS        Returns a two-dimensional array of two-dimensional arrays. 
;;               The first element is the section name, the second element is a 
;;               two-dimensional array consisting of the key/data pairs. 
;; 
;;                 Consider the following simple INI file: 
;;                  [COLORS] 
;;                  Apple=Red 
;;                  Lime=Green 
;;                  [TASTES] 
;;                  Apple=sweet 
;;                  Lime=sour 
;; 
;;                 The call $aINI = IniArray('inifile.ini') would return an array that 
;;                 contained the following values: 
;;                  $aINI[0,0] contains "COLORS" 
;;                  $aINI[1,0] contains an array of data/value pairs from this section 
;;                  - extract with $aData = $aINI[1,0] 
;;                   $aData[0,0] contains "Apple"; $aData[1,0] contains "Red" 
;;                   $aData[0,1] contains "Lime"; $aData[1,1] contains "Green" 
;; 
;;                  $aINI[0,1] contains "TASTES" 
;;                  $aINI[1,1] contains Array of data/value pairs from this section 
;;                  - extract with $aData = $aINI[1,1] 
;;                   $aData[0,0] contains "Apple"; $aData[1,0] contains "Sweet" 
;;                   $aData[0,1] contains "Lime"; $aData[1,1] contains "Sour" 
;; 
;;                  ; the following would return "Sweet", just like ReadProfileString 
;;                  $Taste = ReadIniArray($aINI, 'TASTES', 'Apple') 
;; 
;; 
;;DEPENDENCIES   none 
;; 
;;TESTED WITH    W2K, WXP, W2K3, W2K8, W7 
;; 
;;EXAMPLES       INI FILE READ: 
;;                   $aIni = IniArray('testfile.ini') 
;;                   'File contains ' UBound($aIni, 2) + 1 ' sections' ? 
;; 
;;                 ENUMERATE: 
;;                   For $I = 0 to UBound($aIni, 2) 
;;                     $aData = $aIni[1, $I] 
;;                   
;;                     'Section: ' $aIni[0, $I] ' contains ' UBound($aData, 2) + 1 ' Data/Value elements' ? 
;;                     ' Press a key: ' get $ ? 
;;                    
;;                     For $J = 0 to UBound($aData, 2) 
;;                       $J ': ' $aData[0, $J] ' = ' $aData[1, $J] ? 
;;                     Next 
;;                   Next 
;; 
;;                  ELEMENT READ: 
;;                   ; Return a specific value 
;;                   $Value = ReadIniArray($aIni, 'SectionName', 'keyname') 
;;                   ; Return an array of all section names 
;;                   $aSections = Split(ReadIniArray($aIni), Chr(10)) 
;;                   ; Return an array of Keys in a specific section 
;;                   $aKeys = Split(ReadIniArray($aIni, 'SectionName'), Chr(10)) 
;; 
;;                  ELEMENT WRITE/UPDATE: 
;;                   ; Write a key/value pair in the named section 
;;                   $aIni = WriteIniArray($aIni, 'SectionName', 'keyname', 'Data') 
;; 
;;                  ELEMENT DELETE: 
;;                   ; Remove the named key from the defined section 
;;                   $aIni = WriteIniArray($aIni, 'SectionName', 'keyname') 
;; 
;;                  SECTION DELETE: 
;;                   ; Remove all key/value pairs from the section, deleting the section 
;;                   $aIni = WriteIniArray($aIni, 'SectionName') 
;; 
;;                  INI FILE WRITE: 
;;                   ; Flush the array to the file and then reload the array 
;;                   $aIni = IniArray('testfile.ini', $aIni) 
; 
Function IniArray($_fSrcFile, OPTIONAL $_aDataWrite, OPTIONAL $_Options)
 
  Dim $_                                        ; temp var 
  Dim $_Fp					; file pointer 
  Dim $_C, $_I, $_J, $K				; index pointers 
  Dim $_AddLine, $_NoRead			; Option Vars 
  Dim $_Sect					; Section Name 
  Dim $_aDat					; Data pair array 
  Dim $_aSect[1, 499], $_aData[1, 499]		; Section & Data Arrays 
  Dim $_Invalid					; Invalid data found - warning 
  
  ; if we have an array instead of a file name, it's file content. 
  If VarType($_fSrcFile) > 8192 GoTo 'IA_FILEDATA' EndIf
 
  $_NoRead = 0					; perform read after write 
 
  If $_Options
    If $_Options & 1
      $_AddLine = @CRLF
    EndIf
    If $_Options & 2
      $_NoRead = 1				; write & exit w/o re-reading 
    EndIf
  EndIf
 
 
  ; Obtain a File Handle for Read or Write operations 
  ; ============================================================ 
  $_Fp = FreeFileHandle				; locate an available file handle 
  If Not $_Fp
    Exit 1					; none available - exit 
  EndIf
 
  ; WRITE: verify that we have properly formatted data 
  ; ============================================================ 
  If VarType($_aDataWrite)  > 0			; Write the array to the INI file and exit 
    If VarType($_aDataWrite) < 8192		; data but not an array - exit! 
      Exit 87
    EndIf
 
    Del $_fSrcFile				; delete any pre-existing file 
    $_ = Open($_Fp, $_fSrcFile, 5)		; open the file for write/create 
    If @ERROR Exit @ERROR EndIf			; exit if error opening 
 
    ; Write the section data. If no data exists in the section, do not write anything! 
    For $_I = 0 to UBound($_aDataWrite, 2)
      $_Sect  = $_aDataWrite[0, $_I]		; Section name to write 
      $_aData = $_aDataWrite[1, $_I]		; Data array 
      If UBound($_aData, 2) > -1		; create Sect and write data if data is present 
        $_ = '[' + $_Sect + ']' + @CRLF		; section name 
        $_C = 0
        For $_J = 0 to UBound($_aData, 2)	; key/data pairs 
          If $_aData[1, $_J]			; only write keys that have non-null data 
            $_C = 1
            $_ = $_ + $_aData[0, $_J] + '=' + $_aData[1, $_J] + @CRLF
          EndIf
        Next
        If $_C
          $_ = WriteLine($_Fp, $_ + $_AddLine)	; write the output line 
	EndIf
        If @ERROR
	  $_ = Close($_Fp)			; close the file 
	  Exit @ERROR
	EndIf		; exit if error writing 
      EndIf
    Next
 
    $_ = Close($_Fp)				; close the file 
    ReDim $_aData[1, 0]				; clear array 
 
    If $_NoRead
      exit 0					; exit here without a re-read of the freshly written data 
    EndIf
 
  EndIf
 
  ; READ: Load the ini file into an array 
  ; ============================================================ 
  $_I = -1  $_J = -1				; Initialize index pointers 
  
  $_ = Open($_Fp, $_fSrcFile, 2)		; open the file for read 
  If @ERROR
    ReDim $_aSect[1, 0]
    ReDim $_aData[1, 0]
    $_aSect[1, 0] = $_aData
    $IniArray = $_aSect				; return an empty array 
    Exit @ERROR					; exit if error opening 
  EndIf
 
  ReDim $_aSect[1, 499], $_aData[1, 499]	; Prep Section & Data Arrays for Read 
 
  $_ = Trim(ReadLine($_Fp))			; remove leading/trailing spaces 
 
  While Not @ERROR				; loop through the file contents 
    If Left($_, 1) = '['			; found a section 
 
      If $_I >= 0				; process prior section data, if any 
        If $_J >= 0
        ReDim Preserve $_aData[1, $_J]		; trim data array to size 
        $_aSect[1, $_I] = $_aData
        ReDim $_aData[1, 499]			; create the  
        $_J = -1
        Else
          $_I = $_I - 1				; ignore empty sections 
        EndIf
      EndIf
 
      $_I = $_I + 1				; increment the section index 
      If $_I Mod 499 = 0 And $_I > 0
        ReDim Preserve $_aSect[1, $_I + 500]
      EndIf
      $_aSect[0, $_I] = Split(SubStr($_, 2), ']')[0]
    Else
      If Not InStr(';#', Left($_, 1)) And Len($_) > 2
        $_aDat = Split($_, '=')			; break into array 
        If UBound($_aDat) > 1			; does data have embedded = signs? 
          $_ = $_aDat[1]
          For $K = 2 to UBound($_aDat)		; extract them into a single value 
            $_ = $_ + '=' + $_aDat[$K]
          Next
          $_aDat[1] = $_
          ReDim Preserve $_aDat[1]
        EndIf
        If UBound($_aDat) < 1			; bad data 
          $_Invalid = 1
        Else
          $_J = $_J + 1				; increment the data index 
          If $_J Mod 499 = 0 And $_J > 0
            ReDim Preserve $_aData[1, $_J + 500]
          EndIf
          $_aData[0, $_J] = $_aDat[0]		; Store the value 
          $_aData[1, $_J] = $_aDat[1]		; Store the data 
        EndIf
      EndIf
    EndIf
 
    $_ = Trim(ReadLine($_Fp))			; remove leading/trailing spaces 
  Loop						; done with input data 
 
  $_ = Close($_Fp)				; close the file 
  $_ = 2					; prep for Not Found exit 
 
  ; process the last/only section 
  If $_I >= 0
    If $_J >= 0
      ReDim Preserve $_aData[1, $_J]		; trim data array to size 
      $_aSect[1, $_I] = $_aData
    Else
      ReDim Preserve $_aData[1, 0]
      $_aSect[1, $_I] = $_aData
    EndIf
    ReDim Preserve $_aSect[1, $_I]		; trim section array to size 
    $_ = 0
    If $_Invalid $_ = 1 EndIf
  EndIf
 
  $IniArray = $_aSect				; return the array 
 
  Exit $_					; exit success 
 
 
  ; Parse an array of file data in INI format instead of reading the file directly 
:IA_FILEDATA
  $_I = -1  $_J = -1				; Initialize index pointers 
 
  ReDim $_aSect[1, 499], $_aData[1, 499]	; Prep Section & Data Arrays for Read 
 
  For Each $_ in $_fSrcFile			; loop through the file contents 
    $_ = Trim($_)
    If Left($_, 1) = '['			; found a section 
 
      If $_I >= 0				; process prior section data, if any 
        If $_J >= 0
        ReDim Preserve $_aData[1, $_J]		; trim data array to size 
        $_aSect[1, $_I] = $_aData
        ReDim $_aData[1, 499]			; create the  
        $_J = -1
        Else
          $_I = $_I - 1				; ignore empty sections 
        EndIf
      EndIf
 
      $_I = $_I + 1				; increment the section index 
      If $_I Mod 499 = 0 And $_I > 0
        ReDim Preserve $_aSect[1, $_I + 500]
      EndIf
      $_aSect[0, $_I] = Split(SubStr($_, 2), ']')[0]
    Else
      If Not InStr(';#', Left($_, 1)) And Len($_) > 2
        $_aDat = Split($_, '=')			; break into array 
        If UBound($_aDat) > 1			; does data have embedded = signs? 
          $_ = $_aDat[1]
          For $K = 2 to UBound($_aDat)		; extract them into a single value 
            $_ = $_ + '=' + $_aDat[$K]
          Next
          $_aDat[1] = $_
          ReDim Preserve $_aDat[1]
        EndIf
        If UBound($_aDat) < 1			; bad data 
          $_Invalid = 1
        Else
          $_J = $_J + 1				; increment the data index 
          If $_J Mod 499 = 0 And $_J > 0
            ReDim Preserve $_aData[1, $_J + 500]
          EndIf
          $_aData[0, $_J] = $_aDat[0]		; Store the value 
          $_aData[1, $_J] = $_aDat[1]		; Store the data 
        EndIf
      EndIf
    EndIf
 
  Next						; done with input data 
 
  $_ = 2					; prep for Not Found exit 
 
  ; process the last/only section 
  If $_I >= 0
    If $_J >= 0
      ReDim Preserve $_aData[1, $_J]		; trim data array to size 
      $_aSect[1, $_I] = $_aData
    Else
      ReDim Preserve $_aData[1, 0]
      $_aSect[1, $_I] = $_aData
    EndIf
    ReDim Preserve $_aSect[1, $_I]		; trim section array to size 
    $_ = 0
    If $_Invalid $_ = 1 EndIf
  EndIf
 
  $IniArray = $_aSect				; return the array 
 
  Exit $_					; exit success 
 
EndFunction
 
 
Function ReadIniArray($_aIniFile, OPTIONAL $_Section, OPTIONAL $_Key)
 
  ; exit immediately if the data format is invalid 
  If VarType($_aIniFile) < 8192			; data but not an array - exit! 
    Exit 87
  EndIf
 
  Dim $_aSectIdx[UBound($_aIniFile, 2)]		; Section Index Array 
  Dim $_I, $_J					; Index pointers 
  Dim $_aData					; Array of Key/Data pairs 
 
  ; Create a section index array 
  For $_I = 0 to UBound($_aIniFile, 2)
    $_aSectIdx[$_I] = $_aIniFile[0, $_I]
  Next
 
  ; If the Section is null, return a delimited string of Sections [same as ReadProfileString(file)] 
  If Not $_Section
    $ReadIniArray = Join($_aSectIdx, Chr(10))
    Exit 0
  EndIf
 
  ; Search the index for a section 
  $_I = aScan($_aSectIdx, $_Section)
  If $_I < 0 Exit 2 EndIf			; section not found - Exit 
 
  $_aData = $_aIniFile[1, $_I]			; Extract the key/value array 
 
  ; Create a Key index for the requested section 
  Dim $_aKeyIdx[UBound($_aData, 2)]
  For $_J = 0 to UBound($_aData, 2)
    $_aKeyIdx[$_J] = $_aData[0, $_J] 
  Next
 
  ; If the Key is null, return a delimited string of Keys [same as ReadProfileString(file, section)] 
  If Not $_Key
    $ReadIniArray = Join($_aKeyIdx, Chr(10))
    Exit 0
  EndIf
 
  ; Search the index for a Key 
  $_J = aScan($_aKeyIdx, $_Key)
  If $_J < 0 Exit 2 EndIf			; Key not found 
 
  $ReadIniArray = $_aData[1, $_J]
 
  Exit 0
 
EndFunction
 
 
Function WriteIniArray($_aIniFile, $_Section, OPTIONAL $_Key, OPTIONAL $_Data)
 
  ; exit immediately if the data format is invalid 
  If VarType($_aIniFile) < 8192			; data but not an array - exit! 
    Exit 87
  EndIf
 
  Dim $_aSectIdx[UBound($_aIniFile, 2)]		; Section Index Array 
  Dim $_I, $_J					; Index pointers 
  Dim $_aData					; Key/Data array 
 
 
  ; Create a section index array 
  For $_I = 0 to UBound($_aIniFile, 2)
    $_aSectIdx[$_I] = $_aIniFile[0, $_I]
  Next
 
  ; Search the index for a section 
  $_I = aScan($_aSectIdx, $_Section)
 
  ; If the section does not exist and Keydata is present - add the section and key/value pair (new Sect:Key:Data) 
  If $_I < 0					; section not found - Add if Data is present 
    If $_Key And $_Data
      $_I = 0					; default to empty array 
      If $_aIniFile[0, 0]
        $_I = UBound($_aIniFile, 2) + 1		; find next section index 
      EndIf
      ReDim Preserve $_aIniFile[1, $_I]		; Add new section 
      ReDim $_aData[1, 0]			; create data array 
      $_aData[0,0] = $_Key			; key 
      $_aData[1,0] = $_Data			; data 
      $_aIniFile[0, $_I] = $_Section		; section name 
      $_aIniFile[1, $_I] = $_aData		; section data 
    Else
      Exit 0					; nothing to do 
    EndIf
  Else						; the section does exist, locate the Key 
 
    ; If the Key is null, delete the section (delete Section) 
    If Not $_Key
      ReDim $_aData[1, 0]			; create empty keys array 
      $_aIniFile[1, $_I] = $_aData		; write to section 
      $_aIniFile[0, $_I] = ''			; write null section name 
    Else
      $_aData = $_aIniFile[1, $_I]		; Extract the key/value array 
      ; Create a Key index for the requested section 
      Dim $_aKeyIdx[UBound($_aData, 2)]
      For $_J = 0 to UBound($_aData, 2)
        $_aKeyIdx[$_J] = $_aData[0, $_J]	; create the index 
      Next
      $_J = aScan($_aKeyIdx, $_Key)		; find the key 
 
      ; If the key does not exist, add the key/data (new Key/data) 
      If $_J < 0
        If $_Data
          $_aData = $_aIniFile[1, $_I]		; array of key/data pairs 
          $_J = UBound($_aData, 2) + 1		; find next key index 
          ReDim Preserve $_aData[1, $_J]	; create data array 
          $_aData[0, $_J] = $_Key		; key 
          $_aData[1, $_J] = $_Data		; data 
          $_aIniFile[1, $_I] = $_aData		; section data 
        Else
	  $WriteIniArray = $_aIniFile		; return original array 
          Exit 0				; nothing to do (no data) 
        EndIf
 
      Else					; the key exists - either Update or Delete 
    
        ; if the data is not null, write the new data (update key) 
        If $_Data
          $_aData[1, $_J] = $_Data
 
        ; If the data is null, write empty key and data values (delete key) 
        Else
          $_aData[0, $_J] = ''			; delete key 
          $_aData[1, $_J] = ''			; clear data value 
        EndIf
        $_aIniFile[1, $_I] = $_aData		; update the array 
      EndIf
 
    EndIf
 
  EndIf
 
  $WriteIniArray = $_aIniFile
 
  Exit 0
 
EndFunction
 
 
 
Function EnumIniArray($_fSrcArray, OPTIONAL $_fSectName)
 
  Dim $_fSectList
 
  ; die if the file doesn't exist 
  If VarType($_fSrcArray) < 8192
    Exit 2
  EndIf
 
  ; Get the list of sections or keys 
  $_fSectList = ReadIniArray($_fSrcArray, $_fSectName, '')
  ; Return if error occurred 
  If @ERROR
    Exit @ERROR
  EndIf
 
  ; If len is >0, return an array of sections 
  ; If len is 0, either no sections or keys exist, or an invalid section name was specified. Return nothing. 
  If Len($_fSectList) > 0
    $EnumIniArray = Split($_fSectList, Chr(10))
    Exit 0
  EndIf
 
  ; return an error here for value not found (no sections or no keys in section) 
  Exit 13
 
EndFunction