Ini File Handler

Martin

Well-known member
CX Code Contributor
3rd Party Module Dev
Tutorial Author
3rd Party Tool Dev
Joined
Jun 19, 2017
Messages
332
I know there is some basic ini file handling but the solutions from ignition and pyro lack sections so I came up with my own, works for all targets where filestreams are supported so no HTML5.


This is just a start and currently it can only read but not write, but I hope it will be useful for someone:


Code:
Strict

Import brl.filestream

Class KeyValuePair
Public
  Field _isValid:Bool
  Field _key:String
  Field _value:String
End Class

Class IniHandler
Private
  Field _sections:StringMap<StringMap<String>>
  Field _modified:StringMap<StringMap<String>>
  Field _iniString:String

Public
  Method New()
    _sections = New StringMap<StringMap<String>>()
    _modified = New StringMap<StringMap<String>>()
  End Method

  Method open:Void(path:String)
    Local file:FileStream = FileStream.Open("cerberus://data/" + path, "r")
    If file
      _iniString = file.ReadString()
      file.Close()
    Else
      Print "File '" + path + "' not found!"
    EndIf
    
    refresh()
    file.Close()
  End Method

  Method doesSectionExist:Bool(sectionName:String)
    Return _sections.Contains(sectionName)
  End Method

  Method doesKeyExist:Bool(sectionName:String, key:String)
    Local section:StringMap<String>
    ' check if the section exists
    If _sections.Contains(sectionName)
      section = _sections.Get(sectionName)
      Return section.Contains(key)
    Else
      Return False
    EndIf
  End Method

  Method readValue:String(sectionName:String, key:String, defaultValue:String)
    If Not _sections.Contains(sectionName)
      Return defaultValue
    EndIf

    Local section:StringMap<String>
    section = _sections.Get(sectionName)
    If Not section.Contains(key)
      Return defaultValue
    EndIf

    Return section.Get(key)
  End Method

  Method readValue:Int(sectionName:String, key:String, defaultValue:Int)
    Local str:String = readValue(sectionName, key, String(defaultValue))
    str = splitComment(str)
    Return Int(str)
  End Method

  Method readValue:Float(sectionName:String, key:String, defaultValue:Float)
    Local str:String = readValue(sectionName, key, String(defaultValue))
    str = splitComment(str)
    Return Float(str)
  End Method

  Method readValue:Bool(sectionName:String, key:String, defaultValue:Bool)
    Local defaultStr:String = "False"
    If defaultValue Then defaultStr = "True"
    Local str:String = readValue(sectionName, key, defaultStr)
    str = splitComment(str)
    If str.ToLower() = "true"
      Return True
    Else
      Return False
    EndIf
  End Method

  Method readArray:Int[](sectionName:String, key:String, defaultValue:Int[])
    Local defaultStr:String
    For Local i:Int = 0 Until defaultValue.Length
      defaultStr += String(defaultValue[i])
      If i < defaultValue.Length - 1
        defaultStr += ","
      EndIf
    Next
    Local str:String = readValue(sectionName, key, defaultStr)
    str = splitComment(str)
    Local val:String[] = str.Split(",")
    ' convert to int
    Local valFinal:Int[] = New Int[val.Length]
    For Local i:Int = 0 Until val.Length
      valFinal[i] = Int(val[i])
    Next

    Return valFinal
  End Method

  Method readArray:Float[](sectionName:String, key:String, defaultValue:Float[])
    Local defaultStr:String
    For Local i:Int = 0 Until defaultValue.Length
      defaultStr += String(defaultValue[i])
      If i < defaultValue.Length - 1
        defaultStr += ","
      EndIf
    Next
    Local str:String = readValue(sectionName, key, defaultStr)
    str = splitComment(str)
    Local val:String[] = str.Split(",")
    ' convert to float
    Local valFinal:Float[] = New Float[val.Length]
    For Local i:Int = 0 Until val.Length
      valFinal[i] = Float(val[i])
    Next

    Return valFinal
  End Method

  Method readArray:Bool[](sectionName:String, key:String, defaultValue:Bool[])
    Local defaultStr:String
    For Local i:Int = 0 Until defaultValue.Length
      If defaultValue[i]
        defaultStr += "True"
      Else
        defaultStr += "False"
      EndIf
      If i < defaultValue.Length - 1
        defaultStr += ","
      EndIf
    Next
    Local str:String = readValue(sectionName, key, defaultStr)
    str = splitComment(str)
    Local val:String[] = str.Split(",")
    ' convert to bool
    Local valFinal:Bool[] = New Bool[val.Length]
    For Local i:Int = 0 Until val.Length
      If val[i].ToLower() = "true"
        valFinal[i] = True
      Else
        valFinal[i] = False
      EndIf
    Next

    Return valFinal
  End Method

Private
  Method refresh:Void()
    ' clear local cache
    _sections.Clear()
    _modified.Clear()
   
    Local currentSection:StringMap<String> = Null
    Local sectionName:String

    Local lines:String[] = _iniString.Trim().Split("~n")
    For Local str:String = EachIn lines
      ' remove possible "carriage return"
      If str.EndsWith("~r")
        str = str[..str.Length-1]
      EndIf

      ' check for section names
      sectionName = parseSectionName(str)
      If sectionName.Length > 0
        ' only the first occurrence of a section is loaded
        If _sections.Contains(sectionName)
          currentSection = Null
        Else
          currentSection = New StringMap<String>()
          _sections.Add(sectionName, currentSection)
        EndIf
      ElseIf currentSection <> Null
        ' check for key+value pair
        Local keyValuePair:KeyValuePair = parseKeyValuePair(str)
        If keyValuePair._isValid
          ' only the first occurrence of a key is loaded
          If Not currentSection.Contains(keyValuePair._key)
            currentSection.Add(keyValuePair._key, keyValuePair._value)
          EndIf
        EndIf
      EndIf
    Next
  End Method

  Method splitComment:String(str:String)
    Local retStr:String[]
    If str.Contains("//")
      retStr = str.Split("//")
    ElseIf str.Contains("#")
      retStr = str.Split("#")
    ElseIf str.Contains(";")
      retStr = str.Split(";")
    Else
      retStr = New String[1]
      retStr[0] = str
    EndIf

    Return retStr[0].Trim()
  End Method

  Method parseSectionName:String(s:String)
    If s.StartsWith("//") Or s.StartsWith("#") Or s.StartsWith(";") ' comment
      Return ""
    EndIf
    If Not s.StartsWith("[")
      Return ""
    EndIf
    If Not s.EndsWith("]")
      Return ""
    EndIf
    If s.Length < 3
      Return ""
    EndIf

    Return s[1..s.Length-1]
  End Method

  Method parseKeyValuePair:KeyValuePair(s:String)
    Local keyValuePair:KeyValuePair = New KeyValuePair()
    If s.StartsWith("//") Or s.StartsWith("#") Or s.StartsWith(";") ' comment
      keyValuePair._isValid = False
      Return keyValuePair
    EndIf

    Local i:Int = s.Find("=")
    If i = 0
      keyValuePair._isValid = False
      Return keyValuePair
    EndIf

    keyValuePair._key = s[0..i].Trim()
    If keyValuePair._key.Length = 0
      keyValuePair._isValid = False
      Return keyValuePair
    EndIf

    Local j:Int = s.Length - i - 1
    If j > 0
      keyValuePair._value = s[i+1..].Trim()
    Else
      keyValuePair._value = ""
    EndIf

    keyValuePair._isValid = True
    Return keyValuePair
  End Method
End Class


Example ini file:

Code:
// This is a comment
# This too!
; another comment

[General]
StringVal = Hello World
FloatVal  = 123.45 // some comment behind
IntVal    = 123
BoolVal   = false
IntArr    = 1, 2, 3
FloatArr  = 1.2, 3.4, 5.6
BoolArr   = true, false, true

Please note for those ini files: you can have comments everywhere but for a string value comments are currently ignored. Might add some quotes "" to mark the string.


Example usage:
Code:
Local ini:IniHandler  = New IniHandler()
Local file:FileStream = ini.open("config.ini")

Local sval:String = ini.readValue("General", "StringVal", "NOT SET")
Local ival:Int    = ini.readValue("General", "IntVal", 0)
Local fval:Float  = ini.readValue("General", "FloatVal", 0.0)
Local bval:Bool   = ini.readValue("General", "BoolVal", False)

Local iarr:Int[]   = ini.readArray("General", "IntArr", [1, 2, 3])
Local farr:Float[] = ini.readArray("General", "FloatArr", [1.0, 2.0, 3.0])
Local barr:Bool[]  = ini.readArray("General", "BoolArr", [True, False, True])

Please let me know if you would like have anything added.
 
Last edited:

MikeHart

Administrator
3rd Party Module Dev
3rd Party Target Dev
3rd Party Tool Dev
Joined
Jun 19, 2017
Messages
3,346
Cool, very useful.
 

MikeHart

Administrator
3rd Party Module Dev
3rd Party Target Dev
3rd Party Tool Dev
Joined
Jun 19, 2017
Messages
3,346
Thanks Martin.
 
Top Bottom