Utility components ConfigFile object
Overview | Format and usage | Creation | Members reference

ConfigFile class is a component that allows you to read/write Universal Structured Data (UDS) from/to different medias/formats. Structured configurations is a synonym of UDS when it is used in a format and for purpose of configuration. UDS data are trees of data separated in sections (which can be nested). Component loads/creates them into collections (using VarDictionary objects), the application is able to fill/change them and save the changed data.  ConfigFile defines abstract structure and supports different formats and medias. The created/loaded in memory data tree can be stored to different targets and formats. Current version of ConfigFile component supports internally 3 formats: text (see Universal Data Structure), windows registry and binary streams. Of course the binary format is the most powerful and supports all the features but if you need something more human readable or compatibility with other applications the other formats are at hand 

Overview

Structured configurations are very powerful tool and they can be used for various purposes - from configuration and data exchange to a DB replacement in case of small amount of data. These data structures are the same structures used by the structured data classes in our C++ libraries. Their origin is Jacked-Objects library. The standard comes not from a file format, but from the internal architecture standards implemented by the C++ (and Jacked) classes in our libraries. Its capabilities cover not only configurations but also network and inter-process data transfers, flexible and portable object persistence mechanisms, data hierarchy common for our applications. Historically the first usage of these data classes was in 1996 and overlaps with the years of XML growth. Our architecture was developed because of the same reasons and thus it has many similarities with the XML, but don't forget it was born as an abstract data scheme and not over a document format specification. Also it is oriented to the programmer and thus supports value types from the very beginning. The class in this DLL is for simple usage, in general the structured data classes and the standard are the backbone of our applications. Read more detailed definitions here: Universal Data Structure.

How the ConfigFile represents the data in memory

ConfigFile class uses trees of collections/dictionaries to represent the UDS in memory (Logical Data Structure). VarDictionary objects are used for that purpose, their Info property holds additional object - NodeInfo. This object helps you to determine the node meaning (record or section) and holds the class info (see UDS for the logical data structure definition). Collections in the data tree are of two types: Sections and Records. In short the rules about them are:

  • Sections - may contain other sections (sub-sections) and records as items.
  • Records - may contain only values and no sections or other records.

Result of the Read, ReadFromRegistry or ReadFromBinaryStream methods strictly follows the above rules, but if you are creating the data to be saved later through the one of the Write methods you can use one compromise: To simplify the usage of the collections it is allowed to add values in the sections. During the write operation a record will be created for all the values with the same names found in the section and the values will be assumed as unnamed. Probably this sounds a bit confusing at a first look, but it makes the usage of the collections intuitive and simple (take a look at the examples). Syntax for accessing a value stored in the section collection equals the syntax for accessing an unnamed value in a record thus you don't need to create record for every new value you want to add to the data structure. However the correct usage will require you to use the CreateSection and CreateRecord methods to create the elements of the tree and then attach them to the node where you want them. So, to create a simple tree like this (see the text format description in UDS):

{ A:
  (int)V1=123
  { B:
    (string)V2=Blah
    (string)V2=Blah Blah
  } B;
} A;

You will need code like this:

Set cf = Server.CreateObject("newObjects.utilctls.ConfigFile")
Set uds = cf.CreateSection
uds.Add "A", cf.CreateSection
uds("A").Add "V1", cf.CreateRecord
uds("A")("V1").Add "", CLng(123)
uds("A").Add "B", cf.CreateSection
uds("A")("B").Add "V2", cf.CreateRecord
uds("A")("B")("V2").Add "","Blah"
uds("A")("B")("V2").Add "","Blah Blah"

You can save it to a text file like this:

cf.Write "myfile.txt", uds

Or to a binary stream (lets use a file in the sample):

Set sf = Server.CreateObject("newObjects.utilctls.SFMain")
Set file = sf.CreateFile(Server.MapPath("myfile.bin"))
cf.WriteToBinaryStream file, uds

As we mentioned above the nodes of the data tree are represented by VarDictionary objects configured to behave as follows:

Sections are represented by VarDictionary object with behavior properties set to:

firstItemAsRoot = false
itemsAssignmentAllowed = true
enumItems = false
allowUnnamedValues = true
allowDuplicateNames = true
extractValues = false

Records are represented by VarDictionary object with behavior properties set to:

firstItemAsRoot = true
itemsAssignmentAllowed = true
enumItems = false
allowUnnamedValues = true
allowDuplicateNames = true
extractValues = true

Note that you should not store any objects as values and you should use CreateRecord and CreateSection  (see below) methods to create the sections and the records. If you violate the structure requirements the Write methods will fail to store it. However if you are not going to save the structure (for example you read something that is initial configuration or something like it) you can do whatever you want - attach other objects, values of not supported types and so on. This may help you create quite complex and flexible application, but you should always be aware of the fact that this will make the structure unsaveable.

Another recommendation is to use the VBScript conversion functions (or other ways to do so) when setting values. The implicit value conversions may produce unexpected results. For example the  string "123" will implicitly be saved as string, but you may want to create integer value, so using the CLng will guarantee that the type of the value will be exactly what you want.

Creation:

Default (registered as Both) - i.e. the object is created in the same COM apartment.

  • ProgID:  newObjects.utilctls.ConfigFile
  • CLSID: {262DE78D-982E-4B0F-8230-D99D079EA7FA}

Freethreaded registration.

  • ProgID:  newObjects.utilctls.ConfigFile.free
  • CLSID: {FCF4F139-90E0-47a9-8DC0-62B1B216CC41}

Methods:

Read/Write from/to text file

Read

Set variable = object.Read(fileName)
Attempts to read the file pointed by the string passed as fileName parameter and returns a tree of collections that represents the data from the file. 
If the file was not found, can not be read or format is wrong error will be generated.

Write

object.Write(fileName,dataTree)
Attempts to save the collections in the dataTree parameter to the file specified by the fileName string parameter. dataTree must point to a collection that represents Section. Records can not be saved if they are not part of a section.

Read/Write from/to registry

ReadFromRegistry

Set variable = object.ReadFromRegistry(branch,path)
Attempts to read the registry branch specified:
branch is a number that specifies the root registry branch:

0 HKEY_CLASSES_ROOT
1 HKEY_CURRENT_USER
2 HKEY_LOCAL_MACHINE
3 HKEY_USERS
4 HKEY_PERFORMANCE_DATA
5 HKEY_CURRENT_CONFIG
6 HKEY_DYN_DATA

Note that except the first 3 branches the rest are supported by some Windows versions only - use them with care!

path - is the full key name. For example if you want to load the branch containing the settings for your program this will be something like: "SOFTWARE\MyCompany\MyApplication".

Note: The class names are saved/read as the default values of the keys.

WriteToRegistry

object.WriteToRegistry(branch,path,dataTree)
Attempts to save the collections in the registry branch - see the parameters for the ReadFromRegistry for details.

Read/Write from/to binary stream

ReadFromBinaryStream

Set variable = object.ReadFromBinaryStream(strm)
Attempts to read structured data from a stream. The strm parameter can be a SFStream object or another object that supports IStream interface (this is most useful for VB and C++ programmers, script programmers may need something else than SFStream only if some external objects are used).

See the Storages and Files on how you can access files, memory blocks, OLE files as streams. In theory you can use any stream to read/read structured data from/to it. Even COM components supplied by other vendors can be used if they support the IStream (standard Win32 interface) interface. This allows the data to be transferred or stored in almost unlimited medias and the usual file system files are only one of the many possible storages.

The binary format is not described in this article but detailed information on it will be added to the documentation as separate chapter in order to allow other developers to write format compatible solutions (on other OS-es for example).

The binary format supports all the structured data features and data types.

WriteToBinaryStream

object.WriteToBinaryStream(strm, dataTree)
Attempts to save the collections in the binary stream - see the ReadFromBinaryStream method for more information.

Helper methods

To avoid need to configure manually the VarDictionary objects you prepare for the data trees these methods shall be used to create new nodes. The result returned by each of these methods is a VarDictionary object with NodeInfo object attached and configured. Although you can add new records with only one value by assigning values to the sections it is recommended to create new records, add them to the sections and then add the value(s) to the record. The values stored in the sections are in fact rule violations and support for them is provided only as efforts-saving feature that can be used by experienced developers to save some coding work. This support is a syntax based trick - e.g. if you have section "A" and a record "V" with one value in it the syntax: o("A")("V") will be the same as in case the value is stored in the section directly (without record) but this happens because of the automatic resolution of the default values of the objects (the record's default value is the first value stored in it). Therefore this feature may lead to confusions in some cases (especially if you are using records with multiple values) and we strongly recommend to avoid it in production environments.

CreateSection

Set variable = object.CreateSection()
Creates new empty section collection. ClassName in the Node info (accessible through the Info property of the collection object) is set to empty string -  you can set this value after obtaining the newly created collection.
See above how the TextConfig class represents sections and records.

CreateRecord

Set variable = object.CreateRecord()
Creates new empty record collection. See above how the TextConfig class represents sections and records.

Configuration properties

PreserveStringsWide

object.PreserveStringsWide = value
variable = object.PreserveStringsWide

Boolean value that specifies how the strings will be preserved when the data is written. This is suitable for the binary format only. The text and registry format depend on the media limitations and currently ASNI code page is used to convert them.
When this value is set to True the string values written to a binary stream are saved as wide strings (UNICODE).

Default is False.

PreserveUnsignedInt

object.PreserveUnsignedInt = value
variable = object.PreserveUnsignedInt

Boolean value that specifies whether the unsigned integer values will be preserved when the data is written. This is suitable for the binary format only (WriteToBinaryStream). In the earlier versions of UDS no unsigned values have been supported. This property is intended to allow the developers to control the behavior. 

What will happen if you create data for a program that uses an earlier version with this property set to True. No fatal error will occur however the program will not be able to read any unsigned integer values from the data. Note that scripts very often rely on implicit type conversions. This means that if the property is False all the integer values will be converted to signed integers, when it is set to True the unsigned integers will be preserved. Therefore if you are not using explicitly type conversion routines when setting values in the data tree the result may be different according to the property value.

Default is False.

ErrorMode

object.ErrorMode = value
variable = object.ErrorMode

Error mode can be currently 0, 1 or 2

0 - default. Pass the parameters "as is". If the value of one or more of the configuration properties is not compatible with the write/read method used an error will occur only if the operations performed concern it. For example if the write method does not allow wide strings an error will not occur as long as no strings are saved.

1 -  Adjust options. The values of the configuration properties incompatible with the write/read method used will be automatically discarded/adjusted if possible. Perhaps this mode is the most convenient one. It allows you to avoid frequent changes of the configuration properties while using different write/read methods.

2 - Check options. When a value of a configuration property is incompatible with the write/read method used an error occurs before any work is done.

newObjects Copyright 2001-2005 newObjects [ ]