Jump to content

EASY Multidimensional Associative Arrays


OHB
 Share

Recommended Posts

This is definitely a bug (and Jesus Christ I wish AutoIt had real dictionaries and lists like Python. I could have parsed the INI file and had my way with the data in 5 minutes instead of the hours I have wasted trying to do it in AutoIT). Once again, I have this INI section:

[Products]
;internal name = Display Name | Folder Name | Installer
C3Communicator=Avistar C3 Communicator|Avistar C3 Communicator|C3CommunicatorSetup.msi
CitrixRTME=Citrix RealTime Media Engine|Citrix HDX|Citrix HDX RealTime Media Engine.msi
CitrixRTC=Citrix RealTime Connector|Citrix HDX|HDX RealTime Connector LC.msi
C3Unified=Avistar C3 Unified|C3 Unified - Microsoft Lync Edition|C3UnifiedLyncSetup.msi

Then I have this code:

$sConf = @ScriptDir & "Build_Auto_Installer" ; Name of the ini file ( '.ini' will be added automatically)
Dim $Folder, $Installer, $Name,$Product, $Products
_ReadAssocFromIni($sConf)


GetProducts()      ;code is in previous post

x_display("C3Communicator")
$Product = "Avistar C3 Communicator"

for $item in $Products
    MsgBox(0, "", $item)                                     ; displays "C3Communicatior"
    MsgBox(0, "Product", x($item&'.Name'))                   ; displays "Avistar C3 Communicator"
    MsgBox(0, "Product", x($item&'.Name'))                   ; displays "0". Obviously it should display "Avistar C3 Communicator"
    if x($item&'.Name') == $Product Then
        MsgBox(0, "Product", x($item&'.Installer'))          ; should display "C3CommunicatorSetup.msi", but always displays "0"
    EndIf
Next
Edited by robinsiebler
Link to comment
Share on other sites

  • 1 year later...

I'm getting an error when attempting to call a hash that doesn't exist.  it appears that the code does not return 0 as it states in the function. (edit 10/23/2013) Well it's not that it fails on a call to an non-existent hash.  It fails if you put a value on the parent.     

X('parent',value)

x('parent.test)  ----- this will throw the error.

Here's the error: 

<filepath>multidemensionalassociativearray.au3 (51) : ==> Variable must be of type "Object".:

ElseIf Not $cur.exists( $last_key ) Then
ElseIf Not $cur^ ERROR
 
Here's the Code To reproduce:

#include



x('test.1.test','test')
x('test.2.test','test2')
x('test.2.test.name','non-default name')
x('default.name','Default Name')

For $index in x('test')
$name = x('test.' & $index & '.test.name')
If $name = 0 Then
$name = x('default.name')
EndIf
ConsoleWrite('Test.' & $index & '.test:' & @TAB & $name & @CRLF)
Next

 
 
I'm using the code from the original post.  Most of the time edits are performed to that code so if i have to update it from an addtional post please let me know the number and i'll do so.
Just in case here's the code from the original post as of 10.23.2013

#include-once


; #INDEX# =======================================================================================================================
; Title .........: xHashCollection
; AutoIt Version : 3.3.4.0
; Language ......: English
; Description ...: Create and use Multidimentional Associative Arrays
; Author ........: OHB
; ===============================================================================================================================
Global $_xHashCollection = ObjCreate( "Scripting.Dictionary" ), $_xHashCache

; #FUNCTION# ====================================================================================================================
; Name...........: x
; Description ...: Gets or sets a value in an Associative Array
; Syntax.........: SET: x( $sKey , $vValue )
; GET: x( $key )
; Parameters ....: $sKey - the key to set or get. Examples:
; x( 'foo' ) gets value of foo
; x( 'foo.bar' ) gets value of bar which is a key of foo
; $bar = "baz"
; x( 'foo.$bar' ) gets value of baz which is a key of foo (variables are expanded)
; Return values .: Success - When setting, return the value set. When getting, returns the requested value.
; Failure - Returns a 0
; Author ........: OHB
; ===============================================================================================================================
Func x( $sKey = '' , $vValue = '' )
$func = "get"
If @NumParams <> 1 Then $func = "set"
If $sKey == '' Then
If $func == "get" Then
Return $_xHashCollection
Else
$_xHashCollection.removeAll
Return ''
EndIf
EndIf
$parts = StringSplit( $sKey , "." )
$last_key = $parts[$parts[0]]
$cur = $_xHashCollection
For $x = 1 To $parts[0] - 1
If Not $cur.exists( $parts[$x] ) Then
If $func == "get" Then Return
$cur.add( $parts[$x] , ObjCreate( "Scripting.Dictionary" ) )
EndIf
$cur = $cur.item( $parts[$x] )
Next
If IsPtr( $vValue ) Then $vValue = String( $vValue )
If $func == "get" Then
If Not $cur.exists( $last_key ) Then Return
$item = $cur.item( $last_key )
Return $item
ElseIf Not $cur.exists( $last_key ) Then
$cur.add( $last_key , $vValue )
Else
$cur.item( $last_key ) = $vValue
EndIf
Return $vValue
EndFunc

; #FUNCTION# ====================================================================================================================
; Name...........: x_unset
; Description ...: Removes a key from an Associative Array
; Syntax.........: x_unset( $sKey )
; Parameters ....: $sKey - the key to remove.
; Return values .: Success - True
; Failure - False
; Author ........: OHB
; ===============================================================================================================================
Func x_unset( $sKey )
If $sKey == '' Then Return x( '' , '' )
$parts = StringSplit( $sKey , "." )
$cur = $_xHashCollection
For $x = 1 To $parts[0] - 1
If Not $cur.exists( $parts[$x] ) Then Return False
$cur = $cur.item( $parts[$x] )
Next
$cur.remove( $parts[$parts[0]] )
Return True
EndFunc

; #FUNCTION# ====================================================================================================================
; Name...........: x_display
; Description ...: Displays the contents of an Associative Array
; Syntax.........: x_display( $sKey )
; Parameters ....: $sKey - the key to display. Examples:
; x_display() displays everything
; x_display( 'foo' ) displays the contents of foo
; Author ........: OHB
; ===============================================================================================================================
Func x_display( $key = '' )
$text = $key
If $key <> '' Then $text &= " "
$text &= StringTrimRight( _x_display( x( $key ) , '' ) , 2 )
$wHnd = GUICreate( "Array " & $key , 700 , 500 )
GUISetState( @SW_SHOW , $wHnd )
$block = GUICtrlCreateEdit( $text , 5 , 5 , 690 , 490 )
GUICtrlSetFont( $block , 10 , 400 , -1 , 'Courier' )
While 1
If GUIGetMsg() == -3 Then ExitLoop
WEnd
GUISetState( @SW_HIDE , $wHnd )
GUIDelete( $wHnd )
EndFunc

; #INTERNAL_USE_ONLY# ===========================================================================================================
; Name...........: _x_display
; Description ...: Itterates through an array and builds output for x_display
; Author ........: OHB
; ===============================================================================================================================
Func _x_display( $item , $tab )
If IsObj( $item ) Then
$text = 'Array (' & @CRLF
$itemAdded = False
For $i In $item
$text &= $tab & " [" & $i & "] => " & _x_display( $item.item($i) , $tab & " " )
$itemAdded = True
Next
If Not $itemAdded Then $text &= @CRLF
$text &= $tab & ')'
ElseIf IsArray( $item ) Then
$text = "Array"
$totalItems = 1
$dimensions = UBound( $item , 0 )
For $dimension = 1 To $dimensions
$size = UBound( $item , $dimension )
$totalItems *= $size
$text &= "[" & $size & "]"
Next
$text &= " (" & @CRLF
For $itemID = 0 To $totalItems - 1
$idName = ''
$idNum = $itemID
For $dimension = 1 To $dimensions - 1
$mul = ( $totalItems / UBound( $item , $dimension ) )
$a = Floor( $idNum / $mul )
$idName &= '[' & $a & ']'
$idNum -= ( $a * $mul )
Next
$idName &= '[' & $idNum & ']'
$text &= $tab & " " & $idName & " => " & _x_display( Execute( "$item" & $idName ) , $tab & " " )
Next
$text &= $tab & ")"
Else
$text = $item
EndIf
$text &= @CRLF
Return $text
EndFunc

Edited by ibigpapa
Link to comment
Share on other sites

  • 2 years later...

Please help, I try to use this but it won't work. It keeps return the error :

--> Press Ctrl+Alt+Break to Restart or Ctrl+Break to Stop
"C:\Users\zenzoploen\Desktop\LineAutoPostNetwork_src\myfunctions.au3" (575) : ==> Variable used without being declared.:
Local $cur = $_xHashCollection
Local $cur = ^ ERROR
->09:24:22 AutoIt3.exe ended.rc:1
I wonder why? since $_xHashCollection is already declared as Global

Link to comment
Share on other sites

  • 3 months later...

OHB, thank you so much for this UDF. It's helped to make something I'm working on much, much easier to manage.

Since I'm relying on this so heavily, I found that simply seeing the contents of my 'x' dictionary in plain text (via x_display()) was becoming increasingly difficult for me to navigate. As such, I added a couple functions that provide output in a similar way as x_display, but using a TreeView control. Thought I'd share it here as a way of giving back a little.

If you add the following code to your associative-arrays.au3 file, you can call x_display_treeview() in exactly the same way as x_display() - it even takes an argument of a branch from which to start, as with the original x_display function. I didn't bother getting too fancy with being able to resize the resultant popup window, but feel free to tweak it as needed.

I've also configured it to auto-expand every node by default. If that doesn't suit you, just change (or comment out) the GuiCtrlSetState line close to the end.

Here's the code:

 

Global  $DictTreeview

Func x_display_treeview( $key = '' )

    $text = $key
    If $key <> '' Then $text &= " "
    $wHnd = GUICreate( "Array " & $key , 700 , 900, -1, -1)
    GUISetState( @SW_SHOW , $wHnd )
    _x_display_treeview( x( $key ))
    While 1
        If GUIGetMsg() == -3 Then ExitLoop
    WEnd
    GUISetState( @SW_HIDE , $wHnd )
    GUIDelete( $wHnd )
    GUICtrlDelete($DictTreeview)
    $DictTreeview = 0
EndFunc

Func _x_display_treeview($item, $parent = 0)
    Local $TreeViewItem

    If Not $DictTreeview > 0 Then
        $DictTreeview = GUICtrlCreateTreeView(0, 0, 700, 900)
        If $parent = 0 Then $parent = GUICtrlCreateTreeViewItem("Dictionary", $DictTreeview)
    Endif

    If IsObj( $item ) Then
        For $i in $item
            _x_display_treeview($item.item($i), GUICtrlCreateTreeViewItem($i, $parent))
        Next
    ElseIf IsArray( $item ) Then
        For $i in $item
            _x_display_treeview($item.item($i), GUICtrlCreateTreeViewItem($i, $parent))
        Next
    Else
        $ParentText = GUICtrlRead($parent, 1)
        GUICtrlSetData($parent, $ParentText & " = " & $item)
    EndIf
    GUICtrlSetState($parent, $GUI_EXPAND)
    Return $TreeViewItem
EndFunc

 

I hope this comes in handy for someone.

Cheers,

Jeemo

An emoticon is worth a dozen words.

Link to comment
Share on other sites

  • 4 years later...

Its 2020 and i still use this very useful UDF.

I've come to expand the x_display function (originally only for Dictionarys and Arrays) to also handle Maps and MultiDimensional Arrays with > 2 Dimensions (There was a bug with the original code which would not display those correctly)

And the best is that you can nest and mix any combination of Dicts, MultiDim Arrays and Maps.

Here is an Example:

Dim $t1[2] = ["A","B"]

Dim $m[]
Dim $subm[]
$m[12345678] = "This"
$m["12345678"] = "That"
$m.maptest1 = $subm
$m.maptest1.submaptest1 = 3.141
$m["maptest1"][111] = "string"
$m.maptest1.submaptest3 = $t1


Dim $t2[3][2] = [[1,2],[3,$m],[5,6]]

$x = ObjCreate("Scripting.Dictionary")
$x.add("main", ObjCreate("Scripting.Dictionary"))
$x.Item("main").add( "test1", "hello")
$x.Item("main").add( "test2", $t2)
$x.add("sub", ObjCreate("Scripting.Dictionary"))
$x.Item("sub").add( "sub1", "world")
$x.Item("sub").add( "sub2", 3.141)

Dim $t[2][3][4] = [[[1,2,3,4],[4,5,6,7],[7,$x,9,0]],[[11,22,33,44],[44,55,66,77],[77,88,99,00]]]

Dim $p[4][2][3] = _
[ _
    [ _
        ["272,00,47","01,47,37","325,56,50"], _
        ["325,57,11","04,14,11","04,01,10"] _
    ], _
    [ _
        ["272,00,40","01,33,53","01,41,00"], _
        ["325,56,50","04,01,10","272,00,40"] _
    ], _
    [ _
        [$t,"01,41,00","01,47,37"], _
        ["323,50,01","04,04,15","272,00,47"] _
    ], _
    [ _
        ["272,00,40","01,41,00","01,47,37"], _
        ["323,50,01","325,56,50","01,33,53"] _
    ] _
]


;~ ConsoleWrite( _VarDump($p)& @CRLF)
x_display($p, 0, "$p")


Exit



; #FUNCTION# ====================================================================================================================
; Name...........: x_display
; Description ...: Writes the contents of an Associative Array to the Console
; Syntax.........: x_display( $key = '' , $MultiArr3DView = False, $sName = '' )
; Parameters ....: $sKey - the value to display. If empty, outputs the content of $_xHashCollection
;                  $MultiArr3DView - display multidimensional arrays in a tiled manner
;                  $sName - prefix output with user string (for example the varibale name)
; Examples:
;                  x_display()          displays everything
;                  x_display( 'foo' )   displays the contents of foo
; Authors .......: OHB <me at orangehairedboy dot com>, qsek
; ===============================================================================================================================
Func x_display( $key = '' , $MultiArr3DView = False, $sName = '')
    Local $text
    If Not IsObj($key) And Not isMap($key) And Not isArray($key)Then $text = $key
    $text &= StringTrimRight( _x_display($key ,$sName , '  ', 1, '', $MultiArr3DView ) , 2 )
    ConsoleWrite($text & @LF)
EndFunc


; #INTERNAL_USE_ONLY# ===========================================================================================================
; Name...........: _x_display
; Description ...: Itterates through a map/Dictionary/array and builds output for x_display
; Authors .......: OHB <me at orangehairedboy dot com>, qsek
; ===============================================================================================================================
Func _x_display( $item , $sName = "", $tab = '', $level = 1, $sArrSubscr = "", $MultiArr3DView = False)
    Static $_MultiArr3DView = ($MultiArr3DView+0 = True)
    Local $text
    If $sName <> "" Then $text &= $sName&":"
    If IsMap( $item ) Then

        If $level = 1 Then $text &= ' => '& "Map"

        $text &= ' {' & @CRLF
        $aMapKeys = MapKeys($item)
        For $i In $aMapKeys
            $save = $item[$i]
            $a = IsString($i) ? "'" : ''
            $text &= $tab & "["&$a & $i & $a&"]" & _x_display( $item[$i] ,"" , $tab&"  ", IsMap($save) ? $level+1 : 1)
        Next
        $text &= StringTrimRight($tab, 2) & '}'

    ElseIf IsObj( $item ) Then

        If $level = 1 Then $text = ' => '& ObjName($item, 1)

        $text &= ' (' & @CRLF
        For $i In $item
            $value = $item.item($i)
            $a = IsString($i) ? "'" : ''
            $text &= $tab &"["&$a & $i &$a&"]" & _x_display( $value , "", $tab&"  " , IsObj($value) ? $level+1 : 1 )
        Next
        $text &= StringTrimRight($tab, 2) & ')'

    ElseIf IsArray( $item ) Then

        If $level = 1 Then
            $text &= ' => '& "Array"
            $dimensions = UBound( $item , 0 )
            For $dimension = 1 To $dimensions
                $size = UBound( $item , $dimension )
                $text &= "[" & $size & "]"
            Next
            $text &= " ("  & @CRLF
            $sArrSubscr = ""; clear from possible "sub" flag
        EndIf
        ;Loop through all elements within the current dimension
        For $i = 0 to Ubound($item, $level)-1
            If $level < Ubound($item, 0) Then
                ; we need to go deeper, pass on subscription string. Also pass original array so we end up here again
                $text &= _x_display( $item , "" ,$tab, $level+1, $sArrSubscr&"[" & $i & "]")
            Else
                ; we reached the last dimension, Execute the array subscription string

                If $_MultiArr3DView And $i > 0 Then
                    If UBound( $item , 0 ) > 1 Then
                        $text &= " , " ; Separation between first Dimension items on one line
                    Else
                        $text &= @CRLF&$tab ; If ony 1 dim array -> put every item on new line
                    EndIf
                Else
                    $text &= $tab
                EndIf

                $value = Execute( "$item" & $sArrSubscr&"[" & $i & "]")
                $text &= $sArrSubscr&"[" & $i & "]" & _x_display( $value , "" , $tab&"  ", 1, "sub")

                If $_MultiArr3DView And StringRight($text, 2) = @CRLF Then $text &= $tab

            EndIf
        Next

        If $level = 1 Then
            If $_MultiArr3DView and UBound( $item , 0 ) = 1 Then $text &= @CRLF
            $text &= StringTrimRight($tab, 2) & ')'
            If Not $_MultiArr3DView Then $text &= @CRLF
        EndIf

        If Not $_MultiArr3DView Then Return $text

    Else

        $p = IsString($item) ? '"' : '' ; Put Strings in ""
        $text = ' = '&$p&$item&$p
        If $_MultiArr3DView And $sArrSubscr = "sub" Then Return $text

    EndIf

    $text &= @CRLF

    Return $text
EndFunc

 

Output:

$p: => Array[4][2][3] (
  [0][0][0] = "272,00,47"
  [0][0][1] = "01,47,37"
  [0][0][2] = "325,56,50"
  [0][1][0] = "325,57,11"
  [0][1][1] = "04,14,11"
  [0][1][2] = "04,01,10"
  [1][0][0] = "272,00,40"
  [1][0][1] = "01,33,53"
  [1][0][2] = "01,41,00"
  [1][1][0] = "325,56,50"
  [1][1][1] = "04,01,10"
  [1][1][2] = "272,00,40"
  [2][0][0] => Array[2][3][4] (
    [0][0][0] = 1
    [0][0][1] = 2
    [0][0][2] = 3
    [0][0][3] = 4
    [0][1][0] = 4
    [0][1][1] = 5
    [0][1][2] = 6
    [0][1][3] = 7
    [0][2][0] = 7
    [0][2][1] => Dictionary (
      ['main'] (
        ['test1'] = "hello"
        ['test2'] => Array[3][2] (
          [0][0] = 1
          [0][1] = 2
          [1][0] = 3
          [1][1] => Map {
            [12345678] = "This"
            ['12345678'] = "That"
            ['maptest1'] {
              ['submaptest1'] = 3.141
              [111] = "string"
              ['submaptest3'] => Array[2] (
                [0] = "A"
                [1] = "B"
              )
            }
          }
          [2][0] = 5
          [2][1] = 6
        )
      )
      ['sub'] (
        ['sub1'] = "world"
        ['sub2'] = 3.141
      )
    )
    [0][2][2] = 9
    [0][2][3] = 0
    [1][0][0] = 11
    [1][0][1] = 22
    [1][0][2] = 33
    [1][0][3] = 44
    [1][1][0] = 44
    [1][1][1] = 55
    [1][1][2] = 66
    [1][1][3] = 77
    [1][2][0] = 77
    [1][2][1] = 88
    [1][2][2] = 99
    [1][2][3] = 0
  )
  [2][0][1] = "01,41,00"
  [2][0][2] = "01,47,37"
  [2][1][0] = "323,50,01"
  [2][1][1] = "04,04,15"
  [2][1][2] = "272,00,47"
  [3][0][0] = "272,00,40"
  [3][0][1] = "01,41,00"
  [3][0][2] = "01,47,37"
  [3][1][0] = "323,50,01"
  [3][1][1] = "325,56,50"
  [3][1][2] = "01,33,53"
)

 

Same Output but with Option $MultiArr3DView = True:

$p: => Array[4][2][3] (
  [0][0][0] = "272,00,47" , [0][0][1] = "01,47,37" , [0][0][2] = "325,56,50"
  [0][1][0] = "325,57,11" , [0][1][1] = "04,14,11" , [0][1][2] = "04,01,10"

  [1][0][0] = "272,00,40" , [1][0][1] = "01,33,53" , [1][0][2] = "01,41,00"
  [1][1][0] = "325,56,50" , [1][1][1] = "04,01,10" , [1][1][2] = "272,00,40"

  [2][0][0] => Array[2][3][4] (
    [0][0][0] = 1 , [0][0][1] = 2 , [0][0][2] = 3 , [0][0][3] = 4
    [0][1][0] = 4 , [0][1][1] = 5 , [0][1][2] = 6 , [0][1][3] = 7
    [0][2][0] = 7 , [0][2][1] => Dictionary (
      ['main'] (
        ['test1'] = "hello"
        ['test2'] => Array[3][2] (
          [0][0] = 1 , [0][1] = 2
          [1][0] = 3 , [1][1] => Map {
            [12345678] = "This"
            ['12345678'] = "That"
            ['maptest1'] {
              ['submaptest1'] = 3.141
              [111] = "string"
              ['submaptest3'] => Array[2] (
                [0] = "A"
                [1] = "B"
              )
            }
          }
          
          [2][0] = 5 , [2][1] = 6
        )
      )
      ['sub'] (
        ['sub1'] = "world"
        ['sub2'] = 3.141
      )
    )
     , [0][2][2] = 9 , [0][2][3] = 0

    [1][0][0] = 11 , [1][0][1] = 22 , [1][0][2] = 33 , [1][0][3] = 44
    [1][1][0] = 44 , [1][1][1] = 55 , [1][1][2] = 66 , [1][1][3] = 77
    [1][2][0] = 77 , [1][2][1] = 88 , [1][2][2] = 99 , [1][2][3] = 0

  )
   , [2][0][1] = "01,41,00" , [2][0][2] = "01,47,37"
  [2][1][0] = "323,50,01" , [2][1][1] = "04,04,15" , [2][1][2] = "272,00,47"

  [3][0][0] = "272,00,40" , [3][0][1] = "01,41,00" , [3][0][2] = "01,47,37"
  [3][1][0] = "323,50,01" , [3][1][1] = "325,56,50" , [3][1][2] = "01,33,53"

)

If you need an even more verbose Output with Types and Ubounds there is also this excellent VarDump UDF from @jchd

Edited by qsek
Teamspeak 3 User Viewer - Quick and functional TS3 Query script, which shows online users.Cached Screenshot Deleter - Deletes older Fraps Screenshots if they exceed a specified limit.Unresolved Topics:Intercept and modify dragdrop text behaviour in scite
Link to comment
Share on other sites

Nice. May I suggest you dump integral and string keys differently?

Local $m[]      ; $m is a Map
$m[12345678] = "This"
$m["12345678"] = "That"

vd($m)  ; my vardump function

yields:

Map[2]
      [12345678]             => String (4)         'This'
      ['12345678']           => String (4)         'That'

 

This wonderful site allows debugging and testing regular expressions (many flavors available). An absolute must have in your bookmarks.
Another excellent RegExp tutorial. Don't forget downloading your copy of up-to-date pcretest.exe and pcregrep.exe here
RegExp tutorial: enough to get started
PCRE v8.33 regexp documentation latest available release and currently implemented in AutoIt beta.

SQLitespeed is another feature-rich premier SQLite manager (includes import/export). Well worth a try.
SQLite Expert (freeware Personal Edition or payware Pro version) is a very useful SQLite database manager.
An excellent eBook covering almost every aspect of SQLite3: a must-read for anyone doing serious work.
SQL tutorial (covers "generic" SQL, but most of it applies to SQLite as well)
A work-in-progress SQLite3 tutorial. Don't miss other LxyzTHW pages!
SQLite official website with full documentation (may be newer than the SQLite library that comes standard with AutoIt)

Link to comment
Share on other sites

  • 3 years later...
On 4/7/2012 at 3:49 AM, qsek said:

LWC's ini functions were helpful but i wanted more.expandcollapsepopup

 

I know it's more than a decade later, but thanks! Nevertheless, it's too bad you liked them and didn't end up using them.

I refer mostly to _ReadAssocFromIni - by priority of issues:

1. The following crashes the program when a section is empty:

If @error Then Return SetError(1, 0, 0)

and thus should be replaced with my original code:

If @error Then ; If empty section then ignore (treat as void)
         ContinueLoop
EndIf

2. The following hardcodes the program to just INI files and ignores the fact there are also sub-types like INF:

$myIni = $myIni&".ini"

I suggest just let the user control the extension and fall back on a default extension:

Func _ReadAssocFromIni($myIni = 'config.ini', ...

3. Not everyone want to manage several ini files, so I suggest change:

Func _ReadAssocFromIni($myIni = 'config', $mySection = '', $sSep = "|")
Local $sIni = StringLeft($myIni,StringInStr($myIni,".")-1)
...
x($aSection[$i]&"."&$sectionArray[$x][0], $posS)

to:

Func _ReadAssocFromIni($myIni = 'config', $mySection = '', $sSep = "|", $multi = True)
If $multi Then
   Local $sIni = StringLeft($myIni,StringInStr($myIni,".")-1)
EndIf
...
x(($multi ? ($aSection[$i] & ".") : "") & $sectionArray[$x][0], $posS)

 

On 10/27/2020 at 10:16 PM, qsek said:

@jchd good suggestion, updated in my post.

Would you considering adding my changes?

 

Other than that, I know Scripting.Dictionary forces all keys to be unique, but i wonder if there's a way to support duplicated keys using some sort of trick, like if it already exists and the user wishes so, add an index (like turn a secondary this into this2).
This interests me because one of my programs got a request to support multiple keys, and obviously it's not their problem it's a shortcoming of Scripting.Dictionary.

Edited by LWC
Added quote
Link to comment
Share on other sites

4 hours ago, LWC said:

1. The following crashes the program when a section is empty:

If @error Then Return SetError(1, 0, 0)

Im not sure what you mean by "crashing the program" with returning an error from the function.

I think this should be the intended return. The Loop iterates the $sectionArray that is either:

  • generated from IniReadSectionNames, which shuld not generate an error because the sections were just read and should exist. And even if not (due to external changing) the user should be informed of that through an error.
  • or is filled with only this one section the user specifically requested in the optional $mySection parameter. In which case the user should be informed if that section does not exist by an error, just like IniReadSectionNames does.

If, in your case, he gets an empty section, where should he know, that it is really empty or if it doesnt exist? An error return is the apropriate way imho.

4 hours ago, LWC said:

2. The following hardcodes the program to just INI files and ignores the fact there are also sub-types like INF:

$myIni = $myIni&".ini"

I suggest just let the user control the extension and fall back on a default extension:

Func _ReadAssocFromIni($myIni = 'config.ini', ...

 

I intentionally wanted to hide the "extension mangement" from the user so it is more clear, that you should use the same string for the root item as well as the name part of the ini file. But as i was cheking your suggestion, i came to ask myself "Why not both?". If you dont care about the extension, you just use Func _ReadAssocFromIni() or _ReadAssocFromIni("myconfig") like before. In this case ".ini" will be added. If you specify an extension, it will use this to load the file but only the name part for the AssoArray key will be used:

Func _ReadAssocFromIni($myIni = 'config.ini', $mySection = '', $sSep = "|")
    If Not StringInStr($myIni,".") Then $myIni &= ".ini"
    Local $sIni = StringLeft($myIni,StringInStr($myIni,".")-1)

 

4 hours ago, LWC said:

3. Not everyone want to manage several ini files, so I suggest change:

Func _ReadAssocFromIni($myIni = 'config', $mySection = '', $sSep = "|")
Local $sIni = StringLeft($myIni,StringInStr($myIni,".")-1)
...
x($aSection[$i]&"."&$sectionArray[$x][0], $posS)

 

 

The ReadAssocFrom and WriteAssocToIni functions are a subset of The AssoArray UDF. I think most users will work with this UDF mainly to be able to use values in a nested, treepath-like fashion within a script. My goal was to provide a way to store a subset of these variables in ini form if you were using the UDF anyway (this is mainly my usecase, too). So in order to not not conflict with my existing keys in the main dictionary, it is sensible to keep the extra "config" key for your ini management and be able to store other data in the main dictionary without it being written to the config unintentionally. Its also more clear if you access a config value with x() to have the config name within the key string so you know it is a config value that will be stored later eventually. If this would be a separate AssocIni UDF where the user was not intended to use the ini dictionary for something else, this would be more logical.

But then, some more things would have to change. Anyway thanks for your suggestions.

 

4 hours ago, LWC said:

Other than that, I know Scripting.Dictionary forces all keys to be unique, but i wonder if there's a way to support duplicated keys

You are right, dictionarys were not designed like that. Either you solve this by using numbered sub keys and write additional x functions for adding, deleting, and iterating already existing subkeys to be able to use it more "array-like".

Or you create an array to store subkeys, and store this array as value of your key. This would enable you to use existing autoit functions for array management, but you would have to unpack/pack the array everytime you want to read/write any values.

If you are really fancy, you could incorporate arrayidexes into the dot annotation, so you can use it like x("section.key[3].subkey"), x("section.key[-3]"), x("section.key[+]","value") etc.

Edited by qsek
Teamspeak 3 User Viewer - Quick and functional TS3 Query script, which shows online users.Cached Screenshot Deleter - Deletes older Fraps Screenshots if they exceed a specified limit.Unresolved Topics:Intercept and modify dragdrop text behaviour in scite
Link to comment
Share on other sites

@LWC I just discovered that the Json UDF from @Ward works with the Scripting Dictionary and added Dot Notation a while ago, including array management.

I did some tests and you absolutely can use x and json function interchangeably. Is this a solution for you usecase?

#include "Json.au3"
#include <AssoArrays.au3>

$x = $_xHashCollection
x("section","key1")
Json_Put($x, ".hello", "world")
Json_Put($x, ".foo.bar", "foobar")
x_del("foo.bar")
x_del("foo")

x("main","") ; mixing x with json
Json_Put($x, ".main.test[0]", "1111") ; manual adding of elements
Json_Put($x, ".main.test[1]", "2222") ; manual adding of elements

$a = x("main.test") ; adding with autoit functions by unpacking/packing
_ArrayAdd($a, "3333")
x("main.test",$a)

Json_Put($x, ".main.test["&UBound(x("main.test"))&"]", "1234") ; adding elements at end without unpacking

; iteration
For $val In x("main.test")
    ConsoleWrite("$val: " & $val & @CRLF)
Next

; iteration with index
For $i = 0 To UBound(x("main.test"))-1
    ConsoleWrite(x("main.test")[$i] & @CRLF)
Next

; writing without unpacking
Json_Put($x, ".main.test[1]", "0000")

; reading without unpacking
$val = Json_Get($x, ".main.test[1]")
ConsoleWrite("Json_Get: " & $val & @CRLF)

x_display()

Exit

 

Edited by qsek
Teamspeak 3 User Viewer - Quick and functional TS3 Query script, which shows online users.Cached Screenshot Deleter - Deletes older Fraps Screenshots if they exceed a specified limit.Unresolved Topics:Intercept and modify dragdrop text behaviour in scite
Link to comment
Share on other sites

Thanks for adding the following!

Func _ReadAssocFromIni($myIni = 'config.ini', ...

But due to still forcing $sIni, overwriting will occur if you have both test.ini and test.inf, etc.. I don't disagree with your logic, just asking why not simply using my small $multi suggestion. If not, then another way is needed to avoid overwriting. The easiest way would be to just include the extension in there.

About crashing the program, I'll rephrase. If your INI file looks like:

[section1]
this=that

[section2]
;Blank or containing comments

[section3]
foo=bar

[section4]
what=here

...
[section 100]
...

Then only section1 will make it in. While technically you might say it's not crash, losing 100 sections is a major issue in my view (even losing "just" one non empty section is a no-go).

I mean, your error check knows to avoid a technical crash for empty sections, but it just assumes other sections should be avoided as well. In my version if any section out there is not empty, then it deserves to be read.

Last but not least, about the JSON UDF, how to I utilize it if my INI file looks like:

[section]
this=that
this=what

How will it treat "this" that has 2 different values?

Edited by LWC
Used note
Link to comment
Share on other sites

Well, looks like you didn't take anything at all from my original function, including support for removing mid-sentence comments (e.g. value=this; comment about what is this).

So I've decided to just add (or re-add) the following to a customized version of your function:

  1. Support for duplicated keys - using your own functions (instead of JSON UDF)
  2. Support for central settings (i.e. $multi False - I kept your True as default)
  3. Support for not ignoring sections that appear after an empty section
  4. Re-added my original function's support for IniReadSectionNames_alt and IniReadSection_alt - to support locked Autorun.inf
  5. Re-added my original function's support for removing mid-sentence comments, as I feel it makes no sense to store them in an array

Unless you decide to include it in your UDF, I recommend others to download your UDF, since it's amazing overall, but just add my _ReadAssocFromIni_alt to their scripts instead of the UDF _ReadAssocFromIni if they desire the aforementioned benefits.
If this interests anyone feel free to ask me for a _WriteAssocToIni_alt too.

Unfortunately, for duplicated keys I had to use the UDF _ArrayToString meaning it makes it mandatory to include Array.au3...

#include <Array.au3>

;read AssocArray from IniFile Section
;returns number of items read - sets @error on failure
Func _ReadAssocFromIni_alt($myIni = 'config.ini', $multi = True, $mySection = '', $sSep = "|")
    If Not StringInStr($myIni,".") Then $myIni &= ".ini"
    if $multi then
        Local $sIni = StringLeft($myIni,StringInStr($myIni,".")-1)
    EndIf

    If $mySection == '' Then
        $aSection = IniReadSectionNames_alt($myIni); All sections
        If @error Then Return SetError(@error, 0, 0)
    Else
        Dim $aSection[2] = [1,$mySection]; specific Section
    EndIf

    For $i = 1 To UBound($aSection)-1

        Local $sectionArray = IniReadSection_alt($myIni, $aSection[$i])
        If @error Then ContinueLoop
        For $x = 1 To $sectionArray[0][0]
            local $valTemp = ($multi ? $sIni&"." : "")&$aSection[$i]&"."&$sectionArray[$x][0]
            If x($valTemp) or IsArray(x($valTemp)) Then
                $sectionArray[$x][1] = (x($valTemp) ? x($valTemp) : _ArrayToString(x($valTemp), $sSep)) & $sSep & $sectionArray[$x][1]
            EndIf
            $sectionArray[$x][1] = StringStripWS(StringSplit($sectionArray[$x][1], ";")[1], 3) ; Support for mid-sentence comments
            ;$sectionArray[$x][1] = StringStripWS($value[1], 3)
            If StringInStr($sectionArray[$x][1], $sSep) then
                $posS = _MakePosArray($sectionArray[$x][1], $sSep)
            Else
                $posS = $sectionArray[$x][1]
            EndIf
            x($valTemp, $posS)
        Next

    next
    Return $sectionArray[0][0]
EndFunc   ;==>_ReadAssocFromIni

Func IniReadSectionNames_alt($hIniLocation)
    local $aSections = IniReadSectionNames($hIniLocation)
    if not @error then
        return $aSections
    EndIf
    local $filecontent = FileRead($hIniLocation)
    if @error Then
        SetError(@error)
    else
        local $aSections = StringRegExp($filecontent, '^|(?m)^\s*\[([^\]]+)', 3)
        $aSections[0] = UBound($aSections) - 1
        return $aSections
    EndIf
EndFunc

Func IniReadSection_alt($hIniLocation, $aSection)
    local $aKV = IniReadSection($hIniLocation, $aSection)
    if not @error then
        return $aKV
    else
        IniReadSectionNames($hIniLocation) ; In case reading a specific section failed, try reading them all
        if not @error Then
            SetError(1) ; Fake an error if reading them all worked, proving reading a specific section isn't the problem
        else
            local $filecontent = FileRead($hIniLocation)
            if @error Then
                SetError(@error)
            else
                local $value = StringRegExp($filecontent, "\Q" & $aSection & ']\E\s+([^\[]+)', 1)
                If Not IsArray($value) Then
                    SetError(1) ; Fake an error if a section couldn't be found
                else
                    If StringInStr($value[0], @CRLF, 1, 1) Then
                        $value = StringSplit(StringStripCR($value[0]), @LF)
                    ElseIf StringInStr($value[0], @LF, 1, 1) Then
                        $value = StringSplit($value[0], @LF)
                    Else
                        $value = StringSplit($value[0], @CR)
                    EndIf
                    Local $aKV[1][2]
                    For $xCount = 1 To $value[0]
                        If $value[$xCount] = "" Or StringLeft($value[$xCount], 1) = ";" Then ContinueLoop
                        ReDim $aKV[UBound($aKV) + 1][UBound($aKV, 2)]
                        $value_temp = StringSplit($value[$xCount], "=", 2)
                        $aKV[UBound($aKV) - 1][0] = $value_temp[0]
                        $aKV[UBound($aKV) - 1][1] = $value_temp[1]
                        $aKV[0][0] += 1
                    Next
                    If $aKV[0][0] = "" Then SetError(1) ; Fake an error if a section is empty
                    return $aKV
                EndIf
            EndIf
        EndIf
    EndIf
EndFunc

 

Edited by LWC
Removed tests
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...