Simple INI Reader and Writer

4 replies to this topic
Posted 1+ years ago #1
Goodlookinguy

Edit 3/5/13: I'm not sure what I was talking about in the below edit...but I hope I haven't made any changes to it since this...I think.
Edit 7/13/12: I'm reworking several parts of this at the moment. The next stable version of it will be between 7/13-7/21 probably.

I ported a BlitzMAX INI reader and writer for some of my stuff. Decided it didn't have enough features and added some. I figured it was worth sharing since there weren't any other INI type file readers.

I'm heading to bed at the time of this posting, so I'll just leave this list of methods, functions, and constants until I come back. When I come back later, I'll document and explain anything that needs to be explained.

#Rem
	Title: INI file reader and writer
	Original Author: JoshK
	Monkey Port and Additions/Optimizations/Changes:  Nicholas Grant
	Date: 6/20/2012 rev2
	Description: INI property reader and writer
	Original Code: http://www.blitzbasic.com/codearcs/codearcs.php?code=2814
#End

Class Ini
Private
	Field _Map:StringMap<StringMap<String>> = New StringMap<StringMap<String>>()
	Field _UsingQuotes:Bool
	
Public
	
	Method New( useQuotes:Bool = AcceptQuotes )
		_UsingQuotes = useQuotes
	End
	
	
	
	Method ContainsKey:Bool( key:String, section:String = DefaultSection )
		If _Map.Contains(section) Then Return _Map.Get(section).Contains(key)
		Return False
	End
	
	Method ContainsSection:Bool( section:String = DefaultSection )
		Return _Map.Contains(section)
	End
	
	
	
	Method Add:Void( key:String, value:String, section:String = DefaultSection )
		Local subMap:StringMap<String>
		
		If Not _Map.Contains(section) Then _Map.Set(section, New StringMap<String>())
		
		subMap = _Map.Get(section)
		subMap.Add(key, value)
	End
	
	Method Set:Void( key:String, value:String, section:String = DefaultSection )
		Local subMap:StringMap<String>
		
		If Not _Map.Contains(section) Then _Map.Set(section, New StringMap<String>())
		
		subMap = _Map.Get(section)
		subMap.Set(key, value)
	End
	
	Method Get:String( key:String, section:String = DefaultSection, defaultValue:String = "" )
		Local subMap:StringMap<String>
		
		If _Map.Contains(section)
			subMap = _Map.Get(section)
			If Not subMap.Contains(key) Then Return defaultValue
			Return subMap.Get(key)
		End
		
		Return defaultValue
	End
	
	Method GetInt:Int( key:String, section:String = DefaultSection, defaultValue:Int = 0 )
		Local value:String = Get(key, section)
		If value <> "" Then Return Int(value)
		Return defaultValue
	End
	
	Method GetFloat:Float( key:String, section:String = DefaultSection, defaultValue:Float = 0.0 )
		Local value:String = Get(key, section)
		If value <> "" Then Return Float(value)
		Return defaultValue
	End
	
	Method GetIntArray:Int[]( key:String, section:String = DefaultSection, defaultValue:Int[] = [] )
		Local intValues:Int[]
		Local values:String[] = Get(key, section).Split(",")
		
		If values.Length() <= 0 Then Return defaultValue
		
		intValues = New Int[values.Length()]
		
		For Local index:Int = 0 Until values.Length()
			intValues[index] = Int(values[index].Trim())
		Next
		
		Return intValues
	End
	
	Method GetFloatArray:Float[]( key:String, section:String = DefaultSection, defaultValue:Float[] = [] )
		Local floatValues:Float[]
		Local values:String[] = Get(key, section).Split(",")
		
		If values.Length() <= 0 Then Return defaultValue
		
		floatValues = New Float[values.Length()]
		
		For Local index:Int = 0 Until values.Length()
			floatValues[index] = Float(values[index].Trim())
		Next
		
		Return floatValues
	End
	
	
	
	Method GetAsFile:String() ' Use only when needed, as this has massive string concatination
		Local section:String
		Local subMap:StringMap<String>
		Local key:String
		Local data:String = ""
		
		For section = EachIn _Map.Keys()
			subMap = _Map.Get(section)
			
			If Not subMap.IsEmpty()
				data += "[" + section + "]~n"
				
				For key = EachIn subMap.Keys()
					If _UsingQuotes
						data += key + "=~q" + subMap.Get(key) + "~q~n"
					Else
						data += key + "=" + subMap.Get(key) + "~n"
					End
				Next
			End
		Next
		
		Return data
	End
	
	Function Load:Ini( data:String, quoteUsage:Bool = AcceptQuotes )
		Local ini:Ini = New Ini(quoteUsage)
		Local enclosure:Int
		Local lines:String[]
		Local section:String
		Local key:String
		Local value:String
		Local delimiterIndex:Int = -1
		
		lines = data.Split("~n")
		
		For Local line:String = EachIn lines
			line = line.Trim()
			
			If line.Length() > 0
				If line[0] = 59 Then Continue ' ;
				
				If line[0] = 91 And line[line.Length() - 1] = 93 ' [ and ]
					section = line[1..-1]
				Else
					delimiterIndex = line.Find("=")
					
					If delimiterIndex > 0
						key = line[..delimiterIndex].Trim()
						value = line[delimiterIndex + 1..].Trim()
						
						If value.Length() > 0 And key.Length() > 0
							If quoteUsage
								Local leftEnclosure:Int = value[0]
								Local rightEnclosure:Int = value[value.Length() - 1]
								
								If leftEnclosure = rightEnclosure And (leftEnclosure = 39 Or leftEnclosure = 34)
									value = value[1..-1]
								End
							End
							
							ini.Set(key, value, section)
						End
					End
				End
			End
		Next
		
		Return ini
	End
	
	Function Create:Ini( useQuotes:Bool = UseQuotes )
		Return New Ini(useQuotes)
	End
	
	
	
	Method GetSections:String[]()
		Local index:Int = 0
		Local sections:String[] = New String[_Map.Count()]
		
		For Local section:String = EachIn _Map.Keys()
			sections[index] = section
			index += 1
		Next
		
		Return sections
	End
	
	Method GetKeys:String[]( section:String )
		Local index:Int = 0
		Local keys:String[]
		
		If _Map.Contains(section)
			keys = New String[_Map.Get(section).Count()]
			
			For Local key:String = EachIn _Map.Get(section).Keys()
				keys[index] = key
				index += 1
			Next
		End
		
		Return keys
	End
	
	
	
	Method Merge:Void( data:String, mergeMode:Bool = IgnoreSetKeys, useQuotes:Bool = AcceptQuotes )
		Local ini:Ini = Ini.Load(data, useQuotes)
		
		Local sections:String[] = ini.GetSections()
	
		For Local section:String = EachIn sections
			For Local key:String = EachIn ini.GetKeys(section)
				If mergeMode
					Add(key, ini.Get(key, section), section)
				Else
					Set(key, ini.Get(key, section), section)
				End
			Next
		Next
	End
	
	Method Merge:Void( ini:Ini, mergeMode:Bool = IgnoreSetKeys )
		Local sections:String[] = ini.GetSections()
	
		For Local section:String = EachIn sections
			For Local key:String = EachIn ini.GetKeys(section)
				If mergeMode
					Add(key, ini.Get(key, section), section)
				Else
					Set(key, ini.Get(key, section), section)
				End
			Next
		Next
	End
	
	
	
	
	' Default section
	Const DefaultSection:String = "ini"
	
	' Quote mode
	Const AcceptQuotes:Bool = True
	Const IgnoreQuotes:Bool = False
	
		' Legacy quote mode constants
		Const UseQuotes:Bool = True
		Const NoQuotes:Bool = False
	
	' Merge mode
	Const IgnoreSetKeys:Bool = True
	Const OverwriteKeys:Bool = False
End

Test

Import cIni

Function Main:Int()
	Local iniData:String = "[Data]~nValue 1 = 123456789~nValue 2 = 9.123456~nValue 3 = 15, 7.5, 3.75"
	Local ini:Ini = Ini.Load(iniData)
	
	Print ini.Get("Value 1", "Data") + ini.Get("Value 2", "Data")
	Print String(ini.GetInt("Value 1", "Data"))
	Print String(ini.GetFloat("Value 1", "Data"))
	Print String(ini.GetFloat("Value 2", "Data"))
	Print String(ini.GetInt("Value 2", "Data"))
	Print String(ini.GetInt("Value 1", "Data") + ini.GetFloat("Value 2", "Data"))
	Print ini.Get("Value 3", "Data")
	Print "==================================="
	
	Local intArray:Int[] = ini.GetIntArray("Value 3", "Data")
	Local floatArray:Float[] = ini.GetFloatArray("Value 3", "Data")
	
	For Local intVal:Int = EachIn intArray
		Print "Int -> " + String(intVal)
	Next
	
	For Local floatVal:Float = EachIn floatArray
		Print "Flt -> " + String(floatVal)
	Next
	
	Return 0
End

Methods, Functions, and Constants

'- Methods
 * ContainsKey:Bool( key:String, section:String )
 * ContainsSection:Bool( section:String )
 * Add:Void( key:String, value:String, section:String )
 * Set:Void( key:String, value:String, section:String )
 * Get:String( key:String, section:String, defaultValue:String )
 * GetInt:Int( key:String, section:String, defaultValue:Int )
 * GetFloat:Float( key:String, section:String, defaultValue:Float )
 * GetIntArray:Int[]( key:String, section:String, defaultValue:Int[] )
 * GetFloatArray:Float[]( key:String, section:String, defaultValue:Float[] )
 * GetAsFile:String()
 * GetSections:String[]()
 * GetKeys:String[]( section:String )
 * Merge:Void( data:String, mergeMode:Bool, useQuotes:Bool )
 * Merge:Void( ini:Ini, mergeMode:Bool )

'- Functions
 * Load:Ini( data:String, useQuotes:Bool )
 * Create:Ini( useQuotes:Bool )

'- Consts
 * DefaultSection:String
'-- How to handle quote reading
 * AcceptQuotes:Bool '<-- Take only the contents wrapped in the quotes if in quotes
 * IgnoreQuotes:Bool '<-- Take everything including quotes at beginning and end
 * UseQuotes:Bool
 * NoQuotes:Bool
'-- Merge Modes
 * IgnoreSetKeys:Bool
 * OverwriteKeys:Bool
 
Posted 1+ years ago #2
degac

Nice to share.
I wrote a similar thing for Bmax (years ago): I will add something about 'arrays-keys' (like KEYS=1,2,3) for multiple keys-value.

 
Posted 6 months ago #3
[VOLOFORCE]

I am sorry... but how exactly does this work?

Like say, I want to read an INI-file.

In which directory do I have to put it?

Then, how do I save? Or create a new one?

 
Posted 5 months ago #4
Goodlookinguy

This reads and writes INI files in memory. You have to handle the saving and loading since there are numerous systems people have designed for virtually saving data and loading it.

 
Posted 4 months ago #5
[VOLOFORCE]

Oh, ok. Now I see, thanks. I was just confused by this. Mostly use JSON but INI can be handy.