• Dear Cerberus X User!

    As we prepare to transition the forum ownership from Mike to Phil (TripleHead GmbH), we need your explicit consent to transfer your user data in accordance with our amended Terms and Rules in order to be compliant with data protection laws.

    Important: If you accept the amended Terms and Rules, you agree to the transfer of your user data to the future forum owner!

    Please read the new Terms and Rules below, check the box to agree, and click "Accept" to continue enjoying your Cerberus X Forum experience. The deadline for consent is April 5, 2024.

    Do not accept the amended Terms and Rules if you do not wish your personal data to be transferred to the future forum owner!

    Accepting ensures:

    - Continued access to your account with a short break for the actual transfer.

    - Retention of your data under the same terms.

    Without consent:

    - You don't have further access to your forum user account.

    - Your account and personal data will be deleted after April 5, 2024.

    - Public posts remain, but usernames indicating real identity will be anonymized. If you disagree with a fictitious name you have the option to contact us so we can find a name that is acceptable to you.

    We hope to keep you in our community and see you on the forum soon!

    All the best

    Your Cerberus X Team

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
344
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:
Back
Top Bottom