AutoHotkey Homepage AutoHotkey Community
Let's help each other out
 
 FAQFAQ   SearchSearch   MemberlistMemberlist   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

[Class] IniFile 0.6.6 - Object-oriented INI file handling
Goto page 1, 2  Next
 
Reply to topic    AutoHotkey Community Forum Index -> Scripts & Functions
View previous topic :: View next topic  
Author Message
bmcclure



Joined: 24 Nov 2007
Posts: 769

PostPosted: Sun Mar 01, 2009 2:09 am    Post subject: [Class] IniFile 0.6.6 - Object-oriented INI file handling Reply with quote

Class Library: IniFile
Version: 0.6.6

Description
Provides an object-oriented set of classes for working with INI files, INI sections, and INI fields in many different ways.

Download
IniFile.zip

Requirements

Features
  • Offers function aliases similar to titan's "INI Library"
  • Load and save INI files from disk
  • Load an INI file from a variable, and output it directly back to variable
  • Loads entire INI file in memory and stores it as objects for all operations
  • Duplicate (clone) all or part of an INI file
  • Rename keys and sections
  • Find sections and keys by pattern (regex) or standard match
  • List sections and keys in original or sorted order (with any sorting options)
  • Automatically standardize quotes, comments, and whitespace per your chosen settings
  • Supports header fields (any lines of data before the first section starts)
  • Much more!

Example
Code:
#NoEnv
SendMode Input
SetWorkingDir %A_ScriptDir%

test =
(
[General]
SomeKey=SomeValue

; A comment by itself
[Games\Stuff]
StuffGame = Another value ; And a comment
StuffTime = "A third value" ; Another comment
)

MsgBox,Creating IniFile_test.ini with content:`n%test%

FileDelete,IniFile_test.ini
FileAppend,%test%,IniFile_test.ini

File := IniFile_new("IniFile_test.ini")

IniFile_load(File)

MsgBox,% "Loaded value of [General] SomeKey: " IniFile_get(File, "General", "SomeKey")

MsgBox,% "Finding keys with 'Stuff' in Games\Stuff:`n" IniFile_findKeys(File, "Games\Stuff", "Stuff")

MsgBox,% "Finding sections with G followed by e in their name (regex pattern search):`n" IniFile_findSections(File, "G.*?e")

MsgBox,Moving StuffGame to the General section
IniFile_move(File, "Games\Stuff", "StuffGame", "General")
MsgBox,Copying StuffTime to A New Section
IniFile_copy(FIle, "Games\Stuff", "StuffTime", "A New Section")

MsgBox,% "Listing all sections:`n" IniFile_listSections(File)

MsgBox,% "Listing all keys under General:`n" IniFile_listKeys(File, "General")

MsgBox,Merging section A New Section into section General
IniFile_mergeSections(File, "General", "A New Section", true)

MsgBox,Duplicating File to File2
File2 := IniFile_new()
IniFile_duplicate(File, File2, true)

MsgBox,Saving File2 to IniFile_test2.ini
IniFile_setFilePath(File2, "IniFile_test2.ini")
IniFile_setOverwrite(File2, true)
IniFile_save(File2)

FileRead, contents, IniFile_test2.ini
MsgBox,Contents of newly saved IniFile_test2.ini:`n%contents%

IniFile_setIgnoreBlankLines(File2, true)

IniFile_save(File2)
FileRead, contents, IniFile_test2.ini
MsgBox,Contents of same file with no blank lines:`n%contents%

IniFile_setIgnoreComments(File2, true)
IniFile_setIgnoreBlankLines(File2, false)
IniFile_save(File2)
FileRead, contents, IniFile_test2.ini
MsgBox,Contents of same file with no comments:`n%contents%

IniFile_setIgnoreComments(File2, false)
IniFIle_setQuoteValues(File2, true)
IniFile_save(File2)
FileRead, contents, IniFile_test2.ini
MsgBox,Same file again with quoted values:`n%contents%

content_with_duplicates =
(
; This is a header comment
; This is another header comment
; This is a third header comment
This header comment looks like a key!

[General]
Key1 = Value1 ; Comment 1
Key2 = Value2 ; Comment 2
Key3 = Value3

[SomeSection]
; Comment 3
Key1 = Value4
; This is a note about Key2
Key2 = Value5 ; Comment 4

[SomeOtherSection]
Key1 = Value6
Key2 = Value7; Comment 5

[SomeSection]
Key1 = "Value8" ; Comment 6
Key2 = "Value9" ; Comment 7
Key3 = "Value10"
Key1 = "Value11"
Key2 = "Value12"
)

File3 := IniFile_new()
IniFile_setDuplicateSections(File3, true)
IniFile_setDuplicateKeys(File3, true)
IniFile_loadFromVar(File3, content_with_duplicates)

contents := IniFile_export(File3)

MsgBox,Ini file loaded from variable and directly output (no files), with duplicates in tact:`n%contents%

MsgBox,% "The fourth loaded header comment was:`n" IniFile_getHeaderComment(File3, 4)

MsgBox,% "Comment from SomeSection -> Key2:`n" IniFile_getComment(File3, "SomeSection", "Key2")
MsgBox,% "The comment directly above Key2:`n" IniFile_getComment(File3, "SomeSection", "Key2", -1)

MsgBox,% "Listing all sorted sections:`n" IniFIle_listSections(File3, "U")

MsgBox,Deleting test files...
FileDelete, IniFile_test.ini
FileDelete, IniFile_test2.ini

IniFile_destroy(File)
IniFile_destroy(File2)
IniFile_destroy(File3)

MsgBox,Cleaned up IniFile object. Finished...

_________________
Ben

My Trac projects
My Wiki
[Broken] - My music


Last edited by bmcclure on Sun Mar 08, 2009 5:09 pm; edited 17 times in total
Back to top
View user's profile Send private message
bmcclure



Joined: 24 Nov 2007
Posts: 769

PostPosted: Sun Mar 01, 2009 2:12 am    Post subject: Reply with quote

Features include:
- Load to and save from INI files in one go (no individual reads/writes)
- Work directly with file, section, and field objects, not with raw strings
- Get and set any key in any section easily
- Copy, move, duplicate, and merge sections or entire files
- Choose your whitespace and quote options, and change them at any time
- Easily output to raw text: a single line, an entire section, or the whole file using the export functions
- Aliases exist for all functions from Titan's INI Library

Note, that I have created aliases for pretty much all of Titan's functions in my library, meaning you can use the exact same syntax in most cases (only prefixed with IniFile_ instead of Ini_ to keep compatibility with the many libraries already called ini.ahk).

I'm still implementing a few features, but wanted to get it out anyway, since it seems to work for me.

Still tweaking the regex string that parses a key/value/comment field. So far, it works for me! Note, this is NOT regex-heavy. One RegExMatch is used to parse section headers, and one RegExMatch is used to parse all fields from a line.

I'm open to suggestions, especially since these classes are in their infancy.

Documentation to come, hopefully tonight.

My reason for creating this library was essentially bugs I found in Titan's library that I wasn't sure how to fix (using forward slashes in section titles, saving to file, etc). Since I'm on an OOP kick, I figured, why not Smile
_________________
Ben

My Trac projects
My Wiki
[Broken] - My music
Back to top
View user's profile Send private message
bmcclure



Joined: 24 Nov 2007
Posts: 769

PostPosted: Mon Mar 02, 2009 5:31 am    Post subject: Reply with quote

The only function this is lacking that is present in the titan's library is an ExportToXML function.

I'm saving that until I finish creating my DOM implementation in AHK, at which point I will be able to implement native DOM support in any of my classes.

I'm definitely open to feature requests, or issues anyone's having. When I finish adding my DOM classes, I'll swing back through and document this, as well as perform some additional code refactoring.
_________________
Ben

My Trac projects
My Wiki
[Broken] - My music
Back to top
View user's profile Send private message
animeaime



Joined: 04 Nov 2008
Posts: 1045

PostPosted: Tue Mar 03, 2009 3:05 am    Post subject: Reply with quote

Great script, makes using ini files MUCH easier.

Questions:
1) Can I use IniFile_load to load from a new file - using an existing IniFile object, or is there something that needs to be done first?

2) What happens if the same section/key is used twice?

Edit:
3) I'm in need to have the returns from Ini_findSections/Keys and Ini_listSections/keys sorted, so I'm going to modify the code to do this, and if you want, I can upload the modifed function. I'll make sure it's backwards compatable, so it can be used to return values in order or sorted. I'm also going to see if I can't have it support passing a function to allow a custom sort.

Update:
Here are the updated functions to add in sorting. These functions are backward-compatable, so they don't affect current scripts. The parts in red are the additions I made. Hope you will consider adding this functionality to your library.

Edit2:
Forgot to mention that the SortOptions value is the options for the sort (can be blank). Specify false (0), or leave blank, to return results in order (unsorted).

Code:
IniFile_findSections(IniFileObject, sectionExpression = "", SortOptions = false) {
   if !sections := IniFile_getSections(IniFileObject)
      return false

   found := ""
   Loop,% Vector_size(sections) {
      if !section := Vector_get(sections, A_Index)
         continue

      title := IniSection_getTitle(section)

      if RegExMatch(title, "i)" sectionExpression) {
         if (found)
            found .= "`n"

         found .= title
      }
   }

   if (SortOptions != false)   
       Sort, found, %SortOptions%
   

   
   return found
}


Code:
IniFile_listSections(IniFileObject, SortOptions = false) {
   if !sections := IniFile_getSections(IniFileObject)
      return false

   if !size := Vector_size(sections)
      return false

   found := ""
   loop, %size% {
      if !section := Vector_get(sections, A_Index)
         return false

      if !title := IniSection_getTitle(section)
         return false
      
      if (found)
         found .= "`n"
      
      found .= title
   }

   if (SortOptions != false)
       Sort, found, %SortOptions%
   


   return found
}


Code:
IniFile_findKeys(IniFileObject, section, keyExpression = "", SortOptions = false) {
   if !sections := IniFile_getSections(IniFileObject)
      return false

   if !index := Vector_indexOf(sections, section, 1, "getTitle")
      return false

   section := Vector_get(sections, index)

   return IniSection_findRegex(section, keyExpression, SortOptions)
}


Code:
IniFile_listKeys(IniFileObject, section, SortOptions = false) {
   if !sections := IniFile_getSections(IniFileObject)
      return false
   
   if !index := Vector_indexOf(sections, section, 1, "getTitle")
      return false
   
   if !section := Vector_get(sections, index)
      return false
   
   return IniSection_listKeys(section, SortOptions)
}


Code:
IniSection_findRegex(IniSectionObject, keyExpression = "", SortOptions = false) {
   if !fields := IniSection_getFields(IniSectionObject)
      return false

   found := ""
   Loop,% Vector_size(fields) {
      if !field := Vector_get(fields, A_Index)
         continue

      key := IniField_getKey(field)

      if RegExMatch(key, "i)" keyExpression) {
         if (found)
            found .= "`n"

         found .= key
      }
   }

   if (SortOptions != false)
       Sort, found, %SortOptions%
   


   return found
}


Code:
IniSection_listKeys(IniSectionObject, SortOptions = false) {
   if !fields := IniSection_getFields(IniSectionObject)
      return false
   
   if !size := Vector_size(fields)
      return false

   keys := ""
   Loop, %size% {
      if !field := Vector_get(fields, A_Index)
         continue
      
      if !key := IniField_getKey(field)
         continue
      
      if (keys)
         keys .= "`n"
      
      keys .= key
   }

   if (SortOptions != false)
       Sort, keys, %SortOptions%
   


   return keys
}

_________________
As always, if you have any further questions, don't hesitate to ask.

Add OOP to your scripts via the Class Library. Check out my scripts.
Back to top
View user's profile Send private message Send e-mail
animeaime



Joined: 04 Nov 2008
Posts: 1045

PostPosted: Tue Mar 03, 2009 4:51 am    Post subject: Reply with quote

Another question.

Is it possible to rename sections/keys?

Ex:
Code:
;rename a section
IniFile_renameSection(IniFileObject, section, newSection)

;rename a key in a section
IniFile_renameKey(IniFileObject, section, key, newKey)



Likewise, can you add a getters and setters for the key's comments?
_________________
As always, if you have any further questions, don't hesitate to ask.

Add OOP to your scripts via the Class Library. Check out my scripts.
Back to top
View user's profile Send private message Send e-mail
bmcclure



Joined: 24 Nov 2007
Posts: 769

PostPosted: Tue Mar 03, 2009 6:23 am    Post subject: Reply with quote

To answer your questions:
1. IniFile_load was designed to load from an INI file into an existing IniFile object. You can load into a blank object, or load into an object that's already populated, and it should simply add the new data (if not I'll fix that--it's untested currently).

So, you could potentially use IniFile_load() multiple times on the same object to combine INI files. Of course, as the above, this is untested currently, so if it's not working I'll fix accordingly. So far I've just loaded into new objects.

Because of this intended behavior, the current file contents should not be cleared when using load(). As a result, one quirk is that if you change a document, and then reload that document from file before saving it, existing keys will be overwritten and changed keys will be recreated.

2. It always uses an existing section or key if it already exists. Keys are in the context of their parent section, and sections are in the context of the whole IniFile.

I think you could use the same IniField object in multiple sections, but as you know modifying it in one section would change it everywhere it exists (if it's the same object, and not a copy).

--

Thanks for the updated functions--they'll be included in the next release!

I'll make helper functions for renaming sections and changing comments.

It can also be done by getting the object and directly using its getter/setter, or by creating a new IniField node manually and then adding it to an IniSection object.

I'm also going to make a 'clone' function in each object that creates and returns an exact duplicate of the object and its children.
_________________
Ben

My Trac projects
My Wiki
[Broken] - My music
Back to top
View user's profile Send private message
animeaime



Joined: 04 Nov 2008
Posts: 1045

PostPosted: Tue Mar 03, 2009 7:20 am    Post subject: Reply with quote

Thanks for the feedback.

Refering to #1 again: Is there a way to "unload" a file, to then load (from scratch) a new one? Or just destroy the object, create it anew, and then load the new object? Could you add a wrapper for this?


Sorry about the confusion, #2 was asking if the same key/section existed in the ini file.

For example:

IniFile (IniTest.ini):
Code:
[Section 1]
Key1 = Value1
Key1 = Value2

[Section 1]
Key2 = Value3


TestCode:
Code:
IniFile := "IniTest.ini"

;Outputs "Value1"
MsgBox, % IniRead(IniFile, "Section 1", "Key1")

;Outputs ""
MsgBox, % IniRead(IniFile, "Section 1", "Key2")


myIniFile := IniFile_new(IniFile)
IniFile_load(myIniFile)

;Outputs "Value1"
MsgBox, % IniFile_get(myIniFile, "Section 1", "Key1")

;Incorrectly Outputs "Value3"
Msgbox, % IniFile_get(myIniFile, "Section 1", "Key2")
return

IniRead(FileName, Section, Key, Default = " ")
{
    IniRead, Value, %Filename%, %Section%, %Key%, %Default%
    return Value
}

_________________
As always, if you have any further questions, don't hesitate to ask.

Add OOP to your scripts via the Class Library. Check out my scripts.
Back to top
View user's profile Send private message Send e-mail
bmcclure



Joined: 24 Nov 2007
Posts: 769

PostPosted: Tue Mar 03, 2009 7:46 am    Post subject: Reply with quote

1. There is no unload method currently, as the class doesn't store which keys were loaded from the load() function, and I don't think the intended use would be to clear the entire object.

Unless that would be what you're referring to? It would be simple to create an unload() function which simply destroys all sections (and subsequently the fields in them). However, if there were sections/fields in the IniFile object before you used load(), they would be removed, too.

Otherwise I can try to come up with a way to store the keys that were loaded so that they can again be unloaded without affecting the rest of the object.

Or, per your other suggestion, perhaps a reload() function which allows specifying of a new filename, and which will essentially clear the object and load from file again?

2. The same key can exist several times in the Ini file, but only once per section, in order for the helper setter to work (that allows you to set by key name, rather than set a field object). This is be design in most Ini files, as well. if there is a need to support multiple lines with the same key in the same section, I can try to come up with a way to do that. Some functions wouldn't work properly, however, if they are designed to look up the key by name.
_________________
Ben

My Trac projects
My Wiki
[Broken] - My music
Back to top
View user's profile Send private message
animeaime



Joined: 04 Nov 2008
Posts: 1045

PostPosted: Tue Mar 03, 2009 7:56 am    Post subject: Reply with quote

bmcclure wrote:
Or, per your other suggestion, perhaps a reload() function which allows specifying of a new filename, and which will essentially clear the object and load from file again?

I think so. I need a function to remove all the sections/keys in the object - as if it was newly created. This would allow reloading from the same file (or a new file).

2. I guess I'm wording it wrong. Maybe using the word bug will set the correct train of thought.

I think that duplicate keys should be ignored, likewise with duplicate sections. However, you code doesn't do that (or something weird is happening).

I quote the example I gave.

IniFile:
Code:
[Section 1]
Key1 = Value1
Key1 = Value2

[Section 1]
;This key should NOT be loaded into any Section,
;because Section 1 already exists
Key2 = Value3


Test file:
Code:
IniFile := "IniTest.ini"

;Outputs "Value1"
MsgBox, % IniRead(IniFile, "Section 1", "Key1")

;Outputs ""
MsgBox, % IniRead(IniFile, "Section 1", "Key2")


myIniFile := IniFile_new(IniFile)
IniFile_load(myIniFile)

;Outputs "Value1"
MsgBox, % IniFile_get(myIniFile, "Section 1", "Key1")

;Incorrectly Outputs "Value3"
Msgbox, % IniFile_get(myIniFile, "Section 1", "Key2")
return

IniRead(FileName, Section, Key, Default = " ")
{
    IniRead, Value, %Filename%, %Section%, %Key%, %Default%
    return Value
}


AHK doesn't see a "Key2" in "Section 1", because its not in "Section 1" (well, not the first Section 1 at least). However, you code sees the 2nd "Section 1" and adds "Key2" from it.
_________________
As always, if you have any further questions, don't hesitate to ask.

Add OOP to your scripts via the Class Library. Check out my scripts.
Back to top
View user's profile Send private message Send e-mail
bmcclure



Joined: 24 Nov 2007
Posts: 769

PostPosted: Tue Mar 03, 2009 8:05 am    Post subject: Reply with quote

Unless I'm not thinking about this in the correct frame of reference, the behavior the IniFile class is showing seems to be a better approach.

With AHK's INI functions, you're working with one line at a time, so even if it doesn't see the extra section, it should be preserved in the file. IniFile is designed to work with entire files at once, however, so if that was the default behavior, chunks of your INI file could be missing once you use IniFile, if you had duplicate sections or keys.

IniFile, currently I believe, will combine the sections into one section, which conforms more to what INI files should be I think.

However, I realize this still may not be desired.

The problem is that there is no clear standard for INI file, and no pre-set way to deal with conflicts, duplicates, etc.

I think I'm going to add two new attribute to the IniFile class to specify how to handle duplicate sections and duplicate keys in sections.

The way I see it there should be three possible ways to handle them:
1. Current method, combine the sections, overwriting duplicate keys with whichever one comes later in the file

2. Keep both sections separate, but you will not be able to access the second section with shortcut functions. You'll have to get the second section object from the vector and use its helper functions to access its fields. For duplicate fields, you'll have to go even deeper and get the second field object directly from the vector in order to edit that one. The advantage, however, would be a preserved INI file if you have a program that relies on there being two separate but identically named sections/keys

3. Ignore the duplicate section / key completely, so that it is never loaded in the first place. It won't be read, and won't be in the file if you use the save() function. I think this is the method you're requesting?
_________________
Ben

My Trac projects
My Wiki
[Broken] - My music
Back to top
View user's profile Send private message
animeaime



Joined: 04 Nov 2008
Posts: 1045

PostPosted: Tue Mar 03, 2009 8:49 am    Post subject: Reply with quote

Well, I'm just thinking for constency with AHKs IniRead there needs to be something, but I agree it can get crazy fast, especially because all the cool stuff your IniFile can do that AHK can't.

I looked over the code and it seems that duplicate keys (same section) are added, not overwritten. And duplicate sections are "merged" (i.e. a new section isn't created). What about the option of creating a new section each time? This would allow values in the same section but not the first appearance of that section to be ignored.

In your load there is a check to see if the section exists, what about a parameter that would instruct the load function to create a new section every time (even if it already exists). This would require a small adjustment (which provides a performance gain anyway). Right now, your code checks for the section each time by doing a search. However, this search only needs to be done when the current section isn't the previous section. So, store the previous section (and its object), then compare the current section with the previous section. If they match, use the associated object (no need to look it up). If they don't match, do a search, or if the paramater instructs a new section to be created each time, create a new section - no search. This will cut back on the number of searches, while still adding this functionality, and while not modifing the code (much).

I'll rewrite the IniFile_load to do this and post it, and you tell me if you like it. I have an idea how to do it, so I think it's easier this way. As a disclaimer: I hope you don't take offense with me tweaking your code. It's just a habbit of mine to tweak good code to suit my needs. I'm not trying to take control (yeah, that sounds believable... Very Happy)
_________________
As always, if you have any further questions, don't hesitate to ask.

Add OOP to your scripts via the Class Library. Check out my scripts.
Back to top
View user's profile Send private message Send e-mail
bmcclure



Joined: 24 Nov 2007
Posts: 769

PostPosted: Tue Mar 03, 2009 8:59 am    Post subject: Reply with quote

Not at all, just as I like that you value input given on the OOP library, I really value the enhancements and thought you've put into helping me with my code as well.

Anything that will make it more useful to you, or me, or others, is worthwhile to me!

Thanks again.
_________________
Ben

My Trac projects
My Wiki
[Broken] - My music
Back to top
View user's profile Send private message
animeaime



Joined: 04 Nov 2008
Posts: 1045

PostPosted: Tue Mar 03, 2009 2:59 pm    Post subject: Reply with quote

Ok. Modified the IniFile_load to create a new section each time. I have no clue how to overload the two functionalities into one, so it might be better to name it something else. Maybe something like IniFile_AhkLoad since this function mimics AHK's functionality. It still stores all the values and sections, but it creates a new IniSection for each Section - no lookup to see if a section with the specified title already exists. This allows the get method to function like AHK's IniRead would.

Code:
;modified by animeaime
;1) remove "im)" RegExMatch options as they are not needed
;2) creates a new section obj for each section - does not do a search to see if it exists
;3) changes second if to else if (and removed continue)
;4) commented out <match1 := ""> (to be removed) as it is not needed
IniFile_AhkLoad(IniFileObject, fromFile = "") {
   if (fromFile = "")
      if !fromFile := IniFile_getFilePath(IniFileObject)
         return false

   if !FileExist(fromFile)
      return false

   if !sections := IniFile_getSections(IniFileObject)
      return false

   Loop, Read, %fromFile%
   {
      line = %A_LoopReadLine%

      ; Check for section
        ;not needed
        ;match1 := ""
      if RegExMatch(line, "^\s*\[([^\]]+)\]\s*$", match) {
         section := match1

            ;creates a new Section everytime - no look up
            sectionObj := IniSection_new(section)
            Vector_add(sections, sectionObj)

      }

      ; Extract key, value, and comment from line, if available
      else if RegExMatch(line, "^\s*(([^=]+?)\s*=\s*""?(.*?)""?)?\s*(;\s?(.*))?$", match) {
         field := IniField_new(match2, match3, match5)

         IniSection_addField(sectionObj, field)
      }
   }

   return true
}


Also, can you have IniFile_get return the specified default if the section doesn't exist (i.e !index). Currently, it returns false (0).

Code:
IniFile_get(IniFileObject, section, key, default = "") {
   if !sections := IniFile_getSections(IniFileObject)
      return false

    ;modified by animeaime - returns <default> when section doesn't exist
   if !index := Vector_indexOf(sections, section, 1, "getTitle")
      return default

   section := Vector_get(sections, index)

   return IniSection_get(section, key, default)
}

I'm going to see if I can't figure out how to filter the find and list functions to limit them to the first instance of a section/key. This way, there can be two copies of certain functions - one that mimics AHK and one that is the current behavior. This way, the design doesn't need to change - so there are no limits added, but IniFile can be used as a substitute for IniRead for those like me who want AHK functionality, with the ease of using an IniFile.

Update: I think the unique option (U) for the sort solves the duplicate problem. Since there is no way to tell on which Load something was added (maybe someone else needs this ability, but I don't), remove duplicates will allow the filter to behave like AHK - assuming you only load a single file, and use ReLoad (when added) instead of Load to load the next file - so only one IniFile's contents are loaded at a time.
_________________
As always, if you have any further questions, don't hesitate to ask.

Add OOP to your scripts via the Class Library. Check out my scripts.
Back to top
View user's profile Send private message Send e-mail
bmcclure



Joined: 24 Nov 2007
Posts: 769

PostPosted: Tue Mar 03, 2009 6:36 pm    Post subject: Reply with quote

New version coming today with all the discussed changes, and a few bonuses:

1. IniSection_lastNonEmpty has been heavily optimized

2. quoteValues attribute supported in IniFile, as well as in the paraters of the export functions. This will tell IniFile whether or not to put quotes around all values.

3. ignoreComments and splitComments attributes have been added to IniFile and the parameters of the export functions. IgnoreComments will simply not output any comment information. SplitComments will split comments that are on the same line as a key/value pair to their own line.

4. IniFile_renameKey and IniFile_renameSection have been implemented

5. Adding a second load function to preserve duplicate sections. Will add an option to decide which function to use automatically.

6. Improved vector searching to in most cases use a single Vector call instead of two.

7. Adding many sorting options. Sorting can be done within some functions, as well as its own separate operation (sortKeys or sortSections). There will also be an autoSort attribute in IniFile which tells it whether to keep the file sorted.

8. Some code fixes and enhancements (thanks animeaime Smile )

Maybe more...

I'll post the new version as soon as I finish implementing and testing.
_________________
Ben

My Trac projects
My Wiki
[Broken] - My music
Back to top
View user's profile Send private message
bmcclure



Joined: 24 Nov 2007
Posts: 769

PostPosted: Tue Mar 03, 2009 8:44 pm    Post subject: Reply with quote

I found a simple way to overload the load() function with the ability to keep duplicate sections. Duplicate keys are already created, so I'm working on the ability to stop that now.

I also created (and demonstrated in the test file of the new version) a new function to load (and reload) INI content from a variable instead of a file.

Code:
IniFile_load(IniFileObject, fromFile = "", duplicateSections = "", duplicateKeys = "") {
   if (fromFile = "")
      if !fromFile := IniFile_getFilePath(IniFileObject)
         return false

   if !FileExist(fromFile)
      return false

   if !sections := IniFile_getSections(IniFileObject)
      return false
   
   if (duplicateSections = "")
      duplicateSections := IniFile_getDuplicateSections(IniFileObject)
   
   if (duplicateKeys = "")
      duplicateKeys := IniFile_getDuplicateKeys(IniFileObject)
   
   Loop, Read, %fromFile%
   {
      line = %A_LoopReadLine%

      ; Check for section
      if RegExMatch(line, "im)^\s*\[([^\]]+)\]\s*$", match) {
         section := match1
         
         addSection := false
         if (duplicateSections) {
            addSection := true
         } else if !index := Vector_indexOf(sections, section, 1, "getTitle") {
               addSection := true
         } else sectionObj := Vector_get(sections, index)
         
         if (addSection) {
            sectionObj := IniSection_new(section)
            
            Vector_add(sections, sectionObj)
         }
         
         continue
      }

      ; Extract key, value, and comment from line, if available
      if RegExMatch(line, "im)^\s*(([^=]+?)\s*=\s*""?(.*?)""?)?\s*(;\s?(.*))?$", match) {
         field := IniField_new(match2, match3, match5)

         IniSection_addField(sectionObj, field)
      }
   }

   return true
}


Code:
iniFile_loadFromVar(IniFileObject, data = "", duplicateSections = "", duplicateKeys = "") {
   if (data = "")
      if !data := IniFile_getData(IniFileObject)
         return false

   if !sections := IniFile_getSections(IniFileObject)
      return false
   
   if (duplicateSections = "")
      duplicateSections := IniFile_getDuplicateSections(IniFileObject)
   
   if (duplicateKeys = "")
      duplicateKeys := IniFile_getDuplicateKeys(IniFileObject)
   
   Loop, Parse, data, `n
   {
      line = %A_LoopField%

      ; Check for section
      if RegExMatch(line, "im)^\s*\[([^\]]+)\]\s*$", match) {
         section := match1
         
         addSection := false
         if (duplicateSections) {
            addSection := true
         } else if !index := Vector_indexOf(sections, section, 1, "getTitle") {
               addSection := true
         } else sectionObj := Vector_get(sections, index)
         
         if (addSection) {
            sectionObj := IniSection_new(section)
            
            Vector_add(sections, sectionObj)
         }
         
         continue
      }

      ; Extract key, value, and comment from line, if available
      if RegExMatch(line, "im)^\s*(([^=]+?)\s*=\s*""?(.*?)""?)?\s*(;\s?(.*))?$", match) {
         field := IniField_new(match2, match3, match5)

         IniSection_addField(sectionObj, field)
      }
   }

   return true
}


Trying to figure out the best way to put a getComment and setComment function in IniFIle.

What I've come up with so far, is basing the comment off of a section or key, plus or minus a line offset.

For instance, to get the comment from the same line as Section1, Key1, you'd use:
IniFile_getComment(File, "Section1", "key1") ; The line comment

Or to get the comment on the line above it:
IniFile_getComment(File, "Section1", "key1", -1) ; Right above the key

Or to get the comment from a section offset:
IniFile_getComment(File, "Section1", "", 1) ; right below the section declaration

Is that simple enough, or can you think of a better way to access the comments (other than searching, which I might support also)?


Also, based on my work with the DOM, I think a null value (or "" in AHK's case) might be more logical than a 0 (false) when a function fails. Do you think I should change most of my return false lines to return "" so as not to be confused with an actual return value of 0?


I'm also implementing a headerFields Vector that stores everything before the first section in the INI file, and then saves it again with the INI file.

I'll make some new functions for working with header fields.
_________________
Ben

My Trac projects
My Wiki
[Broken] - My music
Back to top
View user's profile Send private message
Display posts from previous:   
Reply to topic    AutoHotkey Community Forum Index -> Scripts & Functions All times are GMT
Goto page 1, 2  Next
Page 1 of 2

 
Jump to:  
You can post new topics in this forum
You can reply to topics in this forum


Powered by phpBB © 2001, 2005 phpBB Group