LCOV - code coverage report
Current view: top level - src - hsd_data.f90 (source / functions) Coverage Total Hit
Test: coverage.info Lines: 56.7 % 127 72
Test Date: 2026-02-15 21:36:29 Functions: 75.0 % 8 6

            Line data    Source code
       1              : !> hsd-data — Multi-format structured data IO library for Fortran.
       2              : !>
       3              : !> This is the main public API module. It re-exports everything from
       4              : !> hsd-fortran and adds unified multi-format IO via data_load/data_dump.
       5              : !>
       6              : !> ## Example usage
       7              : !>
       8              : !> ```fortran
       9              : !> use hsd_data
      10              : !> type(hsd_table) :: root
      11              : !> type(hsd_error_t), allocatable :: error
      12              : !>
      13              : !> call data_load("input.hsd", root, error)
      14              : !> call data_dump(root, "output.json", error)
      15              : !> ```
      16              : module hsd_data
      17              :   ! Re-export the full hsd-fortran public API
      18              :   use hsd, only: dp, sp, &
      19              :       & hsd_error_t, &
      20              :       & HSD_STAT_OK, HSD_STAT_SYNTAX_ERROR, HSD_STAT_UNCLOSED_TAG, &
      21              :       & HSD_STAT_UNCLOSED_ATTRIB, HSD_STAT_UNCLOSED_QUOTE, HSD_STAT_ORPHAN_TEXT, &
      22              :       & HSD_STAT_INCLUDE_CYCLE, HSD_STAT_INCLUDE_DEPTH, HSD_STAT_FILE_NOT_FOUND, &
      23              :       & HSD_STAT_IO_ERROR, HSD_STAT_TYPE_ERROR, HSD_STAT_NOT_FOUND, &
      24              :       & HSD_STAT_SCHEMA_ERROR, &
      25              :       & hsd_node, hsd_table, hsd_value, hsd_node_ptr, hsd_iterator, &
      26              :       & new_table, new_value, &
      27              :       & VALUE_TYPE_NONE, VALUE_TYPE_STRING, VALUE_TYPE_INTEGER, &
      28              :       & VALUE_TYPE_REAL, VALUE_TYPE_LOGICAL, VALUE_TYPE_ARRAY, VALUE_TYPE_COMPLEX, &
      29              :       & hsd_load, hsd_load_string, hsd_dump, hsd_dump_to_string, &
      30              :       & hsd_visitor_t, hsd_accept, &
      31              :       & hsd_get, hsd_get_or, hsd_get_or_set, hsd_get_matrix, &
      32              :       & hsd_set, &
      33              :       & hsd_get_child, hsd_get_table, hsd_has_child, hsd_remove_child, &
      34              :       & hsd_get_type, hsd_is_table, hsd_is_value, hsd_is_array, &
      35              :       & hsd_child_count, hsd_get_keys, hsd_get_attrib, hsd_has_attrib, &
      36              :       & hsd_set_attrib, hsd_rename_child, hsd_get_choice, &
      37              :       & hsd_merge, hsd_clone, hsd_table_equal, &
      38              :       & hsd_require, hsd_validate_range, hsd_validate_one_of, hsd_get_with_unit, &
      39              :       & hsd_schema_t, hsd_field_def_t, &
      40              :       & FIELD_REQUIRED, FIELD_OPTIONAL, &
      41              :       & FIELD_TYPE_ANY, FIELD_TYPE_STRING, FIELD_TYPE_INTEGER, &
      42              :       & FIELD_TYPE_REAL, FIELD_TYPE_LOGICAL, FIELD_TYPE_TABLE, &
      43              :       & FIELD_TYPE_ARRAY, FIELD_TYPE_COMPLEX, &
      44              :       & schema_init, schema_destroy, schema_add_field, schema_add_field_enum, &
      45              :       & schema_validate, schema_validate_strict
      46              : 
      47              :   ! hsd-data specific modules
      48              :   use hsd_data_common, only: &
      49              :       & DATA_FMT_AUTO, DATA_FMT_HSD, DATA_FMT_XML, DATA_FMT_JSON, &
      50              :       & DATA_FMT_TOML, DATA_FMT_HDF5, DATA_FMT_YAML, &
      51              :       & data_detect_format, data_format_available
      52              : 
      53              :   ! Backends
      54              :   use hsd_data_hsd, only: &
      55              :       & hsd_backend_load, hsd_backend_load_string, &
      56              :       & hsd_backend_dump, hsd_backend_dump_to_string
      57              :   use hsd_data_xml_parser, only: xml_parse_file, xml_parse_string
      58              :   use hsd_data_xml_writer, only: xml_dump_file, xml_dump_to_string
      59              :   use hsd_data_json_parser, only: json_parse_file, json_parse_string
      60              :   use hsd_data_json_writer, only: json_dump_file, json_dump_to_string
      61              :   use hsd_data_yaml_parser, only: yaml_parse_file, yaml_parse_string
      62              :   use hsd_data_yaml_writer, only: yaml_dump_file, yaml_dump_to_string
      63              : #ifdef WITH_TOML
      64              :   use hsd_data_toml, only: &
      65              :       & toml_backend_load, toml_backend_load_string, &
      66              :       & toml_backend_dump, toml_backend_dump_to_string
      67              : #endif
      68              : #ifdef WITH_HDF5
      69              :   use hsd_data_hdf5, only: hdf5_backend_load, hdf5_backend_dump
      70              : #endif
      71              : 
      72              :   implicit none(type, external)
      73              :   private
      74              : 
      75              :   ! --- Re-export hsd-fortran public symbols ---
      76              : 
      77              :   ! Types
      78              :   public :: dp, sp
      79              :   public :: hsd_error_t
      80              :   public :: hsd_node, hsd_table, hsd_value, hsd_node_ptr, hsd_iterator
      81              :   public :: new_table, new_value
      82              :   public :: hsd_visitor_t, hsd_accept
      83              : 
      84              :   ! Status codes
      85              :   public :: HSD_STAT_OK, HSD_STAT_SYNTAX_ERROR, HSD_STAT_UNCLOSED_TAG
      86              :   public :: HSD_STAT_UNCLOSED_ATTRIB, HSD_STAT_UNCLOSED_QUOTE, HSD_STAT_ORPHAN_TEXT
      87              :   public :: HSD_STAT_INCLUDE_CYCLE, HSD_STAT_INCLUDE_DEPTH, HSD_STAT_FILE_NOT_FOUND
      88              :   public :: HSD_STAT_IO_ERROR, HSD_STAT_TYPE_ERROR, HSD_STAT_NOT_FOUND
      89              :   public :: HSD_STAT_SCHEMA_ERROR
      90              : 
      91              :   ! Value type constants
      92              :   public :: VALUE_TYPE_NONE, VALUE_TYPE_STRING, VALUE_TYPE_INTEGER
      93              :   public :: VALUE_TYPE_REAL, VALUE_TYPE_LOGICAL, VALUE_TYPE_ARRAY
      94              :   public :: VALUE_TYPE_COMPLEX
      95              : 
      96              :   ! Accessors / mutators / query
      97              :   public :: hsd_get, hsd_get_or, hsd_get_or_set, hsd_get_matrix
      98              :   public :: hsd_set
      99              :   public :: hsd_get_child, hsd_get_table, hsd_has_child, hsd_remove_child
     100              :   public :: hsd_get_type, hsd_is_table, hsd_is_value, hsd_is_array
     101              :   public :: hsd_child_count, hsd_get_keys, hsd_get_attrib, hsd_has_attrib
     102              :   public :: hsd_set_attrib, hsd_rename_child, hsd_get_choice
     103              :   public :: hsd_merge, hsd_clone, hsd_table_equal
     104              : 
     105              :   ! Validation / schema
     106              :   public :: hsd_require, hsd_validate_range, hsd_validate_one_of, hsd_get_with_unit
     107              :   public :: hsd_schema_t, hsd_field_def_t
     108              :   public :: FIELD_REQUIRED, FIELD_OPTIONAL
     109              :   public :: FIELD_TYPE_ANY, FIELD_TYPE_STRING, FIELD_TYPE_INTEGER
     110              :   public :: FIELD_TYPE_REAL, FIELD_TYPE_LOGICAL, FIELD_TYPE_TABLE
     111              :   public :: FIELD_TYPE_ARRAY, FIELD_TYPE_COMPLEX
     112              :   public :: schema_init, schema_destroy, schema_add_field, schema_add_field_enum
     113              :   public :: schema_validate, schema_validate_strict
     114              : 
     115              :   ! --- hsd-data public symbols ---
     116              : 
     117              :   ! HSD format IO (from hsd-fortran)
     118              :   public :: hsd_load, hsd_load_string, hsd_dump, hsd_dump_to_string
     119              : 
     120              :   ! Format constants
     121              :   public :: DATA_FMT_AUTO, DATA_FMT_HSD, DATA_FMT_XML, DATA_FMT_JSON
     122              :   public :: DATA_FMT_TOML, DATA_FMT_HDF5, DATA_FMT_YAML
     123              : 
     124              :   ! Unified IO
     125              :   public :: data_load, data_load_string
     126              :   public :: data_dump, data_dump_to_string
     127              :   public :: data_detect_format, data_format_available
     128              :   public :: data_convert
     129              : 
     130              : contains
     131              : 
     132              :   !> Load structured data from a file into an HSD tree.
     133              :   !>
     134              :   !> If fmt is DATA_FMT_AUTO (default), the format is detected from the file
     135              :   !> extension. Supported extensions: .hsd, .xml, .json, .toml, .h5/.hdf5
     136              :   !>
     137              :   !> When root_name is given the document element (XML) or first top-level
     138              :   !> child (HSD/JSON) must have that name, otherwise an error is returned.
     139              :   !>
     140              :   !> @param filename   Path to the input file.
     141              :   !> @param root       Output HSD tree (overwritten on success).
     142              :   !> @param error      Optional error descriptor; allocated on failure.
     143              :   !> @param fmt        Optional format constant (DATA_FMT_*). Default: auto-detect.
     144              :   !> @param root_name  Optional expected root tag name.
     145              :   !> @param wrap_name   Optional: wrap loaded content in a named root element.
     146              :   !>                    If present, the loaded content is placed as a child
     147              :   !>                    named wrap_name inside a new anonymous document table.
     148              :   !>                    If the loaded tree already has a single child whose
     149              :   !>                    name matches wrap_name, no additional wrapping is
     150              :   !>                    applied (idempotent).
     151          432 :   subroutine data_load(filename, root, error, fmt, root_name, wrap_name)
     152              :     character(len=*), intent(in) :: filename
     153              :     type(hsd_table), intent(out) :: root
     154              :     type(hsd_error_t), allocatable, intent(out), optional :: error
     155              :     integer, intent(in), optional :: fmt
     156              :     character(len=*), intent(in), optional :: root_name
     157              :     character(len=*), intent(in), optional :: wrap_name
     158              : 
     159          162 :     integer :: actual_fmt
     160              : 
     161          162 :     if (present(fmt)) then
     162          141 :       actual_fmt = fmt
     163              :     else
     164           21 :       actual_fmt = DATA_FMT_AUTO
     165              :     end if
     166              : 
     167          162 :     if (actual_fmt == DATA_FMT_AUTO) then
     168           47 :       actual_fmt = data_detect_format(filename)
     169           47 :       if (actual_fmt < 0) then
     170            0 :         if (present(error)) then
     171            0 :           allocate(error)
     172            0 :           error%code = HSD_STAT_IO_ERROR
     173            0 :           error%message = "Cannot detect format from extension: " // trim(filename)
     174              :         end if
     175            0 :         return
     176              :       end if
     177              :     end if
     178              : 
     179           57 :     select case (actual_fmt)
     180              :     case (DATA_FMT_HSD)
     181           57 :       call hsd_backend_load(filename, root, error)
     182              :     case (DATA_FMT_XML)
     183           23 :       call xml_parse_file(filename, root, error)
     184              :     case (DATA_FMT_JSON)
     185           34 :       call json_parse_file(filename, root, error)
     186              :     case (DATA_FMT_YAML)
     187           19 :       call yaml_parse_file(filename, root, error)
     188              : #ifdef WITH_TOML
     189              :     case (DATA_FMT_TOML)
     190           16 :       call toml_backend_load(filename, root, error)
     191              : #endif
     192              : #ifdef WITH_HDF5
     193              :     case (DATA_FMT_HDF5)
     194           13 :       call hdf5_backend_load(filename, root, error)
     195              : #endif
     196              :     case default
     197          324 :       if (present(error)) then
     198            0 :         allocate(error)
     199            0 :         error%code = HSD_STAT_IO_ERROR
     200            0 :         error%message = "Unsupported or unavailable format"
     201              :       end if
     202              :     end select
     203              : 
     204              :     ! Validate root_name if loading succeeded and root_name is provided
     205          162 :     if (present(root_name)) then
     206            4 :       if (present(error)) then
     207            4 :         if (allocated(error)) return
     208              :       end if
     209            4 :       call check_root_name_(root, root_name, error)
     210              :     end if
     211              : 
     212              :     ! Wrap in a named root element if requested
     213          162 :     if (present(wrap_name)) then
     214            0 :       if (present(error)) then
     215            0 :         if (allocated(error)) return
     216              :       end if
     217            0 :       call wrap_root_(root, wrap_name)
     218              :     end if
     219              : 
     220          162 :   end subroutine data_load
     221              : 
     222              :   !> Load structured data from a string.
     223              :   !>
     224              :   !> The format must be specified explicitly (no auto-detection from string
     225              :   !> content). HDF5 is not supported for string loading.
     226              :   !>
     227              :   !> @param source    Character string containing the serialized data.
     228              :   !> @param root      Output HSD tree (overwritten on success).
     229              :   !> @param fmt       Format constant (DATA_FMT_HSD, DATA_FMT_XML, DATA_FMT_JSON).
     230              :   !> @param error     Optional error descriptor; allocated on failure.
     231              :   !> @param filename  Optional filename for error messages (informational only).
     232         1990 :   subroutine data_load_string(source, root, fmt, error, filename)
     233              :     character(len=*), intent(in) :: source
     234              :     type(hsd_table), intent(out) :: root
     235              :     integer, intent(in) :: fmt
     236              :     type(hsd_error_t), allocatable, intent(out), optional :: error
     237              :     character(len=*), intent(in), optional :: filename
     238              : 
     239          176 :     select case (fmt)
     240              :     case (DATA_FMT_HSD)
     241           35 :       call hsd_backend_load_string(source, root, error, filename)
     242              :     case (DATA_FMT_XML)
     243           29 :       call xml_parse_string(source, root, error, filename)
     244              :     case (DATA_FMT_JSON)
     245           46 :       call json_parse_string(source, root, error, filename)
     246              :     case (DATA_FMT_YAML)
     247            3 :       call yaml_parse_string(source, root, error, filename)
     248              : #ifdef WITH_TOML
     249              :     case (DATA_FMT_TOML)
     250           28 :       call toml_backend_load_string(source, root, error, filename)
     251              : #endif
     252              :     case default
     253          141 :       if (present(error)) then
     254            0 :         allocate(error)
     255            0 :         error%code = HSD_STAT_IO_ERROR
     256            0 :         error%message = "Unsupported or unavailable format"
     257              :       end if
     258              :     end select
     259              : 
     260          162 :   end subroutine data_load_string
     261              : 
     262              :   !> Dump an HSD tree to a file in the specified format.
     263              :   !>
     264              :   !> If fmt is DATA_FMT_AUTO (default), the format is detected from the file
     265              :   !> extension. The file is created or overwritten.
     266              :   !>
     267              :   !> @param root      The HSD tree to serialize.
     268              :   !> @param filename  Path to the output file.
     269              :   !> @param error     Optional error descriptor; allocated on failure.
     270              :   !> @param fmt       Optional format constant (DATA_FMT_*). Default: auto-detect.
     271              :   !> @param pretty    Optional flag for pretty-printing (default: .true.).
     272           50 :   subroutine data_dump(root, filename, error, fmt, pretty)
     273              :     type(hsd_table), intent(in) :: root
     274              :     character(len=*), intent(in) :: filename
     275              :     type(hsd_error_t), allocatable, intent(out), optional :: error
     276              :     integer, intent(in), optional :: fmt
     277              :     logical, intent(in), optional :: pretty
     278              : 
     279           50 :     integer :: actual_fmt
     280              : 
     281           50 :     if (present(fmt)) then
     282           49 :       actual_fmt = fmt
     283              :     else
     284            1 :       actual_fmt = DATA_FMT_AUTO
     285              :     end if
     286              : 
     287           50 :     if (actual_fmt == DATA_FMT_AUTO) then
     288           27 :       actual_fmt = data_detect_format(filename)
     289           27 :       if (actual_fmt < 0) then
     290            0 :         if (present(error)) then
     291            0 :           allocate(error)
     292            0 :           error%code = HSD_STAT_IO_ERROR
     293            0 :           error%message = "Cannot detect format from extension: " // trim(filename)
     294              :         end if
     295            0 :         return
     296              :       end if
     297              :     end if
     298              : 
     299           16 :     select case (actual_fmt)
     300              :     case (DATA_FMT_HSD)
     301           16 :       call hsd_backend_dump(root, filename, error, pretty)
     302              :     case (DATA_FMT_XML)
     303            7 :       call xml_dump_file(root, filename, error, pretty)
     304              :     case (DATA_FMT_JSON)
     305            7 :       call json_dump_file(root, filename, error, pretty)
     306              :     case (DATA_FMT_YAML)
     307            6 :       call yaml_dump_file(root, filename, error, pretty)
     308              : #ifdef WITH_TOML
     309              :     case (DATA_FMT_TOML)
     310            1 :       call toml_backend_dump(root, filename, error, pretty)
     311              : #endif
     312              : #ifdef WITH_HDF5
     313              :     case (DATA_FMT_HDF5)
     314           13 :       call hdf5_backend_dump(root, filename, error, pretty)
     315              : #endif
     316              :     case default
     317          100 :       if (present(error)) then
     318            0 :         allocate(error)
     319            0 :         error%code = HSD_STAT_IO_ERROR
     320            0 :         error%message = "Unsupported or unavailable format"
     321              :       end if
     322              :     end select
     323              : 
     324          191 :   end subroutine data_dump
     325              : 
     326              :   !> Dump an HSD tree to a string in the specified format.
     327              :   !>
     328              :   !> HDF5 is not supported for string output.
     329              :   !>
     330              :   !> @param root    The HSD tree to serialize.
     331              :   !> @param output  Allocatable string receiving the serialized output.
     332              :   !> @param fmt     Format constant (DATA_FMT_HSD, DATA_FMT_XML, DATA_FMT_JSON).
     333              :   !> @param pretty  Optional flag for pretty-printing (default: .true.).
     334              :   !> @param error   Optional error descriptor; allocated on failure.
     335          222 :   subroutine data_dump_to_string(root, output, fmt, pretty, error)
     336              :     type(hsd_table), intent(in) :: root
     337              :     character(len=:), allocatable, intent(out) :: output
     338              :     integer, intent(in) :: fmt
     339              :     logical, intent(in), optional :: pretty
     340              :     type(hsd_error_t), allocatable, intent(out), optional :: error
     341              : 
     342          274 :     select case (fmt)
     343              :     case (DATA_FMT_HSD)
     344           52 :       call hsd_backend_dump_to_string(root, output, pretty)
     345              :     case (DATA_FMT_XML)
     346           44 :       call xml_dump_to_string(root, output, pretty)
     347              :     case (DATA_FMT_JSON)
     348           72 :       call json_dump_to_string(root, output, pretty)
     349              :     case (DATA_FMT_YAML)
     350            5 :       call yaml_dump_to_string(root, output, pretty)
     351              : #ifdef WITH_TOML
     352              :     case (DATA_FMT_TOML)
     353           49 :       call toml_backend_dump_to_string(root, output, pretty)
     354              : #endif
     355              :     case default
     356            0 :       output = ""
     357          222 :       if (present(error)) then
     358            0 :         allocate(error)
     359            0 :         error%code = HSD_STAT_IO_ERROR
     360            0 :         error%message = "Unsupported format for dump_to_string"
     361              :       end if
     362              :     end select
     363              : 
     364           50 :   end subroutine data_dump_to_string
     365              : 
     366              :   !> Convert a file from one format to another.
     367              :   !>
     368              :   !> Convenience routine: loads the input file and dumps to the output file.
     369              :   !> Formats default to DATA_FMT_AUTO (detected from file extensions).
     370              :   !>
     371              :   !> @param input_file   Path to the source file.
     372              :   !> @param output_file  Path to the destination file.
     373              :   !> @param error        Optional error descriptor; allocated on failure.
     374              :   !> @param input_fmt    Optional input format (DATA_FMT_*). Default: auto-detect.
     375              :   !> @param output_fmt   Optional output format (DATA_FMT_*). Default: auto-detect.
     376            2 :   subroutine data_convert(input_file, output_file, error, input_fmt, output_fmt)
     377              :     character(len=*), intent(in) :: input_file
     378              :     character(len=*), intent(in) :: output_file
     379              :     type(hsd_error_t), allocatable, intent(out), optional :: error
     380              :     integer, intent(in), optional :: input_fmt
     381              :     integer, intent(in), optional :: output_fmt
     382              : 
     383            2 :     type(hsd_table) :: root
     384              : 
     385            2 :     call data_load(input_file, root, error, input_fmt)
     386            2 :     if (present(error)) then
     387            2 :       if (allocated(error)) return
     388              :     end if
     389              : 
     390            2 :     call data_dump(root, output_file, error, output_fmt)
     391              : 
     392          260 :   end subroutine data_convert
     393              : 
     394              : 
     395              :   ! ---------------------------------------------------------------------------
     396              :   !  Private helpers
     397              :   ! ---------------------------------------------------------------------------
     398              : 
     399              :   !> Check that root contains a child matching expected_name.
     400              :   !>
     401              :   !> After loading, the root table is anonymous.  In XML the document element
     402              :   !> is unwrapped so its children are direct children of root; in HSD/JSON
     403              :   !> the top-level keys become children.  We check that at least one top-level
     404              :   !> child has the expected name.
     405            4 :   subroutine check_root_name_(root, expected_name, error)
     406              :     type(hsd_table), intent(in) :: root
     407              :     character(len=*), intent(in) :: expected_name
     408              :     type(hsd_error_t), allocatable, intent(inout), optional :: error
     409              : 
     410            4 :     if (hsd_has_child(root, expected_name, case_insensitive=.true.)) return
     411              : 
     412            1 :     if (present(error)) then
     413            1 :       allocate(error)
     414            1 :       error%code = HSD_STAT_IO_ERROR
     415              :       error%message = "Expected root element '" // expected_name &
     416            1 :           & // "' not found"
     417              :     end if
     418              : 
     419            6 :   end subroutine check_root_name_
     420              : 
     421              : 
     422              :   !> Wrap the loaded root table in a named element if not already present.
     423              :   !>
     424              :   !> If root already has a single child whose name matches wrap_name (case-
     425              :   !> insensitive), no wrapping is applied (idempotent). Otherwise, a new
     426              :   !> anonymous root table is created with the old content placed inside
     427              :   !> a child named wrap_name.
     428            0 :   subroutine wrap_root_(root, wrap_name)
     429              :     type(hsd_table), intent(inout) :: root
     430              :     character(len=*), intent(in) :: wrap_name
     431              : 
     432            0 :     type(hsd_table) :: new_root, wrapper
     433              :     class(hsd_node), pointer :: first_child
     434            0 :     integer :: ii
     435              : 
     436              :     ! Check idempotency: if root has exactly one child matching wrap_name, done
     437            0 :     if (root%num_children == 1) then
     438            0 :       call root%get_child(1, first_child)
     439            0 :       if (associated(first_child)) then
     440            0 :         if (allocated(first_child%name)) then
     441              :           block
     442            0 :             character(len=:), allocatable :: lower_child, lower_wrap
     443            0 :             lower_child = to_lower_(first_child%name)
     444            0 :             lower_wrap = to_lower_(wrap_name)
     445            0 :             if (lower_child == lower_wrap) return
     446              :           end block
     447              :         end if
     448              :       end if
     449              :     end if
     450              : 
     451              :     ! Build wrapper: anonymous root -> wrap_name table -> old children
     452            0 :     call new_table(wrapper, name=wrap_name)
     453            0 :     do ii = 1, root%num_children
     454            0 :       call root%get_child(ii, first_child)
     455            0 :       if (associated(first_child)) then
     456              :         select type (first_child)
     457              :         type is (hsd_table)
     458            0 :           call wrapper%add_child(first_child)
     459              :         type is (hsd_value)
     460            0 :           call wrapper%add_child(first_child)
     461              :         end select
     462              :       end if
     463              :     end do
     464              : 
     465              :     ! Replace root content
     466            0 :     call new_table(new_root)
     467            0 :     call new_root%add_child(wrapper)
     468            0 :     root = new_root
     469              : 
     470            4 :   end subroutine wrap_root_
     471              : 
     472              : 
     473              :   !> Simple case-insensitive lowering (ASCII only)
     474            0 :   pure function to_lower_(str) result(lower)
     475              :     character(len=*), intent(in) :: str
     476              :     character(len=:), allocatable :: lower
     477            0 :     integer :: ii, ic
     478            0 :     lower = str
     479            0 :     do ii = 1, len(lower)
     480            0 :       ic = iachar(lower(ii:ii))
     481            0 :       if (ic >= iachar('A') .and. ic <= iachar('Z')) then
     482            0 :         lower(ii:ii) = achar(ic + 32)
     483              :       end if
     484              :     end do
     485            0 :   end function to_lower_
     486              : 
     487            0 : end module hsd_data
        

Generated by: LCOV version 2.0-1