March 01, 2021, 10:28:01 PM

Author Topic: [bb] INI-format data files by Yasha [ 1+ years ago ]  (Read 661 times)

Offline BlitzBot

  • Jr. Member
  • **
  • Posts: 1
[bb] INI-format data files by Yasha [ 1+ years ago ]
« on: June 29, 2017, 12:28:39 AM »
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[/url]).

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:
Code: [Select]

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:
Code: [Select]
; 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
  1. ; INI file read/write library
  2. ;=============================
  3.  
  4.  
  5. ; Public API:
  6. ;
  7. ; - INI_Create: Create a new, empty INI structure from scratch with an optional comment
  8. ;               Structures created in this way will attempt to auto-format themselves
  9. ;
  10. ; - INI_Load: Load an INI structure from a file
  11. ;             Structures created in this way will not apply any auto-formatting
  12. ;
  13. ; - INI_LoadString: Load an INI structure from a string
  14. ;                   This is otherwise identical to INI_Load
  15. ;
  16. ; - INI_Write: Write an INI structure out to a text file
  17. ;
  18. ; - INI_WriteString: Write an INI structure out to a string
  19. ;                    As far as possible this is identical to INI_Write
  20. ;
  21. ; - INI_Free: Free an INI structure and all of its elements
  22. ;
  23. ; - INI_AddSection: Add a new, empty section to an INI structure
  24. ;                   No check is performed to see if a section with that name already exists
  25. ;
  26. ; - INI_RemoveSection: Remove a section (and optionally all of its properties) from an INI structure
  27. ;                      In the case of duplicate sections, only the first is removed
  28. ;                      If the section does not exist, an error is logged and no action is taken
  29. ;
  30. ; - INI_AddProperty: Add a property to an INI structure (optionally in a given section)
  31. ;                    No check is performed to see if a property with that name already exists
  32. ;
  33. ; - INI_GetValue: Retrieve a value from a given property (optionally in a given section)
  34. ;                 If the property does not exist, the empty string is returned and an error logged
  35. ;
  36. ; - INI_SetValue: Set the value of a given property (optionally in a given section)
  37. ;                 If the property does not exist, an error is logged and no action is taken
  38. ;
  39. ; - INI_GetComment: Retrieve a comment from a given property (optionally in a given section)
  40. ;                   If the property does not exist, the empty string is returned and an error logged
  41. ;
  42. ; - INI_SetComment: Set the comment of a given property (optionally in a given section)
  43. ;                   If the property does not exist, an error is logged and no action is taken
  44. ;
  45. ; - INI_RemoveProperty: Remove a property from an INI structure (optionally in a given section)
  46. ;                       If the property does not exist, an error is logged and no action is taken
  47. ;
  48. ;
  49. ; All the remaining functions, whose names begin with INI_private_, are part of the internal
  50. ; implementation and should not be called directly.
  51. ;
  52. ; The LogError function, called when an error is encountered, is not provided in this library.
  53. ; Instead, link it to a suitable logging function for your application.
  54. ;
  55.  
  56.  
  57. Type INI_File
  58.         Field imported, comment$
  59.         Field sCount, sList.INI_Section
  60.         Field pCount, pList.INI_Property
  61. End Type
  62.  
  63. Type INI_Stream
  64.         Field sPtr, sData
  65.         Field source$
  66. End Type
  67.  
  68. Type INI_Section
  69.         Field name$, comment$
  70.         Field pCount, start.INI_Property
  71.        
  72.         Field pv.INI_Section, nx.INI_Section
  73. End Type
  74.  
  75. Type INI_Property
  76.         Field hasValue
  77.         Field key$, value$, comment$
  78.        
  79.         Field pv.INI_Property, nx.INI_Property
  80. End Type
  81.  
  82.  
  83. ; Create a new, empty INI structure
  84. Function INI_Create.INI_File(comment$ = "")
  85.         Local i.INI_File = New INI_File
  86.        
  87.         If comment <> ""
  88.                 icomment = comment
  89.                 ipList = INI_private_CreateProperty(i, Null, False, "", "", "", Null)   ;Spacer line
  90.                 Local c.INI_Property = INI_private_CreateProperty(i, Null, False, "", "", comment, ipList)
  91.                 INI_private_CreateProperty(i, Null, False, "", "", "", c)       ;Another spacer
  92.         EndIf
  93.        
  94.         Return i
  95. End Function
  96.  
  97. ; Load an INI structure from a file
  98. Function INI_Load.INI_File(filename$)
  99.         Local s.INI_Stream, i.INI_File
  100.        
  101.         If FileType(filename) <> 1              ;Replace with appropriate error function for your program
  102.                 LogError "Could not open "+Chr(34)+filename+Chr(34)+": file not found"
  103.                 Return Null
  104.         EndIf
  105.        
  106.         s = INI_private_LoadINIFileStream(filename)
  107.         i = INI_private_ReadStream(s)
  108.         INI_private_FreeINIFileStream s
  109.        
  110.         Return i
  111. End Function
  112.  
  113. ; Load an INI structure from a string
  114. Function INI_LoadString.INI_File(val$)
  115.         Local s.INI_Stream, i.INI_File
  116.        
  117.         s = INI_private_ReadINIFileSTream(val)
  118.         i = INI_private_ReadStream(s)
  119.         INI_private_FreeINIFileStream s
  120.        
  121.         Return i
  122. End Function
  123.  
  124. ; Write an INI structure out to a text file
  125. Function INI_Write(ini.INI_File, filename$)
  126.         Local f = WriteFile(filename), p.INI_Property, outL$
  127.        
  128.         If f = 0
  129.                 LogError "Unable to write to file "+Chr(34)+filename+Chr(34)
  130.                 Return
  131.         EndIf
  132.        
  133.         p = inipList
  134.         While p <> Null
  135.                 If phasValue
  136.                         outL = pkey + " = " + pvalue
  137.                         If pcomment <> "" Then outL = outL + "    ;" + pcomment
  138.                 Else
  139.                         If pvalue = ""
  140.                                 If pcomment <> "" Then outL = ";" + pcomment : Else outL = ""
  141.                         Else
  142.                                 outL = "[ " + pvalue + " ]"
  143.                                 If pcomment <> "" Then outL = outL + "    ;" + pcomment
  144.                         EndIf
  145.                 EndIf
  146.                 WriteLine f, outL
  147.                
  148.                 p = p
  149. x
  150.         Wend
  151.         If Not iniimported Then WriteLine f, ""
  152.        
  153.         CloseFile f
  154. End Function
  155.  
  156. ; Write an INI structure out to a string
  157. Function INI_WriteString$(ini.INI_File)
  158.         Local outS$, p.INI_Property, outL$
  159.        
  160.         p = inipList
  161.         While p <> Null
  162.                 If phasValue
  163.                         outL = pkey + " = " + pvalue
  164.                         If pcomment <> "" Then outL = outL + "    ;" + pcomment
  165.                 Else
  166.                         If pvalue = ""
  167.                                 If pcomment <> "" Then outL = ";" + pcomment : Else outL = ""
  168.                         Else
  169.                                 outL = "[ " + pvalue + " ]"
  170.                                 If pcomment <> "" Then outL = outL + "    ;" + pcomment
  171.                         EndIf
  172.                 EndIf
  173.                 outS = outS + outL + Chr(13) + Chr(10)
  174.                
  175.                 p = p
  176. x
  177.         Wend
  178.         If Not iniimported Then outS = outS + Chr(13) + Chr(10)
  179.        
  180.         Return outS
  181. End Function
  182.  
  183. ; Free an INI structure and all of its elements
  184. Function INI_Free(ini.INI_File)
  185.         Local p.INI_Property, op.INI_Property
  186.        
  187.         p = inipList
  188.         While p <> Null
  189.                 op = p
  190.                 p = p
  191. x
  192.                 Delete op
  193.         Wend
  194.        
  195.         Local s.INI_Section, os.INI_Section
  196.        
  197.         s = inisList
  198.         While s <> Null
  199.                 os = s
  200.                 s = s
  201. x
  202.                 Delete os
  203.         Wend
  204.        
  205.         Delete ini
  206. End Function
  207.  
  208.  
  209. ; Add a new, empty section to an INI structure
  210. Function INI_AddSection(ini.INI_File, name$, comment$)
  211.         Local s.INI_Section, os.INI_Section
  212.        
  213.         s = inisList
  214.         While s <> Null
  215.                 os = s
  216.                 s = s
  217. x
  218.         Wend
  219.        
  220.         Local p.INI_Property, op.INI_Property
  221.        
  222.         p = inipList
  223.         While p <> Null
  224.                 op = p
  225.                 p = p
  226. x
  227.         Wend
  228.        
  229.         INI_private_CreateSection ini, name, comment, os, op
  230. End Function
  231.  
  232. ; Remove a section (and optionally all of its properties) from an INI structure
  233. Function INI_RemoveSection(ini.INI_File, name$, freeProperties = True)
  234.         Local s.INI_Section, s_pv.INI_Section, s_nx.INI_Section
  235.        
  236.         s = inisList
  237.         While s <> Null
  238.                 If s
  239. ame = name Then Exit
  240.                 s = s
  241. x
  242.         Wend
  243.        
  244.         If s = Null
  245.                 LogError "Could not find section "+Chr(34)+name+Chr(34)+" to remove"
  246.                 Return
  247.         EndIf
  248.        
  249.         If freeProperties
  250.                 Local  p.INI_Property, p_pv.INI_Property, p_nx.INI_Property, i
  251.                
  252.                 p_pv = sstartpv
  253.                 p = sstart
  254.                 p_nx = p
  255. x
  256.                
  257.                 For i = 0 To spCount    ;Not an error - we're also processing the zero element
  258.                         p_nx = p
  259. x
  260.                         Delete p
  261.                         p = p_nx
  262.                 Next
  263.                
  264.                 If p_pv <> Null Then p_pv
  265. x = p_nx
  266.                 If p_nx <> Null Then p_nxpv = p_pv
  267.         EndIf
  268.        
  269.         If spv <> Null Then spv
  270. x = s
  271. x
  272.         If s
  273. x <> Null Then s
  274. xpv = spv
  275.         Delete s
  276. End Function
  277.  
  278.  
  279. ; Add a property to an INI structure (optionally in a given section)
  280. Function INI_AddProperty(ini.INI_File, sec$, key$, value$, comment$ = "")
  281.         Local s.INI_Section = Null, i, p.INI_Property, n.INI_Property
  282.        
  283.         If sec <> ""
  284.                 s = inisList
  285.                 While s <> Null
  286.                         If s
  287. ame = sec Then Exit
  288.                         s = s
  289. x
  290.                 Wend
  291.                
  292.                 If s = Null
  293.                         LogError "Could not find section "+Chr(34)+sec+Chr(34)+"; property not added"
  294.                         Return
  295.                 EndIf
  296.                
  297.                 p = sstart
  298.                 For i = 1 To spCount
  299.                         p = p
  300. x
  301.                 Next
  302.         Else
  303.                 n = inipList
  304.                 While n <> Null
  305.                         p = n
  306.                         n = n
  307. x
  308.                 Wend
  309.         EndIf
  310.        
  311.         ;Note that we did NOT check for duplicate names - this is not an error
  312.         INI_private_CreateProperty(ini, s, True, key, value, comment, p)
  313. End Function
  314.  
  315. ; Retrieve a value from a given property (optionally in a given section)
  316. Function INI_GetValue$(ini.INI_File, sec$, key$)
  317.         Local p.INI_Property[0], s.INI_Section[0]
  318.        
  319.         INI_private_GetProperty ini, sec, key, p, s             ;Use out parameters
  320.        
  321.         If p[0] <> Null Then Return p[0]value : Else Return ""
  322. End Function
  323.  
  324. ; Set the value of a given property (optionally in a given section)
  325. Function INI_SetValue(ini.INI_File, sec$, key$, val$)
  326.         Local p.INI_Property[0], s.INI_Section[0]
  327.        
  328.         INI_private_GetProperty ini, sec, key, p, s             ;Use out parameters
  329.        
  330.         If p[0] <> Null Then p[0]value = val
  331. End Function
  332.  
  333. ; Retrieve a comment from a given property (optionally in a given section)
  334. Function INI_GetComment$(ini.INI_File, sec$, key$)
  335.         Local p.INI_Property[0], s.INI_Section[0]
  336.        
  337.         INI_private_GetProperty ini, sec, key, p, s             ;Use out parameters
  338.        
  339.         If p[0] <> Null Then Return p[0]comment : Else Return ""
  340. End Function
  341.  
  342. ; Set the comment of a given property (optionally in a given section)
  343. Function INI_SetComment(ini.INI_File, sec$, key$, cmmt$)
  344.         Local p.INI_Property[0], s.INI_Section[0]
  345.        
  346.         INI_private_GetProperty ini, sec, key, p, s             ;Use out parameters
  347.        
  348.         If p[0] <> Null Then p[0]comment = cmmt
  349. End Function
  350.  
  351. ; Remove a property from an INI structure (optionally in a given section)
  352. Function INI_RemoveProperty(ini.INI_File, sec$, key$)
  353.         Local p.INI_Property[0], s.INI_Section[0], s2.INI_Section, p2.INI_Property, i
  354.        
  355.         INI_private_GetProperty ini, sec, key, p, s             ;Use out parameters
  356.        
  357.         If sec = ""             ;Make sure that if the property belongs to a section, it gets removed properly
  358.                 s2 = inisList
  359.                 While s2 <> Null
  360.                         p2 = s2start
  361.                         For i = 1 To s2pCount
  362.                                 p2 = p2
  363. x
  364.                                 If p2hasValue
  365.                                         If p2key = key Then Exit        ;The list is ordered, so break on the first match
  366.                                 EndIf
  367.                         Next
  368.                         If i <= s2pCount Then Exit
  369.                         s2 = s2
  370. x
  371.                 Wend
  372.                 If s2 <> Null
  373.                         If p2 = p[0] Then s[0] = s2
  374.                 EndIf
  375.         EndIf
  376.        
  377.         If p[0] <> Null
  378.                 If s[0] <> Null Then s[0]pCount = s[0]pCount - 1
  379.                 If p[0]pv <> Null Then p[0]pv
  380. x = p[0]
  381. x
  382.                 If p[0]
  383. x <> Null Then p[0]
  384. xpv = p[0]pv
  385.                 Delete p[0]
  386.         EndIf
  387. End Function
  388.  
  389.  
  390.  
  391. ; Internal function: load an INI structure from a bank stream
  392. Function INI_private_ReadStream.INI_File(s.INI_Stream)
  393.         Local i.INI_File = INI_Create(), error, ls.INI_Section, lp.INI_Property
  394.        
  395.         iimported = True
  396.        
  397.         While ssPtr < BankSize(ssData) - 2
  398.                 INI_private_SkipWhitespace s
  399.                 Local c = PeekByte(ssData, ssPtr)
  400.                
  401.                 If c = 59               ;Semicolon
  402.                         lp = INI_private_ReadCommentLine(s, i, ls, lp)  ;Doesn't raise any errors
  403.                        
  404.                 ElseIf c = 91           ;[ (open bracket)
  405.                         ls = INI_private_ReadSectionDef(s, i, ls, lp)
  406.                         If ls = Null Then error = True : Else lp = lsstart
  407.                        
  408.                 ElseIf INI_private_IsValidNameChar(c)           ;Key name
  409.                         lp = INI_private_ReadPropertyDef(s, i, ls, lp)
  410.                         If lp = Null Then error = True
  411.                        
  412.                 ElseIf INI_private_CheckNewline(s)              ;Try to swallow a newline
  413.                         lp = INI_private_CreateProperty(i, ls, False, "", "", "", lp)
  414.                        
  415.                 Else
  416.                         error = True
  417.                 EndIf
  418.                
  419.                 If error        ;In the event of error, break off (should we try to continue instead?)
  420.                         Local lineNo[0], lineVal$[0]
  421.                         INI_private_GetCurrentLine s, lineNo, lineVal
  422.                         LogError "Error reading data from "+ssource+": line "+lineNo[0]+" ( "+lineVal[0]+" ) is not a valid property or section definition"
  423.                         INI_Free i
  424.                         Return Null
  425.                 EndIf
  426.         Wend
  427.        
  428.         Return i
  429. End Function
  430.  
  431. ; Internal function: read a comment line into the INI structure for preservation
  432. Function INI_private_ReadCommentLine.INI_Property(s.INI_Stream, i.INI_File, ls.INI_Section, lp.INI_Property)
  433.         Local cmmt$
  434.         ssPtr = ssPtr + 1
  435.         While Not INI_private_CheckNewline(s)
  436.                 cmmt = cmmt + Chr(PeekByte(ssData, ssPtr))
  437.                 ssPtr = ssPtr + 1
  438.         Wend
  439.         Return INI_private_CreateProperty(i, ls, False, "", "", cmmt, lp)
  440. End Function
  441.  
  442. ; Internal function: read a section definition into the INI structure
  443. Function INI_private_ReadSectionDef.INI_Section(s.INI_Stream, i.INI_File, ls.INI_Section, lp.INI_Property)
  444.         Local name$, c, cmmt$
  445.        
  446.         ssPtr = ssPtr + 1
  447.         INI_private_SkipWhitespace s
  448.        
  449.         c = PeekByte(ssData, ssPtr)
  450.         While INI_private_IsValidNameChar(c)
  451.                 name = name + Chr(c)
  452.                 ssPtr = ssPtr + 1
  453.                 c = PeekByte(ssData, ssPtr)
  454.         Wend
  455.        
  456.         INI_private_SkipWhitespace s
  457.        
  458.         ; If there was no closing bracket, or there is an invalid character, return Null as error
  459.         If PeekByte(ssData, ssPtr) <> 93 Or name = "" Then Return Null
  460.         ssPtr = ssPtr + 1
  461.        
  462.         INI_private_SkipWhitespace s
  463.        
  464.         If PeekByte(ssData, ssPtr) = 59         ;Semicolon - comment
  465.                 ssPtr = ssPtr + 1
  466.                 While Not INI_private_CheckNewline(s)
  467.                         cmmt = cmmt + Chr(PeekByte(ssData, ssPtr))
  468.                         ssPtr = ssPtr + 1
  469.                 Wend
  470.         ElseIf Not INI_private_CheckNewline(s)          ;Newline or comments only; otherwise, return Null as error
  471.                 Return Null
  472.         EndIf
  473.        
  474.         Return INI_private_CreateSection(i, name, cmmt, ls, lp)
  475. End Function
  476.  
  477. ; Internal function: read a property definition into the INI structure
  478. Function INI_private_ReadPropertyDef.INI_Property(s.INI_Stream, i.INI_File, ls.INI_Section, lp.INI_Property)
  479.         Local name$, val$, cmmt$, c, inQuotes, inBraces, esc
  480.        
  481.         INI_private_SkipWhitespace s
  482.        
  483.         c = PeekByte(ssData, ssPtr)
  484.         While INI_private_IsValidNameChar(c)
  485.                 name = name + Chr(c)
  486.                 ssPtr = ssPtr + 1
  487.                 c = PeekByte(ssData, ssPtr)
  488.         Wend
  489.        
  490.         INI_private_SkipWhitespace s
  491.        
  492.         ; If there is no equal sign, or there is an invalid character, return Null as error
  493.         If PeekByte(ssData, ssPtr) <> 61 Or name = "" Then Return Null
  494.         ssPtr = ssPtr + 1
  495.        
  496.         INI_private_SkipWhitespace s
  497.        
  498.         Repeat
  499.                 c = PeekByte(ssData, ssPtr)
  500.                
  501.                 If Not inBraces
  502.                         If INI_private_CheckNewline(s) Then Exit                ;Braces can contain newlines
  503.                         If (Not inQuotes) And (c = 59) Then Exit                ;Semicolon
  504.                        
  505.                         If c = 92 Then esc = True               ;Backslash (escape character)
  506.                         If c = 34 And esc = False Then inQuotes = Not inQuotes : Else esc = -1
  507.                         If c <> 92 Then esc = False
  508.                 EndIf
  509.                
  510.                 If Not inQuotes
  511.                         If c = 123 Then inBraces = inBraces + 1         ;Opening brace
  512.                         If c = 125 And inBraces > 0 Then inBraces = inBraces - 1                ;Closing brace
  513.                 EndIf
  514.                
  515.                 If esc = -1 Then val = Left(val, Len(val) - 1) : esc = False
  516.                 val = val + Chr(c)
  517.                 ssPtr = ssPtr + 1
  518.                
  519.                 If ssPtr = BankSize(ssData)             ;Unmatched brace could lead to hitting the EOF
  520.                         LogError "Unmatched braces in peoperty value"
  521.                         Return Null
  522.                 EndIf
  523.         Forever
  524.        
  525.         If val = "" Then Return Null            ;If there is no value, return Null as an error
  526.        
  527.         val = Trim(val)         ;Remove trailing unquoted whitespace
  528.        
  529.         ;If the whole of val is quoted or braced, remove the outer layer of quoting
  530.         If (Left(val, 1) = Chr(34) And Right(val, 1) = Chr(34)) Or (Left(val, 1) = "{" And Right(val, 1) = "}")
  531.                 val = Mid(val, 2, Len(val) - 2)
  532.         EndIf
  533.        
  534.         If c = 59               ;Semicolon - comment
  535.                 ssPtr = ssPtr + 1
  536.                 While Not INI_private_CheckNewline(s)
  537.                         cmmt = cmmt + Chr(PeekByte(ssData, ssPtr))
  538.                         ssPtr = ssPtr + 1
  539.                 Wend
  540.         EndIf
  541.        
  542.         Return INI_private_CreateProperty(i, ls, True, name, val, cmmt, lp)
  543. End Function
  544.  
  545. ; Internal function: create a property object
  546. Function INI_private_CreateProperty.INI_Property(i.INI_File, s.INI_Section, hasVal, key$, val$, cmmt$, pv.INI_Property)
  547.         Local p.INI_Property = New INI_Property
  548.        
  549.         phasValue = hasVal
  550.         pkey = key
  551.         pvalue = val
  552.         pcomment = cmmt
  553.         ppv = pv
  554.        
  555.         ipCount = ipCount + 1
  556.         If ipList = Null Then ipList = p
  557.        
  558.         If s <> Null Then spCount = spCount + 1
  559.        
  560.         If pv <> Null
  561.                 p
  562. x = pv
  563. x
  564.                 pv
  565. x = p
  566.                 If p
  567. x <> Null Then p
  568. xpv = p
  569.         EndIf
  570.        
  571.         Return p
  572. End Function
  573.  
  574. ; Internal function: create a section object
  575. Function INI_private_CreateSection.INI_Section(i.INI_File, name$, cmmt$, pv.INI_Section, pp.INI_Property)
  576.         Local s.INI_Section = New INI_Section
  577.        
  578.         s
  579. ame = name
  580.         scomment = cmmt
  581.         spv = pv
  582.        
  583.         If Not iimported                ;If this is being created ex nihilo, apply basic formatting here
  584.                 pp = INI_private_CreateProperty(i, Null, False, "", "", "", pp)
  585.                 pp = INI_private_CreateProperty(i, Null, False, "", "", "", pp)         ;Two spacers?
  586.                 pp = INI_private_CreateProperty(i, Null, False, "", "", cmmt, pp)               ;Comment
  587.                 sstart = INI_private_CreateProperty(i, s, False, "", name, "", pp)              ;And the start line
  588.                 INI_private_CreateProperty i, Null, False, "", "", "", sstart           ;Final spacer
  589.         Else
  590.                 sstart = INI_private_CreateProperty(i, s, False, "", name, cmmt, pp)    ;Otherwise, don't adjust the formatting
  591.         EndIf
  592.        
  593.         spCount = 0     ;Reset after adding sstart
  594.        
  595.         isCount = isCount + 1
  596.         If isList = Null Then isList = s
  597.        
  598.         If pv <> Null Then pv
  599. x = s
  600.        
  601.         Return s
  602. End Function
  603.  
  604. ; Internal function: retrieve a property object by key, optionally in a given section
  605. Function INI_private_GetProperty.INI_Property(ini.INI_File, sec$, key$, p_out.INI_Property[0], s_out.INI_Section[0])
  606.         Local p.INI_Property, s.INI_Section, i
  607.        
  608.         If sec = ""             ;No section, check all properties in order
  609.                 p = inipList
  610.                 While p <> Null
  611.                         If phasValue
  612.                                 If pkey = key Then Exit
  613.                         EndIf
  614.                         p = p
  615. x
  616.                 Wend
  617.                
  618.                 If p = Null Then LogError "Could not find property "+Chr(34)+key+Chr(34)
  619.                
  620.         Else
  621.                 s = inisList
  622.                 While s <> Null
  623.                         If s
  624. ame = sec Then Exit
  625.                         s = s
  626. x
  627.                 Wend
  628.                
  629.                 If s = Null
  630.                         LogError "Could not find section "+Chr(34)+sec+Chr(34)+" to retrieve property"
  631.                         Return Null
  632.                 EndIf
  633.                
  634.                 p = sstart              ;Start itself is a placeholder
  635.                 For i = 1 To spCount
  636.                         p = p
  637. x
  638.                         If phasValue
  639.                                 If pkey = key Then Exit
  640.                         EndIf
  641.                 Next
  642.                
  643.                 If i > spCount
  644.                         LogError "Could not find property "+Chr(34)+key+Chr(34)+" in section "+Chr(34)+sec+Chr(34)
  645.                         Return Null
  646.                 EndIf
  647.         EndIf
  648.        
  649.         ; Use out parameters to return multiple values
  650.         p_out[0] = p
  651.         s_out[0] = s
  652. End Function
  653.  
  654. ; Internal function: create a new bank stream object
  655. Function INI_private_CreateINIFileStream.INI_Stream(source$, size)
  656.         Local s.INI_Stream = New INI_Stream
  657.        
  658.         s = New INI_Stream
  659.         ssData = CreateBank(size + 2)
  660.         PokeShort ssData, size, $0A     ;Append a final line-ending to simplify checking
  661.         ssPtr = 0
  662.         ssource = source
  663.        
  664.         Return s
  665. End Function
  666.  
  667. ; Internal function: load a bank stream from a file
  668. Function INI_private_LoadINIFileStream.INI_Stream(filename$)
  669.         Local size, s.INI_Stream, f
  670.        
  671.         If FileType(filename) <> 1              ;Replace with appropriate error function for your program
  672.                 LogError "Could not open "+Chr(34)+filename+Chr(34)+": file not found"
  673.                 Return Null
  674.         EndIf
  675.        
  676.         size = FileSize(filename)
  677.         s = INI_private_CreateINIFileStream(filename, size)
  678.        
  679.         f = ReadFile(filename)
  680.         ReadBytes ssData, f, 0, size
  681.         CloseFile f
  682.        
  683.         Return s
  684. End Function
  685.  
  686. ; Internal function: create a bank stream from a string
  687. Function INI_private_ReadINIFileSTream.INI_Stream(val$)
  688.         Local c, s.INI_Stream, i, l = Len(val)
  689.        
  690.         s = INI_private_CreateINIFileStream("string", l)
  691.        
  692.         For i = 1 To l
  693.                 PokeByte ssData, i - 1, Asc(Mid(val, i, 1))
  694.         Next
  695.        
  696.         Return s
  697. End Function
  698.  
  699. ; Internal function: free a bank stream
  700. Function INI_private_FreeINIFileStream(s.INI_Stream)
  701.         FreeBank ssData
  702.         Delete s
  703. End Function
  704.  
  705. ; Internal function: skip tabs and spaces in a bank stream
  706. Function INI_private_SkipWhitespace(s.INI_Stream)
  707.         Local c = PeekByte(ssData, ssPtr)
  708.         While c = 9 Or c = 32
  709.                 ssPtr = ssPtr + 1
  710.                 c = PeekByte(ssData, ssPtr)
  711.         Wend
  712. End Function
  713.  
  714. ; Internal function: test if a character is valid for use in a property or section name
  715. Function INI_private_IsValidNameChar(c)
  716.         If c >= 48 And c <= 57 Then Return True         ;Digit 0-9
  717.         If c >= 65 And c <= 90 Then Return True         ;Letter A-Z
  718.         If c >= 97 And c <= 122 Then Return True                ;Letter a-z
  719.         If c = 92 Or c = 58 Or c = 95 Or c = 46 Then Return True                ;Colon, backslash, underscore, dot
  720.         Return False
  721. End Function
  722.  
  723. ; Internal function: test for a newline (and swallow it if present)
  724. Function INI_private_CheckNewline(s.INI_Stream)
  725.         If PeekShort(ssData, ssPtr) = $A0D
  726.                 ssPtr = ssPtr + 2
  727.                 Return True
  728.         ElseIf PeekByte(ssData, ssPtr) = 10 Or PeekByte(ssData, ssPtr) = 13
  729.                 ssPtr = ssPtr + 1
  730.                 Return True
  731.         Else
  732.                 Return False
  733.         EndIf
  734. End Function
  735.  
  736. ; Internal function: Get the current line contents and number (for error messages) - uses out-parameters
  737. Function INI_private_GetCurrentLine(s.INI_Stream, lineNo[0], lineVal$[0])
  738.         Local i, lIndex, lNo = 1        ;Start at 1
  739.        
  740.         For i = 0 To ssPtr
  741.                 If PeekShort(ssData, i) = $A0D
  742.                         lNo = lNo + 1
  743.                         lIndex = i + 2
  744.                         i = i + 1
  745.                 ElseIf PeekByte(ssData, i) = 10 Or PeekByte(ssData, i) = 13
  746.                         lNo = lNo + 1
  747.                         lIndex = i + 1
  748.                 EndIf
  749.         Next
  750.         lineNo[0] = lNo
  751.        
  752.         lineVal[0] = ""
  753.         While PeekByte(ssData, lIndex) <> 10 And PeekByte(ssData, lIndex) <> 13
  754.                 lineVal[0] = lineVal[0] + Chr(PeekByte(ssData, lIndex))
  755.                 lIndex = lIndex + 1
  756.         Wend
  757. End Function
  758.  
  759.  
  760. ;~IDEal Editor Parameters:
  761. ;~F#3A#40#45#4C#55#63#73#7E#9D#B7#CF#E4#10A#12A#133#13C#145#14E#170#198
  762. ;~F#1A3#1C6#20A#222#23E#26C#279#28C#299#29F#2A8#2B1#2BE
  763. ;~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:
Code: [Select]
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:
Code: [Select]
- = 100 ; true data healt
+ = 20  ; False data
W = 50  ; False data
34= 100 ; true data Score.
Thanks Yasha [/i]

 

SimplePortal 2.3.6 © 2008-2014, SimplePortal