LCOV - code coverage report
Current view: top level - src/api - hsd_api.f90 (source / functions) Coverage Total Hit
Test: coverage.info Lines: 93.8 % 1191 1117
Test Date: 2026-03-19 13:06:14 Functions: 100.0 % 77 77

            Line data    Source code
       1              : !> Unified HSD API module
       2              : !> Merges functionality from accessors, mutators, and query modules.
       3              : module hsd_api
       4              :   use hsd_constants, only: dp
       5              :   use hsd_utils, only: to_lower, string_buffer_t
       6              :   use hsd_error, only: HSD_STAT_OK, HSD_STAT_NOT_FOUND, HSD_STAT_TYPE_ERROR
       7              :   use hsd_types, only: hsd_node_t, hsd_node_ptr_t, new_table, new_value, &
       8              :     & NODE_TYPE_TABLE, NODE_TYPE_VALUE, &
       9              :     & VALUE_TYPE_NONE, VALUE_TYPE_STRING, VALUE_TYPE_INTEGER, &
      10              :     & VALUE_TYPE_REAL, VALUE_TYPE_LOGICAL, VALUE_TYPE_ARRAY, &
      11              :     & VALUE_TYPE_COMPLEX
      12              :   implicit none (type, external)
      13              :   private
      14              : 
      15              :   ! --- Declarations ---
      16              : !> HSD query and navigation operations
      17              : !>
      18              : !> This module provides functionality for navigating HSD tree structures,
      19              : !> introspecting node types, and performing tree operations like merging
      20              : !> and cloning.
      21              : 
      22              :   ! Public types
      23              :   public :: hsd_node_ptr_t
      24              : 
      25              :   ! Public procedures
      26              :   public :: hsd_get_child, hsd_get_table
      27              :   public :: hsd_has_child
      28              :   public :: hsd_remove_child
      29              :   public :: hsd_get_type, hsd_is_table, hsd_is_value, hsd_is_array
      30              :   public :: hsd_child_count, hsd_get_keys
      31              :   public :: hsd_get_attrib, hsd_has_attrib, hsd_set_attrib
      32              :   public :: hsd_rename_child
      33              :   public :: hsd_get_choice
      34              :   public :: hsd_get_children
      35              :   public :: hsd_get_child_tables
      36              :   public :: hsd_merge, hsd_clone
      37              :   public :: hsd_table_equal
      38              :   public :: hsd_set_processed
      39              :   public :: hsd_has_value_children
      40              :   public :: hsd_get_name
      41              : 
      42              : !> HSD data accessors (getters)
      43              : !>
      44              : !> This module provides interfaces and implementations for retrieving data
      45              : !> from HSD tables. It supports type-safe access to scalars, arrays, and
      46              : !> matrices.
      47              : 
      48              :   ! Public interfaces
      49              :   public :: hsd_get, hsd_get_or_set, hsd_get_matrix
      50              :   public :: hsd_get_inline_text
      51              : 
      52              :   !> Generic interface for getting values
      53              :   !>
      54              :   !> All procedures accept an optional `stat` parameter for error status.
      55              :   interface hsd_get
      56              :     module procedure :: hsd_get_string
      57              :     module procedure :: hsd_get_integer
      58              :     module procedure :: hsd_get_real_dp
      59              :     module procedure :: hsd_get_logical
      60              :     module procedure :: hsd_get_complex_dp
      61              :     module procedure :: hsd_get_integer_array
      62              :     module procedure :: hsd_get_real_dp_array
      63              :     module procedure :: hsd_get_logical_array
      64              :     module procedure :: hsd_get_string_array
      65              :     module procedure :: hsd_get_complex_dp_array
      66              :   end interface hsd_get
      67              : 
      68              :   !> Generic interface for getting values with default, writing default back to tree if absent
      69              :   !>
      70              :   !> If the key is not found, the default value is
      71              :   !> written back into the tree. This is critical for generating processed output
      72              :   !> (e.g., dftb_pin.hsd) that contains all defaults.
      73              :   !> stat is HSD_STAT_NOT_FOUND when default is used, HSD_STAT_OK when key existed.
      74              :   interface hsd_get_or_set
      75              :     module procedure :: hsd_get_or_set_string
      76              :     module procedure :: hsd_get_or_set_integer
      77              :     module procedure :: hsd_get_or_set_real_dp
      78              :     module procedure :: hsd_get_or_set_logical
      79              :     module procedure :: hsd_get_or_set_complex_dp
      80              :     module procedure :: hsd_get_or_set_integer_array
      81              :     module procedure :: hsd_get_or_set_real_dp_array
      82              :     module procedure :: hsd_get_or_set_logical_array
      83              :   end interface hsd_get_or_set
      84              : 
      85              :   !> Generic interface for getting 2D matrices
      86              :   interface hsd_get_matrix
      87              :     module procedure :: hsd_get_integer_matrix
      88              :     module procedure :: hsd_get_real_dp_matrix
      89              :     module procedure :: hsd_get_complex_dp_matrix
      90              :   end interface hsd_get_matrix
      91              : 
      92              : !> HSD data mutators (setters)
      93              : !>
      94              : !> This module provides interfaces and implementations for modifying HSD tables.
      95              : !> It supports type-safe setting of scalars and arrays, with automatic path
      96              : !> creation for nested structures.
      97              : 
      98              :   ! Public interface
      99              :   public :: hsd_set
     100              :   public :: hsd_clear_children
     101              : 
     102              :   !> Generic interface for setting values by path
     103              :   interface hsd_set
     104              :     module procedure :: hsd_set_string
     105              :     module procedure :: hsd_set_integer
     106              :     module procedure :: hsd_set_real_dp
     107              :     module procedure :: hsd_set_logical
     108              :     module procedure :: hsd_set_complex_dp
     109              :     module procedure :: hsd_set_integer_array
     110              :     module procedure :: hsd_set_real_dp_array
     111              :     module procedure :: hsd_set_logical_array
     112              :     module procedure :: hsd_set_complex_dp_array
     113              :     module procedure :: hsd_set_string_array
     114              :     module procedure :: hsd_set_integer_matrix
     115              :     module procedure :: hsd_set_real_dp_matrix
     116              :     module procedure :: hsd_set_complex_dp_matrix
     117              :   end interface hsd_set
     118              : 
     119              : 
     120              :   contains
     121              : 
     122              :   ! --- Implementations ---
     123              : 
     124              :   !> Normalize a path string by removing leading/trailing slashes and collapsing
     125              :   !> consecutive slashes. E.g. "/Geometry//Periodic/" → "Geometry/Periodic"
     126          800 :   pure function normalize_path(path) result(normalized)
     127              :     character(len=*), intent(in) :: path
     128              :     character(len=:), allocatable :: normalized
     129              : 
     130          800 :     integer :: i, n, out_len
     131          800 :     logical :: prev_was_slash
     132              : 
     133          800 :     n = len_trim(path)
     134          800 :     if (n == 0) then
     135            3 :       normalized = ""
     136            3 :       return
     137              :     end if
     138              : 
     139              :     block
     140          797 :       character(len=n) :: buf
     141          797 :       out_len = 0
     142          797 :       prev_was_slash = .true.  ! treat start as after slash to skip leading "/"
     143              : 
     144         7725 :       do i = 1, n
     145         7725 :         if (path(i:i) == '/') then
     146          220 :           if (.not. prev_was_slash) then
     147          215 :             out_len = out_len + 1
     148          215 :             buf(out_len:out_len) = '/'
     149              :           end if
     150          220 :           prev_was_slash = .true.
     151              :         else
     152         6708 :           out_len = out_len + 1
     153         6708 :           buf(out_len:out_len) = path(i:i)
     154         6708 :           prev_was_slash = .false.
     155              :         end if
     156              :       end do
     157              : 
     158              :       ! Remove trailing slash
     159          797 :       if (out_len > 0) then
     160          796 :         if (buf(out_len:out_len) == '/') out_len = out_len - 1
     161              :       end if
     162              : 
     163          797 :       if (out_len > 0) then
     164          796 :         normalized = buf(1:out_len)
     165              :       else
     166            1 :         normalized = ""
     167              :       end if
     168              :     end block
     169              : 
     170          800 :   end function normalize_path
     171              : 
     172              :   !> Resolve a path into parent table + leaf name.
     173           19 :   subroutine resolve_path_parent_(table, path, parent_table, child_name, stat)
     174              :     type(hsd_node_t), intent(in), target :: table
     175              :     character(len=*), intent(in) :: path
     176              :     type(hsd_node_t), pointer, intent(out) :: parent_table
     177              :     character(len=:), allocatable, intent(out) :: child_name
     178              :     integer, intent(out) :: stat
     179              : 
     180              :     type(hsd_node_t), pointer :: parent_node
     181           19 :     character(len=:), allocatable :: norm, parent_path
     182           19 :     integer :: last_slash, local_stat
     183              : 
     184           19 :     nullify(parent_table)
     185           19 :     child_name = ""
     186           19 :     norm = normalize_path(path)
     187           19 :     if (len(norm) == 0) then
     188            2 :       stat = HSD_STAT_NOT_FOUND
     189            2 :       return
     190              :     end if
     191              : 
     192           17 :     last_slash = index(norm, "/", back=.true.)
     193           17 :     if (last_slash > 0) then
     194            8 :       parent_path = norm(1:last_slash-1)
     195            8 :       child_name = norm(last_slash+1:)
     196            8 :       call hsd_get_child(table, parent_path, parent_node, local_stat)
     197            8 :       if (local_stat /= HSD_STAT_OK .or. .not. associated(parent_node)) then
     198            1 :         stat = HSD_STAT_NOT_FOUND
     199            1 :         return
     200              :       end if
     201            7 :       if (parent_node%node_type == NODE_TYPE_TABLE) then
     202            4 :         parent_table => parent_node
     203              :       else
     204            3 :         stat = HSD_STAT_TYPE_ERROR
     205            3 :         return
     206              :       end if
     207              :     else
     208            9 :       parent_table => table
     209            9 :       child_name = norm
     210              :     end if
     211              : 
     212           13 :     stat = HSD_STAT_OK
     213          819 :   end subroutine resolve_path_parent_
     214              : 
     215              :   !> Check if a table has a child with given name
     216           26 :   function hsd_has_child(table, name) result(has)
     217              :     type(hsd_node_t), intent(in), target :: table
     218              :     character(len=*), intent(in) :: name
     219              :     logical :: has
     220              : 
     221              :     type(hsd_node_t), pointer :: child
     222           26 :     integer :: stat
     223              : 
     224           28 :     if (index(name, "/") > 0) then
     225              :       ! Path-based lookup - navigate through nested tables
     226            2 :       call hsd_get_child(table, name, child, stat)
     227            2 :       has = (stat == HSD_STAT_OK .and. associated(child))
     228              :     else
     229           24 :       has = table%has_child(name)
     230              :     end if
     231              : 
     232           45 :   end function hsd_has_child
     233              : 
     234              :   !> Remove a child from a table by name
     235              :   !>
     236              :   !> Supports path-based navigation with "/" separator for nested tables.
     237              :   !> The last component of the path is the child to remove.
     238            7 :   subroutine hsd_remove_child(table, path, stat)
     239              :     type(hsd_node_t), intent(inout), target :: table
     240              :     character(len=*), intent(in) :: path
     241              :     integer, intent(out), optional :: stat
     242              : 
     243              :     type(hsd_node_t), pointer :: parent_table
     244            7 :     character(len=:), allocatable :: child_name
     245            7 :     integer :: local_stat
     246              : 
     247            7 :     call resolve_path_parent_(table, path, parent_table, child_name, local_stat)
     248            7 :     if (local_stat /= HSD_STAT_OK) then
     249            2 :       if (present(stat)) stat = local_stat
     250            2 :       return
     251              :     end if
     252              : 
     253            5 :     call parent_table%remove_child_by_name(child_name, local_stat)
     254            5 :     if (present(stat)) stat = local_stat
     255              : 
     256           33 :   end subroutine hsd_remove_child
     257              : 
     258              :   !> Get the type of a value at the given path
     259              :   !>
     260              :   !> Returns one of: VALUE_TYPE_NONE (not found or is table), VALUE_TYPE_STRING,
     261              :   !> VALUE_TYPE_INTEGER, VALUE_TYPE_REAL, VALUE_TYPE_LOGICAL, VALUE_TYPE_ARRAY,
     262              :   !> VALUE_TYPE_COMPLEX
     263            9 :   function hsd_get_type(table, path) result(val_type)
     264              :     type(hsd_node_t), intent(in), target :: table
     265              :     character(len=*), intent(in) :: path
     266              :     integer :: val_type
     267              : 
     268              :     type(hsd_node_t), pointer :: child
     269            9 :     integer :: local_stat
     270              : 
     271            9 :     val_type = VALUE_TYPE_NONE
     272            9 :     call hsd_get_child(table, path, child, local_stat)
     273              : 
     274            9 :     if (local_stat /= 0 .or. .not. associated(child)) return
     275              : 
     276            6 :     if (child%node_type == NODE_TYPE_VALUE) then
     277            5 :       val_type = child%value_type
     278              :     end if
     279              : 
     280           16 :   end function hsd_get_type
     281              : 
     282              :   !> Check if the node at path is a table (container)
     283           20 :   function hsd_is_table(table, path) result(is_tbl)
     284              :     type(hsd_node_t), intent(in), target :: table
     285              :     character(len=*), intent(in) :: path
     286              :     logical :: is_tbl
     287              : 
     288              :     type(hsd_node_t), pointer :: child
     289           20 :     integer :: local_stat
     290              : 
     291           20 :     is_tbl = .false.
     292           20 :     call hsd_get_child(table, path, child, local_stat)
     293              : 
     294           20 :     if (local_stat /= 0 .or. .not. associated(child)) return
     295              : 
     296           19 :     if (child%node_type == NODE_TYPE_TABLE) then
     297           18 :       is_tbl = .true.
     298              :     end if
     299              : 
     300           29 :   end function hsd_is_table
     301              : 
     302              :   !> Check if the node at path is a value (leaf)
     303            5 :   function hsd_is_value(table, path) result(is_val)
     304              :     type(hsd_node_t), intent(in), target :: table
     305              :     character(len=*), intent(in) :: path
     306              :     logical :: is_val
     307              : 
     308              :     type(hsd_node_t), pointer :: child
     309            5 :     integer :: local_stat
     310              : 
     311            5 :     is_val = .false.
     312            5 :     call hsd_get_child(table, path, child, local_stat)
     313              : 
     314            5 :     if (local_stat /= 0 .or. .not. associated(child)) return
     315              : 
     316            4 :     if (child%node_type == NODE_TYPE_VALUE) then
     317            3 :       is_val = .true.
     318              :     end if
     319              : 
     320           25 :   end function hsd_is_value
     321              : 
     322              :   !> Check if the node at path contains array data
     323            1 :   function hsd_is_array(table, path) result(is_arr)
     324              :     type(hsd_node_t), intent(in), target :: table
     325              :     character(len=*), intent(in) :: path
     326              :     logical :: is_arr
     327              : 
     328            2 :     is_arr = (hsd_get_type(table, path) == VALUE_TYPE_ARRAY)
     329              : 
     330            6 :   end function hsd_is_array
     331              : 
     332              :   !> Get the number of children in a table at the given path
     333              :   !>
     334              :   !> Returns 0 if path not found or is not a table
     335           10 :   function hsd_child_count(table, path) result(count)
     336              :     type(hsd_node_t), intent(in), target :: table
     337              :     character(len=*), intent(in) :: path
     338              :     integer :: count
     339              : 
     340              :     type(hsd_node_t), pointer :: child
     341           10 :     integer :: local_stat
     342              : 
     343           10 :     count = 0
     344              : 
     345           10 :     if (len_trim(path) == 0) then
     346              :       ! Empty path means the root table itself
     347            5 :       count = table%num_children
     348            5 :       return
     349              :     end if
     350              : 
     351            5 :     call hsd_get_child(table, path, child, local_stat)
     352              : 
     353            5 :     if (local_stat /= 0 .or. .not. associated(child)) return
     354              : 
     355            5 :     if (child%node_type == NODE_TYPE_TABLE) then
     356            4 :       count = child%num_children
     357              :     end if
     358              : 
     359           11 :   end function hsd_child_count
     360              : 
     361              :   !> Get the keys (child names) from a table at the given path
     362            9 :   subroutine hsd_get_keys(table, path, keys, stat)
     363              :     type(hsd_node_t), intent(in), target :: table
     364              :     character(len=*), intent(in) :: path
     365              :     character(len=:), allocatable, intent(out) :: keys(:)
     366              :     integer, intent(out), optional :: stat
     367              : 
     368              :     type(hsd_node_t), pointer :: child
     369            9 :     integer :: local_stat
     370              : 
     371            9 :     if (present(stat)) stat = HSD_STAT_OK
     372              : 
     373            9 :     if (len_trim(path) == 0) then
     374              :       ! Empty path means the root table itself
     375            5 :       call table%get_keys(keys)
     376            5 :       return
     377              :     end if
     378              : 
     379            4 :     call hsd_get_child(table, path, child, local_stat)
     380              : 
     381            4 :     if (local_stat /= 0 .or. .not. associated(child)) then
     382            1 :       allocate(character(len=1) :: keys(0))
     383            1 :       if (present(stat)) stat = HSD_STAT_NOT_FOUND
     384            1 :       return
     385              :     end if
     386              : 
     387            3 :     if (child%node_type == NODE_TYPE_TABLE) then
     388            2 :       call child%get_keys(keys)
     389              :     else
     390            1 :       allocate(character(len=1) :: keys(0))
     391            1 :       if (present(stat)) stat = HSD_STAT_TYPE_ERROR
     392              :     end if
     393              : 
     394           19 :   end subroutine hsd_get_keys
     395              : 
     396              :   !> Get a child node by path (using / as separator)
     397          781 :   subroutine hsd_get_child(table, path, child, stat)
     398              :     type(hsd_node_t), intent(in), target :: table
     399              :     character(len=*), intent(in) :: path
     400              :     type(hsd_node_t), pointer, intent(out) :: child
     401              :     integer, intent(out), optional :: stat
     402              : 
     403          781 :     character(len=:), allocatable :: norm
     404              : 
     405          781 :     child => null()
     406              :     ! stat will be overriden by subroutine below.
     407          770 :     if (present(stat)) stat = HSD_STAT_OK
     408              : 
     409          781 :     norm = normalize_path(path)
     410          781 :     if (len(norm) == 0) then
     411            2 :       if (present(stat)) stat = HSD_STAT_NOT_FOUND
     412            2 :       return
     413              :     end if
     414              : 
     415              :     ! Delegate to recursive helper
     416          779 :     call get_first_child_table(table, norm, child, stat)
     417              : 
     418          790 :   end subroutine hsd_get_child
     419              : 
     420              :   !> Helper to navigate path and get child
     421          978 :   recursive subroutine get_first_child_table(table, path, child, stat)
     422              :     type(hsd_node_t), intent(in), target :: table
     423              :     character(len=*), intent(in) :: path
     424              :     type(hsd_node_t), pointer, intent(out) :: child
     425              :     integer, intent(out), optional :: stat
     426              : 
     427          978 :     character(len=:), allocatable :: remaining, segment
     428              :     type(hsd_node_t), pointer :: current
     429          978 :     integer :: sep_pos
     430              : 
     431          978 :     child => null()
     432          978 :     remaining = path
     433              : 
     434              :     ! Get first segment
     435          978 :     sep_pos = index(remaining, "/")
     436          978 :     if (sep_pos > 0) then
     437          202 :       segment = remaining(1:sep_pos-1)
     438          202 :       remaining = remaining(sep_pos+1:)
     439              :     else
     440          776 :       segment = remaining
     441          776 :       remaining = ""
     442              :     end if
     443              : 
     444              :     ! Find child with this name
     445          978 :     call table%get_child_by_name(segment, current)
     446              : 
     447          978 :     if (.not. associated(current)) then
     448           84 :       if (present(stat)) stat = HSD_STAT_NOT_FOUND
     449           84 :       return
     450              :     end if
     451              : 
     452              :     ! If no more path, return this node
     453          894 :     if (len_trim(remaining) == 0) then
     454          693 :       child => current
     455          693 :       if (present(stat)) stat = HSD_STAT_OK
     456          693 :       return
     457              :     end if
     458              : 
     459              :     ! Otherwise, recurse into child table
     460          201 :     if (current%node_type == NODE_TYPE_TABLE) then
     461          199 :       call get_first_child_table(current, remaining, child, stat)
     462              :     else
     463            2 :       if (present(stat)) stat = HSD_STAT_NOT_FOUND
     464              :     end if
     465              : 
     466         2737 :   end subroutine get_first_child_table
     467              : 
     468              :   !> Get a table child by path
     469           18 :   subroutine hsd_get_table(table, path, child_table, stat)
     470              :     type(hsd_node_t), intent(in), target :: table
     471              :     character(len=*), intent(in) :: path
     472              :     type(hsd_node_t), pointer, intent(out) :: child_table
     473              :     integer, intent(out), optional :: stat
     474              : 
     475              :     type(hsd_node_t), pointer :: child
     476           18 :     integer :: local_stat
     477              : 
     478           18 :     child_table => null()
     479           18 :     call hsd_get_child(table, path, child, local_stat)
     480              : 
     481           18 :     if (associated(child)) then
     482           17 :       if (child%node_type == NODE_TYPE_TABLE) then
     483            5 :         child_table => child
     484            5 :         child_table%processed = .true.
     485            5 :         if (present(stat)) stat = HSD_STAT_OK
     486              :       else
     487           12 :         if (present(stat)) stat = HSD_STAT_NOT_FOUND
     488              :       end if
     489              :     else
     490            1 :       if (present(stat)) stat = local_stat
     491              :     end if
     492              : 
     493           36 :   end subroutine hsd_get_table
     494              : 
     495              :   !> Get an attribute from a node at the given path
     496              :   !>
     497              :   !> Example: For `LatticeConstant [Angstrom] = 5.4`, the attribute is "Angstrom"
     498           11 :   subroutine hsd_get_attrib(table, path, attrib, stat)
     499              :     type(hsd_node_t), intent(in), target :: table
     500              :     character(len=*), intent(in) :: path
     501              :     character(len=:), allocatable, intent(out) :: attrib
     502              :     integer, intent(out), optional :: stat
     503              : 
     504              :     type(hsd_node_t), pointer :: child
     505           11 :     integer :: local_stat
     506              : 
     507           11 :     call hsd_get_child(table, path, child, local_stat)
     508              : 
     509           11 :     if (local_stat /= 0 .or. .not. associated(child)) then
     510            1 :       if (present(stat)) stat = HSD_STAT_NOT_FOUND
     511            1 :       attrib = ""
     512            1 :       return
     513              :     end if
     514              : 
     515              :     ! Node exists - return OK regardless of whether attribute is set
     516           10 :     if (allocated(child%attrib)) then
     517            8 :       attrib = child%attrib
     518              :     else
     519            2 :       attrib = ""
     520              :     end if
     521           10 :     if (present(stat)) stat = HSD_STAT_OK
     522              : 
     523           29 :   end subroutine hsd_get_attrib
     524              : 
     525              :   !> Check if a node at the given path has an attribute
     526            5 :   function hsd_has_attrib(table, path) result(has)
     527              :     type(hsd_node_t), intent(in), target :: table
     528              :     character(len=*), intent(in) :: path
     529              :     logical :: has
     530              : 
     531              :     type(hsd_node_t), pointer :: child
     532            5 :     integer :: local_stat
     533              : 
     534            5 :     has = .false.
     535            5 :     call hsd_get_child(table, path, child, local_stat)
     536              : 
     537            5 :     if (local_stat /= 0 .or. .not. associated(child)) return
     538              : 
     539            4 :     has = allocated(child%attrib)
     540              : 
     541           16 :   end function hsd_has_attrib
     542              : 
     543              :   !> Set an attribute on a node at the given path
     544              :   !>
     545              :   !> Example: Setting "Angstrom" on `LatticeConstant` makes it render as
     546              :   !> `LatticeConstant [Angstrom] = 5.4`
     547            2 :   subroutine hsd_set_attrib(table, path, attrib, stat)
     548              :     type(hsd_node_t), intent(inout), target :: table
     549              :     character(len=*), intent(in) :: path
     550              :     character(len=*), intent(in) :: attrib
     551              :     integer, intent(out), optional :: stat
     552              : 
     553              :     type(hsd_node_t), pointer :: child
     554            2 :     integer :: local_stat
     555              : 
     556            2 :     call hsd_get_child(table, path, child, local_stat)
     557              : 
     558            2 :     if (local_stat /= 0 .or. .not. associated(child)) then
     559            1 :       if (present(stat)) stat = HSD_STAT_NOT_FOUND
     560            1 :       return
     561              :     end if
     562              : 
     563            1 :     child%attrib = attrib
     564            1 :     if (present(stat)) stat = HSD_STAT_OK
     565              : 
     566            7 :   end subroutine hsd_set_attrib
     567              : 
     568              :   !> Rename a child of a table
     569              :   !>
     570              :   !> Finds the child with `old_name` and changes its name to `new_name`.
     571              :   !> The child's position in the table is preserved. The name index is
     572              :   !> invalidated and rebuilt on next lookup.
     573            3 :   subroutine hsd_rename_child(table, old_name, new_name, stat)
     574              :     type(hsd_node_t), intent(inout), target :: table
     575              :     character(len=*), intent(in) :: old_name
     576              :     character(len=*), intent(in) :: new_name
     577              :     integer, intent(out), optional :: stat
     578              : 
     579              :     type(hsd_node_t), pointer :: child
     580            3 :     integer :: local_stat
     581              :     type(hsd_node_t), pointer :: parent_table
     582            3 :     character(len=:), allocatable :: child_old_name
     583              : 
     584            3 :     call resolve_path_parent_(table, old_name, parent_table, child_old_name, local_stat)
     585            3 :     if (local_stat /= HSD_STAT_OK) then
     586            0 :       if (present(stat)) stat = local_stat
     587            0 :       return
     588              :     end if
     589              : 
     590              :     ! Find the child by name
     591            3 :     call parent_table%get_child_by_name(child_old_name, child)
     592              : 
     593            3 :     if (.not. associated(child)) then
     594            1 :       if (present(stat)) stat = HSD_STAT_NOT_FOUND
     595            1 :       return
     596              :     end if
     597              : 
     598              :     ! Rename it
     599            2 :     child%name = new_name
     600              : 
     601            2 :     if (present(stat)) stat = HSD_STAT_OK
     602              : 
     603            5 :   end subroutine hsd_rename_child
     604              : 
     605              :   !> Get a polymorphic child for dispatch (choice pattern)
     606              :   !>
     607              :   !> This is a convenience for the common HSD pattern where a table has a single
     608              :   !> child whose name is the selector. For example:
     609              :   !>   Driver = ConjugateGradient { ... }
     610              :   !> Here the child's name ("ConjugateGradient") determines the variant and its
     611              :   !> contents are the variant's parameters.
     612              :   !>
     613              :   !> If `path` is empty, looks at the direct children of `table`.
     614              :   !> Returns the name and typed table pointer of the first table child found.
     615            4 :   subroutine hsd_get_choice(table, path, choice_name, choice_table, stat)
     616              :     type(hsd_node_t), intent(in), target :: table
     617              :     character(len=*), intent(in) :: path
     618              :     character(len=:), allocatable, intent(out) :: choice_name
     619              :     type(hsd_node_t), pointer, intent(out) :: choice_table
     620              :     integer, intent(out), optional :: stat
     621              : 
     622              :     type(hsd_node_t), pointer :: parent_node, child
     623              :     type(hsd_node_t), pointer :: parent_table
     624            4 :     integer :: ii, local_stat
     625              : 
     626            4 :     choice_name = ""
     627            4 :     choice_table => null()
     628              : 
     629              :     ! Navigate to parent
     630            4 :     if (len_trim(path) > 0) then
     631            4 :       call hsd_get_child(table, path, parent_node, local_stat)
     632            4 :       if (local_stat /= 0 .or. .not. associated(parent_node)) then
     633            1 :         if (present(stat)) stat = HSD_STAT_NOT_FOUND
     634            1 :         return
     635              :       end if
     636            3 :       if (parent_node%node_type == NODE_TYPE_TABLE) then
     637            3 :         parent_table => parent_node
     638              :       else
     639            0 :         if (present(stat)) stat = HSD_STAT_TYPE_ERROR
     640            0 :         return
     641              :       end if
     642              :     else
     643            0 :       parent_table => table
     644              :     end if
     645              : 
     646              :     ! Find first table child
     647            4 :     do ii = 1, parent_table%num_children
     648            2 :       call parent_table%get_child(ii, child)
     649            2 :       if (.not. associated(child)) cycle
     650            4 :       if (child%node_type == NODE_TYPE_TABLE) then
     651            1 :         if (allocated(child%name)) then
     652            1 :           choice_name = to_lower(child%name)
     653              :         end if
     654            1 :         choice_table => child
     655            1 :         choice_table%processed = .true.
     656            1 :         if (present(stat)) stat = HSD_STAT_OK
     657            1 :         return
     658              :       end if
     659              :     end do
     660              : 
     661              :     ! No table child found - check if there's a value child (leaf dispatch)
     662            2 :     do ii = 1, parent_table%num_children
     663            1 :       call parent_table%get_child(ii, child)
     664            1 :       if (.not. associated(child)) cycle
     665            2 :       if (child%node_type == NODE_TYPE_VALUE) then
     666            1 :         if (allocated(child%string_value)) then
     667            1 :           choice_name = to_lower(child%string_value)
     668            0 :         else if (allocated(child%name)) then
     669            0 :           choice_name = to_lower(child%name)
     670              :         end if
     671            1 :         if (present(stat)) stat = HSD_STAT_OK
     672            1 :         return
     673              :       end if
     674              :     end do
     675              : 
     676            1 :     if (present(stat)) stat = HSD_STAT_NOT_FOUND
     677              : 
     678            7 :   end subroutine hsd_get_choice
     679              : 
     680              :   !> Get all children of a table that match a given name (case-insensitive)
     681              :   !>
     682              :   !> Supports path-based lookup: "Geometry/Atom" will navigate to the
     683              :   !> "Geometry" table then collect all children named "Atom".
     684              :   !> Returns an array of hsd_node_ptr_t pointing to the matching children.
     685              :   !> If no children match, an empty (size-0) array is returned and stat is OK.
     686            5 :   subroutine hsd_get_children(table, path, children, stat)
     687              :     type(hsd_node_t), intent(in), target :: table
     688              :     character(len=*), intent(in) :: path
     689              :     type(hsd_node_ptr_t), allocatable, intent(out) :: children(:)
     690              :     integer, intent(out), optional :: stat
     691              : 
     692            5 :     call collect_named_children_(table, path, children, stat, tables_only=.false.)
     693              : 
     694            4 :   end subroutine hsd_get_children
     695              : 
     696              : 
     697              :   !> Get all table children of a table that match a given name (case-insensitive)
     698              :   !>
     699              :   !> Like hsd_get_children but returns only table-type children.
     700              :   !> Supports path-based lookup: "Geometry/Atom" will navigate to the
     701              :   !> "Geometry" table then collect all table children named "Atom".
     702              :   !> If no children match, an empty (size-0) array is returned.
     703            4 :   subroutine hsd_get_child_tables(table, path, children, stat)
     704              :     type(hsd_node_t), intent(in), target :: table
     705              :     character(len=*), intent(in) :: path
     706              :     type(hsd_node_ptr_t), allocatable, intent(out) :: children(:)
     707              :     integer, intent(out), optional :: stat
     708              : 
     709            4 :     character(len=:), allocatable :: child_name
     710              :     type(hsd_node_t), pointer :: child
     711              :     type(hsd_node_t), pointer :: parent_table
     712            4 :     integer :: local_stat, i, count
     713            4 :     character(len=:), allocatable :: lower_name
     714              : 
     715            0 :     call resolve_path_parent_(table, path, parent_table, child_name, local_stat)
     716            4 :     if (local_stat /= HSD_STAT_OK) then
     717            2 :       allocate(children(0))
     718            2 :       if (present(stat)) stat = local_stat
     719            2 :       return
     720              :     end if
     721              : 
     722            2 :     lower_name = to_lower(child_name)
     723              : 
     724              :     ! First pass: count table matches
     725            2 :     count = 0
     726            8 :     do i = 1, parent_table%num_children
     727            6 :       call parent_table%get_child(i, child)
     728            6 :       if (.not. associated(child)) cycle
     729            6 :       if (.not. allocated(child%name)) cycle
     730            6 :       if (to_lower(child%name) /= lower_name) cycle
     731            7 :       if (child%node_type == NODE_TYPE_TABLE) then
     732            5 :         count = count + 1
     733              :       end if
     734              :     end do
     735              : 
     736              :     ! Allocate result
     737            7 :     allocate(children(count))
     738              : 
     739              :     ! Second pass: fill pointers (table children only)
     740            2 :     count = 0
     741            8 :     do i = 1, parent_table%num_children
     742            6 :       call parent_table%get_child(i, child)
     743            6 :       if (.not. associated(child)) cycle
     744            6 :       if (.not. allocated(child%name)) cycle
     745            6 :       if (to_lower(child%name) /= lower_name) cycle
     746            7 :       if (child%node_type == NODE_TYPE_TABLE) then
     747            5 :         count = count + 1
     748            5 :         children(count)%node => child
     749            5 :         child%processed = .true.
     750              :       end if
     751              :     end do
     752              : 
     753            2 :     if (present(stat)) stat = HSD_STAT_OK
     754              : 
     755           13 :   end subroutine hsd_get_child_tables
     756              : 
     757              : 
     758              :   !> Merge two HSD tables (overlay pattern)
     759              :   !>
     760              :   !> Values from `overlay` are merged into `base`. If a key exists in both,
     761              :   !> the value from `overlay` takes precedence (unless it's a table,
     762              :   !> in which case they are merged recursively).
     763           19 :   recursive subroutine hsd_merge(base, overlay, stat)
     764              :     type(hsd_node_t), intent(inout) :: base
     765              :     type(hsd_node_t), intent(in) :: overlay
     766              :     integer, intent(out), optional :: stat
     767              : 
     768              :     type(hsd_node_t), pointer :: overlay_child, base_child
     769           19 :     type(hsd_node_t) :: cloned_table
     770           19 :     type(hsd_node_t) :: cloned_value
     771           19 :     integer :: i, local_stat
     772              : 
     773           19 :     if (present(stat)) stat = HSD_STAT_OK
     774              : 
     775              :     ! Iterate over overlay children
     776           43 :     do i = 1, overlay%num_children
     777           24 :       call overlay%get_child(i, overlay_child)
     778           24 :       if (.not. associated(overlay_child)) cycle
     779           24 :       if (.not. allocated(overlay_child%name)) cycle
     780              : 
     781              :       ! Check if base has this child
     782           24 :       call base%get_child_by_name(overlay_child%name, base_child)
     783              : 
     784           43 :       if (.not. associated(base_child)) then
     785              :         ! Child doesn't exist in base - clone and add it
     786            8 :         if (overlay_child%node_type == NODE_TYPE_TABLE) then
     787            2 :           call clone_table(overlay_child, cloned_table)
     788            2 :           call base%add_child(cloned_table)
     789            8 :         else if (overlay_child%node_type == NODE_TYPE_VALUE) then
     790            4 :           call clone_value(overlay_child, cloned_value)
     791            4 :           call base%add_child(cloned_value)
     792              :         end if
     793              :       else
     794              :         ! Child exists - handle based on type
     795           18 :         if (overlay_child%node_type == NODE_TYPE_TABLE) then
     796              :           ! If both are tables, merge recursively
     797            5 :           if (base_child%node_type == NODE_TYPE_TABLE) then
     798            5 :             call hsd_merge(base_child, overlay_child, local_stat)
     799            5 :             if (present(stat) .and. local_stat /= HSD_STAT_OK) stat = local_stat
     800              :           else
     801              :             ! Base is not a table but overlay is - skip (could log warning)
     802              :           end if
     803           13 :         else if (overlay_child%node_type == NODE_TYPE_VALUE) then
     804              :           ! Overlay value replaces base value
     805           26 :           if (base_child%node_type == NODE_TYPE_VALUE) then
     806           13 :             call clone_value(overlay_child, cloned_value)
     807              :             ! Replace the value content
     808           13 :             base_child%value_type = cloned_value%value_type
     809              :             ! Copy attribute from overlay
     810           13 :             if (allocated(cloned_value%attrib)) then
     811            1 :               base_child%attrib = cloned_value%attrib
     812              :             else
     813           12 :               if (allocated(base_child%attrib)) deallocate(base_child%attrib)
     814              :             end if
     815              :             ! Clear stale fields before overwriting
     816           13 :             if (allocated(base_child%string_value)) &
     817           13 :                 & deallocate(base_child%string_value)
     818              :             ! Copy new values from clone
     819           13 :             if (allocated(cloned_value%string_value)) &
     820           13 :                 & base_child%string_value = cloned_value%string_value
     821              :           else
     822              :             ! Type mismatch - skip
     823              :           end if
     824              :         end if
     825              :       end if
     826              :     end do
     827              : 
     828           23 :   end subroutine hsd_merge
     829              : 
     830              :   !> Clone a table (deep copy)
     831           11 :   recursive subroutine clone_table(source, dest)
     832              :     type(hsd_node_t), intent(in) :: source
     833              :     type(hsd_node_t), intent(out) :: dest
     834              : 
     835              :     type(hsd_node_t), pointer :: child
     836           11 :     type(hsd_node_t) :: cloned_subtable
     837           11 :     type(hsd_node_t) :: cloned_value
     838           11 :     integer :: i
     839              : 
     840           11 :     call new_table(dest, name=source%name)
     841           11 :     if (allocated(source%attrib)) dest%attrib = source%attrib
     842           11 :     dest%line = source%line
     843              : 
     844           26 :     do i = 1, source%num_children
     845           15 :       call source%get_child(i, child)
     846           15 :       if (.not. associated(child)) cycle
     847              : 
     848           31 :       if (child%node_type == NODE_TYPE_TABLE) then
     849            5 :         call clone_table(child, cloned_subtable)
     850            5 :         call dest%add_child(cloned_subtable)
     851           20 :       else if (child%node_type == NODE_TYPE_VALUE) then
     852           10 :         call clone_value(child, cloned_value)
     853           10 :         call dest%add_child(cloned_value)
     854              :       end if
     855              :     end do
     856              : 
     857           11 :   end subroutine clone_table
     858              : 
     859              :   !> Clone a value (deep copy)
     860           54 :   subroutine clone_value(source, dest)
     861              :     type(hsd_node_t), intent(in) :: source
     862              :     type(hsd_node_t), intent(out) :: dest
     863              : 
     864           27 :     call new_value(dest, name=source%name)
     865           27 :     if (allocated(source%attrib)) dest%attrib = source%attrib
     866           27 :     dest%line = source%line
     867           27 :     dest%value_type = source%value_type
     868              : 
     869           27 :     if (allocated(source%string_value)) dest%string_value = source%string_value
     870              : 
     871           27 :   end subroutine clone_value
     872              : 
     873              :   !> Deep clone an entire HSD table tree
     874            8 :   subroutine hsd_clone(source, dest, stat)
     875              :     type(hsd_node_t), intent(in) :: source
     876              :     type(hsd_node_t), intent(out) :: dest
     877              :     integer, intent(out), optional :: stat
     878              : 
     879            4 :     call clone_table(source, dest)
     880            4 :     if (present(stat)) stat = HSD_STAT_OK
     881              : 
     882           27 :   end subroutine hsd_clone
     883              : 
     884              :     !> Compare two HSD tables for structural and value equality
     885              :     !>
     886              :     !> Returns .true. if both tables have the same children (by name),
     887              :     !> the same structure (tables vs values), and the same values.
     888              :     !> Comparison is recursive for nested tables.
     889              :     !> Child order does not matter — children are matched by name.
     890              :     !> Name comparison is case-insensitive to match HSD conventions.
     891           17 :     recursive function hsd_table_equal(a, b) result(equal)
     892              :       type(hsd_node_t), intent(in), target :: a
     893              :       type(hsd_node_t), intent(in), target :: b
     894              :       logical :: equal
     895              : 
     896              :       type(hsd_node_t), pointer :: child_a, child_b
     897           17 :       integer :: i
     898              : 
     899           17 :       equal = .false.
     900              : 
     901              :       ! Quick check: same number of children
     902           17 :       if (a%num_children /= b%num_children) return
     903              : 
     904              :       ! Check that every child in a has a matching child in b
     905           30 :       do i = 1, a%num_children
     906           20 :         call a%get_child(i, child_a)
     907           20 :         if (.not. associated(child_a)) return
     908           20 :         if (.not. allocated(child_a%name)) return
     909              : 
     910              :         ! Look for matching child in b
     911           20 :         call b%get_child_by_name(child_a%name, child_b)
     912           20 :         if (.not. associated(child_b)) return
     913              : 
     914              :         ! Compare node types and values
     915           30 :         if (.not. nodes_equal(child_a, child_b)) return
     916              :       end do
     917              : 
     918           10 :       equal = .true.
     919              : 
     920           14 :     end function hsd_table_equal
     921              : 
     922              :     !> Compare two nodes for equality (recursive for tables)
     923           20 :     recursive function nodes_equal(a, b) result(equal)
     924              :       type(hsd_node_t), intent(in), target :: a
     925              :       type(hsd_node_t), intent(in), target :: b
     926              :       logical :: equal
     927              : 
     928           20 :       equal = .false.
     929              : 
     930              :       ! Both must be the same dynamic type
     931           20 :       if (a%node_type == NODE_TYPE_TABLE) then
     932            3 :         if (b%node_type == NODE_TYPE_TABLE) then
     933            3 :           equal = hsd_table_equal(a, b)
     934              :         end if
     935              : 
     936           17 :       else if (a%node_type == NODE_TYPE_VALUE) then
     937           17 :         if (b%node_type == NODE_TYPE_VALUE) then
     938           17 :           equal = values_equal(a, b)
     939              :         end if
     940              :       end if
     941              : 
     942           20 :     end function nodes_equal
     943              : 
     944              :     !> Compare two value nodes for equality
     945           17 :     function values_equal(a, b) result(equal)
     946              :       type(hsd_node_t), intent(in) :: a
     947              :       type(hsd_node_t), intent(in) :: b
     948              :       logical :: equal
     949              : 
     950           17 :       equal = .false.
     951              : 
     952              :       ! Must have the same value type
     953            0 :       if (a%value_type /= b%value_type) return
     954              : 
     955              :       ! Compare string_value
     956           17 :       if (allocated(a%string_value) .and. allocated(b%string_value)) then
     957           17 :         if (a%string_value /= b%string_value) return
     958            0 :       else if (allocated(a%string_value) .neqv. allocated(b%string_value)) then
     959            0 :         return
     960              :       end if
     961              : 
     962           13 :       equal = .true.
     963              : 
     964           17 :     end function values_equal
     965              : 
     966              : 
     967              :   !> Set the processed flag on a table and optionally all its descendants.
     968              :   !>
     969              :   !> When `recursive` is `.true.`, walks the entire subtree rooted at `table`
     970              :   !> and sets `%processed = .true.` on every node (tables and values).
     971              :   !> When `recursive` is `.false.` (the default), only the given table itself
     972              :   !> is marked.
     973            6 :   recursive subroutine hsd_set_processed(table, recursive)
     974              :     type(hsd_node_t), intent(inout), target :: table
     975              :     logical, intent(in), optional :: recursive
     976              : 
     977            6 :     logical :: do_recurse
     978            6 :     integer :: ii
     979              :     type(hsd_node_t), pointer :: child
     980              : 
     981            6 :     do_recurse = .false.
     982            6 :     if (present(recursive)) do_recurse = recursive
     983              : 
     984            6 :     table%processed = .true.
     985              : 
     986            6 :     if (.not. do_recurse) return
     987              : 
     988           12 :     do ii = 1, table%num_children
     989            8 :       call table%get_child(ii, child)
     990            8 :       if (.not. associated(child)) cycle
     991              : 
     992           12 :       if (child%node_type == NODE_TYPE_TABLE) then
     993            2 :         call hsd_set_processed(child, recursive=.true.)
     994            6 :       else if (child%node_type == NODE_TYPE_VALUE) then
     995            6 :         child%processed = .true.
     996              :       end if
     997              :     end do
     998              : 
     999           17 :   end subroutine hsd_set_processed
    1000              : 
    1001              : 
    1002              :   !> Check whether a table has any value children (inline data).
    1003            3 :   function hsd_has_value_children(table) result(has)
    1004              :     type(hsd_node_t), intent(in), target :: table
    1005              :     logical :: has
    1006              : 
    1007            3 :     integer :: ii
    1008              :     type(hsd_node_t), pointer :: child
    1009              : 
    1010            3 :     has = .false.
    1011            4 :     do ii = 1, table%num_children
    1012            2 :       call table%get_child(ii, child)
    1013            2 :       if (.not. associated(child)) cycle
    1014            4 :       if (child%node_type == NODE_TYPE_VALUE) then
    1015            1 :         has = .true.
    1016            1 :         return
    1017              :       end if
    1018              :     end do
    1019              : 
    1020            3 :   end function hsd_has_value_children
    1021              : 
    1022              : 
    1023              :   !> Get the lowercased name of a node.
    1024              :   !>
    1025              :   !> If the node's name is unset or blank, returns the `default` string
    1026              :   !> (which itself defaults to "" if not provided).
    1027            6 :   subroutine hsd_get_name(node, name, default)
    1028              :     type(hsd_node_t), intent(in) :: node
    1029              :     character(len=:), allocatable, intent(out) :: name
    1030              :     character(len=*), intent(in), optional :: default
    1031              : 
    1032            6 :     character(len=:), allocatable :: fallback
    1033              : 
    1034            6 :     if (present(default)) then
    1035            2 :       fallback = default
    1036              :     else
    1037            4 :       fallback = ""
    1038              :     end if
    1039              : 
    1040            6 :     if (allocated(node%name)) then
    1041            2 :       if (len_trim(node%name) > 0) then
    1042            2 :         name = to_lower(node%name)
    1043              :       else
    1044            0 :         name = fallback
    1045              :       end if
    1046              :     else
    1047            4 :       name = fallback
    1048              :     end if
    1049              : 
    1050            9 :   end subroutine hsd_get_name
    1051              : 
    1052              :   !> Get the inline text VALUE node from a table node.
    1053              :   !>
    1054              :   !> Looks for a child named "#text" and returns the hsd_value pointer.
    1055           20 :   subroutine get_inline_value_(table, val_node, stat)
    1056              :     type(hsd_node_t), intent(in), target :: table
    1057              :     type(hsd_node_t), pointer, intent(out) :: val_node
    1058              :     integer, intent(out) :: stat
    1059              : 
    1060              :     type(hsd_node_t), pointer :: child
    1061              : 
    1062           20 :     nullify(val_node)
    1063           20 :     call table%get_child_by_name("#text", child)
    1064           20 :     if (associated(child)) then
    1065            9 :       if (child%node_type == NODE_TYPE_VALUE) then
    1066            9 :         val_node => child
    1067            9 :         stat = HSD_STAT_OK
    1068            9 :         return
    1069              :       end if
    1070              :     end if
    1071              : 
    1072           11 :     stat = HSD_STAT_NOT_FOUND
    1073              : 
    1074           26 :   end subroutine get_inline_value_
    1075              : 
    1076              :   !> Helper: Get the value node at the given path, handling inline text tables transparently
    1077          435 :   subroutine get_value_node_(table, path, val_node, stat)
    1078              :     type(hsd_node_t), intent(in), target :: table
    1079              :     character(len=*), intent(in) :: path
    1080              :     type(hsd_node_t), pointer, intent(out) :: val_node
    1081              :     integer, intent(out) :: stat
    1082              : 
    1083              :     type(hsd_node_t), pointer :: child
    1084              : 
    1085          435 :     nullify(val_node)
    1086              : 
    1087          435 :     call hsd_get_child(table, path, child, stat)
    1088          435 :     if (stat /= 0 .or. .not. associated(child)) then
    1089           32 :       stat = HSD_STAT_NOT_FOUND
    1090           32 :       return
    1091              :     end if
    1092              : 
    1093          403 :     child%processed = .true.
    1094              : 
    1095          403 :     if (child%node_type == NODE_TYPE_VALUE) then
    1096          394 :       val_node => child
    1097          394 :       stat = HSD_STAT_OK
    1098            9 :     else if (child%node_type == NODE_TYPE_TABLE) then
    1099              :       ! Try to extract inline value
    1100            9 :       call get_inline_value_(child, val_node, stat)
    1101            9 :       if (stat == HSD_STAT_NOT_FOUND) stat = HSD_STAT_TYPE_ERROR
    1102              :     else
    1103            0 :       stat = HSD_STAT_TYPE_ERROR
    1104              :     end if
    1105          455 :   end subroutine get_value_node_
    1106              : 
    1107              :   !> Get string value by path
    1108           42 :   subroutine hsd_get_string(table, path, val, stat)
    1109              :     type(hsd_node_t), intent(in), target :: table
    1110              :     character(len=*), intent(in) :: path
    1111              :     character(len=:), allocatable, intent(out) :: val
    1112              :     integer, intent(out), optional :: stat
    1113              : 
    1114              :     type(hsd_node_t), pointer :: vnode
    1115           42 :     integer :: local_stat
    1116              : 
    1117           42 :     call get_value_node_(table, path, vnode, local_stat)
    1118              : 
    1119           42 :     if (local_stat == HSD_STAT_OK) then
    1120           35 :       call vnode%get_string(val, local_stat)
    1121              :     else
    1122            7 :       val = ""
    1123              :     end if
    1124              : 
    1125           42 :     if (present(stat)) stat = local_stat
    1126              : 
    1127          435 :   end subroutine hsd_get_string
    1128              : 
    1129              :   !> Get integer value by path
    1130          291 :   subroutine hsd_get_integer(table, path, val, stat)
    1131              :     type(hsd_node_t), intent(in), target :: table
    1132              :     character(len=*), intent(in) :: path
    1133              :     integer, intent(out) :: val
    1134              :     integer, intent(out), optional :: stat
    1135              : 
    1136              :     type(hsd_node_t), pointer :: vnode
    1137          291 :     integer :: local_stat
    1138              : 
    1139          291 :     call get_value_node_(table, path, vnode, local_stat)
    1140              : 
    1141          291 :     if (local_stat == HSD_STAT_OK) then
    1142          282 :       call vnode%get_integer(val, local_stat)
    1143              :     else
    1144            9 :       val = 0
    1145              :     end if
    1146              : 
    1147          291 :     if (present(stat)) stat = local_stat
    1148           42 :   end subroutine hsd_get_integer
    1149              : 
    1150              :   !> Get double precision real value by path
    1151           29 :   subroutine hsd_get_real_dp(table, path, val, stat)
    1152              :     type(hsd_node_t), intent(in), target :: table
    1153              :     character(len=*), intent(in) :: path
    1154              :     real(dp), intent(out) :: val
    1155              :     integer, intent(out), optional :: stat
    1156              : 
    1157              :     type(hsd_node_t), pointer :: vnode
    1158           29 :     integer :: local_stat
    1159              : 
    1160           29 :     call get_value_node_(table, path, vnode, local_stat)
    1161              : 
    1162           29 :     if (local_stat == HSD_STAT_OK) then
    1163           22 :       call vnode%get_real(val, local_stat)
    1164              :     else
    1165            7 :       val = 0.0_dp
    1166              :     end if
    1167              : 
    1168           29 :     if (present(stat)) stat = local_stat
    1169          291 :   end subroutine hsd_get_real_dp
    1170              : 
    1171              :   !> Get logical value by path
    1172           40 :   subroutine hsd_get_logical(table, path, val, stat)
    1173              :     type(hsd_node_t), intent(in), target :: table
    1174              :     character(len=*), intent(in) :: path
    1175              :     logical, intent(out) :: val
    1176              :     integer, intent(out), optional :: stat
    1177              : 
    1178              :     type(hsd_node_t), pointer :: vnode
    1179           40 :     integer :: local_stat
    1180              : 
    1181           40 :     call get_value_node_(table, path, vnode, local_stat)
    1182              : 
    1183           40 :     if (local_stat == HSD_STAT_OK) then
    1184           32 :       call vnode%get_logical(val, local_stat)
    1185              :     else
    1186            8 :       val = .false.
    1187              :     end if
    1188              : 
    1189           40 :     if (present(stat)) stat = local_stat
    1190           29 :   end subroutine hsd_get_logical
    1191              : 
    1192              :   !> Get complex value by path
    1193           33 :   subroutine hsd_get_complex_dp(table, path, val, stat)
    1194              :     type(hsd_node_t), intent(in), target :: table
    1195              :     character(len=*), intent(in) :: path
    1196              :     complex(dp), intent(out) :: val
    1197              :     integer, intent(out), optional :: stat
    1198              : 
    1199              :     type(hsd_node_t), pointer :: vnode
    1200           33 :     integer :: local_stat
    1201              : 
    1202           33 :     call get_value_node_(table, path, vnode, local_stat)
    1203              : 
    1204           33 :     if (local_stat == HSD_STAT_OK) then
    1205           27 :       call vnode%get_complex(val, local_stat)
    1206              :     else
    1207            6 :       val = (0.0_dp, 0.0_dp)
    1208              :     end if
    1209              : 
    1210           33 :     if (present(stat)) stat = local_stat
    1211           40 :   end subroutine hsd_get_complex_dp
    1212              : 
    1213              :   !> Get integer array by path (supports space/comma/newline separated values)
    1214           23 :   subroutine hsd_get_integer_array(table, path, val, stat)
    1215              :     type(hsd_node_t), intent(in), target :: table
    1216              :     character(len=*), intent(in) :: path
    1217              :     integer, allocatable, intent(out) :: val(:)
    1218              :     integer, intent(out), optional :: stat
    1219              : 
    1220              :     type(hsd_node_t), pointer :: child
    1221           23 :     integer :: local_stat
    1222              : 
    1223           23 :     call hsd_get_child(table, path, child, local_stat)
    1224              : 
    1225           23 :     if (local_stat /= 0 .or. .not. associated(child)) then
    1226            3 :       if (present(stat)) stat = HSD_STAT_NOT_FOUND
    1227            3 :       allocate(val(0))
    1228            3 :       return
    1229              :     end if
    1230              : 
    1231           20 :     child%processed = .true.
    1232              : 
    1233           20 :     if (child%node_type == NODE_TYPE_VALUE) then
    1234           17 :       call child%get_int_array(val, local_stat)
    1235           17 :       if (present(stat)) stat = local_stat
    1236            3 :     else if (child%node_type == NODE_TYPE_TABLE) then
    1237            3 :       block
    1238              :         type(hsd_node_t), pointer :: vnode
    1239            3 :         call get_inline_value_(child, vnode, local_stat)
    1240            3 :         if (local_stat == 0) then
    1241            1 :           call vnode%get_int_array(val, local_stat)
    1242            1 :           if (present(stat)) stat = local_stat
    1243              :         else
    1244            2 :           if (present(stat)) stat = HSD_STAT_TYPE_ERROR
    1245            2 :           allocate(val(0))
    1246              :         end if
    1247              :       end block
    1248              :     else
    1249            0 :       if (present(stat)) stat = HSD_STAT_TYPE_ERROR
    1250            0 :       allocate(val(0))
    1251              :     end if
    1252              : 
    1253           56 :   end subroutine hsd_get_integer_array
    1254              : 
    1255              :   !> Get double precision real array by path
    1256           15 :   subroutine hsd_get_real_dp_array(table, path, val, stat)
    1257              :     type(hsd_node_t), intent(in), target :: table
    1258              :     character(len=*), intent(in) :: path
    1259              :     real(dp), allocatable, intent(out) :: val(:)
    1260              :     integer, intent(out), optional :: stat
    1261              : 
    1262              :     type(hsd_node_t), pointer :: child
    1263           15 :     integer :: local_stat
    1264              : 
    1265           15 :     call hsd_get_child(table, path, child, local_stat)
    1266              : 
    1267           15 :     if (local_stat /= 0 .or. .not. associated(child)) then
    1268            3 :       if (present(stat)) stat = HSD_STAT_NOT_FOUND
    1269            3 :       allocate(val(0))
    1270            3 :       return
    1271              :     end if
    1272              : 
    1273           12 :     child%processed = .true.
    1274              : 
    1275           12 :     if (child%node_type == NODE_TYPE_VALUE) then
    1276           10 :       call child%get_real_array(val, local_stat)
    1277           10 :       if (present(stat)) stat = local_stat
    1278            2 :     else if (child%node_type == NODE_TYPE_TABLE) then
    1279            2 :       block
    1280              :         type(hsd_node_t), pointer :: vnode
    1281            2 :         call get_inline_value_(child, vnode, local_stat)
    1282            2 :         if (local_stat == 0) then
    1283            1 :           call vnode%get_real_array(val, local_stat)
    1284            1 :           if (present(stat)) stat = local_stat
    1285              :         else
    1286            1 :           if (present(stat)) stat = HSD_STAT_TYPE_ERROR
    1287            1 :           allocate(val(0))
    1288              :         end if
    1289              :       end block
    1290              :     else
    1291            0 :       if (present(stat)) stat = HSD_STAT_TYPE_ERROR
    1292            0 :       allocate(val(0))
    1293              :     end if
    1294              : 
    1295           38 :   end subroutine hsd_get_real_dp_array
    1296              : 
    1297              :   !> Get logical array by path
    1298           16 :   subroutine hsd_get_logical_array(table, path, val, stat)
    1299              :     type(hsd_node_t), intent(in), target :: table
    1300              :     character(len=*), intent(in) :: path
    1301              :     logical, allocatable, intent(out) :: val(:)
    1302              :     integer, intent(out), optional :: stat
    1303              : 
    1304              :     type(hsd_node_t), pointer :: child
    1305           16 :     integer :: local_stat
    1306              : 
    1307           16 :     call hsd_get_child(table, path, child, local_stat)
    1308              : 
    1309           16 :     if (local_stat /= 0 .or. .not. associated(child)) then
    1310            3 :       if (present(stat)) stat = HSD_STAT_NOT_FOUND
    1311            3 :       allocate(val(0))
    1312            3 :       return
    1313              :     end if
    1314              : 
    1315           13 :     child%processed = .true.
    1316              : 
    1317           13 :     if (child%node_type == NODE_TYPE_VALUE) then
    1318           11 :       call child%get_logical_array(val, local_stat)
    1319           11 :       if (present(stat)) stat = local_stat
    1320            2 :     else if (child%node_type == NODE_TYPE_TABLE) then
    1321            2 :       block
    1322              :         type(hsd_node_t), pointer :: vnode
    1323            2 :         call get_inline_value_(child, vnode, local_stat)
    1324            2 :         if (local_stat == 0) then
    1325            1 :           call vnode%get_logical_array(val, local_stat)
    1326            1 :           if (present(stat)) stat = local_stat
    1327              :         else
    1328            1 :           if (present(stat)) stat = HSD_STAT_TYPE_ERROR
    1329            1 :           allocate(val(0))
    1330              :         end if
    1331              :       end block
    1332              :     else
    1333            0 :       if (present(stat)) stat = HSD_STAT_TYPE_ERROR
    1334            0 :       allocate(val(0))
    1335              :     end if
    1336              : 
    1337           31 :   end subroutine hsd_get_logical_array
    1338              : 
    1339              :   !> Get string array by path (preserves quoted strings)
    1340           14 :   subroutine hsd_get_string_array(table, path, val, stat)
    1341              :     type(hsd_node_t), intent(in), target :: table
    1342              :     character(len=*), intent(in) :: path
    1343              :     character(len=:), allocatable, intent(out) :: val(:)
    1344              :     integer, intent(out), optional :: stat
    1345              : 
    1346              :     type(hsd_node_t), pointer :: child
    1347           14 :     integer :: local_stat
    1348              : 
    1349           14 :     call hsd_get_child(table, path, child, local_stat)
    1350              : 
    1351           14 :     if (local_stat /= 0 .or. .not. associated(child)) then
    1352            1 :       if (present(stat)) stat = HSD_STAT_NOT_FOUND
    1353            1 :       allocate(character(len=1) :: val(0))
    1354            1 :       return
    1355              :     end if
    1356              : 
    1357           13 :     child%processed = .true.
    1358              : 
    1359           13 :     if (child%node_type == NODE_TYPE_VALUE) then
    1360           11 :       call child%get_string_array(val, local_stat)
    1361           11 :       if (present(stat)) stat = local_stat
    1362            2 :     else if (child%node_type == NODE_TYPE_TABLE) then
    1363            2 :       block
    1364              :         type(hsd_node_t), pointer :: vnode
    1365            2 :         call get_inline_value_(child, vnode, local_stat)
    1366            2 :         if (local_stat == 0) then
    1367            1 :           call vnode%get_string_array(val, local_stat)
    1368            1 :           if (present(stat)) stat = local_stat
    1369              :         else
    1370            1 :           if (present(stat)) stat = HSD_STAT_TYPE_ERROR
    1371            1 :           allocate(character(len=1) :: val(0))
    1372              :         end if
    1373              :       end block
    1374              :     else
    1375            0 :       if (present(stat)) stat = HSD_STAT_TYPE_ERROR
    1376            0 :       allocate(character(len=1) :: val(0))
    1377              :     end if
    1378              : 
    1379           30 :   end subroutine hsd_get_string_array
    1380              : 
    1381              :   !> Get complex array by path
    1382           11 :   subroutine hsd_get_complex_dp_array(table, path, val, stat)
    1383              :     type(hsd_node_t), intent(in), target :: table
    1384              :     character(len=*), intent(in) :: path
    1385              :     complex(dp), allocatable, intent(out) :: val(:)
    1386              :     integer, intent(out), optional :: stat
    1387              : 
    1388              :     type(hsd_node_t), pointer :: child
    1389           11 :     integer :: local_stat
    1390              : 
    1391           11 :     call hsd_get_child(table, path, child, local_stat)
    1392              : 
    1393           11 :     if (local_stat /= 0 .or. .not. associated(child)) then
    1394            1 :       if (present(stat)) stat = HSD_STAT_NOT_FOUND
    1395            1 :       allocate(val(0))
    1396            1 :       return
    1397              :     end if
    1398              : 
    1399           10 :     child%processed = .true.
    1400              : 
    1401           10 :     if (child%node_type == NODE_TYPE_VALUE) then
    1402            8 :       call child%get_complex_array(val, local_stat)
    1403            8 :       if (present(stat)) stat = local_stat
    1404            2 :     else if (child%node_type == NODE_TYPE_TABLE) then
    1405            2 :       block
    1406              :         type(hsd_node_t), pointer :: vnode
    1407            2 :         call get_inline_value_(child, vnode, local_stat)
    1408            2 :         if (local_stat == 0) then
    1409            1 :           call vnode%get_complex_array(val, local_stat)
    1410            1 :           if (present(stat)) stat = local_stat
    1411              :         else
    1412            1 :           if (present(stat)) stat = HSD_STAT_TYPE_ERROR
    1413            1 :           allocate(val(0))
    1414              :         end if
    1415              :       end block
    1416              :     else
    1417            0 :       if (present(stat)) stat = HSD_STAT_TYPE_ERROR
    1418            0 :       allocate(val(0))
    1419              :     end if
    1420              : 
    1421           25 :   end subroutine hsd_get_complex_dp_array
    1422              : 
    1423              :   !> Get 2D integer matrix by path (rows separated by newlines or semicolons)
    1424              :   !> Handles both value nodes and table nodes (where content is in unnamed children)
    1425              :   !>
    1426              :   !> If `order` is present and set to "column-major", the returned matrix is
    1427              :   !> transposed so that text rows map to Fortran columns (column-major layout).
    1428              :   !> Default is text-layout (row-major).
    1429           19 :   subroutine hsd_get_integer_matrix(table, path, val, nrows, ncols, stat, order)
    1430              :     type(hsd_node_t), intent(in), target :: table
    1431              :     character(len=*), intent(in) :: path
    1432              :     integer, allocatable, intent(out) :: val(:,:)
    1433              :     integer, intent(out) :: nrows, ncols
    1434              :     integer, intent(out), optional :: stat
    1435              :     character(len=*), intent(in), optional :: order
    1436              : 
    1437              :     type(hsd_node_t), pointer :: child
    1438           19 :     integer :: local_stat
    1439              : 
    1440           19 :     call hsd_get_child(table, path, child, local_stat)
    1441              : 
    1442           19 :     if (local_stat /= 0 .or. .not. associated(child)) then
    1443            1 :       if (present(stat)) stat = HSD_STAT_NOT_FOUND
    1444            1 :       allocate(val(0,0))
    1445            1 :       nrows = 0
    1446            1 :       ncols = 0
    1447            1 :       return
    1448              :     end if
    1449              : 
    1450           18 :     child%processed = .true.
    1451              : 
    1452           18 :     if (child%node_type == NODE_TYPE_VALUE) then
    1453            7 :       call child%get_int_matrix(val, nrows, ncols, local_stat)
    1454            7 :       if (present(stat)) stat = local_stat
    1455           11 :     else if (child%node_type == NODE_TYPE_TABLE) then
    1456              :       ! Table nodes store matrix data as unnamed child values
    1457           11 :       call get_int_matrix_from_table(child, val, nrows, ncols, local_stat)
    1458           11 :       if (present(stat)) stat = local_stat
    1459              :     else
    1460            0 :       if (present(stat)) stat = HSD_STAT_TYPE_ERROR
    1461            0 :       allocate(val(0,0))
    1462            0 :       nrows = 0
    1463            0 :       ncols = 0
    1464              :     end if
    1465              : 
    1466              :     ! Transpose if column-major order requested
    1467           18 :     if (present(order)) then
    1468            1 :       if (order == "column-major" .and. nrows > 0 .and. ncols > 0) then
    1469            1 :         block
    1470            1 :           integer, allocatable :: tmp(:,:)
    1471            1 :           integer :: swap
    1472            1 :           allocate(tmp(ncols, nrows))
    1473            9 :           tmp = transpose(val)
    1474            1 :           call move_alloc(tmp, val)
    1475            1 :           swap = nrows
    1476            1 :           nrows = ncols
    1477            1 :           ncols = swap
    1478              :         end block
    1479              :       end if
    1480              :     end if
    1481              : 
    1482           30 :   end subroutine hsd_get_integer_matrix
    1483              : 
    1484              :   !> Get 2D real matrix by path
    1485              :   !> Handles both value nodes and table nodes (where content is in unnamed children)
    1486              :   !>
    1487              :   !> If `order` is present and set to "column-major", the returned matrix is
    1488              :   !> transposed so that text rows map to Fortran columns (column-major layout).
    1489              :   !> Default is text-layout (row-major).
    1490           15 :   subroutine hsd_get_real_dp_matrix(table, path, val, nrows, ncols, stat, order)
    1491              :     type(hsd_node_t), intent(in), target :: table
    1492              :     character(len=*), intent(in) :: path
    1493              :     real(dp), allocatable, intent(out) :: val(:,:)
    1494              :     integer, intent(out) :: nrows, ncols
    1495              :     integer, intent(out), optional :: stat
    1496              :     character(len=*), intent(in), optional :: order
    1497              : 
    1498              :     type(hsd_node_t), pointer :: child
    1499           15 :     integer :: local_stat
    1500              : 
    1501           15 :     call hsd_get_child(table, path, child, local_stat)
    1502              : 
    1503           15 :     if (local_stat /= 0 .or. .not. associated(child)) then
    1504            1 :       if (present(stat)) stat = HSD_STAT_NOT_FOUND
    1505            1 :       allocate(val(0,0))
    1506            1 :       nrows = 0
    1507            1 :       ncols = 0
    1508            1 :       return
    1509              :     end if
    1510              : 
    1511           14 :     child%processed = .true.
    1512              : 
    1513           14 :     if (child%node_type == NODE_TYPE_VALUE) then
    1514            6 :       call child%get_real_matrix(val, nrows, ncols, local_stat)
    1515            6 :       if (present(stat)) stat = local_stat
    1516            8 :     else if (child%node_type == NODE_TYPE_TABLE) then
    1517              :       ! Table nodes store matrix data as unnamed child values
    1518            8 :       call get_real_matrix_from_table(child, val, nrows, ncols, local_stat)
    1519            8 :       if (present(stat)) stat = local_stat
    1520              :     else
    1521            0 :       if (present(stat)) stat = HSD_STAT_TYPE_ERROR
    1522            0 :       allocate(val(0,0))
    1523            0 :       nrows = 0
    1524            0 :       ncols = 0
    1525              :     end if
    1526              : 
    1527              :     ! Transpose if column-major order requested
    1528           14 :     if (present(order)) then
    1529            1 :       if (order == "column-major" .and. nrows > 0 .and. ncols > 0) then
    1530            1 :         block
    1531            1 :           real(dp), allocatable :: tmp(:,:)
    1532            1 :           integer :: swap
    1533            1 :           allocate(tmp(ncols, nrows))
    1534            7 :           tmp = transpose(val)
    1535            1 :           call move_alloc(tmp, val)
    1536            1 :           swap = nrows
    1537            1 :           nrows = ncols
    1538            1 :           ncols = swap
    1539              :         end block
    1540              :       end if
    1541              :     end if
    1542              : 
    1543           34 :   end subroutine hsd_get_real_dp_matrix
    1544              : 
    1545              :   !> Extract integer matrix from table with unnamed value children
    1546           11 :   subroutine get_int_matrix_from_table(tbl, mat, nrows, ncols, stat)
    1547              :     type(hsd_node_t), intent(in) :: tbl
    1548              :     integer, allocatable, intent(out) :: mat(:,:)
    1549              :     integer, intent(out) :: nrows, ncols, stat
    1550              : 
    1551              :     type(hsd_node_t), pointer :: child
    1552           11 :     character(len=:), allocatable :: combined_text, str_val
    1553           11 :     integer :: i, local_stat
    1554              : 
    1555              :     ! Combine all unnamed value children into single text
    1556           11 :     combined_text = ""
    1557           22 :     do i = 1, tbl%num_children
    1558           11 :       call tbl%get_child(i, child)
    1559           22 :       if (associated(child)) then
    1560           11 :         if (child%node_type == NODE_TYPE_VALUE) then
    1561              :           ! Include unnamed, empty-named, or #text-named value nodes
    1562              :           block
    1563           11 :             logical :: is_text_child
    1564           11 :             is_text_child = .not. allocated(child%name)
    1565           11 :             if (.not. is_text_child) &
    1566           11 :                 & is_text_child = (len_trim(child%name) == 0 .or. child%name == "#text")
    1567           11 :             if (is_text_child) then
    1568           10 :               call child%get_string(str_val, local_stat)
    1569           10 :               if (local_stat == 0 .and. len_trim(str_val) > 0) then
    1570           10 :                 if (len(combined_text) > 0) then
    1571            1 :                   combined_text = combined_text // char(10) // str_val
    1572              :                 else
    1573            9 :                   combined_text = str_val
    1574              :                 end if
    1575              :               end if
    1576              :             end if
    1577              :           end block
    1578              :         end if
    1579              :       end if
    1580              :     end do
    1581              : 
    1582           11 :     if (len_trim(combined_text) == 0) then
    1583            2 :       allocate(mat(0,0))
    1584            2 :       nrows = 0
    1585            2 :       ncols = 0
    1586            2 :       stat = HSD_STAT_OK
    1587            2 :       return
    1588              :     end if
    1589              : 
    1590              :     ! Parse the combined text as a matrix
    1591           36 :     block
    1592            9 :       type(hsd_node_t) :: temp_val
    1593            9 :       call new_value(temp_val)
    1594            9 :       call temp_val%set_raw(combined_text)
    1595            9 :       call temp_val%get_int_matrix(mat, nrows, ncols, stat)
    1596           36 :       call temp_val%destroy()
    1597              :     end block
    1598              : 
    1599           26 :   end subroutine get_int_matrix_from_table
    1600              : 
    1601              :   !> Extract real matrix from table with unnamed value children
    1602            8 :   subroutine get_real_matrix_from_table(tbl, mat, nrows, ncols, stat)
    1603              :     type(hsd_node_t), intent(in) :: tbl
    1604              :     real(dp), allocatable, intent(out) :: mat(:,:)
    1605              :     integer, intent(out) :: nrows, ncols, stat
    1606              : 
    1607              :     type(hsd_node_t), pointer :: child
    1608            8 :     character(len=:), allocatable :: combined_text, str_val
    1609            8 :     integer :: i, local_stat
    1610              : 
    1611              :     ! Combine all unnamed value children into single text
    1612            8 :     combined_text = ""
    1613           17 :     do i = 1, tbl%num_children
    1614            9 :       call tbl%get_child(i, child)
    1615           17 :       if (associated(child)) then
    1616            9 :         if (child%node_type == NODE_TYPE_VALUE) then
    1617              :           ! Include unnamed, empty-named, or #text-named value nodes
    1618              :           block
    1619            9 :             logical :: is_text_child
    1620            9 :             is_text_child = .not. allocated(child%name)
    1621            9 :             if (.not. is_text_child) &
    1622            9 :                 & is_text_child = (len_trim(child%name) == 0 .or. child%name == "#text")
    1623            9 :             if (is_text_child) then
    1624            8 :               call child%get_string(str_val, local_stat)
    1625            8 :               if (local_stat == 0 .and. len_trim(str_val) > 0) then
    1626            8 :                 if (len(combined_text) > 0) then
    1627            1 :                   combined_text = combined_text // char(10) // str_val
    1628              :                 else
    1629            7 :                   combined_text = str_val
    1630              :                 end if
    1631              :               end if
    1632              :             end if
    1633              :           end block
    1634              :         end if
    1635              :       end if
    1636              :     end do
    1637              : 
    1638            8 :     if (len_trim(combined_text) == 0) then
    1639            1 :       allocate(mat(0,0))
    1640            1 :       nrows = 0
    1641            1 :       ncols = 0
    1642            1 :       stat = HSD_STAT_OK
    1643            1 :       return
    1644              :     end if
    1645              : 
    1646              :     ! Parse the combined text as a matrix
    1647           28 :     block
    1648            7 :       type(hsd_node_t) :: temp_val
    1649            7 :       call new_value(temp_val)
    1650            7 :       call temp_val%set_raw(combined_text)
    1651            7 :       call temp_val%get_real_matrix(mat, nrows, ncols, stat)
    1652           28 :       call temp_val%destroy()
    1653              :     end block
    1654              : 
    1655           19 :   end subroutine get_real_matrix_from_table
    1656              : 
    1657              :   !> Get 2D complex matrix by path
    1658              :   !> Handles both value nodes and table nodes (where content is in unnamed children)
    1659              :   !>
    1660              :   !> If `order` is present and set to "column-major", the returned matrix is
    1661              :   !> transposed so that text rows map to Fortran columns (column-major layout).
    1662              :   !> Default is text-layout (row-major).
    1663            5 :   subroutine hsd_get_complex_dp_matrix(table, path, val, nrows, ncols, stat, order)
    1664              :     type(hsd_node_t), intent(in), target :: table
    1665              :     character(len=*), intent(in) :: path
    1666              :     complex(dp), allocatable, intent(out) :: val(:,:)
    1667              :     integer, intent(out) :: nrows, ncols
    1668              :     integer, intent(out), optional :: stat
    1669              :     character(len=*), intent(in), optional :: order
    1670              : 
    1671              :     type(hsd_node_t), pointer :: child
    1672            5 :     integer :: local_stat
    1673              : 
    1674            5 :     call hsd_get_child(table, path, child, local_stat)
    1675              : 
    1676            5 :     if (local_stat /= 0 .or. .not. associated(child)) then
    1677            0 :       if (present(stat)) stat = HSD_STAT_NOT_FOUND
    1678            0 :       allocate(val(0,0))
    1679            0 :       nrows = 0
    1680            0 :       ncols = 0
    1681            0 :       return
    1682              :     end if
    1683              : 
    1684            5 :     child%processed = .true.
    1685              : 
    1686            5 :     if (child%node_type == NODE_TYPE_VALUE) then
    1687            1 :       call child%get_complex_matrix(val, nrows, ncols, local_stat)
    1688            1 :       if (present(stat)) stat = local_stat
    1689            4 :     else if (child%node_type == NODE_TYPE_TABLE) then
    1690              :       ! Table nodes store matrix data as unnamed child values
    1691            4 :       call get_complex_matrix_from_table(child, val, nrows, ncols, local_stat)
    1692            4 :       if (present(stat)) stat = local_stat
    1693              :     else
    1694            0 :       if (present(stat)) stat = HSD_STAT_TYPE_ERROR
    1695            0 :       allocate(val(0,0))
    1696            0 :       nrows = 0
    1697            0 :       ncols = 0
    1698              :     end if
    1699              : 
    1700              :     ! Transpose if column-major order requested
    1701            5 :     if (present(order)) then
    1702            1 :       if (order == "column-major" .and. nrows > 0 .and. ncols > 0) then
    1703            1 :         block
    1704            1 :           complex(dp), allocatable :: tmp(:,:)
    1705            1 :           integer :: swap
    1706            1 :           allocate(tmp(ncols, nrows))
    1707            9 :           tmp = transpose(val)
    1708            1 :           call move_alloc(tmp, val)
    1709            1 :           swap = nrows
    1710            1 :           nrows = ncols
    1711            1 :           ncols = swap
    1712              :         end block
    1713              :       end if
    1714              :     end if
    1715              : 
    1716           13 :   end subroutine hsd_get_complex_dp_matrix
    1717              : 
    1718              :   !> Extract complex matrix from table with unnamed value children
    1719            4 :   subroutine get_complex_matrix_from_table(tbl, mat, nrows, ncols, stat)
    1720              :     type(hsd_node_t), intent(in) :: tbl
    1721              :     complex(dp), allocatable, intent(out) :: mat(:,:)
    1722              :     integer, intent(out) :: nrows, ncols, stat
    1723              : 
    1724              :     type(hsd_node_t), pointer :: child
    1725            4 :     character(len=:), allocatable :: combined_text, str_val
    1726            4 :     integer :: i, local_stat
    1727              : 
    1728              :     ! Combine all unnamed value children into single text
    1729            4 :     combined_text = ""
    1730            7 :     do i = 1, tbl%num_children
    1731            3 :       call tbl%get_child(i, child)
    1732            7 :       if (associated(child)) then
    1733            3 :         if (child%node_type == NODE_TYPE_VALUE) then
    1734              :           ! Include unnamed, empty-named, or #text-named value nodes
    1735              :           block
    1736            3 :             logical :: is_text_child
    1737            3 :             is_text_child = .not. allocated(child%name)
    1738            3 :             if (.not. is_text_child) &
    1739            3 :                 & is_text_child = (len_trim(child%name) == 0 .or. child%name == "#text")
    1740            3 :             if (is_text_child) then
    1741            3 :               call child%get_string(str_val, local_stat)
    1742            3 :               if (local_stat == 0 .and. len_trim(str_val) > 0) then
    1743            3 :                 if (len(combined_text) > 0) then
    1744            0 :                   combined_text = combined_text // char(10) // str_val
    1745              :                 else
    1746            3 :                   combined_text = str_val
    1747              :                 end if
    1748              :               end if
    1749              :             end if
    1750              :           end block
    1751              :         end if
    1752              :       end if
    1753              :     end do
    1754              : 
    1755            4 :     if (len_trim(combined_text) == 0) then
    1756            1 :       allocate(mat(0,0))
    1757            1 :       nrows = 0
    1758            1 :       ncols = 0
    1759            1 :       stat = HSD_STAT_OK
    1760            1 :       return
    1761              :     end if
    1762              : 
    1763              :     ! Parse the combined text as a matrix
    1764           12 :     block
    1765            3 :       type(hsd_node_t) :: temp_val
    1766            3 :       call new_value(temp_val)
    1767            3 :       call temp_val%set_raw(combined_text)
    1768            3 :       call temp_val%get_complex_matrix(mat, nrows, ncols, stat)
    1769           12 :       call temp_val%destroy()
    1770              :     end block
    1771              : 
    1772            9 :   end subroutine get_complex_matrix_from_table
    1773              : 
    1774              :   ! ===== helpers =====
    1775              : 
    1776              :   !> Copy local status into optional output status.
    1777         1316 :   pure subroutine set_stat_from_local_(local_stat, stat)
    1778              :     integer, intent(in) :: local_stat
    1779              :     integer, intent(out), optional :: stat
    1780              : 
    1781         1052 :     if (present(stat)) stat = local_stat
    1782            4 :   end subroutine set_stat_from_local_
    1783              : 
    1784              :   !> Common helper for hsd_get_or_set status and optional child return.
    1785           26 :   subroutine finalize_get_or_set_(table, path, local_stat, stat, child)
    1786              :     type(hsd_node_t), intent(inout), target :: table
    1787              :     character(len=*), intent(in) :: path
    1788              :     integer, intent(in) :: local_stat
    1789              :     integer, intent(out), optional :: stat
    1790              :     type(hsd_node_t), pointer, intent(out), optional :: child
    1791              : 
    1792           43 :     if (local_stat /= HSD_STAT_OK) then
    1793           17 :       call set_stat_from_local_(local_stat, stat)
    1794              :     else
    1795            9 :       call set_stat_from_local_(HSD_STAT_OK, stat)
    1796              :     end if
    1797              : 
    1798           26 :     if (present(child)) then
    1799           11 :       call hsd_get_table(table, path, child)
    1800           11 :       if (.not. associated(child)) child => table
    1801              :     end if
    1802              : 
    1803         1316 :   end subroutine finalize_get_or_set_
    1804              : 
    1805              :   !> Get or create a value child; fails with TYPE_ERROR if final node is not a value.
    1806         1285 :   subroutine get_or_create_value_child_(table, path, vchild, stat)
    1807              :     type(hsd_node_t), intent(inout), target :: table
    1808              :     character(len=*), intent(in) :: path
    1809              :     type(hsd_node_t), pointer, intent(out) :: vchild
    1810              :     integer, intent(out) :: stat
    1811              : 
    1812              :     type(hsd_node_t), pointer :: child
    1813         1285 :     integer :: local_stat
    1814              : 
    1815         1285 :     nullify(vchild)
    1816         1285 :     call get_or_create_child(table, path, child, local_stat)
    1817         1285 :     if (local_stat /= HSD_STAT_OK) then
    1818            7 :       stat = local_stat
    1819            7 :       return
    1820              :     end if
    1821              : 
    1822         1278 :     if (child%node_type == NODE_TYPE_VALUE) then
    1823         1278 :       vchild => child
    1824         1278 :       stat = HSD_STAT_OK
    1825              :     else
    1826            0 :       stat = HSD_STAT_TYPE_ERROR
    1827              :     end if
    1828         1311 :   end subroutine get_or_create_value_child_
    1829              : 
    1830              :   !> Collect child references matching the final path segment.
    1831            5 :   subroutine collect_named_children_(table, path, children, stat, tables_only)
    1832              :     type(hsd_node_t), intent(in), target :: table
    1833              :     character(len=*), intent(in) :: path
    1834              :     type(hsd_node_ptr_t), allocatable, intent(out) :: children(:)
    1835              :     integer, intent(out), optional :: stat
    1836              :     logical, intent(in) :: tables_only
    1837              : 
    1838            5 :     character(len=:), allocatable :: child_name
    1839              :     type(hsd_node_t), pointer :: child
    1840              :     type(hsd_node_t), pointer :: parent_table
    1841            5 :     integer :: local_stat, i, count
    1842            5 :     character(len=:), allocatable :: lower_name
    1843              : 
    1844            0 :     call resolve_path_parent_(table, path, parent_table, child_name, local_stat)
    1845            5 :     if (local_stat /= HSD_STAT_OK) then
    1846            2 :       allocate(children(0))
    1847            2 :       call set_stat_from_local_(local_stat, stat)
    1848            2 :       return
    1849              :     end if
    1850              : 
    1851            3 :     lower_name = to_lower(child_name)
    1852              : 
    1853            3 :     count = 0
    1854            9 :     do i = 1, parent_table%num_children
    1855            6 :       call parent_table%get_child(i, child)
    1856            6 :       if (.not. associated(child)) cycle
    1857            6 :       if (.not. allocated(child%name)) cycle
    1858            6 :       if (to_lower(child%name) /= lower_name) cycle
    1859            7 :       if (tables_only) then
    1860            0 :         if (child%node_type == NODE_TYPE_TABLE) then
    1861            0 :           count = count + 1
    1862              :         end if
    1863              :       else
    1864            4 :         count = count + 1
    1865              :       end if
    1866              :     end do
    1867              : 
    1868            7 :     allocate(children(count))
    1869            3 :     count = 0
    1870            9 :     do i = 1, parent_table%num_children
    1871            6 :       call parent_table%get_child(i, child)
    1872            6 :       if (.not. associated(child)) cycle
    1873            6 :       if (.not. allocated(child%name)) cycle
    1874            6 :       if (to_lower(child%name) /= lower_name) cycle
    1875            7 :       if (tables_only) then
    1876            0 :         if (child%node_type == NODE_TYPE_TABLE) then
    1877            0 :           count = count + 1
    1878            0 :           children(count)%node => child
    1879            0 :           child%processed = .true.
    1880              :         end if
    1881              :       else
    1882            4 :         count = count + 1
    1883            4 :         children(count)%node => child
    1884            4 :         child%processed = .true.
    1885              :       end if
    1886              :     end do
    1887              : 
    1888            3 :     call set_stat_from_local_(HSD_STAT_OK, stat)
    1889         1295 :   end subroutine collect_named_children_
    1890              : 
    1891              :   !> Mark a named child node as processed
    1892           17 :   subroutine mark_child_processed_(table, path)
    1893              :     type(hsd_node_t), intent(inout), target :: table
    1894              :     character(len=*), intent(in) :: path
    1895              : 
    1896              :     type(hsd_node_t), pointer :: child
    1897           17 :     integer :: local_stat
    1898              : 
    1899           17 :     call hsd_get_child(table, path, child, local_stat)
    1900           17 :     if (local_stat == 0 .and. associated(child)) then
    1901           17 :       child%processed = .true.
    1902              :     end if
    1903              : 
    1904            5 :   end subroutine mark_child_processed_
    1905              : 
    1906              :   ! ===== hsd_get_or_set implementations =====
    1907              : 
    1908              :   !> Get string value with default, writing default back to tree if absent
    1909            3 :   subroutine hsd_get_or_set_string(table, path, val, default, stat, child)
    1910              :     type(hsd_node_t), intent(inout), target :: table
    1911              :     character(len=*), intent(in) :: path
    1912              :     character(len=:), allocatable, intent(out) :: val
    1913              :     character(len=*), intent(in) :: default
    1914              :     integer, intent(out), optional :: stat
    1915              :     type(hsd_node_t), pointer, intent(out), optional :: child
    1916              : 
    1917            3 :     integer :: local_stat
    1918              : 
    1919            3 :     call hsd_get_string(table, path, val, local_stat)
    1920              : 
    1921            3 :     if (local_stat /= 0) then
    1922            2 :       val = default
    1923            2 :       call hsd_set(table, path, default)
    1924            2 :       call mark_child_processed_(table, path)
    1925              :     end if
    1926              : 
    1927            3 :     call finalize_get_or_set_(table, path, local_stat, stat, child)
    1928              : 
    1929           17 :   end subroutine hsd_get_or_set_string
    1930              : 
    1931              :   !> Get integer value with default, writing default back to tree if absent
    1932            4 :   subroutine hsd_get_or_set_integer(table, path, val, default, stat, child)
    1933              :     type(hsd_node_t), intent(inout), target :: table
    1934              :     character(len=*), intent(in) :: path
    1935              :     integer, intent(out) :: val
    1936              :     integer, intent(in) :: default
    1937              :     integer, intent(out), optional :: stat
    1938              :     type(hsd_node_t), pointer, intent(out), optional :: child
    1939              : 
    1940            4 :     integer :: local_stat
    1941              : 
    1942            4 :     call hsd_get_integer(table, path, val, local_stat)
    1943              : 
    1944            4 :     if (local_stat /= 0) then
    1945            2 :       val = default
    1946            2 :       call hsd_set(table, path, default)
    1947            2 :       call mark_child_processed_(table, path)
    1948              :     end if
    1949              : 
    1950            4 :     call finalize_get_or_set_(table, path, local_stat, stat, child)
    1951              : 
    1952            3 :   end subroutine hsd_get_or_set_integer
    1953              : 
    1954              :   !> Get double precision real value with default, writing default back to tree if absent
    1955            2 :   subroutine hsd_get_or_set_real_dp(table, path, val, default, stat, child)
    1956              :     type(hsd_node_t), intent(inout), target :: table
    1957              :     character(len=*), intent(in) :: path
    1958              :     real(dp), intent(out) :: val
    1959              :     real(dp), intent(in) :: default
    1960              :     integer, intent(out), optional :: stat
    1961              :     type(hsd_node_t), pointer, intent(out), optional :: child
    1962              : 
    1963            2 :     integer :: local_stat
    1964              : 
    1965            2 :     call hsd_get_real_dp(table, path, val, local_stat)
    1966              : 
    1967            2 :     if (local_stat /= 0) then
    1968            2 :       val = default
    1969            2 :       call hsd_set(table, path, default)
    1970            2 :       call mark_child_processed_(table, path)
    1971              :     end if
    1972              : 
    1973            2 :     call finalize_get_or_set_(table, path, local_stat, stat, child)
    1974              : 
    1975            4 :   end subroutine hsd_get_or_set_real_dp
    1976              : 
    1977              :   !> Get logical value with default, writing default back to tree if absent
    1978            4 :   subroutine hsd_get_or_set_logical(table, path, val, default, stat, child)
    1979              :     type(hsd_node_t), intent(inout), target :: table
    1980              :     character(len=*), intent(in) :: path
    1981              :     logical, intent(out) :: val
    1982              :     logical, intent(in) :: default
    1983              :     integer, intent(out), optional :: stat
    1984              :     type(hsd_node_t), pointer, intent(out), optional :: child
    1985              : 
    1986            4 :     integer :: local_stat
    1987              : 
    1988            4 :     call hsd_get_logical(table, path, val, local_stat)
    1989              : 
    1990            4 :     if (local_stat /= 0) then
    1991            3 :       val = default
    1992            3 :       call hsd_set(table, path, default)
    1993            3 :       call mark_child_processed_(table, path)
    1994              :     end if
    1995              : 
    1996            4 :     call finalize_get_or_set_(table, path, local_stat, stat, child)
    1997              : 
    1998            2 :   end subroutine hsd_get_or_set_logical
    1999              : 
    2000              :   !> Get complex value with default, writing default back to tree if absent
    2001            3 :   subroutine hsd_get_or_set_complex_dp(table, path, val, default, stat, child)
    2002              :     type(hsd_node_t), intent(inout), target :: table
    2003              :     character(len=*), intent(in) :: path
    2004              :     complex(dp), intent(out) :: val
    2005              :     complex(dp), intent(in) :: default
    2006              :     integer, intent(out), optional :: stat
    2007              :     type(hsd_node_t), pointer, intent(out), optional :: child
    2008              : 
    2009            3 :     integer :: local_stat
    2010              : 
    2011            3 :     call hsd_get_complex_dp(table, path, val, local_stat)
    2012              : 
    2013            3 :     if (local_stat /= 0) then
    2014            2 :       val = default
    2015            2 :       call hsd_set(table, path, default)
    2016            2 :       call mark_child_processed_(table, path)
    2017              :     end if
    2018              : 
    2019            3 :     call finalize_get_or_set_(table, path, local_stat, stat, child)
    2020              : 
    2021            4 :   end subroutine hsd_get_or_set_complex_dp
    2022              : 
    2023              :   !> Get integer array with default, writing default back to tree if absent
    2024            6 :   subroutine hsd_get_or_set_integer_array(table, path, val, default, stat, child)
    2025              :     type(hsd_node_t), intent(inout), target :: table
    2026              :     character(len=*), intent(in) :: path
    2027              :     integer, allocatable, intent(out) :: val(:)
    2028              :     integer, intent(in) :: default(:)
    2029              :     integer, intent(out), optional :: stat
    2030              :     type(hsd_node_t), pointer, intent(out), optional :: child
    2031              : 
    2032            3 :     integer :: local_stat
    2033              : 
    2034            3 :     call hsd_get_integer_array(table, path, val, local_stat)
    2035              : 
    2036            3 :     if (local_stat /= 0) then
    2037            8 :       val = default
    2038            2 :       call hsd_set(table, path, default)
    2039            2 :       call mark_child_processed_(table, path)
    2040              :     end if
    2041              : 
    2042            3 :     call finalize_get_or_set_(table, path, local_stat, stat, child)
    2043              : 
    2044            3 :   end subroutine hsd_get_or_set_integer_array
    2045              : 
    2046              :   !> Get double precision real array with default, writing default back to tree if absent
    2047            6 :   subroutine hsd_get_or_set_real_dp_array(table, path, val, default, stat, child)
    2048              :     type(hsd_node_t), intent(inout), target :: table
    2049              :     character(len=*), intent(in) :: path
    2050              :     real(dp), allocatable, intent(out) :: val(:)
    2051              :     real(dp), intent(in) :: default(:)
    2052              :     integer, intent(out), optional :: stat
    2053              :     type(hsd_node_t), pointer, intent(out), optional :: child
    2054              : 
    2055            3 :     integer :: local_stat
    2056              : 
    2057            3 :     call hsd_get_real_dp_array(table, path, val, local_stat)
    2058              : 
    2059            3 :     if (local_stat /= 0) then
    2060            6 :       val = default
    2061            2 :       call hsd_set(table, path, default)
    2062            2 :       call mark_child_processed_(table, path)
    2063              :     end if
    2064              : 
    2065            3 :     call finalize_get_or_set_(table, path, local_stat, stat, child)
    2066              : 
    2067            3 :   end subroutine hsd_get_or_set_real_dp_array
    2068              : 
    2069              :   !> Get logical array with default, writing default back to tree if absent
    2070            8 :   subroutine hsd_get_or_set_logical_array(table, path, val, default, stat, child)
    2071              :     type(hsd_node_t), intent(inout), target :: table
    2072              :     character(len=*), intent(in) :: path
    2073              :     logical, allocatable, intent(out) :: val(:)
    2074              :     logical, intent(in) :: default(:)
    2075              :     integer, intent(out), optional :: stat
    2076              :     type(hsd_node_t), pointer, intent(out), optional :: child
    2077              : 
    2078            4 :     integer :: local_stat
    2079              : 
    2080            4 :     call hsd_get_logical_array(table, path, val, local_stat)
    2081              : 
    2082            4 :     if (local_stat /= 0) then
    2083            7 :       val = default
    2084            2 :       call hsd_set(table, path, default)
    2085            2 :       call mark_child_processed_(table, path)
    2086              :     end if
    2087              : 
    2088            4 :     call finalize_get_or_set_(table, path, local_stat, stat, child)
    2089              : 
    2090            3 :   end subroutine hsd_get_or_set_logical_array
    2091              : 
    2092              : 
    2093              :   !> Get concatenated text content of all unnamed value children.
    2094              :   !>
    2095              :   !> Iterates children of `table`, collecting text from unnamed or "#text"
    2096              :   !> `hsd_value` nodes. Multiple values are separated by spaces.
    2097            3 :   subroutine hsd_get_inline_text(table, text, stat)
    2098              :     type(hsd_node_t), intent(in), target :: table
    2099              :     character(len=:), allocatable, intent(out) :: text
    2100              :     integer, intent(out), optional :: stat
    2101              : 
    2102            3 :     integer :: ii, local_stat
    2103              :     type(hsd_node_t), pointer :: child
    2104            3 :     character(len=:), allocatable :: piece
    2105              : 
    2106            3 :     text = ""
    2107            8 :     do ii = 1, table%num_children
    2108            5 :       call table%get_child(ii, child)
    2109            5 :       if (.not. associated(child)) cycle
    2110            8 :       if (child%node_type == NODE_TYPE_VALUE) then
    2111              :         block
    2112            4 :           logical :: is_anon_val
    2113            4 :           is_anon_val = .true.
    2114            4 :           if (allocated(child%name)) then
    2115            3 :             if (len_trim(child%name) > 0 .and. child%name /= "#text") then
    2116            0 :               is_anon_val = .false.
    2117              :             end if
    2118              :           end if
    2119            4 :         if (is_anon_val) then
    2120            4 :           call child%get_string(piece, local_stat)
    2121            4 :           if (local_stat == HSD_STAT_OK .and. allocated(piece)) then
    2122            4 :             if (len(text) > 0) then
    2123            2 :               text = text // " " // piece
    2124              :             else
    2125            2 :               text = piece
    2126              :             end if
    2127              :           end if
    2128              :         end if
    2129              :         end block
    2130              :       end if
    2131              :     end do
    2132              : 
    2133            3 :     if (present(stat)) then
    2134            3 :       if (len(text) > 0) then
    2135            2 :         stat = HSD_STAT_OK
    2136              :       else
    2137            1 :         stat = HSD_STAT_NOT_FOUND
    2138              :       end if
    2139              :     end if
    2140              : 
    2141            7 :   end subroutine hsd_get_inline_text
    2142              : 
    2143              : 
    2144              :   !> Set string value by path
    2145           22 :   subroutine hsd_set_string(table, path, val, stat)
    2146              :     type(hsd_node_t), intent(inout) :: table
    2147              :     character(len=*), intent(in) :: path
    2148              :     character(len=*), intent(in) :: val
    2149              :     integer, intent(out), optional :: stat
    2150              : 
    2151              :     type(hsd_node_t), pointer :: vchild
    2152           22 :     integer :: local_stat
    2153              : 
    2154           22 :     call get_or_create_value_child_(table, path, vchild, local_stat)
    2155              : 
    2156           22 :     if (local_stat /= HSD_STAT_OK) then
    2157            2 :       call set_stat_from_local_(local_stat, stat)
    2158            2 :       return
    2159              :     end if
    2160              : 
    2161           20 :     call vchild%set_string(val)
    2162           20 :     call set_stat_from_local_(HSD_STAT_OK, stat)
    2163              : 
    2164           25 :   end subroutine hsd_set_string
    2165              : 
    2166              :   !> Set integer value by path
    2167         1231 :   subroutine hsd_set_integer(table, path, val, stat)
    2168              :     type(hsd_node_t), intent(inout) :: table
    2169              :     character(len=*), intent(in) :: path
    2170              :     integer, intent(in) :: val
    2171              :     integer, intent(out), optional :: stat
    2172              : 
    2173              :     type(hsd_node_t), pointer :: vchild
    2174         1231 :     integer :: local_stat
    2175              : 
    2176         1231 :     call get_or_create_value_child_(table, path, vchild, local_stat)
    2177              : 
    2178         1231 :     if (local_stat /= HSD_STAT_OK) then
    2179            2 :       call set_stat_from_local_(local_stat, stat)
    2180            2 :       return
    2181              :     end if
    2182              : 
    2183         1229 :     call vchild%set_integer(val)
    2184         1229 :     call set_stat_from_local_(HSD_STAT_OK, stat)
    2185              : 
    2186         1253 :   end subroutine hsd_set_integer
    2187              : 
    2188              :   !> Set double precision real value by path
    2189           12 :   subroutine hsd_set_real_dp(table, path, val, stat)
    2190              :     type(hsd_node_t), intent(inout) :: table
    2191              :     character(len=*), intent(in) :: path
    2192              :     real(dp), intent(in) :: val
    2193              :     integer, intent(out), optional :: stat
    2194              : 
    2195              :     type(hsd_node_t), pointer :: vchild
    2196           12 :     integer :: local_stat
    2197              : 
    2198           12 :     call get_or_create_value_child_(table, path, vchild, local_stat)
    2199              : 
    2200           12 :     if (local_stat /= HSD_STAT_OK) then
    2201            1 :       call set_stat_from_local_(local_stat, stat)
    2202            1 :       return
    2203              :     end if
    2204              : 
    2205           11 :     call vchild%set_real(val)
    2206           11 :     call set_stat_from_local_(HSD_STAT_OK, stat)
    2207              : 
    2208         1243 :   end subroutine hsd_set_real_dp
    2209              : 
    2210              :   !> Set logical value by path
    2211           12 :   subroutine hsd_set_logical(table, path, val, stat)
    2212              :     type(hsd_node_t), intent(inout) :: table
    2213              :     character(len=*), intent(in) :: path
    2214              :     logical, intent(in) :: val
    2215              :     integer, intent(out), optional :: stat
    2216              : 
    2217              :     type(hsd_node_t), pointer :: vchild
    2218           12 :     integer :: local_stat
    2219              : 
    2220           12 :     call get_or_create_value_child_(table, path, vchild, local_stat)
    2221              : 
    2222           12 :     if (local_stat /= HSD_STAT_OK) then
    2223            1 :       call set_stat_from_local_(local_stat, stat)
    2224            1 :       return
    2225              :     end if
    2226              : 
    2227           11 :     call vchild%set_logical(val)
    2228           11 :     call set_stat_from_local_(HSD_STAT_OK, stat)
    2229              : 
    2230           24 :   end subroutine hsd_set_logical
    2231              : 
    2232              :   !> Set complex value by path
    2233            8 :   subroutine hsd_set_complex_dp(table, path, val, stat)
    2234              :     type(hsd_node_t), intent(inout) :: table
    2235              :     character(len=*), intent(in) :: path
    2236              :     complex(dp), intent(in) :: val
    2237              :     integer, intent(out), optional :: stat
    2238              : 
    2239              :     type(hsd_node_t), pointer :: vchild
    2240            8 :     integer :: local_stat
    2241              : 
    2242            8 :     call get_or_create_value_child_(table, path, vchild, local_stat)
    2243              : 
    2244            8 :     if (local_stat /= HSD_STAT_OK) then
    2245            1 :       call set_stat_from_local_(local_stat, stat)
    2246            1 :       return
    2247              :     end if
    2248              : 
    2249            7 :     call vchild%set_complex(val)
    2250            7 :     call set_stat_from_local_(HSD_STAT_OK, stat)
    2251              : 
    2252           20 :   end subroutine hsd_set_complex_dp
    2253              : 
    2254              :   !> Set integer array by path
    2255           12 :   subroutine hsd_set_integer_array(table, path, val, stat)
    2256              :     type(hsd_node_t), intent(inout) :: table
    2257              :     character(len=*), intent(in) :: path
    2258              :     integer, intent(in) :: val(:)
    2259              :     integer, intent(out), optional :: stat
    2260              : 
    2261              :     type(hsd_node_t), pointer :: child
    2262            6 :     integer :: local_stat, i
    2263              :     character(len=32) :: buffer
    2264            6 :     type(string_buffer_t) :: buf
    2265              : 
    2266            6 :     call get_or_create_child(table, path, child, local_stat)
    2267              : 
    2268            6 :     if (local_stat /= 0) then
    2269            1 :       if (present(stat)) stat = local_stat
    2270            1 :       return
    2271              :     end if
    2272              : 
    2273            5 :     if (child%node_type == NODE_TYPE_VALUE) then
    2274            5 :       call buf%init()
    2275           22 :       do i = 1, size(val)
    2276           17 :         write(buffer, '(I0)') val(i)
    2277           17 :         if (i > 1) call buf%append_char(' ')
    2278           22 :         call buf%append_str(trim(adjustl(buffer)))
    2279              :       end do
    2280            5 :       call child%set_raw(buf%get_string())
    2281              :     else
    2282            0 :       if (present(stat)) stat = HSD_STAT_TYPE_ERROR
    2283            0 :       return
    2284              :     end if
    2285              : 
    2286            5 :     if (present(stat)) stat = HSD_STAT_OK
    2287              : 
    2288           14 :   end subroutine hsd_set_integer_array
    2289              : 
    2290              :   !> Set double precision real array by path
    2291           10 :   subroutine hsd_set_real_dp_array(table, path, val, stat)
    2292              :     type(hsd_node_t), intent(inout) :: table
    2293              :     character(len=*), intent(in) :: path
    2294              :     real(dp), intent(in) :: val(:)
    2295              :     integer, intent(out), optional :: stat
    2296              : 
    2297              :     type(hsd_node_t), pointer :: child
    2298            5 :     integer :: local_stat, i
    2299              :     character(len=32) :: buffer
    2300            5 :     type(string_buffer_t) :: buf
    2301              : 
    2302            5 :     call get_or_create_child(table, path, child, local_stat)
    2303              : 
    2304            5 :     if (local_stat /= 0) then
    2305            1 :       if (present(stat)) stat = local_stat
    2306            1 :       return
    2307              :     end if
    2308              : 
    2309            4 :     if (child%node_type == NODE_TYPE_VALUE) then
    2310            4 :       call buf%init()
    2311           13 :       do i = 1, size(val)
    2312            9 :         write(buffer, '(G0)') val(i)
    2313            9 :         if (i > 1) call buf%append_char(' ')
    2314           13 :         call buf%append_str(trim(adjustl(buffer)))
    2315              :       end do
    2316            4 :       call child%set_raw(buf%get_string())
    2317              :     else
    2318            0 :       if (present(stat)) stat = HSD_STAT_TYPE_ERROR
    2319            0 :       return
    2320              :     end if
    2321              : 
    2322            4 :     if (present(stat)) stat = HSD_STAT_OK
    2323              : 
    2324           11 :   end subroutine hsd_set_real_dp_array
    2325              : 
    2326              :   !> Set logical array by path
    2327           12 :   subroutine hsd_set_logical_array(table, path, val, stat)
    2328              :     type(hsd_node_t), intent(inout) :: table
    2329              :     character(len=*), intent(in) :: path
    2330              :     logical, intent(in) :: val(:)
    2331              :     integer, intent(out), optional :: stat
    2332              : 
    2333              :     type(hsd_node_t), pointer :: child
    2334            6 :     integer :: local_stat, i
    2335            6 :     type(string_buffer_t) :: buf
    2336              : 
    2337            6 :     call get_or_create_child(table, path, child, local_stat)
    2338              : 
    2339            6 :     if (local_stat /= 0) then
    2340            1 :       if (present(stat)) stat = local_stat
    2341            1 :       return
    2342              :     end if
    2343              : 
    2344            5 :     if (child%node_type == NODE_TYPE_VALUE) then
    2345            5 :       call buf%init()
    2346           18 :       do i = 1, size(val)
    2347           13 :         if (i > 1) call buf%append_char(' ')
    2348           18 :         if (val(i)) then
    2349            8 :           call buf%append_str("Yes")
    2350              :         else
    2351            5 :           call buf%append_str("No")
    2352              :         end if
    2353              :       end do
    2354            5 :       call child%set_raw(buf%get_string())
    2355              :     else
    2356            0 :       if (present(stat)) stat = HSD_STAT_TYPE_ERROR
    2357            0 :       return
    2358              :     end if
    2359              : 
    2360            5 :     if (present(stat)) stat = HSD_STAT_OK
    2361              : 
    2362           11 :   end subroutine hsd_set_logical_array
    2363              : 
    2364              :   !> Set complex array by path
    2365            8 :   subroutine hsd_set_complex_dp_array(table, path, val, stat)
    2366              :     type(hsd_node_t), intent(inout) :: table
    2367              :     character(len=*), intent(in) :: path
    2368              :     complex(dp), intent(in) :: val(:)
    2369              :     integer, intent(out), optional :: stat
    2370              : 
    2371              :     type(hsd_node_t), pointer :: child
    2372            4 :     integer :: local_stat, i
    2373              :     character(len=64) :: buffer
    2374            4 :     type(string_buffer_t) :: buf
    2375              : 
    2376            4 :     call get_or_create_child(table, path, child, local_stat)
    2377              : 
    2378            4 :     if (local_stat /= 0) then
    2379            1 :       if (present(stat)) stat = local_stat
    2380            1 :       return
    2381              :     end if
    2382              : 
    2383            3 :     if (child%node_type == NODE_TYPE_VALUE) then
    2384            3 :       call buf%init()
    2385            8 :       do i = 1, size(val)
    2386            5 :         if (i > 1) call buf%append_char(' ')
    2387            5 :         if (aimag(val(i)) >= 0.0_dp) then
    2388            4 :           write(buffer, '(G0,"+",G0,"i")') real(val(i)), aimag(val(i))
    2389              :         else
    2390            1 :           write(buffer, '(G0,G0,"i")') real(val(i)), aimag(val(i))
    2391              :         end if
    2392            8 :         call buf%append_str(trim(adjustl(buffer)))
    2393              :       end do
    2394            3 :       call child%set_raw(buf%get_string())
    2395              :     else
    2396            0 :       if (present(stat)) stat = HSD_STAT_TYPE_ERROR
    2397            0 :       return
    2398              :     end if
    2399              : 
    2400            3 :     if (present(stat)) stat = HSD_STAT_OK
    2401              : 
    2402           10 :   end subroutine hsd_set_complex_dp_array
    2403              : 
    2404              :   !> Set string array by path
    2405            6 :   subroutine hsd_set_string_array(table, path, val, stat)
    2406              :     type(hsd_node_t), intent(inout) :: table
    2407              :     character(len=*), intent(in) :: path
    2408              :     character(len=*), intent(in) :: val(:)
    2409              :     integer, intent(out), optional :: stat
    2410              : 
    2411              :     type(hsd_node_t), pointer :: child
    2412            3 :     integer :: local_stat, i
    2413            3 :     type(string_buffer_t) :: buf
    2414              : 
    2415            3 :     call get_or_create_child(table, path, child, local_stat)
    2416              : 
    2417            3 :     if (local_stat /= 0) then
    2418            0 :       if (present(stat)) stat = local_stat
    2419            0 :       return
    2420              :     end if
    2421              : 
    2422            3 :     if (child%node_type == NODE_TYPE_VALUE) then
    2423            3 :       call buf%init()
    2424           10 :       do i = 1, size(val)
    2425            7 :         if (i > 1) call buf%append_char(' ')
    2426              :         ! Quote strings containing spaces
    2427           10 :         if (index(val(i), ' ') > 0) then
    2428            4 :           call buf%append_char('"')
    2429            4 :           call buf%append_str(trim(val(i)))
    2430            4 :           call buf%append_char('"')
    2431              :         else
    2432            3 :           call buf%append_str(trim(val(i)))
    2433              :         end if
    2434              :       end do
    2435            3 :       call child%set_raw(buf%get_string())
    2436              :     else
    2437            0 :       if (present(stat)) stat = HSD_STAT_TYPE_ERROR
    2438            0 :       return
    2439              :     end if
    2440              : 
    2441            3 :     if (present(stat)) stat = HSD_STAT_OK
    2442              : 
    2443            7 :   end subroutine hsd_set_string_array
    2444              : 
    2445              :   !> Set integer matrix by path
    2446            2 :   subroutine hsd_set_integer_matrix(table, path, val, stat)
    2447              :     type(hsd_node_t), intent(inout) :: table
    2448              :     character(len=*), intent(in) :: path
    2449              :     integer, intent(in) :: val(:,:)
    2450              :     integer, intent(out), optional :: stat
    2451              : 
    2452              :     type(hsd_node_t), pointer :: child
    2453            1 :     integer :: local_stat, ir, ic
    2454              :     character(len=32) :: buffer
    2455            1 :     type(string_buffer_t) :: buf
    2456              : 
    2457            1 :     call get_or_create_child(table, path, child, local_stat)
    2458              : 
    2459            1 :     if (local_stat /= 0) then
    2460            0 :       if (present(stat)) stat = local_stat
    2461            0 :       return
    2462              :     end if
    2463              : 
    2464            1 :     if (child%node_type == NODE_TYPE_VALUE) then
    2465            1 :       call buf%init()
    2466            3 :       do ir = 1, size(val, 1)
    2467            2 :         if (ir > 1) call buf%append_str(new_line('a'))
    2468            9 :         do ic = 1, size(val, 2)
    2469            6 :           write(buffer, '(I0)') val(ir, ic)
    2470            6 :           if (ic > 1) call buf%append_char(' ')
    2471            8 :           call buf%append_str(trim(adjustl(buffer)))
    2472              :         end do
    2473              :       end do
    2474            1 :       call child%set_raw(buf%get_string())
    2475              :     else
    2476            0 :       if (present(stat)) stat = HSD_STAT_TYPE_ERROR
    2477            0 :       return
    2478              :     end if
    2479              : 
    2480            1 :     if (present(stat)) stat = HSD_STAT_OK
    2481              : 
    2482            4 :   end subroutine hsd_set_integer_matrix
    2483              : 
    2484              :   !> Set double precision real matrix by path
    2485            4 :   subroutine hsd_set_real_dp_matrix(table, path, val, stat)
    2486              :     type(hsd_node_t), intent(inout) :: table
    2487              :     character(len=*), intent(in) :: path
    2488              :     real(dp), intent(in) :: val(:,:)
    2489              :     integer, intent(out), optional :: stat
    2490              : 
    2491              :     type(hsd_node_t), pointer :: child
    2492            2 :     integer :: local_stat, ir, ic
    2493              :     character(len=32) :: buffer
    2494            2 :     type(string_buffer_t) :: buf
    2495              : 
    2496            2 :     call get_or_create_child(table, path, child, local_stat)
    2497              : 
    2498            2 :     if (local_stat /= 0) then
    2499            0 :       if (present(stat)) stat = local_stat
    2500            0 :       return
    2501              :     end if
    2502              : 
    2503            2 :     if (child%node_type == NODE_TYPE_VALUE) then
    2504            2 :       call buf%init()
    2505            6 :       do ir = 1, size(val, 1)
    2506            4 :         if (ir > 1) call buf%append_str(new_line('a'))
    2507           14 :         do ic = 1, size(val, 2)
    2508            8 :           write(buffer, '(G0)') val(ir, ic)
    2509            8 :           if (ic > 1) call buf%append_char(' ')
    2510           12 :           call buf%append_str(trim(adjustl(buffer)))
    2511              :         end do
    2512              :       end do
    2513            2 :       call child%set_raw(buf%get_string())
    2514              :     else
    2515            0 :       if (present(stat)) stat = HSD_STAT_TYPE_ERROR
    2516            0 :       return
    2517              :     end if
    2518              : 
    2519            2 :     if (present(stat)) stat = HSD_STAT_OK
    2520              : 
    2521            3 :   end subroutine hsd_set_real_dp_matrix
    2522              : 
    2523              :   !> Set complex double precision matrix by path
    2524            2 :   subroutine hsd_set_complex_dp_matrix(table, path, val, stat)
    2525              :     type(hsd_node_t), intent(inout) :: table
    2526              :     character(len=*), intent(in) :: path
    2527              :     complex(dp), intent(in) :: val(:,:)
    2528              :     integer, intent(out), optional :: stat
    2529              : 
    2530              :     type(hsd_node_t), pointer :: child
    2531            1 :     integer :: local_stat, ir, ic
    2532              :     character(len=64) :: buffer
    2533            1 :     type(string_buffer_t) :: buf
    2534              : 
    2535            1 :     call get_or_create_child(table, path, child, local_stat)
    2536              : 
    2537            1 :     if (local_stat /= 0) then
    2538            0 :       if (present(stat)) stat = local_stat
    2539            0 :       return
    2540              :     end if
    2541              : 
    2542            1 :     if (child%node_type == NODE_TYPE_VALUE) then
    2543            1 :       call buf%init()
    2544            3 :       do ir = 1, size(val, 1)
    2545            2 :         if (ir > 1) call buf%append_str(new_line('a'))
    2546            9 :         do ic = 1, size(val, 2)
    2547            6 :           if (ic > 1) call buf%append_char(' ')
    2548            6 :           if (aimag(val(ir, ic)) >= 0.0_dp) then
    2549            5 :             write(buffer, '(G0,"+",G0,"i")') real(val(ir, ic)), aimag(val(ir, ic))
    2550              :           else
    2551            1 :             write(buffer, '(G0,G0,"i")') real(val(ir, ic)), aimag(val(ir, ic))
    2552              :           end if
    2553            8 :           call buf%append_str(trim(adjustl(buffer)))
    2554              :         end do
    2555              :       end do
    2556            1 :       call child%set_raw(buf%get_string())
    2557              :     else
    2558            0 :       if (present(stat)) stat = HSD_STAT_TYPE_ERROR
    2559            0 :       return
    2560              :     end if
    2561              : 
    2562            1 :     if (present(stat)) stat = HSD_STAT_OK
    2563              : 
    2564            3 :   end subroutine hsd_set_complex_dp_matrix
    2565              : 
    2566              :   !> Get or create a child node by path, creating intermediate tables as needed
    2567         1313 :   subroutine get_or_create_child(table, path, child, stat)
    2568              :     type(hsd_node_t), intent(inout), target :: table
    2569              :     character(len=*), intent(in) :: path
    2570              :     type(hsd_node_t), pointer, intent(out) :: child
    2571              :     integer, intent(out), optional :: stat
    2572              : 
    2573         1313 :     character(len=:), allocatable :: remaining, segment
    2574              :     type(hsd_node_t), pointer :: current
    2575              :     type(hsd_node_t), pointer :: current_table
    2576         1313 :     type(hsd_node_t) :: new_tbl
    2577         1313 :     type(hsd_node_t) :: new_val
    2578         1313 :     integer :: sep_pos, i
    2579              : 
    2580         1313 :     child => null()
    2581         1313 :     remaining = path
    2582         1313 :     current_table => table
    2583              : 
    2584         1314 :     do while (len_trim(remaining) > 0)
    2585              :       ! Get next segment
    2586         1313 :       sep_pos = index(remaining, "/")
    2587         1313 :       if (sep_pos > 0) then
    2588           11 :         segment = remaining(1:sep_pos-1)
    2589           11 :         remaining = remaining(sep_pos+1:)
    2590              :       else
    2591         1302 :         segment = remaining
    2592         1302 :         remaining = ""
    2593              :       end if
    2594              : 
    2595              :       ! Look for existing child
    2596         1313 :       call current_table%get_child_by_name(segment, current)
    2597              : 
    2598         1313 :       if (.not. associated(current)) then
    2599              :         ! Need to create node
    2600         1295 :         if (len_trim(remaining) > 0) then
    2601              :           ! More path segments: create table
    2602            1 :           call new_table(new_tbl, name=to_lower(segment))
    2603            1 :           call current_table%add_child(new_tbl)
    2604              :           ! Get the newly added child
    2605            1 :           do i = current_table%num_children, 1, -1
    2606            1 :             call current_table%get_child(i, current)
    2607            1 :             if (associated(current)) then
    2608            1 :               if (allocated(current%name)) then
    2609            1 :                 if (to_lower(current%name) == to_lower(segment)) exit
    2610              :               end if
    2611              :             end if
    2612              :           end do
    2613              :         else
    2614              :           ! Final segment: create value node
    2615         1294 :           call new_value(new_val, name=to_lower(segment))
    2616         1294 :           call current_table%add_child(new_val)
    2617              :           ! Get the newly added child
    2618         1294 :           do i = current_table%num_children, 1, -1
    2619         1294 :             call current_table%get_child(i, current)
    2620         1294 :             if (associated(current)) then
    2621         1294 :               if (allocated(current%name)) then
    2622         1294 :                 if (to_lower(current%name) == to_lower(segment)) exit
    2623              :               end if
    2624              :             end if
    2625              :           end do
    2626         1294 :           child => current
    2627         1294 :           if (present(stat)) stat = HSD_STAT_OK
    2628         1294 :           return
    2629              :         end if
    2630              :       end if
    2631              : 
    2632              :       ! Navigate deeper if more path remains
    2633           19 :       if (len_trim(remaining) > 0) then
    2634           11 :         if (current%node_type == NODE_TYPE_TABLE) then
    2635            1 :           current_table => current
    2636              :         else
    2637              :           ! Path segment is not a table, cannot navigate
    2638           10 :           if (present(stat)) stat = HSD_STAT_NOT_FOUND
    2639           10 :           return
    2640              :         end if
    2641              :       else
    2642            8 :         child => current
    2643            8 :         if (present(stat)) stat = HSD_STAT_OK
    2644            8 :         return
    2645              :       end if
    2646              :     end do
    2647              : 
    2648            1 :     if (present(stat)) stat = HSD_STAT_NOT_FOUND
    2649              : 
    2650         1314 :   end subroutine get_or_create_child
    2651              : 
    2652              : 
    2653              :   !> Remove all children from a table node.
    2654              :   !>
    2655              :   !> After this call, the table has zero children. The children array and hash
    2656              :   !> index are fully deallocated so subsequent add_child calls re-initialize
    2657              :   !> correctly.
    2658            1 :   subroutine hsd_clear_children(table)
    2659              :     type(hsd_node_t), intent(inout) :: table
    2660              : 
    2661            1 :     integer :: ii
    2662              :     type(hsd_node_t), pointer :: child
    2663              : 
    2664              :     ! Destroy each child node
    2665            4 :     do ii = 1, table%num_children
    2666            3 :       call table%get_child(ii, child)
    2667            4 :       if (associated(child)) then
    2668            3 :         call child%destroy()
    2669            3 :         deallocate(table%children(ii)%node)
    2670              :       end if
    2671              :     end do
    2672              : 
    2673            1 :     table%num_children = 0
    2674            1 :     if (allocated(table%children)) deallocate(table%children)
    2675              : 
    2676         1313 :   end subroutine hsd_clear_children
    2677              : 
    2678              : 
    2679          217 : end module hsd_api
        

Generated by: LCOV version 2.0-1