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
338
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:
Cool, very useful.
 
Thanks Martin.
 
Back
Top Bottom