!###############################################################################
!
! NAME
!   GO_String - general objects for character strings
!
! PROCEDURES
!
!   call goSplitLine( 'ab#cd', s1, '#', s2, status )
!
!     Splits a string like 'ab#cd' at the first '#', and returns
!     the leading part in s1, and the rest in s2.
!     One or both of s1 and s2 might be empty.
!
!   call goReadFromLine( line, x, status [,sep=','] [,default=value] )
!    
!     Splits the string "line" at the first komma
!     (or the character specified by the optional argument "sep"), 
!     fills the integer|real|logical|character variable "x" with the
!     leading part, and returns the remainder in "line".
!     If the leading part is empty, the default is returned if presend
!     or otherwise an error is raised.
!     
!   call goSplitString( 'aa c', n, values, status )
!
!     Split the input string at white spaces and return fields:
!       n      : integer, number of fields extracted
!       values : character or real array to store fields;
!                error status returned if size or lengths are not sufficient
!   
!   call MatchValue( 'aa' , (/'aa ','bbb','c  ','ddd'/), ind, status [,quiet=.false.] )
!
!     Compare character value with values in character list,
!     return index of matching element.
!     Negative status if not found.
!     Print error messages unless quiet is true.
!     Case independent.
!
!   call MatchValues( 'aa c' , (/'aa ','bbb','c  ','ddd'/), n, values, inds, status [,quiet=.false.] )
!   call MatchValues( '0 1 2', 1, 10                      , n, values      , status [,quiet=.false.] )
!
!     Read the values from the input line and compare with a
!     list of character values or a range of integers. Output:
!       n       : integer, number of values in list (and output arrays)
!       values  : array with found values, same type as input list
!       inds    : integer indices in list with possible values
!
!
!   bb = 'default'
!   call goVarValue( 'aa=1;bb=xyz;cc=U123', ';', 'bb', '=', bb, status )
!
!     Read value from a line with multiple <name><is><value> triples,
!     seperated by the specified character.
!     If multiple matching values are found, the last one is returned.
!     Return status:
!       <0  :  variable not found, val remains the same
!        0  :  variable(s) found, val reset; 
!       >0  :  error
!
!   s = goNum2Str( i [,fmt='(i6)'] )
!
!     Returns a 6-character string with the representation of the
!     integer value i in the first characters.
!     Use 
!       trim(gonum2str(i))
!     to obtain a string of smallest size.
!
!   s2 = goUpCase( s1 )
!   s2 = goLoCase( s1 )
!
!     Convert to upper or lower case
!
!   call goTab2Space( s )
!
!     Replaces each tab-character in s by a space.
!
!   call goReplace( line, key, s, status )
!   call goReplace( line, key, fmt, i, status )
!   call goReplace( line, key, fmt, r, status )
!
!     Replace all instances of the key in the line by the 
!     character replacement s, or by a formatted integer or real value.
!
!   call goTranslate( line, chars, repl, status )
!
!     Replace all instances of the characters in 'chars' by 'repl' .
!     Example:
!       s = '2000-01-02 03:04:05'
!       print *, 'before translation : "'//trim(s)//'"'
!       call goTranslate( s, '-:', ' ', status )
!       print *, 'after  translation : "'//trim(s)//'"'
!     will show:
!       before translation : "2000-01-02 03:04:05"
!       after  translation : "2000 01 02 03 04 05"
!    
!
!### macro's #####################################################
!
#define TRACEBACK write (gol,'("in ",a," (",a,i6,")")') rname, __FILE__, __LINE__ ; call goErr
!
#define IF_NOTOK_RETURN(action) if (status/=0) then; TRACEBACK; action; return; end if
#define IF_ERROR_RETURN(action) if (status> 0) then; TRACEBACK; action; return; end if
!
!#################################################################

module GO_String

  use GO_Print, only : gol, goPr, goErr

  implicit none

  ! --- in/out -----------------------------

  private

  public  ::  goSplitLine
  public  ::  goReadFromLine
  public  ::  goSplitString
  public  ::  goMatchValue
  public  ::  goMatchValues
  public  ::  goVarValue
  public  ::  goNum2str
  public  ::  goUpCase, goLoCase
  public  ::  goWriteKeyNum
  public  ::  goTab2Space
  public  ::  goReplace, goTranslate


  ! --- const ---------------------------------
  
  character(len=*), parameter  ::  mname = 'GO_String'

  
  ! --- interfaces -------------------------------------

  interface goReadFromLine
    module procedure ReadFromLine_i
    module procedure ReadFromLine_r
    module procedure ReadFromLine_l
    module procedure ReadFromLine_s
  end interface

  interface goSplitString
     module procedure goSplitString_s
     module procedure goSplitString_r
  end interface
  
  interface goMatchValue
    module procedure MatchValue_s
  end interface

  interface goMatchValues
    module procedure MatchValues_s
    module procedure MatchValues_i
  end interface

  interface goVarValue
    module procedure goVarValue_s
    module procedure goVarValue_i
    module procedure goVarValue_r
    module procedure goVarValue_l
  end interface

  interface goNum2str
    module procedure num2str_i
    module procedure num2str_r
  end interface

  interface goUpCase
    module procedure UpCase
  end interface

  interface goLoCase
    module procedure LoCase
  end interface

  interface goWriteKeyNum
    module procedure WriteKeyNum
  end interface

  interface goReplace
    module procedure goReplace_s
    module procedure goReplace_i
  end interface


contains


  !**********************************************************************


  subroutine goSplitLine( line, s1, c, s2, status )

    ! --- in/out ----------------------------

    character(len=*), intent(in)      ::  line
    character(len=*), intent(out)     ::  s1
    character(len=1), intent(in)      ::  c
    character(len=*), intent(out)     ::  s2
    integer, intent(out)              ::  status

    ! --- local -----------------------------

    integer                     ::  l, pos
    character(len=len(line))    ::  s

    ! --- begin -----------------------------

    s = line
    l = len_trim(s)

    pos = scan(s,c)
    if ( (pos<1) .or. (pos>l) ) then
      ! s='abcd'  -> s1='abcd', s2=''
      !call AdjustLeft( s1, s(1:l) )
      s1 = AdjustL( s(1:l) )
      s2 = ''
    else if (pos==1) then
      ! s=',' or s=',abcd'  ->  s1='', s2='' or 'abcd'
      s1 = ''
      if (l==1) then
        ! s=','
        s2 = ''
      else
        !call AdjustLeft( s2, s(pos+1:l) )
        s2 = AdjustL( s(pos+1:l) )
      end if
    else
      ! s='ab,' or s='ab,cd'
      !call AdjustLeft( s1, s(1:pos-1) )
      s1 = AdjustL( s(1:pos-1) )
      if (pos==l) then
        ! s='ab,'
        s2 = ''
      else
        ! s='ab,cd'
        !call AdjustLeft( s2, s(pos+1:l) )
        s2 = AdjustL( s(pos+1:l) )
      end if
    end if
    
    ! ok
    status = 0

  end subroutine goSplitLine


  ! ***


!  subroutine AdjustLeft( t, s )
!
!    ! --- in/out ----------------------
!
!    character(len=*), intent(out)   ::  t
!    character(len=*), intent(in)    ::  s
!
!    ! --- local -----------------------
!
!    integer     ::  is,ls, lt
!
!    ! --- local -----------------------
!
!    lt = len(t)
!
!    ls = len_trim(s)
!    if (ls==0) then
!      t = ''
!    else
!      is = 0
!      do
!        is = is + 1
!        if (s(is:is)/=' ') exit
!        if (is==ls) exit
!      end do
!      if (ls-is+1 > lt) then
!        print *, 'AdjustLeft : error : target is to small ', &
!           '(',lt,') to contain "'//s//'".'
!        stop
!      end if
!      t = s(is:ls)
!    end if
!
! end subroutine AdjustLeft


 ! *****************************************************


  subroutine ReadFromLine_i( s, i, status, sep, default )

    ! --- in/out --------------------------

    character(len=*), intent(inout)         ::  s
    integer, intent(inout)                  ::  i
    integer, intent(out)                    ::  status
    
    character(len=1), intent(in), optional  ::  sep
    integer, intent(in), optional           ::  default

    ! --- const ----------------------------
    
    character(len=*), parameter  ::  rname = mname//'/ReadFromLine_i'
    
    ! --- local ----------------------------

    character(len=len(s))     ::  s1, s2
    character(len=1)          ::  thesep

    ! --- begin ----------------------------
    
    ! default seperation character provided as argument:
    thesep = ','
    if (present(sep)) thesep = sep

    ! split at seperation character:
    call goSplitLine( s, s1, thesep, s2, status )
    IF_ERROR_RETURN(status=1)

    ! empty leading part ?
    if ( len_trim(s1) == 0 ) then
      ! default provided ?
      if ( present(default) ) then
        i = default
      else
        write (gol,'("found empty leading part while no default specified:")'); call goErr
        write (gol,'("  line   : `",a,"`")') trim(s); call goErr
        write (gol,'("  sep    : `",a,"`")') trim(thesep); call goErr
        TRACEBACK; status=1; return
      end if
    else
      ! read from leading part:
      read (s1,*,iostat=status) i
      if ( status /= 0 ) then
        write (gol,'(a," while reading integer out of `",a,"`")') trim(s); call goErr
        write (gol,'("in ",a)') rname; call goErr; status=1; return
      end if
    end if
   
    ! return remainder:
    s = s2
    
    ! ok
    status = 0

  end subroutine ReadFromLine_i


  ! ***


  subroutine ReadFromLine_r( s, r, status, sep, default )

    ! --- in/out --------------------------

    character(len=*), intent(inout)         ::  s
    real, intent(out)                       ::  r
    integer, intent(out)                    ::  status

    character(len=1), intent(in), optional  ::  sep
    real, intent(in), optional              ::  default

    ! --- const ------------------------------
    
    character(len=*), parameter  ::  rname = mname//'/ReadFromLine_r'
    
    ! --- local ----------------------------

    character(len=len(s))     ::  s1, s2
    character(len=1)          ::  thesep

    ! --- begin ----------------------------

    ! default seperation character provided as argument:
    thesep = ','
    if (present(sep)) thesep = sep

    ! split at seperation character:
    call goSplitLine( s, s1, thesep, s2, status )
    IF_ERROR_RETURN(status=1)

    ! empty leading part ?
    if ( len_trim(s1) == 0 ) then
      ! default provided ?
      if ( present(default) ) then
        r = default
      else
        write (gol,'("found empty leading part while no default specified:")'); call goErr
        write (gol,'("  line   : `",a,"`")') trim(s); call goErr
        write (gol,'("  sep    : `",a,"`")') trim(thesep); call goErr
        TRACEBACK; status=1; return
      end if
    else
      ! read from leading part:
      read (s1,*,iostat=status) r
      if ( status /= 0 ) then
        write (gol,'("error while reading real out `",a,"`")') trim(s); call goErr
        write (gol,'("in ",a)') rname; call goErr; status=1; return
      end if
    end if

    ! return remainder:
    s = s2

    ! ok
    status = 0

  end subroutine ReadFromLine_r


  ! ***


  subroutine ReadFromLine_l( s, l, status, sep, default )

    ! --- in/out --------------------------

    character(len=*), intent(inout)         ::  s
    logical, intent(out)                    ::  l
    integer, intent(out)                    ::  status

    character(len=1), intent(in), optional  ::  sep
    logical, intent(in), optional           ::  default

    ! --- const ------------------------------
    
    character(len=*), parameter  ::  rname = mname//'/ReadFromLine_l'
    
    ! --- local ----------------------------

    character(len=len(s))     ::  s1, s2
    character(len=1)          ::  thesep

    ! --- begin ----------------------------

    ! default seperation character provided as argument:
    thesep = ','
    if (present(sep)) thesep = sep

    ! split at seperation character:
    call goSplitLine( s, s1, thesep, s2, status )
    IF_ERROR_RETURN(status=1)

    ! empty leading part ?
    if ( len_trim(s1) == 0 ) then
      ! default provided ?
      if ( present(default) ) then
        l = default
      else
        write (gol,'("found empty leading part while no default specified:")'); call goErr
        write (gol,'("  line   : `",a,"`")') trim(s); call goErr
        write (gol,'("  sep    : `",a,"`")') trim(thesep); call goErr
        TRACEBACK; status=1; return
      end if
    else
      ! read from leading part:
      read (s1,*,iostat=status) l
      if ( status /= 0 ) then
        write (gol,'("while reading logical out `",a,"`")') trim(s); call goErr
        write (gol,'("in ",a)') rname; call goErr; status=1; return
      end if
    end if

    ! return remainder:
    s = s2

    ! ok
    status = 0

  end subroutine ReadFromLine_l


  ! ***


  subroutine ReadFromLine_s( s, ss, status, sep )

    ! --- in/out --------------------------

    character(len=*), intent(inout)         ::  s
    character(len=*), intent(out)           ::  ss
    integer, intent(out)                    ::  status

    character(len=1), intent(in), optional  ::  sep

    ! --- const ------------------------------
    
    character(len=*), parameter  ::  rname = mname//'/ReadFromLine_s'
    
    ! --- local ----------------------------

    character(len=len(s))     ::  s1, s2
    character(len=1)          ::  thesep
    integer                   ::  l, ll

    ! --- begin ----------------------------

    ! default seperation character provided as argument:
    thesep = ','
    if (present(sep)) thesep = sep

    ! split at seperation character:
    call goSplitLine( s, s1, thesep, s2, status )
    IF_ERROR_RETURN(status=1)
    
    ! check storage:
    l = len_trim(s1)
    ll = len(ss)
    if ( ll < l ) then
      write (gol,'("size of output string not sufficient:")'); call goErr
      write (gol,'("  first part of input : ",a )') trim(s1) ; call goErr
      write (gol,'("  output length       : ",i4)') ll       ; call goErr
      write (gol,'("in ",a)') rname; call goErr; status=1; return
    end if
    ! store:
    ss = trim(s1)

    ! return remainder:
    s = s2

    ! ok
    status = 0

  end subroutine ReadFromLine_s


  ! *****************************************************


  subroutine goSplitString_s( line, n, values, status, sep )
  
    ! --- in/out --------------------------------
    
    character(len=*), intent(in)            ::  line
    integer, intent(out)                    ::  n
    character(len=*), intent(out)           ::  values(:)
    integer, intent(out)                    ::  status
    character(len=1), intent(in), optional  ::  sep
    
    ! --- const ----------------------------
    
    character(len=*), parameter   ::  rname = mname//'/goSplitString'
    
    ! --- local ---------------------------------
    
    character(len=1)            ::  the_sep
    character(len=len(line))    ::  line_curr
    character(len=len(line))    ::  val

    ! --- begin ---------------------------------
    
    ! seperation character:
    the_sep = ' '
    if ( present(sep) ) the_sep = sep
    
    ! copy input:
    line_curr = line
    
    ! no parts extracted yet:
    n = 0

    ! loop until all elements in line_curr are processed:
    do
      ! empty ? then finished:
      if ( len_trim(line_curr) == 0 ) exit
      ! next number:
      n = n + 1
      ! storage problem ?
      if ( n > size(values) ) then
        write (gol,'("output array is too small:")'); call goErr
        write (gol,'("  input line   : ",a )') trim(line); call goErr
        write (gol,'("  size(values) : ",i4)') size(values); call goErr
        TRACEBACK; status=1; return
      end if
      ! extract leading name:
      call goReadFromLine( line_curr, val, status, sep=the_sep )
      IF_NOTOK_RETURN(status=1)
      ! store value in output:
      values(n) = val
    end do
    
    ! ok
    status = 0
    
  end subroutine goSplitString_s
  
  
  ! *****************************************************
  
  
  subroutine goSplitString_r( line, n, values, status, sep )

    ! --- in/out --------------------------------

    character(len=*), intent(in)            ::  line
    integer, intent(out)                    ::  n
    real, intent(out)                       ::  values(:)
    integer, intent(out)                    ::  status
    character(len=1), intent(in), optional  ::  sep

    ! --- const ----------------------------

    character(len=*), parameter   ::  rname = mname//'/goSplitString'

    ! --- local ---------------------------------

    character(len=1)            ::  the_sep
    character(len=len(line))    ::  line_curr
    real                        ::  val

    ! --- begin ---------------------------------

    ! seperation character:
    the_sep = ' '
    if ( present(sep) ) the_sep = sep

    ! copy input:
    line_curr = line

    ! no parts extracted yet:
    n = 0

    ! loop until all elements in line_curr are processed:
    do
       ! empty ? then finished:
       if ( len_trim(line_curr) == 0 ) exit
       ! next number:
       n = n + 1
       ! storage problem ?
       if ( n > size(values) ) then
          write (gol,'("output array is too small:")'); call goErr
          write (gol,'("  input line   : ",a )') trim(line); call goErr
          write (gol,'("  size(values) : ",i4)') size(values); call goErr
          TRACEBACK; status=1; return
       end if
       ! extract leading name:
       call goReadFromLine( line_curr, val, status, sep=the_sep )
       IF_NOTOK_RETURN(status=1)
       ! store value in output:
       values(n) = val
    end do

    ! ok
    status = 0

  end subroutine goSplitString_r

  ! *******************************************************************
  

  subroutine MatchValue_s( val, list, ind, status, quiet )
  
    ! --- in/out --------------------------------
    
    character(len=*), intent(in)          ::  val
    character(len=*), intent(in)          ::  list(:)
    integer, intent(out)                  ::  ind
    integer, intent(out)                  ::  status
    
    logical, optional                     ::  quiet
    
    ! --- const ----------------------------
    
    character(len=*), parameter   ::  rname = mname//'/MatchValue_s'
    
    ! --- local ---------------------------------
    
    integer                     ::  nlist
    integer                     ::  i
    logical                     ::  verbose

    ! --- begin ---------------------------------
    
    ! shut up ?
    verbose = .true.
    if ( present(quiet) ) verbose = .not. quiet
    
    ! number of items in value list:
    nlist = size(list)
    
    ! search for this name in the global list:
    ind = -1
    do i = 1, nlist
      ! case indendent match ?
      if ( goUpCase(trim(val)) == goUpCase(trim(list(i))) ) then
        ! store index:
        ind = i
        ! do not search any further:
        exit
      end if
    end do
    ! not found ?
    if ( ind < 0 ) then
      if ( verbose ) then
        write (gol,'("name not supported:")'); call goErr
        write (gol,'("  value           : ",a )') trim(val); call goErr
        write (gol,'("  possible values : ")'); call goErr
        do i = 1, nlist
          write (gol,'("    ",i4," ",a)') i, trim(list(i)); call goErr
        end do
        TRACEBACK
      end if
      status=-1; return
    end if

    ! ok
    status = 0
    
  end subroutine MatchValue_s
  
  
  ! ***
  

  subroutine MatchValues_s( line, list, &
                              n, values, inds, &
                              status, quiet )
  
    ! --- in/out --------------------------------
    
    character(len=*), intent(in)          ::  line
    character(len=*), intent(in)          ::  list(:)
    integer, intent(out)                  ::  n
    character(len=*), intent(out)         ::  values(:)
    integer, intent(out)                  ::  inds(:)
    integer, intent(out)                  ::  status
    
    logical, optional                     ::  quiet
    
    ! --- const ----------------------------
    
    character(len=*), parameter   ::  rname = mname//'/MatchValues_s'
    
    ! --- local ---------------------------------
    
    integer                     ::  nlist
    character(len=len(line))    ::  line_curr
    character(len=16)           ::  val
    integer                     ::  ind
    logical                     ::  verbose

    ! --- begin ---------------------------------
    
    ! shut up ?
    verbose = .true.
    if ( present(quiet) ) verbose = .not. quiet
    
    ! nuber of items in value list:
    nlist = size(list)
    
    ! copy input:
    line_curr = line
    
    ! no matching list yet:
    n = 0

    ! loop until all elements in line_curr are processed:
    do
      ! empty ? then finished:
      if ( len_trim(line_curr) == 0 ) exit
      ! next number:
      n = n + 1
      ! storage problem ?
      if ( (n > size(values)) .or. (n > size(inds)) ) then
        write (gol,'("output array is too small:")'); call goErr
        write (gol,'("  input line  : ",a )') trim(line); call goErr
        write (gol,'("  size(values) : ",i4)') size(values); call goErr
        write (gol,'("  size(inds  ) : ",i4)') size(inds  ); call goErr
        TRACEBACK; status=1; return
      end if
      ! extract leading name:
      call goReadFromLine( line_curr, val, status, sep=' ' )
      IF_NOTOK_RETURN(status=1)
      ! store value in output:
      values(n) = val
      ! search for this name in the global list:
      call goMatchValue( val, list, ind, status, quiet )
      ! not found ?
      if ( status /= 0 ) then
        if ( verbose ) then
          write (gol,'("unable to match value with list:")'); call goErr
          write (gol,'("  line            : ",a )') trim(line); call goErr
          write (gol,'("  line element    : ",i3)') n; call goErr
          write (gol,'("  line value      : ",a )') trim(val); call goErr
          TRACEBACK
        end if
        status=1; return
      end if
      ! store:
      inds(n) = ind
    end do
    
    ! empty ?
    if ( n < 1 ) then
      write (gol,'("no values extracted from line :")'); call goErr
      write (gol,'("  ",a)') trim(line); call goErr
      TRACEBACK; status=1; return
    end if
    
    ! ok
    status = 0
    
  end subroutine MatchValues_s
  
  
  ! ***
  

  subroutine MatchValues_i( line, i1, i2, &
                              n, values, &
                              status, quiet )
  
    ! --- in/out --------------------------------
    
    character(len=*), intent(in)          ::  line
    integer, intent(in)                   ::  i1, i2
    integer, intent(out)                  ::  n
    integer, intent(out)                  ::  values(:)
    integer, intent(out)                  ::  status
    
    logical, optional                     ::  quiet
    
    ! --- const ----------------------------
    
    character(len=*), parameter   ::  rname = mname//'/MatchValues_i'
    
    ! --- local ---------------------------------
    
    character(len=len(line))    ::  line_curr
    integer                     ::  val
    logical                     ::  verbose

    ! --- begin ---------------------------------
    
    ! shut up ?
    verbose = .true.
    if ( present(quiet) ) verbose = .not. quiet
    
    ! copy input:
    line_curr = line
    
    ! no matching list yet:
    n = 0

    ! loop until all elements in line_curr are processed:
    do
      ! empty ? then finished:
      if ( len_trim(line_curr) == 0 ) exit
      ! next number:
      n = n + 1
      ! storage problem ?
      if ( n > size(values) ) then
        write (gol,'("output arrays are too small:")'); call goErr
        write (gol,'("  input line  : ",a )') trim(line); call goErr
        write (gol,'("  size(values) : ",i4)') size(values); call goErr
        TRACEBACK; status=1; return
      end if
      ! extract leading name:
      call goReadFromLine( line_curr, val, status, sep=' ' )
      IF_NOTOK_RETURN(status=1)
      ! store value in output:
      values(n) = val
      ! out of range ?
      if ( (val < i1) .or. (val > i2) ) then
        if ( verbose ) then
          write (gol,'("value not in range:")'); call goErr
          write (gol,'("  list            : ",a )') trim(line); call goErr
          write (gol,'("  list element    : ",i3)') n; call goErr
          write (gol,'("  list value      : ",i3)') val; call goErr
          write (gol,'("  possible range  : ",i3," .. ",i3)') i1, i2; call goErr
          TRACEBACK
        end if
        status=1; return
      end if
    end do
    
    ! empty ?
    if ( n < 1 ) then
      write (gol,'("no values extracted from line :")'); call goErr
      write (gol,'("  ",a)') trim(line); call goErr
      TRACEBACK; status=1; return
    end if
    
    ! ok
    status = 0
    
  end subroutine MatchValues_i
  

  ! *****************************************************
  
  !
  !  Read value from line:
  !
  !    bb = 'default'
  !    call goVarValue( 'aa=1;bb=xyz;cc=U123', ';', 'bb', '=', bb, status )
  !
  !  Return status:
  !    <0  :  variable not found, val remains the same
  !     0  :  variable found, val reset; 
  !             for multiple matches, last value is returned
  !    >0  :  error
  !
  
  subroutine goVarValue_s( line, sep, var, is, val, status )
  
    use GO_Print, only : gol, goPr, goErr

    ! --- in/out ---------------------------------
    
    character(len=*), intent(in)     ::  line
    character(len=1), intent(in)     ::  sep
    character(len=*), intent(in)     ::  var
    character(len=1), intent(in)     ::  is
    character(len=*), intent(inout)  ::  val
    integer, intent(out)             ::  status
    
    ! --- const ------------------------------
    
    character(len=*), parameter  ::  rname = mname//'/goVarValue_s'
    
    ! --- local ----------------------------------
    
    character(len=len(line))    ::  line2
    character(len=len(line))    ::  varval
    character(len=16)           ::  var2
    character(len=256)          ::  val2
    
    ! --- begin ----------------------------------
    
    ! copy of input line:
    line2 = line
    
    ! default status: not found:
    status = -1

    ! loop over var=val keys :
    do
      ! no keys left ? then leave
      if ( len_trim(line2) == 0 ) exit
      ! remove leading var=value from line2 :
      call goReadFromLine( line2, varval, status, sep=sep )
      IF_ERROR_RETURN(status=1)
      ! split in var and value:
      call goSplitLine( varval, var2, is, val2, status )
      IF_ERROR_RETURN(status=1)
      ! keys match ?
      if ( trim(var2) == trim(var) ) then
        ! store in output (might overwrite previously stored value):
        val = trim(val2)
        ! set return status to 'found':
        status = 0
      end if
    end do

    ! ok, with status either -1 or 0 :
    return
    
  end subroutine goVarValue_s


  ! ***
      

  subroutine goVarValue_i( line, sep, var, is, val, status )
  
    use GO_Print, only : gol, goPr, goErr

    ! --- in/out ---------------------------------
    
    character(len=*), intent(in)     ::  line
    character(len=1), intent(in)     ::  sep
    character(len=*), intent(in)     ::  var
    character(len=1), intent(in)     ::  is
    integer, intent(inout)           ::  val
    integer, intent(out)             ::  status
    
    ! --- const ------------------------------
    
    character(len=*), parameter  ::  rname = mname//'/goVarValue_i'
    
    ! --- local ----------------------------------
    
    character(len=len(line))    ::  line2
    character(len=len(line))    ::  varval
    character(len=16)           ::  var2
    character(len=256)          ::  val2
    
    ! --- begin ----------------------------------
    
    ! copy of input line:
    line2 = line
    
    ! default status: not found:
    status = -1

    ! loop over var=val keys :
    do
      ! no keys left ? then leave
      if ( len_trim(line2) == 0 ) exit
      ! remove leading var=value from line2 :
      call goReadFromLine( line2, varval, status, sep=sep )
      IF_ERROR_RETURN(status=1)
      ! split in var and value:
      call goSplitLine( varval, var2, is, val2, status )
      IF_ERROR_RETURN(status=1)
      ! keys match ?
      if ( trim(var2) == trim(var) ) then
        ! store in output (might overwrite previously stored value):
        read (val2,'(i6)') val
        ! set return status to 'found':
        status = 0
      end if
    end do

    ! ok, with status either -1 or 0 :
    return

  end subroutine goVarValue_i
    

  ! ***
      

  subroutine goVarValue_r( line, sep, var, is, val, status )
  
    use GO_Print, only : gol, goPr, goErr

    ! --- in/out ---------------------------------
    
    character(len=*), intent(in)     ::  line
    character(len=1), intent(in)     ::  sep
    character(len=*), intent(in)     ::  var
    character(len=1), intent(in)     ::  is
    real, intent(inout)              ::  val
    integer, intent(out)             ::  status
    
    ! --- const ------------------------------
    
    character(len=*), parameter  ::  rname = mname//'/goVarValue_r'
    
    ! --- local ----------------------------------
    
    character(len=len(line))    ::  line2
    character(len=len(line))    ::  varval
    character(len=16)           ::  var2
    character(len=256)          ::  val2
    
    ! --- begin ----------------------------------
    
    ! copy of input line:
    line2 = line

    ! default status: not found:
    status = -1

    ! loop over var=val keys :
    do
      ! no keys left ? then leave
      if ( len_trim(line2) == 0 ) exit
      ! remove leading var=value from line2 :
      call goReadFromLine( line2, varval, status, sep=sep )
      IF_ERROR_RETURN(status=1)
      ! split in var and value:
      call goSplitLine( varval, var2, is, val2, status )
      IF_ERROR_RETURN(status=1)
      ! keys match ?
      if ( trim(var2) == trim(var) ) then
        ! store in output (might overwrite previously stored value):
        read (val2,*) val
        ! set return status to 'found':
        status = 0
      end if
    end do

    ! ok, with status either -1 or 0 :
    return

  end subroutine goVarValue_r


  ! ***
      

  subroutine goVarValue_l( line, sep, var, is, val, status )
  
    use GO_Print, only : gol, goPr, goErr

    ! --- in/out ---------------------------------
    
    character(len=*), intent(in)     ::  line
    character(len=1), intent(in)     ::  sep
    character(len=*), intent(in)     ::  var
    character(len=1), intent(in)     ::  is
    logical, intent(inout)           ::  val
    integer, intent(out)             ::  status
    
    ! --- const ------------------------------
    
    character(len=*), parameter  ::  rname = mname//'/goVarValue_l'
    
    ! --- local ----------------------------------
    
    character(len=len(line))    ::  line2
    character(len=len(line))    ::  varval
    character(len=16)           ::  var2
    character(len=256)          ::  val2
    
    ! --- begin ----------------------------------
    
    ! copy of input line:
    line2 = line
    
    ! default status: not found:
    status = -1

    ! loop over var=val keys :
    do
      ! no keys left ? then leave
      if ( len_trim(line2) == 0 ) exit
      ! remove leading var=value from line2 :
      call goReadFromLine( line2, varval, status, sep=sep )
      IF_ERROR_RETURN(status=1)
      ! split in var and value:
      call goSplitLine( varval, var2, is, val2, status )
      IF_ERROR_RETURN(status=1)
      ! keys match ?
      if ( trim(var2) == trim(var) ) then
        ! store in output (might overwrite previously stored value):
        read (val2,'(l1)') val
        ! set return status to 'found':
        status = 0
      end if
    end do

    ! ok, with status either -1 or 0 :
    return

  end subroutine goVarValue_l
    

  ! *****************************************************

  !---
  ! NAME
  !   gonum2str - prints number into character string
  !
  ! INTERFACE
  !   character(len=20) function gonum2str( x, fmt )
  !     integer [or real]  , intent(in)            ::  x
  !     character(len=*), intent(in), optional  ::  fmt
  !
  ! ARGUMENTS
  !   x
  !     Number to be converted.
  !   fmt
  !     Optional format, following the formats provided
  !     to the 'write' command.
  !     Default values:
  !
  !       type  x             fmt         example  (- is space)
  !       ------------------  ----------  ---------------------
  !       integer             '(i6)'      123---
  !
  ! CHANGES
  !   01/09/1999  Arjo Segers
  !---

  character(len=6) function num2str_i( i, fmt )

    ! --- in/out ----------------------

    integer, intent(in)                     ::  i
    character(len=*), intent(in), optional  ::  fmt

    ! --- local -----------------------

    character(len=6)   ::  s

    ! --- begin -----------------------

    if (present(fmt)) then
      write (s,fmt=fmt) i
    else
      write (s,'(i6)') i
    end if
    num2str_i=adjustl(s)

  end function num2str_i

  character(len=12) function num2str_r( r, fmt )

    ! --- in/out ----------------------

    real, intent(in)                     ::  r
    character(len=*), intent(in), optional  ::  fmt

    ! --- local -----------------------

    character(len=12)   ::  s

    ! --- begin -----------------------

    if (present(fmt)) then
      write (s,fmt=fmt) r
    else
      write (s,'(g10.3)') r
    end if
    num2str_r=adjustl(s)

  end function num2str_r


  ! *** UpCase, LoCase ***


  function UpCase( s )

    ! --- in/out -----------------

    character(len=*), intent(in)    ::  s
    character(len=len(s))           ::  UpCase

    ! --- local ------------------

    integer         ::  i

    ! --- begin ------------------

    do i = 1, len_trim(s)
      select case (s(i:i))
        case ('a') ;  UpCase(i:i) = 'A'
        case ('b') ;  UpCase(i:i) = 'B'
        case ('c') ;  UpCase(i:i) = 'C'
        case ('d') ;  UpCase(i:i) = 'D'
        case ('e') ;  UpCase(i:i) = 'E'
        case ('f') ;  UpCase(i:i) = 'F'
        case ('g') ;  UpCase(i:i) = 'G'
        case ('h') ;  UpCase(i:i) = 'H'
        case ('i') ;  UpCase(i:i) = 'I'
        case ('j') ;  UpCase(i:i) = 'J'
        case ('k') ;  UpCase(i:i) = 'K'
        case ('l') ;  UpCase(i:i) = 'L'
        case ('m') ;  UpCase(i:i) = 'M'
        case ('n') ;  UpCase(i:i) = 'N'
        case ('o') ;  UpCase(i:i) = 'O'
        case ('p') ;  UpCase(i:i) = 'P'
        case ('q') ;  UpCase(i:i) = 'Q'
        case ('r') ;  UpCase(i:i) = 'R'
        case ('s') ;  UpCase(i:i) = 'S'
        case ('t') ;  UpCase(i:i) = 'T'
        case ('u') ;  UpCase(i:i) = 'U'
        case ('v') ;  UpCase(i:i) = 'V'
        case ('w') ;  UpCase(i:i) = 'W'
        case ('x') ;  UpCase(i:i) = 'X'
        case ('y') ;  UpCase(i:i) = 'Y'
        case ('z') ;  UpCase(i:i) = 'Z'
        case default
          UpCase(i:i) = s(i:i)
      end select
    end do

  end function UpCase


  ! ***


  function LoCase( s )

    ! --- in/out -----------------

    character(len=*), intent(in)   ::  s
    character(len=len(s))          ::  LoCase

    ! --- local ------------------

    integer         ::  i

    ! --- begin ------------------

    do i = 1, len_trim(s)
      select case (s(i:i))
        case ('A') ;  LoCase(i:i) = 'a'
        case ('B') ;  LoCase(i:i) = 'b'
        case ('C') ;  LoCase(i:i) = 'c'
        case ('D') ;  LoCase(i:i) = 'd'
        case ('E') ;  LoCase(i:i) = 'e'
        case ('F') ;  LoCase(i:i) = 'f'
        case ('G') ;  LoCase(i:i) = 'g'
        case ('H') ;  LoCase(i:i) = 'h'
        case ('I') ;  LoCase(i:i) = 'i'
        case ('J') ;  LoCase(i:i) = 'j'
        case ('K') ;  LoCase(i:i) = 'k'
        case ('L') ;  LoCase(i:i) = 'l'
        case ('M') ;  LoCase(i:i) = 'm'
        case ('N') ;  LoCase(i:i) = 'n'
        case ('O') ;  LoCase(i:i) = 'o'
        case ('P') ;  LoCase(i:i) = 'p'
        case ('Q') ;  LoCase(i:i) = 'q'
        case ('R') ;  LoCase(i:i) = 'r'
        case ('S') ;  LoCase(i:i) = 's'
        case ('T') ;  LoCase(i:i) = 't'
        case ('U') ;  LoCase(i:i) = 'u'
        case ('V') ;  LoCase(i:i) = 'v'
        case ('W') ;  LoCase(i:i) = 'w'
        case ('X') ;  LoCase(i:i) = 'x'
        case ('Y') ;  LoCase(i:i) = 'y'
        case ('Z') ;  LoCase(i:i) = 'z'
        case default
          LoCase(i:i) = s(i:i)
      end select
    end do

  end function LoCase
  
  
  ! ***
  
  
  subroutine WriteKeyNum( res, key, num )
  
    ! --- in/out ------------------------------
    
    character(len=*), intent(out)    ::  res
    character(len=*), intent(in)     ::  key
    integer, intent(in)              ::  num
    
    ! --- local -------------------------------
    
    integer    ::  anum
    
    ! --- begin -------------------------------
    
    anum = abs(num)
    
    if ( anum <= 9 ) then
      write (res,'(a,i1)') trim(key), anum
    else if ( anum <= 99 ) then
      write (res,'(a,i2)') trim(key), anum
    else if ( anum <= 999 ) then
      write (res,'(a,i3)') trim(key), anum
    else if ( anum <= 9999 ) then
      write (res,'(a,i4)') trim(key), anum
    else if ( anum <= 99999 ) then
      write (res,'(a,i5)') trim(key), anum
    else
      write (res,'(a,i6)') trim(key), anum
    end if
    
  end subroutine WriteKeyNum
    

  ! ***
      

  subroutine goTab2Space( s )

    ! --- in/out -----------------

    character(len=*), intent(inout)    ::  s

    ! --- local ------------------

    integer         ::  pos

    ! --- begin ------------------

    do 
      pos = scan(s,char(9))
      if ( pos == 0 ) exit
      s(pos:pos) = ' '
    end do

  end subroutine goTab2Space


  ! ***
      
      
  subroutine goReplace_s( s, key, repl, status )
  
    ! --- in/out ---------------------------------
    
    character(len=*), intent(inout)     ::  s
    character(len=*), intent(in)        ::  key
    character(len=*), intent(in)        ::  repl
    integer, intent(out)                ::  status
    
    ! --- const ----------------------------------
    
    character(len=*), parameter  ::  rname = mname//'/goReplace_s'
    
    ! --- local ----------------------------------
    
    integer                 ::  n_in, l_in
    integer                 ::  n_out
    integer                 ::  ind
    character(len=len(s))   ::  s_in
    
    ! --- begin ----------------------------------
    
    ! copy input:
    s_in = s
    
    ! empty target:
    s = ''
    n_out = 0
    
    ! number of characters in s:
    l_in = len_trim(s_in)
    
    ! characters copied from s_in to s:
    n_in = 0
    
    ! loop over all matches of key:
    do
      !print *, '---- n_in, l_in : ',n_in, l_in
      ! past end ?
      if ( n_in > l_in ) exit
      !print *, '  -- remaining : "'//s_in(n_in+1:l_in)//'" , key : "'//key//'"'
      ! search key in remaining part of input :
      if ( len(key) < 1 ) then
        ind = 0
      else
        ind = index(s_in(n_in+1:l_in),key)
      end if
      !print *, '  -- index     : ', ind
      ! not found ?
      if ( ind < 1 ) then
        ! add remaining part:
        s = s(1:n_out)//s_in(n_in+1:l_in)
        n_out = n_out + l_in-n_in
        ! leave:
        exit
      end if
      ! add first part:
      s = s(1:n_out)//s_in(n_in+1:n_in+ind-1)
      n_out = n_out + ind-1
      n_in  = n_in + ind-1
      !print *, '  -- out       : ',n_out,'"'//s(1:n_out)//'"'
      ! add replacement:
      s = s(1:n_out)//repl
      n_out = n_out + len(repl)
      n_in  = n_in + len(key)
      !print *, '  -- out       : ',n_out,'"'//s(1:n_out)//'"'
    end do
      
    ! ok
    status = 0
    
  end subroutine goReplace_s


  ! ***
      
      
  subroutine goReplace_i( s, key, fmt, i, status )
  
    ! --- in/out ---------------------------------
    
    character(len=*), intent(inout)     ::  s
    character(len=*), intent(in)        ::  key
    character(len=*), intent(in)        ::  fmt
    integer, intent(in)                 ::  i
    integer, intent(out)                ::  status
    
    ! --- const ----------------------------------
    
    character(len=*), parameter  ::  rname = mname//'/goReplace_i'
    
    ! --- local ----------------------------------
    
    character(len=8)    ::  repl    
    
    ! --- begin ----------------------------------
    
    ! fill replacement:
    write (repl,fmt) i
    
    ! replace value:
    call goReplace( s, key, trim(repl), status )
    IF_NOTOK_RETURN(status=1)    
      
    ! ok
    status = 0
    
  end subroutine goReplace_i
    

  ! ***
      
  ! replace in s all characters in 'chars' by the value in 'repl' .
  
  subroutine goTranslate( s, chars, repl, status )
  
    ! --- in/out ---------------------------------
    
    character(len=*), intent(inout)     ::  s
    character(len=*), intent(in)        ::  chars
    character(len=*), intent(in)        ::  repl
    integer, intent(out)                ::  status
    
    ! --- const ----------------------------------
    
    character(len=*), parameter  ::  rname = mname//'/goTranslate'
    
    ! --- local ----------------------------------
    
    integer           ::  i
    
    ! --- begin ----------------------------------
    
    ! loop over characters to be replaced:
    do i = 1, len(chars)
      ! replace character:
      call goReplace( s, chars(i:i), repl, status )
      IF_NOTOK_RETURN(status=1)
    end do
      
    ! ok
    status = 0
    
  end subroutine goTranslate


end module GO_String


! ######################################################################
! ###
! ### test
! ###
! ######################################################################
!
!program test
!
!  use go_string
!  
!  character(len=32)   ::  s
!  integer             ::  status
!  
!  print *, 'not found ...'
!  s='abcd' ; call goReplace( s, 'q', 'x', status ) ; print *, '"'//trim(s)//'"'
!
!  print *, 'replace 1 character ...'
!  s='abcd' ; call goReplace( s, 'a', 'x', status ) ; print *, '"'//trim(s)//'"'
!  s='abcd' ; call goReplace( s, 'b', 'x', status ) ; print *, '"'//trim(s)//'"'
!  s='abcd' ; call goReplace( s, 'c', 'x', status ) ; print *, '"'//trim(s)//'"'
!  s='abcd' ; call goReplace( s, 'd', 'x', status ) ; print *, '"'//trim(s)//'"'
!    
!  print *, 'empty arguments ...'
!  s=''     ; call goReplace( s, 'a', 'x', status ) ; print *, '"'//trim(s)//'"'
!  s='abcd' ; call goReplace( s, '' , 'x', status ) ; print *, '"'//trim(s)//'"'
!  s='abcd' ; call goReplace( s, 'a', '' , status ) ; print *, '"'//trim(s)//'"'
!
!  print *, 'replace 1 by 2 characters ...'
!  s='abcd' ; call goReplace( s, 'a', 'XY', status ) ; print *, '"'//trim(s)//'"'
!  s='abcd' ; call goReplace( s, 'b', 'XY', status ) ; print *, '"'//trim(s)//'"'
!  s='abcd' ; call goReplace( s, 'd', 'XY', status ) ; print *, '"'//trim(s)//'"'
!
!  print *, 'replace 2 characters by 1 ...'
!  s='abcd' ; call goReplace( s, 'ab', 'x', status ) ; print *, '"'//trim(s)//'"'
!  s='abcd' ; call goReplace( s, 'bc', 'x', status ) ; print *, '"'//trim(s)//'"'
!  s='abcd' ; call goReplace( s, 'cd', 'x', status ) ; print *, '"'//trim(s)//'"'
!
!  print *, 'replace all ...'
!  s='abcdabcda' ; call goReplace( s, 'a', '_', status ) ; print *, '"'//trim(s)//'"'
!  s='abcdabcda' ; call goReplace( s, 'a', '' , status ) ; print *, '"'//trim(s)//'"'
!
!end program test
!
!
