Too Many Parsing Styles

Never knowing where to go

Earth Date 2008.08.13

Posted by Rich Wheadon | Permalink



I recently worked a project where I wanted to import some externally maintained properties/configurations for my program. As I looked at the MANY different formats and parsing techniques we are using at work I realized that everyone who made a configurable piece of code would create a totally different format and parser. (*Sigh* I wrote three out of the dozen or so out in our main systems.) Since I was pretty much given all the rope I needed for the project to be a lasting success, I did some usability research at work for the programmers that would use any sort of framework I could create. Almost unanimously it was agreed that a configurable format similar to property files would work great. I was pleased. As an FYI I am using the parser to pull in various parameters based on FormName to marshall whether my agent does delete/move/copy functions along with varying stuff like dateFields used in criteria and whether it goes to DB2 and what tables in DB2.


The framework takes in the config as an Array of strings, so regardless of whether the rows are stored in a text file, notes document, or embedded in the code somewhere.. an array is what the framework expects. Maybe someday I'll need to extend the constructor to behave differently... but for now we use a field out of a notes document which inherently yields the array to my code.
I used a pair of custom classes to pull it off because that's just the direction I've been going for the past couple of years. If you aren't using/building classes in your LotusScript then you are cheating your self out of highly reusable, extendable, and scalable code. If classes are new to you then this little feature I'm covering might set off a lightbulb since the code pulls in an array and converts it to a bunch of objects which you can retrieve by name and then pull attributes. I won't go into get/set properties, arrays, and methods that you can work into classes as opposed to extremely limited potential held in Types. Okay, I'm rambling.


So... we've got these two custom classes: PropertyFileParser and PropertyObject. The PropertyFileParser allows you to import the "property file" (config) and converts that file into PropertyObject objects which it also stores. The PropertyObject contains all of an individual property's attributes and has methods for you to set/get the values. Pretty simple. If you're tired of reading and want some cut and paste stuff to run with then I've included the code from an .lss below, along with a sample config you can paste into a field to read. Otherwise, I'm nearly done anyway.


The only real rules to the parser are:
1. You have to pass in a string array to the parsePropertyArray() method.
2. The first line of the file is going to establish the attribute name that will become the inter-propertyFile delimiter AND ALSO will serve as the key value used to retrieve your propertyFile configs. So...
The following list:
ID:001
Name:One
Value:Apples
ID:002
Name:Two
Value:Oranges

3. The Delimiter/Key for each grouping of attributes must be unique. Otherwise you overwrite stuff and get real upset.

...will be listed for retrieval as "001" and "002". So if you set it up like that, but are looking for "Apples" or "Oranges" rather than the ID values then you have to iterate every object you import until you find the one you want. (So Make that first value whatever you want to use to retrieve the object.)

I think that's it. Hopefully the documentation in code is good enough.

Have fun and if it's buggy i'd like some feedback so I can fix my own stuff too :o)

Rich

---End Of Story---

Here's the string I paste into a notes field (textField in my POC database): Notice I put an asterisk record in there to help me read larger files.
Form:Person
Setback:0:0:-30:0:0:0
DateFields:createdDate;postedDate
is_DB2Data:True
DB2Schema:SALES
DB2Descriptor:ARCHIVEPERSON
DB2KeyField:DB2Person_ID
is_Parent:True
childView:personChildDocsBE
parentKeyField:parentID
*******************:*******************
Form:Company
Setback:-1:-6:0:0:0:0
DateFields:postedDate
is_DB2Data:True
DB2Schema:SALES
DB2Descriptor:ARCHIVECOMPANY
DB2KeyField:DB2Company_ID
is_Parent:True
childView:companyChildDocsBE
parentKeyField:parentID
*******************:*******************
Form:SiteVisit
Setback:0:0:-60:0:0:0
DateFields:visitDate
is_DB2Data:False
is_Parent:False

Here's the code you can import into a button (or paste)
'================BEGIN COPY/CUT HERE====================
'use_propertyFileParser_Classes:

Option Declare

' *************************************************************
' *************************************************************
' ************* PROPERTYFILEPARSER (CLASS) ********************
' *************************************************************
' *************************************************************
Class propertyFileParser
%REM
*CREATED: 2008.04.25 by Rich Wheadon
*PURPOSE: Parse externally configurable form context information
* for use within programatic processes.
*
* In reality this is going to take an array of values
* and using the first value will establish that as the
* pattern for breaking the file into PropertyObjects
* which will be collected in this parser via parseProperties
* and will return a list of propertyObjects
* with the listTags being the value side (right) of the first
* property sent in each group.
* This class creates n instances of PropertyObject, one for
* each property context coming in, therefore the example below would
* create 2 PropertyObject instances containing the discreet attributes
* each object.
*
* WHAT SHOULD INCOMING DATA LOOK LIKE:
* The data coming into this file can actually appear as the
* contents of several property files, here is an example from
* the configuration "AJCARCHIVE_PROPERTIES" which contains properties
* of two contexts. The first line is ALWAYS assumed to be the property
* that will establish every first record, therefore it must be the same
* for every property grouping read into the class.
* Form:Fallout
* FormUILabel:Fallout
* Priority:1
* DateFieldNames:JOBACCEPTDATE;GUARENDDATE
* ChildViewNames:
* ChildKeyFieldName:<NONE>
* **********************:***********************
* Form:Accounting \ PPD Conversion Summary Form
* FormUILabel:PPD Conversion
* Priority:2
* DateFieldNames:MODIFIEDON;JOBACCEPTDATE
* ChildViewNames:
* ChildKeyFieldName:<NONE>
**********************:***********************

%END REM
listOfPropertyObjects List As propertyObject
Property Get AllPropertyObjects As Variant
AllPropertyObjects = listOfPropertyObjects
End Property
Property Get count As Long
Dim ic As Long
ic=0
Forall pc In listOfPropertyObjects
ic = ic + 1
End Forall
count = ic
End Property
Function getPropertyObject( objectName As String ) As propertyObject
If Iselement(listOfPropertyObjects(objectName)) Then
Set getPropertyObject = listOfPropertyObjects(objectName)
End If
End Function

Function parsePropertyArray( propertiesToParse() As String )
On Error Goto parsePropertyArrayErh
' * From our first "real" value we will begin parsing the property file
' * The first "real" label will become our break into a new property object
Dim i As Integer
Dim headerLabel As String
Dim thePair As Variant
Dim firstRecord As Boolean
Dim firstProperty As Boolean
Dim wkParam1 As String
Dim wkPropertyObject As propertyObject

firstRecord = True
If Isarray(propertiesToParse) Then
If Ubound(propertiesToParse)<1 Then Error 2008, "Property file too small"
Else
Error 2008, "Property file is empty"
End If
For i = 0 To Ubound( propertiesToParse )
If Not(Fulltrim(propertiesToParse(i))) = "" Then
thePair = Split(propertiesToParse(i), ":")
If firstRecord Then
' * Set up the parser breakpoint
headerLabel = thePair(0)
firstRecord = False
End If
If thePair(0)=headerLabel Then
If Len(wkParam1) > 0 Then
wkPropertyObject.setProperty "PROPERTYID", Cstr(i)
' fGenerateID + Cstr(i)
Set listOfPropertyObjects( wkPropertyObject.pName ) = _
wkPropertyObject
End If
wkParam1 = thePair(1)
' * Create a new property object
Set wkPropertyObject = Nothing
Set wkPropertyObject = New propertyObject(wkParam1)
wkPropertyObject.setProperty thePair(0), wkParam1
Else
' * Against my better judgement I am allowing missing colon (:) on empty attributes.
If Ubound(thePair) >= 1 Then
wkPropertyObject.setProperty thePair(0), thePair(1)
Else
wkPropertyObject.setProperty thePair(0), ""
End If
End If
End If
Next
If Not( wkPropertyObject Is Nothing ) Then
If Not( Iselement(listOfPropertyObjects( wkPropertyObject.pName ) ) ) Then
wkPropertyObject.setProperty "PROPERTYID", Cstr(i+1)
Set listOfPropertyObjects( wkPropertyObject.pName ) = wkPropertyObject
End If
End If
If Len(wkParam1) > 0 Then
Set listOfPropertyObjects( wkPropertyObject.pName ) = wkPropertyObject
End If

Exit Function
parsePropertyArrayErh:
Print "Error in PropertyFileParser.parsePropertyArray line:" + Cstr(Erl) + _
" Message:" + Error$
' Set parsePropertyArray = Nothing
Exit Function
End Function
End Class

' *************************************************************
' *************************************************************
' **************** PROPERTYOBJECT (CLASS) *********************
' *************************************************************
' *************************************************************
Class propertyObject
%REM
*CREATED: 2008.04.25 by Rich Wheadon
*PURPOSE: Subclass of PropertyFileParser, object represents
* individual property contexts parsed by the parent
* class.
* ProperyObject instances created by PropertyFileParser
* are retrieved by the PropertyFileParser using getPropertyObject()
* or PropertyFileParser.AllPropertyObjects.
* Once object is retrieved it is directly referenced with
* getters/setters in this class.
%END REM
listOfProperties List As String
myName As String
Property Get pName As String
pName = myName
End Property
Function getAllPropertyList As Variant
' * Return all properties in a string list
getAllPropertyList = listOfProperties
End Function
Function getProperty( propertyName As String )As String
' * Return the value of the property queried
If Iselement(listOfProperties(propertyName)) Then getProperty = listOfProperties(propertyName)
End Function
Function setProperty( propertyName As String, propertyValue As String ) As Boolean
listOfProperties(propertyName) = propertyValue
End Function
Sub new( objectName As String )
On Error Goto propertyObjectNEWErh
If Len(objectName)=0 Then Error 2008, "No name assigned"
myName = objectName
Exit Sub
propertyObjectNEWErh:
myName="ERROR"
Exit Sub
End Sub
End Class
Sub Click(Source As Button)
Dim theProperties() As String
Dim thisWS As New NotesUIWorkspace
Dim thisDoc As NotesUIDocument
Set thisDoc = thisWS.CurrentDocument
Dim thePropertyElements As String

Dim i As Integer
Dim ub As Integer

ub = Ubound(thisDoc.Document.GetItemValue("testField"))
Redim theProperties(ub)

For i = 0 To Ub
theProperties(i)=thisDoc.Document.GetItemValue("testField")(i)
Next

Dim theParser As New PropertyFileParser
Dim propertyCount As Integer
Call theParser.parsePropertyArray( theProperties )

Forall p In theParser.AllPropertyObjects
propertyCount = propertyCount + 1
thePropertyElements = ""
Forall pe In p.getAllPropertyList
If Not(thePropertyElements="") Then thePropertyElements = thePropertyElements + " | "
thePropertyElements = thePropertyElements + Listtag(pe) + ":" + pe
End Forall
Msgbox thePropertyElements, 64, "Parsing Property Object #" + Cstr(propertyCount)
End Forall

End Sub
'================STOP COPY/CUT HERE=====================