!! string handling
!!
	subroutine STR_TRIM(RETSTR, STR, RETLEN) !!
!!
!! if RETSTR=STR then RETSTR is not touched
!!
!! Arguments:
	character*(*) STR, RETSTR !! in,out
	integer RETLEN    	!! out
	integer i

	i=len(str)
	if (str(1:1) .gt. ' ') then
10	  if (str(i:i) .le. ' ') then
	    i=i-1
	    goto 10
	  endif
	else
20	  if (str(i:i) .le. ' ') then
	    if (i .gt. 1) then
	      i=i-1
	      goto 20
	    endif
	  endif
	endif
	retlen=min(len(retstr),i)
	if (retstr .ne. str) then ! avoid copy to retstr if equal
	  retstr=str(1:i)
	endif
	end

!!
	subroutine STR_UPCASE(RETSTR, STR) !!
!!
!! Arguments:
	character STR*(*), RETSTR*(*)	!! in,out
	integer i, ch

	retstr=str
	do i=1,len(retstr)
	  ch=ichar(retstr(i:i))
	  if (ch .ge. ichar('a') .and. ch .le. ichar('z')) then
	    retstr(i:i)=char(ch-(ichar('a')-ichar('A')))
	  endif
	enddo
	end

!!
	subroutine STR_LOWCASE(RETSTR, STR) !!
!!
!! Arguments:
	character STR*(*), RETSTR*(*)	!! in,out
	integer i, ch

	retstr=str
	do i=1,len(retstr)
	  ch=ichar(retstr(i:i))
	  if (ch .ge. ichar('A') .and. ch .le. ichar('Z')) then
	    retstr(i:i)=char(ch+(ichar('a')-ichar('A')))
	  endif
	enddo
	end

!!
	subroutine STR_APPEND(str, length, add) !!
!!
	implicit none

	character*(*) str, add !!
	integer length         !!

	if (len(add)+length .gt. len(str)) then
	  if (length .lt. len(str)) then
	    str(length+1:)=add
	    length=len(str)
	  endif
	else
	  str(length+1:length+len(add))=add
	  length=length+len(add)
	endif
	end

!!
	integer function STR_CMP(str1, str2) !!
!!
!! if strings are equal: return 0
!! else return position of first different character

	character str1*(*), str2*(*) !!

	integer i

	do i=0,min(len(str1),len(str2))-1
	  if (str1(i+1:i+1) .ne. str2(i+1:i+1)) then
	    str_cmp=i+1
	    return
	  endif
	enddo
	do i=len(str1),len(str2)-1
	  if (str2(i+1:i+1) .ne. ' ') then
	    str_cmp=i+1
	    return
	  endif
	enddo
	do i=len(str2),len(str1)-1
	  if (str1(i+1:i+1) .ne. ' ') then
	    str_cmp=i+1
	    return
	  endif
	enddo
	str_cmp=0
	return
	end

!!
	subroutine STR_FIRST_NONBLANK(STR, POS) !!
!!
!! Arguments:
	character*(*) STR       !! in
	integer POS             !! out
	integer i

	do i=1,len(str)
	  if (str(i:i) .gt. ' ') then
	    pos=i
	    return
	  endif
	enddo
	pos=0
	end

!!
	subroutine STR_SPLIT(STR, DELIM, START, ENDE)  !!
!!
!! split string into sequences separated by DELIM
!! for the first sequence set ENDE=0 and START=0 (or START=n for other start position n+1)
!! result: end of list: ENDE=-1
!!         empty sequence: START=ENDE+1
!!         normal sequence: STR(START:ENDE) without delimiter
!!
!! if ENDE has not a legal value, nothing happens

	character STR*(*), DELIM*(*) !! (in) string, delimiter
	integer START, ENDE  !! (in/out) start/end position

	integer i

	if (ende .lt. 0 .or. ende .ge. len(str) .or. start .lt. 0) then
	  ende=-1
	  RETURN
	endif
	if (ende .ne. 0) start=ende+len(delim)
	if (start .ge. len(str)) then
	  if (start .gt. len(str)) then
	    ende=-1
	    RETURN
	  endif
	  i=0
	else
	  i=index(str(start+1:), delim)
	endif
	if (i .eq. 0) then
	  ende=len(str)
	else
	  ende=start+i-1
	endif
	start=start+1
	end

!!
	subroutine STR_GET_ELEM(STR, POS, ELEM) !!
!!
!! reads next element ELEM from string STR(POS:). Elements are separated by
!! spaces combined with one control-char (assume tab) or one comma.
!! return ' ' when STR(POS:) contains only whitespace or when pos is to high
!!
	character STR*(*)  !! (in) input string
	character ELEM*(*) !! (out) element read
	integer   POS      !! (in/out) read position

	integer start


1	if (pos .gt. len(str)) then
	  elem=' '
	  RETURN
	endif
	if (str(pos:pos) .eq. ' ') then
	  pos=pos+1
	  goto 1
	endif
	start=pos
2	if (str(pos:pos) .gt. ' ' .and. str(pos:pos) .ne. ',') then
	  pos=pos+1
	  if (pos .le. len(str)) then
	    goto 2
	  endif
	  pos=pos-1
	endif
	if (str(pos:pos) .eq. ',' .or. str(pos:pos) .lt. ' ') then
	  if (start .eq. pos) then
	    elem=str(start:pos)
	  else
	    elem=str(start:pos-1)
	  endif
	  pos=pos+1
	  RETURN
	endif
	elem=str(start:pos-1)
	if (str(pos:) .eq. ' ') RETURN
3	if (str(pos:pos) .eq. ' ') then
	  pos=pos+1
	  if (pos .gt. len(str)) stop 'STR_GET_ELEM: assertion failed'
	  goto 3
	endif
	if (str(pos:pos) .eq. ',' .or. str(pos:pos) .lt. ' ') then
	  pos=pos+1
	endif
	end

!!
	integer function STR_FIND_ELEM(STR, ELEM) !!
!!	
!! find column index of element ELEM (case insensitive)
!! only the first 64 chars of each element are checked
!! 0 is returned when not found
!!
	character STR*(*), ELEM*(*)
	character ups*64, upe*64
	integer pos, idx

	pos=1
	call str_upcase(upe, elem)
	idx=0

	call str_get_elem(str, pos, ups)
	do while (ups .ne. ' ')
	  idx=idx+1
	  call str_upcase(ups, ups)
	  if (ups .eq. upe) then
	    str_find_elem=idx
	    RETURN
	  endif
	  call str_get_elem(str, pos, ups)
	enddo
	str_find_elem=0
	RETURN
	end

!!
	subroutine STR_SUBSTITUTE(RESULT, STR, OLD, NEW) !!
!!
!! replace all occurences of substring OLD in STR by NEW
!! special case: if NEW=CHAR(0) then a null length string is replaced
!!
	character RESULT*(*), STR*(*) !! (out), (in) strings (must either be equal or not overlap)
	character OLD*(*), NEW*(*)    !! substrings (in)

	integer i,j,k

	i=0
	j=index(str, old)-1
	if (len(new) .eq. 1 .and. new(1:1) .eq. char(0)) then
	  k=0
	  do while (j .ge. 0)
	    if (j .gt. 0) result(k+1:k+j)=str(i+1:i+j)
	    i=i+j+len(old)
	    k=k+j
	    if (i .ge. len(str)) then
	      if (k .lt. len(result)) result(k+1:)=' '
	      RETURN
	    endif
	    j=index(str(i+1:), old)-1
	  enddo
	  if (k .lt. len(result)) result(k+1:)=str(i+1:)
	elseif (len(old) .ge. len(new)) then
	  k=0
	  do while (j .ge. 0)
	    if (j .gt. 0) result(k+1:k+j)=str(i+1:i+j)
	    i=i+j+len(old)
	    k=k+j
	    result(k+1:k+len(new))=new
	    k=k+len(new)
	    if (i .ge. len(str)) then
	      if (k .lt. len(result)) result(k+1:)=' '
	      RETURN
	    endif
	    j=index(str(i+1:), old)-1
	  enddo
	  if (k .lt. len(result)) result(k+1:)=str(i+1:)
	else
	  result=str
	  do while (j .ne. 0)
	    i=i+j
	    result(i+1:)=new//result(i+len(old)+1:)
	    i=i+len(new)
	    if (i .ge. len(str)) RETURN
	    j=index(str(i+1:), old)-1
	  enddo
	endif
	end

	subroutine STR_CRC(CRC,BUF) !!
!!
!! Computes a 16-bit Cyclic Redundancy Check for an character string BUF.
!! Before the first call CRC should be intitalized (i.e. to 0) -
!! between subsequent call it should left untouched.

	integer CRC        !! (in/out) CRC code
	character BUF*(*)  !! characters


	integer init,ireg,i,j,icrctb(0:255),ichr,ib1,ib2,ib3
	character*1 creg(4)
	save icrctb,init,ib1,ib2,ib3
	equivalence (creg,ireg) ! used to get at the 4 bytes in an integer.
	data init /0/

	integer crc1, crc2

	if (init.eq.0) then ! initialize tables?
	  init=1
	  ireg=256*(256*ichar('3')+ichar('2'))+ichar('1')
	  do j=1,4 ! figure out which component of creg addresses which byte of ireg.
	    if (creg(j).eq.'1') ib1=j
	    if (creg(j).eq.'2') ib2=j
	    if (creg(j).eq.'3') ib3=j
	  enddo
	  do j=0,255 ! create CRCs of all characters.
	    ireg=j*256

	    do i=1,8 ! Here is where 8 one-bit shifts, and some XORs with the generator polynomial, are done.
	      ichr=ichar(creg(ib2))
	      ireg=ireg+ireg
	      creg(ib3)=char(0)
	      if(ichr.gt.127)ireg=ieor(ireg,4129)
	    enddo

	    icrctb(j)=ireg
	  enddo
	endif
	ireg=crc
	crc1=ichar(creg(ib1))
	crc2=ichar(creg(ib2))

	do j=1,len(buf) ! Main loop over the characters
	  ireg=icrctb(ieor(ichar(buf(j:j)),crc2))
	  crc2=ieor(ichar(creg(ib2)),crc1)
	  crc1=ichar(creg(ib1))
	enddo
	creg(ib1)=char(crc1)
	creg(ib2)=char(crc2)
	crc=ireg
	return
	end

!!
	subroutine STR_CRC_COMP(CRC, CHR) !!
!!
!! encode CRC (16 bit) as 3 characters in CHR
!!
	integer CRC       !! (in)
	character CHR*3   !! (out)

	integer n,j
	character*41 cs/'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ$+-&_'/

	if (crc .lt. 0 .or. crc .gt. 65535) stop 'STR_CRC_COMP: illegal CRC'
	n=crc
	j=mod(n,41)
	chr(1:1)=cs(j+1:j+1)
	n=n/41
	j=mod(n,41)
	chr(2:2)=cs(j+1:j+1)
	j=n/41
	chr(3:3)=cs(j+1:j+1)
	end

