libSUFR
a LIBrary of Some Useful Fortran Routines
All Classes Namespaces Files Functions Variables Pages
text.f90
Go to the documentation of this file.
1!> \file text.f90 Procedures to manipulate text/strings
2
3
4! Copyright (c) 2002-2025 Marc van der Sluys - Nikhef/Utrecht University - marc.vandersluys.nl
5!
6! This file is part of the libSUFR package,
7! see: http://libsufr.sourceforge.net/
8!
9! This is free software: you can redistribute it and/or modify it under the terms of the European Union
10! Public Licence 1.2 (EUPL 1.2). This software is distributed in the hope that it will be useful, but
11! WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
12! PURPOSE. See the EU Public Licence for more details. You should have received a copy of the European
13! Union Public Licence along with this code. If not, see <https://www.eupl.eu/1.2/en/>.
14
15
16
17
18!***********************************************************************************************************************************
19!> \brief Procedures to manipulate text/strings
20
22 implicit none
23 save
24
25contains
26
27
28 !*********************************************************************************************************************************
29 !> \brief Make a string lower case
30 !!
31 !! \param str String
32 !! \retval lowercase String as lower case
33
34 pure function lowercase(str)
35 implicit none
36 character, intent(in) :: str*(*)
37 character :: lowercase*(len(str))
38 integer :: i,ch
39
40 lowercase = str
41 do i=1,len_trim(lowercase)
42 ch = ichar(lowercase(i:i))
43 if(ch.ge.65.and.ch.le.91) ch = ch + 32
44 lowercase(i:i) = char(ch)
45 end do
46
47 end function lowercase
48 !*********************************************************************************************************************************
49
50
51
52 !*********************************************************************************************************************************
53 !> \brief Make a string upper case
54 !!
55 !! \param str String
56 !! \retval uppercase String as uppercase
57
58 pure function uppercase(str)
59 implicit none
60 character, intent(in) :: str*(*)
61 character :: uppercase*(len(str))
62 integer :: i,ch
63
64 uppercase = str
65 do i=1,len_trim(uppercase)
66 ch = ichar(uppercase(i:i))
67 if(ch.ge.97.and.ch.le.123) ch = ch - 32
68 uppercase(i:i) = char(ch)
69 end do
70
71 end function uppercase
72 !*********************************************************************************************************************************
73
74
75
76 !*********************************************************************************************************************************
77 !> \brief Make a string lower case with an upper-case initial
78 !!
79 !! \param str String
80 !! \retval uppercaseinitial String in lower case, with upper-case initial
81
82 pure function uppercaseinitial(str)
83 implicit none
84 character, intent(in) :: str*(*)
85 character :: uppercaseinitial*(len(str))
86 integer :: i,ic
87
89
90 ! Capitalise first letter:
91 ic = ichar(uppercaseinitial(1:1))
92 if(ic.ge.97.and.ic.le.122) uppercaseinitial(1:1) = char(ic-32)
93
94 ! Make the rest of the letters lower case:
95 do i=2,len_trim(uppercaseinitial)
96 ic = ichar(uppercaseinitial(i:i))
97 if(ic.ge.65.and.ic.le.90) uppercaseinitial(i:i) = char(ic+32)
98 end do
99
100 end function uppercaseinitial
101 !*********************************************************************************************************************************
102
103
104
105 !*********************************************************************************************************************************
106 !> \brief Convert a UTF-16 string to UTF-8
107 !!
108 !! \param str16 UTF-16 string
109 !! \retval UTF16to8 UTF-8 string (about half the length of str16)
110
111 pure function utf16to8(str16)
112 use sufr_system, only: warn
113 implicit none
114 character, intent(in) :: str16*(*)
115 character :: utf16to8*((len(str16)+1)/2)
116 integer :: ic16, ic8
117
118 utf16to8(1:len(utf16to8)) = ' '
119 do ic16=1,len(str16),2
120 ic8 = (ic16+1)/2
121 utf16to8(ic8:ic8) = str16(ic16:ic16)
122 end do
123
124 end function utf16to8
125 !*********************************************************************************************************************************
126
127
128
129 !*********************************************************************************************************************************
130 !> \brief Search and replace occurences of a substring in a string as often as the search string is found
131 !!
132 !! \param string Original string to replace in. Trailing spaces are retained, call with string(1:len_trim(string))
133 !! to ignore them and speed things up.
134 !! \param str_srch Search string
135 !! \param str_repl Replacement string
136 !!
137 !! \note If the search string is part of the replace string, replacement is done in two steps:
138 !! - replace the search string with a random string of the same length
139 !! - replace the random string with the replacement string
140 !! - this should avoid undesired outcomes for the case where the replacement string contains the search string,
141 !! e.g. when replacing '.csv' with '_backup.csv', which will end up as e.g. '_backup_backup_back'
142
143 pure subroutine replace_substring(string, str_srch, str_repl)
144 implicit none
145 character, intent(inout) :: string*(*)
146 character, intent(in) :: str_srch*(*),str_repl*(*)
147 integer, parameter :: lran=100
148 character(len=lRan), parameter :: ranstr = 'G^$("q]WvtDB5VCFCJ/\gAo9|8^wDB|G,?q|Vi)|9wUhN.mKZI6>VMkGa~NkBMk(~F{b?<:kW1TDJ-Gmq8q-eW<WD3=(1M#*MhSy'
149 character :: tmpstr*(len(str_srch))
150 integer :: istart,istop,di,iloop, ltmp,lstr,is,lsrch, il, maxloop
151
152 lstr = len(string)
153 lsrch = len(str_srch)
154 if(lsrch.gt.lstr) return ! Search string is longer than string
155
156 if(index(str_repl, str_srch) .ne. 0) then ! Search string is part of replace string
157 ! Obtain a 'random' temporary string of the same length as the search string. Since ran_str() is
158 ! impure, so will this subroutine be, and hence its dependencies (e.g. dbl2str())
159 !seed = -1
160 !call ran_str(seed, tmpStr)
161
162 ! Construct a 'random' string from slowly changing selections from the hardcoded ranStr
163 ltmp = len(tmpstr)
164 if(ltmp.le.lran) then
165 tmpstr = ranstr(1:ltmp)
166 else
167 istart = 1
168 istop = lran
169 do iloop=1,huge(iloop)-1
170 di = istop-istart
171 tmpstr(istart:istop) = ranstr(1:1+di)
172 if(istop.ge.ltmp) exit
173
174 istart = istop+1
175 istop = min(istart + lran - 1 - (mod(iloop,max(lran-2,lran/10))), ltmp)
176 end do
177
178 end if
179
180
181 ! Step 1: replace search string with temporary (random) string of same length:
182 is = huge(is)
183 maxloop = lstr+1-lsrch ! Prevent infinite loops
184 do il = 1,maxloop
185 is = index(string, str_srch, back=.false.)
186 if(is.le.0) exit
187 if(is.gt.maxloop) exit
188 !print*,il,maxLoop,lstr,is,'###'//string(max(is-5,1):min(is+5,lstr))//'###' ! Debug output
189 string = string(1:is-1)//tmpstr//trim(string(is+lsrch:))
190 end do
191
192 else ! Search string is NOT part of replace string
193 tmpstr = str_srch
194 end if ! Search string is part of replace string
195
196
197 ! Step 2: replace temporary (random or original search) string with replace string:
198 is = huge(is)
199 maxloop = lstr+1-lsrch ! Prevent infinite loops
200 do il = 1,maxloop
201 is = index(string, tmpstr, back=.false.)
202 if(is.le.0) exit
203 if(is.gt.maxloop) exit
204 !print*,il,maxLoop,lstr,is,'###'//string(max(is-5,1):min(is+5,lstr))//'###' ! Debug output
205 string = string(1:is-1)//str_repl//trim(string(is+lsrch:))
206 end do
207
208 end subroutine replace_substring
209 !*********************************************************************************************************************************
210
211
212
213 !*********************************************************************************************************************************
214 !> \brief Remove a substring from a string, if present
215 !!
216 !! \param string String to remove the substring from. Trailing spaces are retained, call with string(1:len_trim(string))
217 !! to ignore them and speed things up.
218 !! \param substr Substring to remove
219 !! \param debug Print debug info (T/F, optional)
220
221 subroutine remove_substring(string,substr, debug)
222 implicit none
223 character, intent(inout) :: string*(*)
224 character, intent(in) :: substr*(*)
225 logical, intent(in), optional :: debug
226
227 integer :: l,ls, i1, il,maxLoop
228 character :: tstr*(len(string))
229 logical :: print_debug
230
231 print_debug = .false.
232 if(present(debug)) print_debug = debug
233
234 ls = len(substr) ! Length of the substring to remove
235 if(ls.lt.1) return ! Zero-length string
236
237 i1 = -1
238 maxloop = ceiling( real(len(string))/real(ls) ) ! Prevent infinite loops
239 do il = 1,maxloop
240 l = len_trim(string)
241
242 i1 = index(string,substr,back=.false.)
243 if(i1.le.0) exit
244
245 tstr = string(1:i1-1)//string(i1+ls:l) ! String gets shorter by ls
246
247 if(print_debug) then
248 print*,string(1:i1-1)
249 print*,string(i1+ls:l)
250 print*,string(i1:i1+ls),i1,l
251 print*,trim(tstr)
252 end if
253
254 string = tstr
255 end do
256
257 end subroutine remove_substring
258 !*********************************************************************************************************************************
259
260
261
262 !*********************************************************************************************************************************
263 !> \brief Count how many times a substring is present in a string.
264 !!
265 !! \param string String to count substrings in.
266 !! \param substr Substring to count.
267 !!
268 !! \retval count Number of times substring was found in string.
269
270 function count_substring(string,substr)
271 implicit none
272 character, intent(in) :: string*(*), substr*(*)
273
274 integer :: count_substring, l,ls, i1, il,maxloop
275 character :: lstr*(len(string)), tstr*(len(string))
276
278
279 ls = len(substr) ! Length of the substring to count
280 if(ls.lt.1) return ! Zero-length string
281
282 i1 = -1
283 maxloop = ceiling( real(len(string))/real(ls) ) ! Prevent infinite loops
284 lstr = string
285 do il = 1,maxloop
286 l = len_trim(lstr)
287
288 i1 = index(trim(lstr),substr,back=.false.)
289 if(i1.le.0) exit
290
291 tstr = lstr(1:i1-1)//lstr(i1+ls:l) ! String gets shorter by ls
292 lstr = tstr
294 !print*,count_substring,i1,trim(lstr)
295 end do
296
297 end function count_substring
298 !*********************************************************************************************************************************
299
300
301
302 !*********************************************************************************************************************************
303 !> \brief Search and replace occurences of a string in a text file. Lines up to 9999 characters only, otherwise a warning
304 !! is given
305 !!
306 !! \param file_in Name of the text file to replace in
307 !! \param file_out Name of the text file to store the result in
308 !! \param str_srch Search string
309 !! \param str_repl Replacement string
310 !!
311 !! \param status Exit status: 0-ok, 1/2: could not open I/O file, 11/12: character array string too small (output)
312
313 subroutine replace_string_in_textfile(file_in, file_out, str_srch, str_repl, status)
315
316 implicit none
317 character, intent(in) :: file_in*(*),file_out*(*), str_srch*(*),str_repl*(*)
318 integer, intent(out) :: status
319 integer :: io,ip,op
320 character :: string*(9999)
321
322 status = 0
323
324 ! Input file:
325 call find_free_io_unit(ip)
326 open(unit=ip, file=trim(file_in), status='old', action='read', iostat=io)
327 if(io.ne.0) then
328 call error('libSUFR replace_string_in_textfile(): could not open file: '//trim(file_in), 0)
329 status = 1
330 return
331 end if
332
333 ! Output file:
334 call find_free_io_unit(op)
335 open(unit=op, file=trim(file_out), status='replace', action='write', iostat=io)
336 if(io.ne.0) then
337 call error('libSUFR replace_string_in_textfile(): could not open file: '//trim(file_out), 0)
338 status = 2
339 return
340 end if
341
342
343 io = 0
344 do while(io.eq.0)
345 read(ip,'(A)', iostat=io) string
346
347 if(len(string).eq.len_trim(string)) then
348 call error('libSUFR replace_string_in_textfile(): character array string too small', 0)
349 status = 11
350 return
351 end if
352
353 call replace_substring(string, str_srch, str_repl)
354
355 if(len(string).eq.len_trim(string)) then
356 call error('libSUFR replace_string_in_textfile(): character array string too small', 0)
357 status = 12
358 return
359 end if
360
361 write(op,'(A)') trim(string)
362 end do
363
364 close(ip)
365 close(op)
366
367 end subroutine replace_string_in_textfile
368 !*********************************************************************************************************************************
369
370
371
372
373
374
375 !*********************************************************************************************************************************
376 !> \brief Verify whether a string contains any of a given list of characters
377 !!
378 !! \param string String to verify
379 !! \param characters List of characters
380 !!
381 !! \retval string_contains_one_of True if the string contains one of the specified characters, otherwise false
382
383 pure function string_contains_one_of(string, characters)
384 implicit none
385 character, intent(in) :: string*(*), characters*(*)
386 logical :: string_contains_one_of
387 integer :: ci
388
390 do ci=1,len_trim(characters)
391 if(index(trim(string),characters(ci:ci)).gt.0) return ! Match found
392 end do
393
394 string_contains_one_of = .false.
395
396 end function string_contains_one_of
397 !*********************************************************************************************************************************
398
399
400
401
402
403
404 !*********************************************************************************************************************************
405 !> \brief Print multiple tab characters
406 !!
407 !! \param number Desired number of tab characters
408 !! \retval tabs String containing tabs
409
410 pure function tabs(number)
411 implicit none
412 integer, intent(in) :: number
413 character :: tabs*(max(number,1))
414 integer :: count
415
416 tabs = ''
417 if(number.gt.0) then
418 do count=1,number
419 tabs(count:count) = char(9)
420 end do
421 end if
422
423 end function tabs
424 !*********************************************************************************************************************************
425
426
427
428 !*********************************************************************************************************************************
429 !> \brief Convert an integer to a character string of the proper length
430 !!
431 !! \param number Integer number to convert
432 !! \retval int2str Character string
433
434 pure function int2str(number)
435 implicit none
436 integer, intent(in) :: number
437 character :: int2str*(max(ceiling(log10(dble(abs(number)+1))),1) - (sign(1,number)-1)/2) ! 0-9 -> 1; 10-99 -> 2; +1 if <0
438
439 write(int2str,'(I0)') number
440
441 end function int2str
442 !*********************************************************************************************************************************
443
444
445 !*********************************************************************************************************************************
446 !> \brief Convert a double-precision real to a nice character string. Difference with the F0 format descriptor:
447 !! replace leading . or -. with 0. and -0. respectively (0.1 iso .1; -0.1 iso -.1).
448 !!
449 !! \param number Value to convert
450 !! \param decim Number of decimals to use
451 !! \param mark Decimal mark to separate the integer and fractional parts; single character, e.g. "," (optional; default: ".")
452 !! \retval dbl2str String containing the double
453
454 pure function dbl2str(number, decim, mark)
455 use sufr_kinds, only: double, long
456 implicit none
457 real(double), intent(in) :: number
458 integer, intent(in) :: decim
459 character, intent(in), optional :: mark*(*)
460 real(double), parameter :: eps = sqrt(epsilon(number)) ! sqrt of epsilon for a double real
461 character :: fmt*(9)
462 integer :: d2slen !, status
463
464 ! The length of dbl2str is derived as follows:
465 ! - ceiling(log10(abs((number)))): 99 gives 2, 999 3, etc.
466 ! - + 10.d0**(-decim)/2.d0: to catch rounding up. E.g. 99.97 with decim=1 gives ceiling(log10(abs((number)))) = 2,
467 ! but we need 3 since 100.0 must be printed - eps no longer needed?
468 ! - - (sign(1_long,floor(number,long))-1)/2: space for negative sign
469 ! - + decim: add the decimals to the total string length
470 ! - + 1: add the decimal separator
471
472 character :: dbl2str*(max(ceiling(log10((abs(number) + 10.d0**(-decim)/2.d0) * (1.d0+eps))),1) - &
473 (sign(1_long,floor(number,long))-1)/2 + decim + 1)
474
475 write(fmt,'(A,I0,A)') '(F0.',max(decim,0),')'
476 write(dbl2str, trim(fmt)) number
477 !write(dbl2str, trim(fmt), iostat=status) number
478 !if(status.ne.0) continue ! Ignore EoR errors
479
480 ! Prepend a zero before leading decimal points:
481 if(dbl2str(1:1).eq.'.') then
482 d2slen = len(dbl2str)
483 dbl2str = '0'//trim(dbl2str(1:d2slen-1))
484 else if(dbl2str(1:2).eq.'-.') then
485 call replace_substring(dbl2str, '-.', '-0.')
486 end if
487
488 if(present(mark)) call replace_substring(dbl2str, '.', mark(1:1)) ! Replace default decimal point with a specified mark
489
490 ! Debug output:
491 !write(*,'(4F9.4, 7I3, 3x, A)') &
492 ! number, &
493 ! 10.d0**(-decim)/2.d0, &
494 ! abs((number + 10.d0**(-decim)/2.d0) * (1.d0+eps)), &
495 ! log10(abs((number + 10.d0**(-decim)/2.d0) * (1.d0+eps))), &
496 ! ceiling(log10(abs((number + 10.d0**(-decim)/2.d0) * (1.d0+eps)))), &
497 ! ceiling(log10((abs(number) + 10.d0**(-decim)/2.d0) * (1.d0+eps))), &
498 ! max(ceiling(log10(abs((number + 10.d0**(-decim)/2.d0) * (1.d0+eps)))),1), &
499 ! (sign(1_long,floor(number,long))-1)/2 + decim + 1, &
500 ! max(ceiling(log10(abs((number + 10.d0**(-decim)/2.d0) * (1.d0+eps)))),1) - &
501 ! (sign(1_long,floor(number,long))-1)/2 + decim + 1, &
502 ! len_trim(dbl2str), &
503 ! max(ceiling(log10(abs((number + 10.d0**(-decim)/2.d0) * (1.d0+eps)))),1) - &
504 ! (sign(1_long,floor(number,long))-1)/2 + decim + 1 - len_trim(dbl2str), &
505 ! '###'//dbl2str//'###'
506
507 end function dbl2str
508 !*********************************************************************************************************************************
509
510
511 !*********************************************************************************************************************************
512 !> \brief Convert a double-precision real to a nice character string. Short alias for dbl2str().
513 !!
514 !! \param number Value to convert
515 !! \param decim Number of decimals to use
516 !! \param mark Decimal mark to separate the integer and fractional parts; single character, e.g. "," (optional; default: ".")
517 !! \retval d2s String containing the double
518
519 pure function d2s(number, decim, mark)
520 use sufr_kinds, only: double, long
521 implicit none
522 real(double), intent(in) :: number
523 integer, intent(in) :: decim
524 character, intent(in), optional :: mark*(*)
525
526 real(double), parameter :: eps = sqrt(epsilon(number)) ! sqrt of epsilon for a double real
527 character :: d2s*(max(ceiling(log10(abs((number + 10.d0**(-decim)/2.d0) * (1.d0+eps)))),1) - &
528 (sign(1_long,floor(number,long))-1)/2 + decim + 1)
529
530 if(present(mark)) then
531 d2s = dbl2str(number, decim, mark)
532 else
533 d2s = dbl2str(number, decim)
534 end if
535
536 end function d2s
537 !*********************************************************************************************************************************
538
539
540 !*********************************************************************************************************************************
541 !> \brief Convert a double-precision real to a nice character string using a comma as decimal mark.
542 !! Alias for dbl2str(number, decim, ',').
543 !!
544 !! \param number Value to convert
545 !! \param decim Number of decimals to use
546 !! \retval d2sc String containing the double
547
548 pure function d2sc(number, decim)
549 use sufr_kinds, only: double, long
550 implicit none
551 real(double), intent(in) :: number
552 integer, intent(in) :: decim
553
554 real(double), parameter :: eps = sqrt(epsilon(number)) ! sqrt of epsilon for a double real
555 character :: d2sc*(max(ceiling(log10(abs((number + 10.d0**(-decim)/2.d0) * (1.d0+eps)))),1) - &
556 (sign(1_long,floor(number,long))-1)/2 + decim + 1)
557
558 d2sc = dbl2str(number, decim, ',')
559
560 end function d2sc
561 !*********************************************************************************************************************************
562
563
564 !*********************************************************************************************************************************
565 !> \brief Convert a single-precision real to a nice character string. Single-precision wrapper for dbl2str.
566 !!
567 !! \param number Value to convert
568 !! \param decim Number of decimals to use
569 !! \param mark Decimal mark to separate the integer and fractional parts; single character, e.g. "," (optional; default: ".")
570 !! \retval real2str String containing the real
571
572 pure function real2str(number, decim, mark)
573 implicit none
574 real, intent(in) :: number
575 integer, intent(in) :: decim
576 character, intent(in), optional :: mark*(*)
577 character :: real2str*(max(ceiling(log10(abs(number)+sqrt(epsilon(number)))),1) - (sign(1,floor(number))-1)/2 + decim + 1)
578
579 if(present(mark)) then
580 real2str = dbl2str(dble(number), decim, mark)
581 else
582 real2str = dbl2str(dble(number), decim)
583 end if
584
585 end function real2str
586 !*********************************************************************************************************************************
587
588
589
590end module sufr_text
591!***********************************************************************************************************************************
592
Provides kinds and related constants/routines.
Definition kinds.f90:26
integer, parameter double
Double-precision float. Precision = 15, range = 307.
Definition kinds.f90:35
integer, parameter long
Long integer.
Definition kinds.f90:31
System-related procedures.
Definition system.f90:23
subroutine error(message, unit)
Print an error to StdOut or StErr.
Definition system.f90:671
subroutine find_free_io_unit(unit)
Find the first unused I/O unit larger than 100.
Definition system.f90:951
subroutine warn(message, unit)
Print a warning to StdOut or StErr.
Definition system.f90:646
Procedures to manipulate text/strings.
Definition text.f90:21
pure character function, dimension(max(number, 1)) tabs(number)
Print multiple tab characters.
Definition text.f90:411
pure character function, dimension((len(str16)+1)/2) utf16to8(str16)
Convert a UTF-16 string to UTF-8.
Definition text.f90:112
subroutine replace_string_in_textfile(file_in, file_out, str_srch, str_repl, status)
Search and replace occurences of a string in a text file. Lines up to 9999 characters only,...
Definition text.f90:314
integer function count_substring(string, substr)
Count how many times a substring is present in a string.
Definition text.f90:271
pure character function, dimension(len(str)) lowercase(str)
Make a string lower case.
Definition text.f90:35
pure subroutine replace_substring(string, str_srch, str_repl)
Search and replace occurences of a substring in a string as often as the search string is found.
Definition text.f90:144
subroutine remove_substring(string, substr, debug)
Remove a substring from a string, if present.
Definition text.f90:222
pure character function, dimension(sign(1, floor(number)) -1) real2str(number, decim, mark)
Convert a single-precision real to a nice character string. Single-precision wrapper for dbl2str.
Definition text.f90:573
pure logical function string_contains_one_of(string, characters)
Verify whether a string contains any of a given list of characters.
Definition text.f90:384
pure character function, dimension(sign(1_long, floor(number, long)) -1) dbl2str(number, decim, mark)
Convert a double-precision real to a nice character string. Difference with the F0 format descriptor:...
Definition text.f90:455
pure character function, dimension(sign(1_long, floor(number, long)) -1) d2s(number, decim, mark)
Convert a double-precision real to a nice character string. Short alias for dbl2str().
Definition text.f90:520
pure character function, dimension(sign(1_long, floor(number, long)) -1) d2sc(number, decim)
Convert a double-precision real to a nice character string using a comma as decimal mark....
Definition text.f90:549
pure character function, dimension(len(str)) uppercase(str)
Make a string upper case.
Definition text.f90:59
pure character function, dimension(sign(1, number) -1) int2str(number)
Convert an integer to a character string of the proper length.
Definition text.f90:435
pure character function, dimension(len(str)) uppercaseinitial(str)
Make a string lower case with an upper-case initial.
Definition text.f90:83