[bb] INI-format data files by Yasha [ 1+ years ago ]

Started by BlitzBot, June 29, 2017, 00:28:39

Previous topic - Next topic

BlitzBot

Title : INI-format data files
Author : Yasha
Posted : 1+ years ago

Description : INI is a simple file format for storing data (usually configuration settings, but potentially anything) in a format that is easy for humans to read and edit as text files (<a href="http://en.wikipedia.org/wiki/Ini_file" target="_blank">Wikipedia</a>).

The format is very simple. Data is held in properties, which are a key/value pair where a name key is associated with a value using the = operator. Properties can be grouped in sections, where a name key in square brackets marks that the following properties belong to the section of that name. And like in Blitz3D, any text following a semicolon is a comment and is ignored by the loader.

The main thing of note is that unlike some other serialization formats (such as XML), sections do not "nest", and the file is completely flat. This means less stuff on screen (at the cost of some structural expression), making them easier to use when you want the end user to be able to edit the file (e.g. to play with settings).

Details: In this implementation, name keys (for both section and property names) may use any of the letters A-Z, a-z (case sensitive), 0-9, and the characters   : . and _. Value strings (the part of a property to the right of the = operator) may consist of any characters, but will end at the newline or a semicolon marking a comment. Between double-quotes, the semicolon may appear (and quotes themselves may be escaped with a backslash) without starting a comment. If an open-brace appears, the text following will be treated as part of the value string until the matching close-brace appears (each open-brace within the string must be matched by a close-brace before the quoted string ends), which allows the string to include newlines and span many lines of text. Everything (whitespace, semicolons) within a braced or quoted section of string is preserved, but if the string is completely enclosed in quotes or braces, the outermost quote/brace characters are removed. Unquoted whitespace is stripped from the ends of the value.

Use: Despite requiring a flat structure, the INI format can store any kind of structured data that can be represented in string form; instead of nesting objects, another section can represent the nested structure, and its "owner" can store a string key, simply listing its section name. It is also possible to store another INI structure within a braced block value, although the result string will have to be parsed separately. Levels, game settings, game rules (such as weapon stats) or even 3D models can be stored easily with this file format.

INI structures can be either created empty, or loaded from a file or string (useful for sub-structures), with the provided constructor and loader functions. Sections and properties may be both added and removed, and the values of properties both read and assigned. Once the structure has been suitably edited, it can be saved back to a file, or a single string (if the structure was created rather than loaded, the library will attempt to format it slightly for readability as well; otherwise it will preserve the original formatting).

Properties may be accessed either as a member of a section, or without specifying the section, in which case the first matching property in the entire structure is returned (using sections is wholly optional). It is also possible to delete a section but leave its properties in place. Any duplicate names (either of sections or properties) are not in error, but instead only the first matching name will be "found" and the second and others will be hidden until the first is deleted (properties of the same name in different sections may be accessed by specifying the section).

In the event of any error, the function "LogError" is called with details, and the function where an error occurred will try to return gracefully with a null value. This means that nonexistent names are not a program-breaking error. "LogError" itself is not provided and should be linked to an appropriate error logging function for your program (since the errors will reflect user-edited data, this probably shouldn't be DebugLog).

Details of the specific functions making up the API are given in the file itself.

Examples: Here's a simple example of an INI file being created from scratch:

Include "INI.bb"



;Example of using INI library to create a new structure

;Need to supply a suitable definition for the error log function
Function LogError(err$)
Print err
End Function


;Create the new INI structure
Local i.INI_File = INI_Create("This is the top comment of the file")


;Setup the sections and data fields
INI_AddSection i, "Sec1", "First section"
INI_AddSection i, "Sec2", "Second section"
INI_AddSection i, "Sec3", "Third section"
INI_AddSection i, "Sec4", "Fourth section"
INI_AddSection i, "Sec5", "Fifth section - note that comments stick around if section is removed"

INI_AddProperty i, "Sec1", "a", "Val A1", "Meh"
INI_AddProperty i, "Sec1", "b", "Val B1"

INI_AddProperty i, "Sec2", "c", "Val C2", "Meh"
INI_AddProperty i, "Sec2", "d", "Val D2"

INI_AddProperty i, "Sec3", "a", "Val A3", "Meh"
INI_AddProperty i, "Sec3", "b", "Val B3"

INI_AddProperty i, "Sec4", "c", "Val C4", "Meh"
INI_AddProperty i, "Sec4", "d", "Val D4"

INI_AddProperty i, "Sec5", "a", "Val A5", "Meh"
INI_AddProperty i, "Sec5", "b", "Val B5"

INI_AddProperty i, "", "postscriptum", "A Final Note", "Right at the end"


;Using DebugLog because Print can't handle multi-line strings correctly
DebugLog "Original structure:"
DebugLog INI_WriteString(i) ;Examine the structure


;Remove some fields we don't want
INI_RemoveSection i, "Sec3", False
INI_RemoveSection i, "Sec5"

INI_RemoveProperty i, "Sec2", "c"

DebugLog "Modified structure:"
DebugLog INI_WriteString(i) ;Now some stuff is gone, examine it again


;Test by reading some values
Print INI_GetValue(i, "Sec4", "c")
Print INI_GetValue(i, "", "a")
Print INI_GetValue(i, "", "postscriptum")

Print ""
Print INI_GetValue(i, "Sec5", "a") ;These two don't exist
Print INI_GetValue(i, "Sec2", "e")


RuntimeStats

;Discard the structure now we're done with it
INI_Free i

RuntimeStats


WaitKey
End



A simple structure is created with five sections with two properties each, plus a sectionless final property. A couple of properties and sections are removed, and some values are read. (This example uses DebugLog to print out the whole structure because it can handle multi-line strings, unlike Print).
Here's a more complex example - a simple level definition:
; Simple example of level data

; List some enemies, who each get their own section
[ Enemies ]
Count = 3
Names = Tom, Dick, Harry

; Enemy descriptions
[ Tom ]
HP = 90
Str = 45
StartPos = 10, 5, 50
StartRot = 0, 47.5, 0
;Braced blocks can hold multi-line definitions, such as script fragments
UpdateFunction = {
(lambda (self player)
(if (< (self distance-to player) threshold)
   (self attack player)
   (self wander-randomly)))
}

[ Dick ]
HP = 84
Str = 30
StartPos = -10, 5, 40
; No rotation data - so Dick will probably start with the default rotation
Shout = "Grargh!" ;Completely quoted or braced values have the quotes  or braces removed
UpdateFunction = DickUpdateFunction

; Section and key names may contain  : . and _ to simulate namespacing
; Nested structures can thus be formed by referencing other sections
[ DickUpdateFunction ]
ScriptLanguage = JavaScript ;Different language from Tom's script
ScriptBody = {
// Everything between the braces is kept, so this comment is part of the script
function(player) {
if (this.distance_to(player) < threshold) {
this.attack(player);
} else {
this.wander_randomly();
}
}
} ; The value only ends when the matching close brace is reached

[ Harry ]
HP = 75
Str = 50
StartPos = 0, 5, 100
StartRot = 0, -180, 0
;Because INI structures can also be loaded from string values, we can also nest
;data by putting another INI structure within a braced block, to read later
;with the INI_LoadString function
UpdateFunction = {
ScriptLanguage = None
ScriptBody = "#!Use default update function"
}


In this, the number of NPCs and a list of their names are given in the first section; the names can then be used to access the sections with the data for each relevant section. Note that not every NPC has every data field; if a field is absent, the empty string is returned (making something of this is up to whoever tried to access the value).

The NPCs also have scripts attached to control their AI - three different ways of representing this are shown. The first NPC simply stores the whole script fragment in a braced multi-line value. The second NPC stores the name of another section; in this case, it's taking advantage of the fact that names can contain the backslash character to make it look like a namespaced identifier. That section then contains the script as a braced value again. The third NPC stores an INI sub-structure in a braced block, which can be loaded with INI_LoadString and read in the same way as the containing file.
It's true that INI isn't as powerful or safe as XML or other formats, and it probably shouldn't be recommended for particularly large, fragile structures such as 3D models. But I prefer the simplicity where it's appropriate. [/i]

Code :
Code (blitzbasic) Select
; INI file read/write library
;=============================


; Public API:
;
; - INI_Create: Create a new, empty INI structure from scratch with an optional comment
;               Structures created in this way will attempt to auto-format themselves
;
; - INI_Load: Load an INI structure from a file
;             Structures created in this way will not apply any auto-formatting
;
; - INI_LoadString: Load an INI structure from a string
;                   This is otherwise identical to INI_Load
;
; - INI_Write: Write an INI structure out to a text file
;
; - INI_WriteString: Write an INI structure out to a string
;                    As far as possible this is identical to INI_Write
;
; - INI_Free: Free an INI structure and all of its elements
;
; - INI_AddSection: Add a new, empty section to an INI structure
;                   No check is performed to see if a section with that name already exists
;
; - INI_RemoveSection: Remove a section (and optionally all of its properties) from an INI structure
;                      In the case of duplicate sections, only the first is removed
;                      If the section does not exist, an error is logged and no action is taken
;
; - INI_AddProperty: Add a property to an INI structure (optionally in a given section)
;                    No check is performed to see if a property with that name already exists
;
; - INI_GetValue: Retrieve a value from a given property (optionally in a given section)
;                 If the property does not exist, the empty string is returned and an error logged
;
; - INI_SetValue: Set the value of a given property (optionally in a given section)
;                 If the property does not exist, an error is logged and no action is taken
;
; - INI_GetComment: Retrieve a comment from a given property (optionally in a given section)
;                   If the property does not exist, the empty string is returned and an error logged
;
; - INI_SetComment: Set the comment of a given property (optionally in a given section)
;                   If the property does not exist, an error is logged and no action is taken
;
; - INI_RemoveProperty: Remove a property from an INI structure (optionally in a given section)
;                       If the property does not exist, an error is logged and no action is taken
;
;
; All the remaining functions, whose names begin with INI_private_, are part of the internal
; implementation and should not be called directly.
;
; The LogError function, called when an error is encountered, is not provided in this library.
; Instead, link it to a suitable logging function for your application.
;


Type INI_File
Field imported, comment$
Field sCount, sList.INI_Section
Field pCount, pList.INI_Property
End Type

Type INI_Stream
Field sPtr, sData
Field source$
End Type

Type INI_Section
Field name$, comment$
Field pCount, start.INI_Property

Field pv.INI_Section, nx.INI_Section
End Type

Type INI_Property
Field hasValue
Field key$, value$, comment$

Field pv.INI_Property, nx.INI_Property
End Type


; Create a new, empty INI structure
Function INI_Create.INI_File(comment$ = "")
Local i.INI_File = New INI_File

If comment <> ""
icomment = comment
ipList = INI_private_CreateProperty(i, Null, False, "", "", "", Null) ;Spacer line
Local c.INI_Property = INI_private_CreateProperty(i, Null, False, "", "", comment, ipList)
INI_private_CreateProperty(i, Null, False, "", "", "", c) ;Another spacer
EndIf

Return i
End Function

; Load an INI structure from a file
Function INI_Load.INI_File(filename$)
Local s.INI_Stream, i.INI_File

If FileType(filename) <> 1 ;Replace with appropriate error function for your program
LogError "Could not open "+Chr(34)+filename+Chr(34)+": file not found"
Return Null
EndIf

s = INI_private_LoadINIFileStream(filename)
i = INI_private_ReadStream(s)
INI_private_FreeINIFileStream s

Return i
End Function

; Load an INI structure from a string
Function INI_LoadString.INI_File(val$)
Local s.INI_Stream, i.INI_File

s = INI_private_ReadINIFileSTream(val)
i = INI_private_ReadStream(s)
INI_private_FreeINIFileStream s

Return i
End Function

; Write an INI structure out to a text file
Function INI_Write(ini.INI_File, filename$)
Local f = WriteFile(filename), p.INI_Property, outL$

If f = 0
LogError "Unable to write to file "+Chr(34)+filename+Chr(34)
Return
EndIf

p = inipList
While p <> Null
If phasValue
outL = pkey + " = " + pvalue
If pcomment <> "" Then outL = outL + "    ;" + pcomment
Else
If pvalue = ""
If pcomment <> "" Then outL = ";" + pcomment : Else outL = ""
Else
outL = "[ " + pvalue + " ]"
If pcomment <> "" Then outL = outL + "    ;" + pcomment
EndIf
EndIf
WriteLine f, outL

p = p
x
Wend
If Not iniimported Then WriteLine f, ""

CloseFile f
End Function

; Write an INI structure out to a string
Function INI_WriteString$(ini.INI_File)
Local outS$, p.INI_Property, outL$

p = inipList
While p <> Null
If phasValue
outL = pkey + " = " + pvalue
If pcomment <> "" Then outL = outL + "    ;" + pcomment
Else
If pvalue = ""
If pcomment <> "" Then outL = ";" + pcomment : Else outL = ""
Else
outL = "[ " + pvalue + " ]"
If pcomment <> "" Then outL = outL + "    ;" + pcomment
EndIf
EndIf
outS = outS + outL + Chr(13) + Chr(10)

p = p
x
Wend
If Not iniimported Then outS = outS + Chr(13) + Chr(10)

Return outS
End Function

; Free an INI structure and all of its elements
Function INI_Free(ini.INI_File)
Local p.INI_Property, op.INI_Property

p = inipList
While p <> Null
op = p
p = p
x
Delete op
Wend

Local s.INI_Section, os.INI_Section

s = inisList
While s <> Null
os = s
s = s
x
Delete os
Wend

Delete ini
End Function


; Add a new, empty section to an INI structure
Function INI_AddSection(ini.INI_File, name$, comment$)
Local s.INI_Section, os.INI_Section

s = inisList
While s <> Null
os = s
s = s
x
Wend

Local p.INI_Property, op.INI_Property

p = inipList
While p <> Null
op = p
p = p
x
Wend

INI_private_CreateSection ini, name, comment, os, op
End Function

; Remove a section (and optionally all of its properties) from an INI structure
Function INI_RemoveSection(ini.INI_File, name$, freeProperties = True)
Local s.INI_Section, s_pv.INI_Section, s_nx.INI_Section

s = inisList
While s <> Null
If s
ame = name Then Exit
s = s
x
Wend

If s = Null
LogError "Could not find section "+Chr(34)+name+Chr(34)+" to remove"
Return
EndIf

If freeProperties
Local  p.INI_Property, p_pv.INI_Property, p_nx.INI_Property, i

p_pv = sstartpv
p = sstart
p_nx = p
x

For i = 0 To spCount ;Not an error - we're also processing the zero element
p_nx = p
x
Delete p
p = p_nx
Next

If p_pv <> Null Then p_pv
x = p_nx
If p_nx <> Null Then p_nxpv = p_pv
EndIf

If spv <> Null Then spv
x = s
x
If s
x <> Null Then s
xpv = spv
Delete s
End Function


; Add a property to an INI structure (optionally in a given section)
Function INI_AddProperty(ini.INI_File, sec$, key$, value$, comment$ = "")
Local s.INI_Section = Null, i, p.INI_Property, n.INI_Property

If sec <> ""
s = inisList
While s <> Null
If s
ame = sec Then Exit
s = s
x
Wend

If s = Null
LogError "Could not find section "+Chr(34)+sec+Chr(34)+"; property not added"
Return
EndIf

p = sstart
For i = 1 To spCount
p = p
x
Next
Else
n = inipList
While n <> Null
p = n
n = n
x
Wend
EndIf

;Note that we did NOT check for duplicate names - this is not an error
INI_private_CreateProperty(ini, s, True, key, value, comment, p)
End Function

; Retrieve a value from a given property (optionally in a given section)
Function INI_GetValue$(ini.INI_File, sec$, key$)
Local p.INI_Property[0], s.INI_Section[0]

INI_private_GetProperty ini, sec, key, p, s ;Use out parameters

If p[0] <> Null Then Return p[0]value : Else Return ""
End Function

; Set the value of a given property (optionally in a given section)
Function INI_SetValue(ini.INI_File, sec$, key$, val$)
Local p.INI_Property[0], s.INI_Section[0]

INI_private_GetProperty ini, sec, key, p, s ;Use out parameters

If p[0] <> Null Then p[0]value = val
End Function

; Retrieve a comment from a given property (optionally in a given section)
Function INI_GetComment$(ini.INI_File, sec$, key$)
Local p.INI_Property[0], s.INI_Section[0]

INI_private_GetProperty ini, sec, key, p, s ;Use out parameters

If p[0] <> Null Then Return p[0]comment : Else Return ""
End Function

; Set the comment of a given property (optionally in a given section)
Function INI_SetComment(ini.INI_File, sec$, key$, cmmt$)
Local p.INI_Property[0], s.INI_Section[0]

INI_private_GetProperty ini, sec, key, p, s ;Use out parameters

If p[0] <> Null Then p[0]comment = cmmt
End Function

; Remove a property from an INI structure (optionally in a given section)
Function INI_RemoveProperty(ini.INI_File, sec$, key$)
Local p.INI_Property[0], s.INI_Section[0], s2.INI_Section, p2.INI_Property, i

INI_private_GetProperty ini, sec, key, p, s ;Use out parameters

If sec = "" ;Make sure that if the property belongs to a section, it gets removed properly
s2 = inisList
While s2 <> Null
p2 = s2start
For i = 1 To s2pCount
p2 = p2
x
If p2hasValue
If p2key = key Then Exit ;The list is ordered, so break on the first match
EndIf
Next
If i <= s2pCount Then Exit
s2 = s2
x
Wend
If s2 <> Null
If p2 = p[0] Then s[0] = s2
EndIf
EndIf

If p[0] <> Null
If s[0] <> Null Then s[0]pCount = s[0]pCount - 1
If p[0]pv <> Null Then p[0]pv
x = p[0]
x
If p[0]
x <> Null Then p[0]
xpv = p[0]pv
Delete p[0]
EndIf
End Function



; Internal function: load an INI structure from a bank stream
Function INI_private_ReadStream.INI_File(s.INI_Stream)
Local i.INI_File = INI_Create(), error, ls.INI_Section, lp.INI_Property

iimported = True

While ssPtr < BankSize(ssData) - 2
INI_private_SkipWhitespace s
Local c = PeekByte(ssData, ssPtr)

If c = 59 ;Semicolon
lp = INI_private_ReadCommentLine(s, i, ls, lp) ;Doesn't raise any errors

ElseIf c = 91 ;[ (open bracket)
ls = INI_private_ReadSectionDef(s, i, ls, lp)
If ls = Null Then error = True : Else lp = lsstart

ElseIf INI_private_IsValidNameChar(c) ;Key name
lp = INI_private_ReadPropertyDef(s, i, ls, lp)
If lp = Null Then error = True

ElseIf INI_private_CheckNewline(s) ;Try to swallow a newline
lp = INI_private_CreateProperty(i, ls, False, "", "", "", lp)

Else
error = True
EndIf

If error ;In the event of error, break off (should we try to continue instead?)
Local lineNo[0], lineVal$[0]
INI_private_GetCurrentLine s, lineNo, lineVal
LogError "Error reading data from "+ssource+": line "+lineNo[0]+" ( "+lineVal[0]+" ) is not a valid property or section definition"
INI_Free i
Return Null
EndIf
Wend

Return i
End Function

; Internal function: read a comment line into the INI structure for preservation
Function INI_private_ReadCommentLine.INI_Property(s.INI_Stream, i.INI_File, ls.INI_Section, lp.INI_Property)
Local cmmt$
ssPtr = ssPtr + 1
While Not INI_private_CheckNewline(s)
cmmt = cmmt + Chr(PeekByte(ssData, ssPtr))
ssPtr = ssPtr + 1
Wend
Return INI_private_CreateProperty(i, ls, False, "", "", cmmt, lp)
End Function

; Internal function: read a section definition into the INI structure
Function INI_private_ReadSectionDef.INI_Section(s.INI_Stream, i.INI_File, ls.INI_Section, lp.INI_Property)
Local name$, c, cmmt$

ssPtr = ssPtr + 1
INI_private_SkipWhitespace s

c = PeekByte(ssData, ssPtr)
While INI_private_IsValidNameChar(c)
name = name + Chr(c)
ssPtr = ssPtr + 1
c = PeekByte(ssData, ssPtr)
Wend

INI_private_SkipWhitespace s

; If there was no closing bracket, or there is an invalid character, return Null as error
If PeekByte(ssData, ssPtr) <> 93 Or name = "" Then Return Null
ssPtr = ssPtr + 1

INI_private_SkipWhitespace s

If PeekByte(ssData, ssPtr) = 59 ;Semicolon - comment
ssPtr = ssPtr + 1
While Not INI_private_CheckNewline(s)
cmmt = cmmt + Chr(PeekByte(ssData, ssPtr))
ssPtr = ssPtr + 1
Wend
ElseIf Not INI_private_CheckNewline(s) ;Newline or comments only; otherwise, return Null as error
Return Null
EndIf

Return INI_private_CreateSection(i, name, cmmt, ls, lp)
End Function

; Internal function: read a property definition into the INI structure
Function INI_private_ReadPropertyDef.INI_Property(s.INI_Stream, i.INI_File, ls.INI_Section, lp.INI_Property)
Local name$, val$, cmmt$, c, inQuotes, inBraces, esc

INI_private_SkipWhitespace s

c = PeekByte(ssData, ssPtr)
While INI_private_IsValidNameChar(c)
name = name + Chr(c)
ssPtr = ssPtr + 1
c = PeekByte(ssData, ssPtr)
Wend

INI_private_SkipWhitespace s

; If there is no equal sign, or there is an invalid character, return Null as error
If PeekByte(ssData, ssPtr) <> 61 Or name = "" Then Return Null
ssPtr = ssPtr + 1

INI_private_SkipWhitespace s

Repeat
c = PeekByte(ssData, ssPtr)

If Not inBraces
If INI_private_CheckNewline(s) Then Exit ;Braces can contain newlines
If (Not inQuotes) And (c = 59) Then Exit ;Semicolon

If c = 92 Then esc = True ;Backslash (escape character)
If c = 34 And esc = False Then inQuotes = Not inQuotes : Else esc = -1
If c <> 92 Then esc = False
EndIf

If Not inQuotes
If c = 123 Then inBraces = inBraces + 1 ;Opening brace
If c = 125 And inBraces > 0 Then inBraces = inBraces - 1 ;Closing brace
EndIf

If esc = -1 Then val = Left(val, Len(val) - 1) : esc = False
val = val + Chr(c)
ssPtr = ssPtr + 1

If ssPtr = BankSize(ssData) ;Unmatched brace could lead to hitting the EOF
LogError "Unmatched braces in peoperty value"
Return Null
EndIf
Forever

If val = "" Then Return Null ;If there is no value, return Null as an error

val = Trim(val) ;Remove trailing unquoted whitespace

;If the whole of val is quoted or braced, remove the outer layer of quoting
If (Left(val, 1) = Chr(34) And Right(val, 1) = Chr(34)) Or (Left(val, 1) = "{" And Right(val, 1) = "}")
val = Mid(val, 2, Len(val) - 2)
EndIf

If c = 59 ;Semicolon - comment
ssPtr = ssPtr + 1
While Not INI_private_CheckNewline(s)
cmmt = cmmt + Chr(PeekByte(ssData, ssPtr))
ssPtr = ssPtr + 1
Wend
EndIf

Return INI_private_CreateProperty(i, ls, True, name, val, cmmt, lp)
End Function

; Internal function: create a property object
Function INI_private_CreateProperty.INI_Property(i.INI_File, s.INI_Section, hasVal, key$, val$, cmmt$, pv.INI_Property)
Local p.INI_Property = New INI_Property

phasValue = hasVal
pkey = key
pvalue = val
pcomment = cmmt
ppv = pv

ipCount = ipCount + 1
If ipList = Null Then ipList = p

If s <> Null Then spCount = spCount + 1

If pv <> Null
p
x = pv
x
pv
x = p
If p
x <> Null Then p
xpv = p
EndIf

Return p
End Function

; Internal function: create a section object
Function INI_private_CreateSection.INI_Section(i.INI_File, name$, cmmt$, pv.INI_Section, pp.INI_Property)
Local s.INI_Section = New INI_Section

s
ame = name
scomment = cmmt
spv = pv

If Not iimported ;If this is being created ex nihilo, apply basic formatting here
pp = INI_private_CreateProperty(i, Null, False, "", "", "", pp)
pp = INI_private_CreateProperty(i, Null, False, "", "", "", pp) ;Two spacers?
pp = INI_private_CreateProperty(i, Null, False, "", "", cmmt, pp) ;Comment
sstart = INI_private_CreateProperty(i, s, False, "", name, "", pp) ;And the start line
INI_private_CreateProperty i, Null, False, "", "", "", sstart ;Final spacer
Else
sstart = INI_private_CreateProperty(i, s, False, "", name, cmmt, pp) ;Otherwise, don't adjust the formatting
EndIf

spCount = 0 ;Reset after adding sstart

isCount = isCount + 1
If isList = Null Then isList = s

If pv <> Null Then pv
x = s

Return s
End Function

; Internal function: retrieve a property object by key, optionally in a given section
Function INI_private_GetProperty.INI_Property(ini.INI_File, sec$, key$, p_out.INI_Property[0], s_out.INI_Section[0])
Local p.INI_Property, s.INI_Section, i

If sec = "" ;No section, check all properties in order
p = inipList
While p <> Null
If phasValue
If pkey = key Then Exit
EndIf
p = p
x
Wend

If p = Null Then LogError "Could not find property "+Chr(34)+key+Chr(34)

Else
s = inisList
While s <> Null
If s
ame = sec Then Exit
s = s
x
Wend

If s = Null
LogError "Could not find section "+Chr(34)+sec+Chr(34)+" to retrieve property"
Return Null
EndIf

p = sstart ;Start itself is a placeholder
For i = 1 To spCount
p = p
x
If phasValue
If pkey = key Then Exit
EndIf
Next

If i > spCount
LogError "Could not find property "+Chr(34)+key+Chr(34)+" in section "+Chr(34)+sec+Chr(34)
Return Null
EndIf
EndIf

; Use out parameters to return multiple values
p_out[0] = p
s_out[0] = s
End Function

; Internal function: create a new bank stream object
Function INI_private_CreateINIFileStream.INI_Stream(source$, size)
Local s.INI_Stream = New INI_Stream

s = New INI_Stream
ssData = CreateBank(size + 2)
PokeShort ssData, size, $0A ;Append a final line-ending to simplify checking
ssPtr = 0
ssource = source

Return s
End Function

; Internal function: load a bank stream from a file
Function INI_private_LoadINIFileStream.INI_Stream(filename$)
Local size, s.INI_Stream, f

If FileType(filename) <> 1 ;Replace with appropriate error function for your program
LogError "Could not open "+Chr(34)+filename+Chr(34)+": file not found"
Return Null
EndIf

size = FileSize(filename)
s = INI_private_CreateINIFileStream(filename, size)

f = ReadFile(filename)
ReadBytes ssData, f, 0, size
CloseFile f

Return s
End Function

; Internal function: create a bank stream from a string
Function INI_private_ReadINIFileSTream.INI_Stream(val$)
Local c, s.INI_Stream, i, l = Len(val)

s = INI_private_CreateINIFileStream("string", l)

For i = 1 To l
PokeByte ssData, i - 1, Asc(Mid(val, i, 1))
Next

Return s
End Function

; Internal function: free a bank stream
Function INI_private_FreeINIFileStream(s.INI_Stream)
FreeBank ssData
Delete s
End Function

; Internal function: skip tabs and spaces in a bank stream
Function INI_private_SkipWhitespace(s.INI_Stream)
Local c = PeekByte(ssData, ssPtr)
While c = 9 Or c = 32
ssPtr = ssPtr + 1
c = PeekByte(ssData, ssPtr)
Wend
End Function

; Internal function: test if a character is valid for use in a property or section name
Function INI_private_IsValidNameChar(c)
If c >= 48 And c <= 57 Then Return True ;Digit 0-9
If c >= 65 And c <= 90 Then Return True ;Letter A-Z
If c >= 97 And c <= 122 Then Return True ;Letter a-z
If c = 92 Or c = 58 Or c = 95 Or c = 46 Then Return True ;Colon, backslash, underscore, dot
Return False
End Function

; Internal function: test for a newline (and swallow it if present)
Function INI_private_CheckNewline(s.INI_Stream)
If PeekShort(ssData, ssPtr) = $A0D
ssPtr = ssPtr + 2
Return True
ElseIf PeekByte(ssData, ssPtr) = 10 Or PeekByte(ssData, ssPtr) = 13
ssPtr = ssPtr + 1
Return True
Else
Return False
EndIf
End Function

; Internal function: Get the current line contents and number (for error messages) - uses out-parameters
Function INI_private_GetCurrentLine(s.INI_Stream, lineNo[0], lineVal$[0])
Local i, lIndex, lNo = 1 ;Start at 1

For i = 0 To ssPtr
If PeekShort(ssData, i) = $A0D
lNo = lNo + 1
lIndex = i + 2
i = i + 1
ElseIf PeekByte(ssData, i) = 10 Or PeekByte(ssData, i) = 13
lNo = lNo + 1
lIndex = i + 1
EndIf
Next
lineNo[0] = lNo

lineVal[0] = ""
While PeekByte(ssData, lIndex) <> 10 And PeekByte(ssData, lIndex) <> 13
lineVal[0] = lineVal[0] + Chr(PeekByte(ssData, lIndex))
lIndex = lIndex + 1
Wend
End Function


;~IDEal Editor Parameters:
;~F#3A#40#45#4C#55#63#73#7E#9D#B7#CF#E4#10A#12A#133#13C#145#14E#170#198
;~F#1A3#1C6#20A#222#23E#26C#279#28C#299#29F#2A8#2B1#2BE
;~C#Blitz3D


Comments :


Guy Fawkes(Posted 1+ years ago)

 I LOVE U MAN! XD NO HOMO :P


Yasha(Posted 1+ years ago)

 Haha, yeah your questions about writing strings and so on made me think you might be interested in this.It was in fact that that gave me the idea to tidy up the comments and put it in the archive.


Guy Fawkes(Posted 1+ years ago)

 I'm glad I've inspired you! :)


Yue(Posted 1+ years ago)

 Question, sorry for my ignorance, as not to step debuglog content if not a file (config.ini)


Yasha(Posted 1+ years ago)

 Sorry Yue, your question didn't translate properly. Can you rephrase it?


Yue(Posted 1+ years ago)

 Hello, thank you for your patience.I can not find the way to move to a txt file so in the example above appears in the debuglog.Now I do not want to appear in the debuglog, if not in a file that can be txt, dat, img etc, which will store game settings.Greetings.Edit:


Yasha(Posted 1+ years ago)

 You mean like this:
Local i.INI_File = ... ;

INI_Write i, "config.ini"
INI_Free i
...?INI_Write always outputs to a text file.The examples use INI_WriteString, which creates an internal B3D string of the whole contents of the INI, but doesn't save anything. The use of DebugLog was just to easily display this string, and isn't necessary for normal use of the library.


Yue(Posted 1+ years ago)

 Yes!Ok, thanks for the help.Now I have another question, and it is related to the following:In INI files, I can get game-related settings, but I worry that such data can be changed by the end user, for example could change the health kits, or the amount of points you have. Is there any way to make the data encrypted?Or in this case would have to resort to putting words strange that only I understand the comments and keys?Greetings.


Yasha(Posted 1+ years ago)

 <div class="quote"> In INI files, I can get game-related settings, but I worry that such data can be changed by the end user, for example could change the health kits, or the amount of points you have. Is there any way to make the data encrypted?Or in this case would have to resort to putting words strange that only I understand the comments and keys? </div>Yeah, INI is really intended to make it easier for the end user to modify the data.You could:-- as you suggest, have keys that aren't obvious (also, just don't bother with comments, if you don't want to make things clear)-- also have values that aren't obvious, where possible-- use INI_WriteString to generate a string, use a generic string-encryptor (maybe there's a suitable one in the archives?) to encrypt the whole thing, and write that encrypted string to a file (reverse the process to read data)


Yue(Posted 1+ years ago)

 Thank you very much for your work, is increasingly less and given me a lot to learn I have noticed that the title of programmer left me even further.A last question, where I can find a generic support Blitz3D encryptor to encrypt strings.Greetings.Edit: Ok, no problem, save file .dll y data file:- = 100 ; true data healt
+ = 20  ; False data
W = 50  ; False data
34= 100 ; true data Score.
Thanks Yasha [/i]