by Ross Mack - GUI Computing
Strings, Strings, Strings, Strings, Everybody Loves Strings…
Last issue I presented an article on taking strings and splitting them into arrays and then turning arrays into strings. I mentioned at the time that the code in question has proved useful in seemingly endless circumstances since then. One example is my article this month on performing simple Database comparisons. In this article I will present another simple library of functions that again rely on my Split and Join functions to do some of their underlying work. If you have not read the article on those functions now might be a good time.
And now back to the story…
During a recent project I not only had to create and manage multiple connections to ODBC data sources, but I also needed to manipulate the connection strings. For those of you unfamiliar with ODBC connection strings, they are simply a string containing a number of values and settings to specify how ODBC should establish a connection to the ODBC data source. An example is below:
As you can see the connection string is a recognisable format of named parameters and their values, separated by semi-colons. The user ID to connect with is specified as "sa" by the "UID=sa" clause, the name of the ODBC Data Source to connect through is specified as "Pubs" (a data source configured in my ODBC Control Panel applet) by the "DSN=Pubs" clause and so on. I was faced with a task of not only extracting values from such strings but also making changes to them.
However, writing the code to extract each little piece of information from the string and then writing more code to manipulate those values and slot them back in became not only laborious, but boring. That seemed like the last thing I needed. So, I wrote a small library to handle such things. I also tried to build it such that the potential scope was broader than just ODBC connection strings. For this reason the delimiter to use between each clause or key/value pair (note that I use the terms 'clause' and 'key/value pair' interchangeably in this article) is configurable, you can use a semi-colon for strings like the one above, but you could just as easily use any character or group of characters.
So, why would you want to deal with such strings? Well, apart from handling ODBC connection strings with ease it can be useful to stuff several named values into a string for use in a number of places. For example you can place a whole pack of information about a field into the Tag property of the textbox or combobox or whatever represents it on a VB form. The idea is sort of like having an INI file in a string. You can also save complicated combinations of data and record types in a simple text file and read it easily using this library. The following might be an extract from such a file:
RecType=Client|Company=ABC Technologies Pty Ltd.|Address=123 Somewhere Street RecType=Employee|Name=Han Solo|Department=Smuggling|EmpID=1003A Department=Quarry|Name=Fred Flintstone|RecType=Employee;
As you can see the records can have the clauses in any sequence, as the library does not require the clauses to be in any particular order. You can also easily omit clauses if they are not appropriate. Again, the library does not care. The astute amongst you will also note that the delimiter between clauses used above is "|" and not ";". This is again because the library allows you to specify whatever delimiter you wish to use.
Isn't all that sort of cool?
OK, let's have a look at some of the code.
Function kvFetchValue (ByVal sConcat As String, ByVal sKey As String, ByVal sDelim As String) Dim sArray() As String Dim nCount As Integer Dim nNum As Integer Dim sResult As String Dim bFound As Integer ' Get an array of all the key/value pairs nCount = Split(sConcat, sDelim, sArray()) nNum = 1 Do ' Check for the existence of key only If sArray(nNum) = sKey Then sResult = "1" bFound = True Else ' Check for a key/value pair If Left$(sArray(nNum), Len(sKey) + 1) = (sKey & "=") Then sResult = Right$(sArray(nNum), Len(sArray(nNum)) - Len(sKey & "=")) bFound = True End If End If nNum = nNum + 1 Loop Until bFound Or (nNum > nCount) ' Return whatever we found kvFetchValue = sResult End Function
This is one of the central functions. It returns a requested value from the string of key/value pairs. The first parameter represents the string of key/value pairs from which to extract the value. The second parameter is the key of the value to be returned. The last parameter is a string to use as the delimiter separating the different key/value pairs.
The first thing the function does is use my split function (from last issue, remember the opening paragraph?) to break the string of key/value pairs up into an array of key/value pairs using the delimiter passed. This means we now have an array of strings representing each clause in the string that we passed in.
That makes the remaining code comparatively simple. Do you think there might be a lesson in there about building simple working code upon simple working code to create something useful? Me neither.
Anyway, then all the function has to do is read through the array of clauses looking for one starting with the key and an equals sign (=) or which contains only the key. If only the key is found a default value of "1" is returned. Otherwise the associated value from the clause is returned. If the key was not found at all an empty string is returned.
To take the ODBC Connection string from above as an example lets see what this function will return given some input values:
|KvFetchValue(sConnect, "UID", ";")||"sa"|
|KvFetchValue(sConnect, "PWD", ";")||""|
|KvFetchValue(sConnect, "Banana", ";")||""|
|KvFetchValue(sConnect, "DSN", ";")||"Pubs"|
As this function is written the search for matching keys is case sensitive. Liberal use of the UCase$() function will rectify that if such is your will.
You will also notice that when requesting the value for PWD the value returned is an empty string as that is effectively the value stored in the string. This is the same as if the key did not appear in the function at all. In some cases this may be a problem.
I considered two ways to overcome this. Using variants to hold the return value would have allowed me to return nulls to indicate values which were not found in the string at all. However, this somewhat complicates the calling code so I opted to instead stick to strings and provide a function that merely returns a boolean value indicating whether or not the key exists in the string. That sort of serves as a slimey introduction to a brief look at the other functions in the library.
Function kvValueExists (ByVal sConcat As String, ByVal sKey As String, ByVal sDelim As String) As Integer
This function works almost identically to kvFetchValue taking the same parameters and having almost identical code except that it merely returns a boolean value indicating if the key was found or not.
Function kvSetValue (ByVal sConcat As String, ByVal sKey As String, ByVal sValue As String, ByVal sDelim As String) As String
This function is used to associate a value with a key in the string of key/value pairs. If the key already exists the value is amended to the value passed, otherwise the key and value are added to the string. This function requires an additional parameter being the value to set the key to. It returns the new string of key/value pairs.
Function kvKillValue (ByVal sConcat As String, ByVal sKey As String, ByVal sDelim As String) As String
This function allows you to remove a key/value pair from the string entirely. If the key is not found in the string the string is left unchanged. The return value is the new string of key/value pairs.
As usual the library is available for download with a small app to demonstrate the calls.
How you use it is up to you, but remember to use it for niceness and not for evil.