flibs/m_vstrings(n) 1.0 "flibs"

NAME

flibs/m_vstrings - Processing strings

TABLE OF CONTENTS

    TABLE OF CONTENTS
    SYNOPSIS
    DESCRIPTION
    OVERVIEW
        Creating a string
        Concatenate two strings
        Modify the case
        Pattern matching
        Validating a string
        Allocatable or pointer
        Dynamic or static buffer
        Design
        Limitations
        Preprocessing
        History
    METHODS
    INTERFACE TO STANDARD FORTRAN
    STATIC METHODS
    TODO
    COPYRIGHT

SYNOPSIS

vstring_new (this ?args?)
vstring_new (this)
vstring_new (this,char_string)
vstring_new (this,vstring)
vstring_new (this,chararray)
vstring_new (this, ncount ?, vstring?)
vstring_free (this)
vstring_exists (this) result ( exists )
vstring_equals (this, stringb ?, nocase,length?) result (equals)
vstring_equals (this, string2 ?, nocase,length?) result (equals)
vstring_cast (this ?, args?)
vstring_cast (this , length , charstring)
vstring_cast (this , charstring)
vstring_cast (this , value)
vstring_cast (this , value)
vstring_cast (this , value)
vstring_length (this) result ( length )
vstring_concat (this,string2) result ( concat_string )
vstring_concat (this,string2) result ( concat_string )
vstring_append (this,string2)
vstring_append (this,string2)
vstring_map (this , map_old , map_new ?, nocase?) result ( stringmap )
vstring_replace (this , first , last ?, newstring?) result ( stringreplace )
vstring_compare (this,string2?, nocase , length?) result ( compare )
vstring_compare (this,string2?, nocase , length?) result ( compare )
vstring_trim (this ?, chars?) result ( trim_string )
vstring_trim (this ?, chars?) result ( trim_string )
vstring_trimleft (this ?, chars?) result ( trim_string )
vstring_trimleft (this ?, chars?) result ( trim_string )
vstring_trimright (this ?, chars?) result ( trim_string )
vstring_trimright (this ?, chars?) result ( trim_string )
vstring_first (this ?, string2 , first?) result ( firstIndex )
vstring_first (this ?, string2 , first?) result ( firstIndex )
vstring_last (this ?, string2 , last?) result ( lastIndex )
vstring_last (this ?, string2 , last?) result ( lastIndex )
vstring_range (this, first, last) result ( string_range )
vstring_index (this , charIndex ) result ( string_index )
vstring_toupper (this?, first??, last?) result ( new_upper )
vstring_tolower (this?, first??, last?) result ( new_lower )
vstring_totitle (this?, first??, last?) result ( new_title )
vstring_reverse (this) result ( new_reverse )
vstring_random (this) result ( new_random )
vstring_match (this , pattern ?, nocase?) result ( match )
vstring_match (this , pattern ?, nocase?) result ( match )
vstring_is (this , class ?, strict? ?, failindex?) result ( isinclass )
vstring_iachar (this) result ( new_iachar )
vstring_ichar (this) result ( new_ichar )
vstring_charindex (this , substring ?, back?) result ( charindex )
vstring_scan (this, substring?, back?) result ( charindex )
vstring_verify (this, substring?, back?) result ( charindex )
vstring_adjustl (this) result ( newstring )
vstring_adjustr (this) result ( newstring )
vstring_achar (i) result ( new_achar )
vstring_char (i) result ( new_achar )

DESCRIPTION

The module m_vstring provides OO services to manage strings of dynamic length. The goal of the current component is to provide higher-level services that the standard fortran currently provides. The component provides methods which mimic the services available in the Tcl language. It provides string comparison, string search and string matching methods. See in test_m_vstring to see a complete example of the services provided.

OVERVIEW

A vstring is an array of characters. The simplest way to create a vstring is with vstring_new from a "character(len=<something>)" string. The length of the vstring is computed dynamically, depending on the current number of characters, with vstring_length. In the following example, the length is 9.

 
    use m_vstring, only : &
      vstring_new, &
      vstring_free, &
      t_vstring, &
      vstring_length
    type ( t_vstring,) :: string1
    integer :: length
    call vstring_new ( string1 , "my string" )
    length = vstring_length (string1)
    call vstring_free( string1 )

Creating a string

With vstring_new, one can also create a new vstring as a copy of an existing vstring. With vstring_new, one can also create a new vstring with an array of characters or with a repeated copy of an existing vstring. Destroy the vstring with vstring_free.

Concatenate two strings

Two vstrings can be concatenated in two ways. The vstring_concat method returns a new vstring computed by the concatenation of the two strings. The vstring_append allows to add the characters of the 2nd vstring at the end of the current string. In the following example, the string3 is "my string is very interesting".

 
    call vstring_new ( string1 , "my string" )
    call vstring_new ( string2 , " is very interesting" )
    string3 = vstring_concat ( string1 , string2 )

Modify the case

The user can modify the case of a vstring. The vstring_tolower creates a new vstring with lower case characters. The vstring_toupper creates a new vstring with upper case characters. The vstring_totitle creates a new vstring with the first letter in upper case and all the other characters to lower case. The user can know if two vstrings are equal with vstring_equals. Two vstrings can be compared with vstring_compare, which is based on the lexicographic order. One can transform one vstring into a new one using a map with vstring_map.

Pattern matching

The vstring_match method provides string-matching services in the glob-style. It manages "*" pattern (which matches 0 or more characters), the "?" pattern (which matches exactly one character), escape sequences and character ranges. The following example show how to compare a file name against a pattern :

 
    call vstring_new ( string1 , "m_vstring.f90" )
    call vstring_new ( pattern , 'm_*.f90' )
    match = vstring_match ( string1 , pattern )

Validating a string

The vstring_is method provides a way of validating data by computing whether the vstring is in a class of data, for example integer, real, digit, alphanumeric, etc... In the following example, the user can check whether the string read on standard input is an integer :

 
    read ( 5 , * ) charstring
    call vstring_new ( string1 , charstring )
    isinteger = call vstring_is ( string1 , "integer" )
    if ( .NOT. isinteger ) then
      ! Generate an error
    endif

If the character set under use is not in one the pre-defined classes of vstring_is, the user can directly call vstring_isincharset or vstring_isinasciirange, which are the basic blocks of vstring_is. Second string argument may be character string The design choice has been made to design the subroutines/functions so that their dummy arguments are generally only of type t_vstring. Another choice would have been to allways take as dummy arguments both t_vstring and "character (len=*)" strings, with module procedure interfaces to make them generic. The last choice ease the work of the client of the current component, which can use directly standard fortran constant strings (for example,

 
    equals = vstring_equals ( string1 , "toto" )

instead of

 
    type ( t_vstring ) :: string2
    call vstring_new ( string2 , "toto" )
    equals = vstring_equals ( string1 , string2 )
    call vstring_free ( string2 )

that is to say 5 lines instead of 1. The main drawback is that the number of interfaces is at least multiplied by 2, if not 4 or 8 when the number of string arguments is more than 2. This makes the unit tests multiplied by the same number, if one want to exercise all the possible interfaces. That way was chosen by the original iso_varying_string module and lead to a heavy component, with a large number of lines and a small number of features, because all the time was lost in the management of such an heavy module. The other drawback is that is breaks the object oriented design so that the "type bound" procedure of F2003 cannot be used. The current choice is to focus mainly on the services provided, not the ease of use. That allows to provide much more features than in the original component, but complicates a little more the use in the client code. The choice done here is that the first argument is allways of type vstring (and called "this"), while the second argument (if any), mays by either of type vstring or of type "character (len=*). That solution allows to keep both consistency and ease of use at the maximum possible level. Several methods are designed this way, for example, vstring_equals, vstring_compare, vstring_append, vstring_concat and others.

Allocatable or pointer

Two implementation of m_vstring are provided, depending on the compiler used :

If none of the macros are defined, the default implementation is _VSTRING_ALLOCATABLE. The two implementations provide exactly the same services. But the "allocatable" implementation allows to manage the vstring which are going out of the current scope so that the use of vstring_free is not necessary and memory leaks do not occur. Instead, with the pointer implementation, the call to vstring_free is strictly necessary (if not, memory is lost each time a new vstring is created). The current version of m_vstring has been tested with the following compilers and versions:

Dynamic or static buffer

The internal algorithms provided by m_vstrings are based on basic fortran character strings. In several situations, the dynamic vstring has to be converted into a basic fortran character buffer string, which size has to be given explicitely in the source code, with the len = <something> statement (in the character ( len = <something>) ). Two solutions are provided, and the user can define the pre-processing macro _VSTRING_STATIC_BUFFER to configure that :

If the _VSTRING_STATIC_BUFFER is defined, then character strings of constant size are used as buffers. If the _VSTRING_STATIC_BUFFER is not defined (which is the default), then character strings of dynamic size are used as buffers. The second solution is more efficient, because the strings are not oversized or undersized, depending on the real number of characters in the dynamic string. But the feature may not be provided by the compiler at hand. For example, problems with the dynamic length character string have been experienced with Intel Fortran 8.

Design

This component has been designed with OO principles in mind. This is why the first argument of every method is named "this", which is the current object. If another string is required as a second argument, it may be either of type dynamic or as a character(len=*) type, to improve usability. This component is meant to evolve following the fortran 2003 standard and OO type-bound procedures.

Limitations

Preprocessing

The following preprocessing macro must be considered :

History

This module was originally based on the iso_varying_string.f90 module by Rich Townsend.

METHODS

In the following definitions, the this argument has, depending on the method, one of the the following definitions :

type ( t_vstring ) , intent(inout) :: this
type ( t_vstring ) , intent(in) :: this
The "intent(in)" or "intent(inout)" declaration depends on the method and is what it is expected to be (if not, it is a bug).
vstring_new (this ?args?)
Generic constructor. Creates the new vstring "this".

vstring_new (this)
Creates a vstring with 0 characters and 0 length.

vstring_new (this,char_string)
character(LEN=*), intent(in) :: char_string
The new vstring is filled with the characters found in "char_string".

vstring_new (this,vstring)
type ( t_vstring ) , intent(in) :: vstring
The new vstring is filled with the characters found in the dynamic string "vstring".

vstring_new (this,chararray)
character(len=1), dimension(:), intent(in) :: chararray
The new vstring is filled with the characters found in the array of characters "chararray".

vstring_new (this, ncount ?, vstring?)
integer, intent(in) :: ncount
type ( t_vstring ) , intent(in), optional :: vstring
Repeat the string ncount times and concatenate the result to create the new string. If not provided, the default string is the blank space. This can be considered as an implementation of "vstring_repeat".

vstring_free (this)
Destructor.

The use of the destructor is OPTIONAL. See the thread " New ISO_VARYING_STRING implementation (without memory leaks)" on comp.lang.fortran : "On most systems, memory is memory :-). However, there is a difference between how ALLOCATABLE variables and POINTER variables are handled. ALLOCATABLE variables are always deallocated automatically when thay go out of scope (unless they have the SAVE attribute). POINTER variables usually are not. The reason is that the program may have associated additional pointers, that aren't going out of scope, with the same target as the one that is."

vstring_exists (this) result ( exists )
logical :: exists
Returns .true. if the string is allocated.

vstring_equals (this, stringb ?, nocase,length?) result (equals)
type ( t_vstring ) , intent(in) :: stringb
logical , intent (in), optional :: nocase
integer , intent (in), optional :: length
logical :: equals
Perform a character-by-character comparison of strings this and string2. Returns true if this and stringb are identical, or .false when not. If nocase is set to true, the case of the characters is not taken into account. The default behaviour is to take into account for case of characters. If length is specified, then only the first length characters are used in the comparison.

vstring_equals (this, string2 ?, nocase,length?) result (equals)
character(len=*), intent(in) :: string2
logical , intent (in), optional :: nocase
integer , intent (in), optional :: length
logical :: equals
Same as previous but with string2 as a character string.

vstring_cast (this ?, args?)
Convert a dynamic string into another fortran data type.

vstring_cast (this , length , charstring)
integer, intent(in) :: length
character ( LEN = length ) , intent(out) :: charstring
Convert a dynamic string into a character string with fixed length. If the number of characters in the target charstring is not large enough, the target charstring is truncated, that is, contains only the first characters of the current dynamic string.

vstring_cast (this , charstring)
character ( LEN = * ) , intent(out) :: charstring
Convert a dynamic string into a character string with automatic length. If the number of characters in the target charstring is not large enough, the target charstring is truncated, that is, contains only the first characters of the current dynamic string.

vstring_cast (this , value)
integer, intent(out) :: value
Returns the integer stored in the current string.

vstring_cast (this , value)
real, intent(out) :: value
Returns the real stored in the current string.

vstring_cast (this , value)
double precision, intent(out) :: value
Returns the double precision stored in the current string.

vstring_length (this) result ( length )
integer :: length
Returns the length of the current dynamic string.

vstring_concat (this,string2) result ( concat_string )
type ( t_vstring ) , intent(in) :: string2
type ( t_vstring ) :: concat_string
Returns a new dynamic string made by the concatenation of two dynamic strings.

vstring_concat (this,string2) result ( concat_string )
character(len=*) , intent(in) :: string2
type ( t_vstring ) :: concat_string
Returns a new string made by the concatenation of the current dynamic string and the given character(len=*) string.

vstring_append (this,string2)
type ( t_vstring ) , intent(in) :: string2
Append the given string at the end of the current string. If the given string string2 is of length greater than zero, that means that the length of the current string will be greater after the call to vstring_append. Note : that method can be called as a convenient alternative to vstring_concat, when the concat is to be done "in place".

vstring_append (this,string2)
character(len=*) , intent(in) :: string2
Same as previous, but with a character(len=*) string2.

vstring_map (this , map_old , map_new ?, nocase?) result ( stringmap )
type ( t_vstring ) , dimension( : ), intent(in) :: map_old
type ( t_vstring ) , dimension( : ), intent(in) :: map_new
logical, intent(in), optional :: nocase
type(t_vstring) :: stringmap
Replaces substrings in string based on the mapping defined by the couple (map_old , map_new). map_old and map_new are arrays of vstrings and are of the same size so that if imap is an index no greater than the size of map_old, map_old ( imap ) is the old string and map_new ( imap ) is the new string. Each instance of a key in the string will be replaced with its corresponding value. Both old and new strings may be multiple characters. If nocase is set to .true., then matching is done without regard to case differences. Replacement is done in an ordered manner, so the old string appearing first in the list will be checked first, and so on. The current string is only iterated over once, so earlier replacements will have no affect for later matches. For example,

 
  vstring_map 1abcaababcabababc [abc,ab,a,1] [1,2,3,0]

will return the string 01321221. Note that if an earlier key is a prefix of a later one, it will completely mask the later one. So if the previous example is reordered like this,

 
  vstring_map 1abcaababcabababc [1,ab,a,abc] [0,2,3,1]

it will return the string 02c322c222c.

vstring_replace (this , first , last ?, newstring?) result ( stringreplace )
integer, intent(in) :: first
integer, intent(in) :: last
type ( t_vstring ) , intent(in), optional :: newstring
type ( t_vstring ) :: stringreplace
Removes a range of consecutive characters from string, starting with the character whose index is first and ending with the character whose index is last. An index of 1 refers to the first character of the string. If newstring is specified, then it is placed in the removed character range.

vstring_compare (this,string2?, nocase , length?) result ( compare )
type ( t_vstring ) , intent(in) :: string2
logical , intent (in), optional :: nocase
integer , intent (in), optional :: length
integer :: compare
Perform a character-by-character comparison of strings this and string2. Returns -1, 0, or 1, depending on whether this is lexicographically less than, equal to, or greater than string2. If nocase is set to true, the case of the characters is not taken into account. The default behaviour is to take into account for case of characters. If length is specified, then only the first length characters are used in the comparison.

vstring_compare (this,string2?, nocase , length?) result ( compare )
character (len=*) , intent(in) :: string2
logical , intent (in), optional :: nocase
integer , intent (in), optional :: length
integer :: compare
Same as previous, but with a character(len=*) string2.

vstring_trim (this ?, chars?) result ( trim_string )
type ( t_vstring ) , intent(in), optional :: chars
type ( t_vstring ) :: trim_string
Returns a new string except that any leading or trailing characters from the set given by chars are removed. If chars is not specified then white space is removed (spaces, tabs, newlines, and carriage returns).

vstring_trim (this ?, chars?) result ( trim_string )
character (len=*) , intent(in), optional :: chars
type ( t_vstring ) :: trim_string
Same as previous, but with a character(len=*) string "chars".

vstring_trimleft (this ?, chars?) result ( trim_string )
type ( t_vstring ) , intent(in), optional :: chars
type ( t_vstring ) :: trim_string
Returns a new string except that any leading characters from the set given by chars are removed. If chars is not specified then white space is removed (spaces, tabs, newlines, and carriage returns).

vstring_trimleft (this ?, chars?) result ( trim_string )
character (len=*) , intent(in), optional :: chars
type ( t_vstring ) :: trim_string
Same as previous, but with a character(len=*) string "chars".

vstring_trimright (this ?, chars?) result ( trim_string )
type ( t_vstring ) , intent(in), optional :: chars
type ( t_vstring ) :: trim_string
Returns a value equal to string except that any trailing characters from the set given by chars are removed. If chars is not specified then white space is removed (spaces, tabs, newlines, and carriage returns).

vstring_trimright (this ?, chars?) result ( trim_string )
character (len=*) , intent(in), optional :: chars
type ( t_vstring ) :: trim_string
Same as previous, but with a character(len=*) string "chars".

vstring_first (this ?, string2 , first?) result ( firstIndex )
type ( t_vstring ) , intent(in) :: string2
integer, intent(in), optional :: first
integer :: firstIndex
Search in the current string for a sequence of characters that exactly match the characters in string2. If found, return the index of the first character in the first such match within the current string. If not found, return 0. If first is specified, then the search is constrained to start with the character in the current string specified by the index.

vstring_first (this ?, string2 , first?) result ( firstIndex )
character(len=*) , intent(in) :: string2
integer, intent(in), optional :: first
integer :: firstIndex
Same as previous, but with a character(len=*) string "string2".

vstring_last (this ?, string2 , last?) result ( lastIndex )
type ( t_vstring ) , intent(in) :: string2
integer, intent(in), optional :: last
integer :: lastIndex
Search in the current string for a sequence of characters that exactly match the characters in string2. If found, return the index of the last character in the first such match within the current string. If not found, return 0. If last is specified, then the search is constrained to start with the character in the current string specified by the index.

vstring_last (this ?, string2 , last?) result ( lastIndex )
character(len=*) , intent(in) :: string2
integer, intent(in), optional :: last
integer :: lastIndex
Same as previous, but with a character(len=*) string "string2".

vstring_range (this, first, last) result ( string_range )
integer, intent(in) :: first
integer, intent(in) :: last
type ( t_vstring ) :: string_range
Returns a range of consecutive characters from string, starting with the character whose index is first and ending with the character whose index is last. An index of 1 refers to the first character of the string. If first is less than 1 then an error is generated. If last is greater than or equal to the length of the string then an error is generated. If first is greater than last then an error is generated.

vstring_index (this , charIndex ) result ( string_index )
integer, intent(in) :: charIndex
type ( t_vstring ) :: string_index
Returns the charIndex'th character of the string argument. A charIndex of 1 corresponds to the first character of the string. If charIndex is less than 1 or greater than or equal to the length of the string then an error is generated.

vstring_toupper (this?, first??, last?) result ( new_upper )
integer, intent(in) :: first
integer, intent(in) :: last
type ( t_vstring ) :: new_upper
Returns a vstring except that all lower (or title) case letters have been converted to upper case. If first is specified, it refers to the first char index in the string to start modifying. If last is specified, it refers to the char index in the string to stop at (inclusive).

vstring_tolower (this?, first??, last?) result ( new_lower )
integer, intent(in) :: first
integer, intent(in) :: last
type ( t_vstring ) :: new_lower
Returns a vstring except that all upper (or title) case letters have been converted to lower case. If first is specified, it refers to the first char index in the string to start modifying. If last is specified, it refers to the char index in the string to stop at (inclusive).

vstring_totitle (this?, first??, last?) result ( new_title )
integer, intent(in) :: first
integer, intent(in) :: last
type ( t_vstring ) :: new_title
Returns a vstring except that the first character in string is converted to upper case, and the rest of the string is converted to lower case. If first is specified, it refers to the first char index in the string to start modifying. If last is specified, it refers to the char index in the string to stop at (inclusive).

vstring_reverse (this) result ( new_reverse )
type ( t_vstring ) :: new_reverse
Return a string that has all characters in reverse order.

vstring_random (this) result ( new_random )
integer, intent(in) :: length
type ( t_vstring ) :: new_random
Fill a string with required length and randomized characters.

vstring_match (this , pattern ?, nocase?) result ( match )
type ( t_vstring ) , intent(in) :: pattern
logical , intent(in) , optional :: nocase
logical :: match
See if pattern matches string; return 1 if it does, 0 if it doesn't. If nocase is specified and true, then the pattern attempts to match against the string in a case insensitive manner. For the two strings to match, their contents must be identical except that the following special sequences may appear in pattern:

The following example is extracted from the unit tests provided with flibs. In the following example, "match" is true.

 
    type ( t_vstring ) :: string1
    logical :: match
    call vstring_new ( string1 , "m_vstring.f90" )
    match = vstring_match ( string1 , "m_*.f90" )
    call vstring_free ( string1 )



vstring_match (this , pattern ?, nocase?) result ( match )
character(len=*) , intent(in) :: pattern
logical , intent(in) , optional :: nocase
logical :: match
Same as previous, with character(len=*) as pattern.

vstring_is (this , class ?, strict? ?, failindex?) result ( isinclass )
character(len=*) , intent(in) :: class
logical, intent(in) , optional :: strict
integer, intent(out) , optional :: failindex
logical :: isinclass
Returns .true. if string is a valid member of the specified character class, otherwise returns .false.. If strict is provided and .true., then an empty string returns .false.. If strict is provided and .false., or not provided, an empty string returns .true.. If failindex is provided, then if the function returns .false., the index in the string where the class was no longer valid will be stored in the variable failindex. The following character classes are recognized (the class name can be abbreviated):

INTERFACE TO STANDARD FORTRAN

These methods or routines are simply an interface of standard fortran string processing routines.

vstring_iachar (this) result ( new_iachar )
integer :: new_iachar
Returns the code for the ASCII character in the character position of C. Example : If this is "@", then iachar is 64.

vstring_ichar (this) result ( new_ichar )
integer :: new_ichar
Returns the code for the character in the first character position of in the systemâs native character set. This is an interface to the standard fortran.

vstring_charindex (this , substring ?, back?) result ( charindex )
type ( t_vstring ) , intent(in) :: substring
logical, intent(in), optional :: back
integer :: charindex
Returns the position of the start of the first occurrence of string substring as a sub-string in the current string, counting from one. If substring is not present in the current string, zero is returned. If the back argument is present and true, the return value is the start of the last occurrence rather than the first. Note: this is a simple interface to the standard fortran intrinsic "index".

vstring_scan (this, substring?, back?) result ( charindex )
type ( t_vstring ) , intent(in) :: substring
logical, intent(in), optional :: back
integer :: charindex
Returns the position of a character of the current string that is in set, or zero if there is no such character. If the logical back is absent or present with value false, the position of the leftmost such character is returned. If back is present with value true, the position of the rightmost such character is returned. Note: this is a simple interface to the standard fortran intrinsic "index".

vstring_verify (this, substring?, back?) result ( charindex )
type ( t_vstring ) , intent(in) :: substring
logical, intent(in), optional :: back
integer :: charindex
Returns the default integer value 0 if each character in the current string appears in set, or the position of a character of the current string that is not in set. If the logical back is absent or present with value false, the position of the left-most such character is returned. If back is present with value true, the position of the rightmost such character is returned. Note: this is a simple interface to the standard fortran intrinsic "verify".

vstring_adjustl (this) result ( newstring )
type ( t_vstring ) :: newstring
Adjusts left to return a string of the same length by removing all leading blanks and inserting the same number of trailing blanks. Note : this is a simple interface to the standard fortran intrinsic "adjustl".

vstring_adjustr (this) result ( newstring )
type ( t_vstring ) :: newstring
Adjusts right to return a string of the same length by removing all trailing blanks and inserting the same number of leading blanks. Note: this is a simple interface to the standard fortran intrinsic "adjustr".

STATIC METHODS

vstring_achar (i) result ( new_achar )
integer, intent (in) :: i
type ( t_vstring ) :: new_achar
Returns the character located at position I in the ASCII collating sequence. This is an interface to the standard fortran.

vstring_char (i) result ( new_achar )
integer, intent (in) :: i
type ( t_vstring ) :: new_achar
Returns the character represented by the integer I. This is an interface to the standard fortran. Example : If i is 64, then char is "@".

TODO

COPYRIGHT

Copyright © 2008 Michael Baudin michael.baudin@gmail.com
Copyright © 2008 Arjen Markus arjenmarkus@sourceforge.net