LCOV - code coverage report
Current view: top level - src/api - hsd_query.f90 (source / functions) Coverage Total Hit
Test: coverage.info Lines: 93.6 % 203 190
Test Date: 2026-02-04 13:26:36 Functions: 100.0 % 17 17

            Line data    Source code
       1              : !> HSD query and navigation operations
       2              : !>
       3              : !> This module provides functionality for navigating HSD tree structures,
       4              : !> introspecting node types, and performing tree operations like merging
       5              : !> and cloning.
       6              : module hsd_query
       7              :   use hsd_utils, only: to_lower
       8              :   use hsd_error, only: HSD_STAT_OK, HSD_STAT_NOT_FOUND, HSD_STAT_TYPE_ERROR
       9              :   use hsd_types, only: hsd_node, hsd_table, hsd_value, new_table, new_value, &
      10              :     VALUE_TYPE_NONE, VALUE_TYPE_ARRAY
      11              :   implicit none (type, external)
      12              :   private
      13              : 
      14              :   ! Public procedures
      15              :   public :: hsd_get_child, hsd_get_table
      16              :   public :: hsd_has_child
      17              :   public :: hsd_remove_child
      18              :   public :: hsd_get_type, hsd_is_table, hsd_is_value, hsd_is_array
      19              :   public :: hsd_child_count, hsd_get_keys
      20              :   public :: hsd_get_attrib, hsd_has_attrib
      21              :   public :: hsd_merge, hsd_clone
      22              : 
      23              : contains
      24              : 
      25              :   !> Check if a table has a child with given name
      26           15 :   function hsd_has_child(table, name, case_insensitive) result(has)
      27              :     type(hsd_table), intent(in), target :: table
      28              :     character(len=*), intent(in) :: name
      29              :     logical, intent(in), optional :: case_insensitive
      30              :     logical :: has
      31              : 
      32           15 :     has = table%has_child(name, case_insensitive)
      33              : 
      34           15 :   end function hsd_has_child
      35              : 
      36              :   !> Remove a child from a table by name
      37              :   !>
      38              :   !> Supports path-based navigation with "/" separator for nested tables.
      39              :   !> The last component of the path is the child to remove.
      40            7 :   subroutine hsd_remove_child(table, path, stat, case_insensitive)
      41              :     type(hsd_table), intent(inout) :: table
      42              :     character(len=*), intent(in) :: path
      43              :     integer, intent(out), optional :: stat
      44              :     logical, intent(in), optional :: case_insensitive
      45              : 
      46              :     class(hsd_node), pointer :: parent_node
      47              :     type(hsd_table), pointer :: parent_table
      48            7 :     character(len=:), allocatable :: child_name, parent_path
      49            7 :     integer :: last_slash, local_stat
      50              : 
      51              :     ! Find the last slash to separate parent path from child name
      52            7 :     last_slash = index(path, "/", back=.true.)
      53              : 
      54            7 :     if (last_slash > 0) then
      55            3 :       parent_path = path(1:last_slash-1)
      56            3 :       child_name = path(last_slash+1:)
      57              : 
      58              :       ! Get the parent table
      59            3 :       call hsd_get_child(table, parent_path, parent_node, local_stat)
      60            3 :       if (local_stat /= HSD_STAT_OK .or. .not. associated(parent_node)) then
      61            0 :         if (present(stat)) stat = HSD_STAT_NOT_FOUND
      62            0 :         return
      63              :       end if
      64              : 
      65            3 :       select type (parent_node)
      66              :       type is (hsd_table)
      67            2 :         parent_table => parent_node
      68            2 :         call parent_table%remove_child_by_name(child_name, local_stat, case_insensitive)
      69            4 :         if (present(stat)) stat = local_stat
      70              :       class default
      71            1 :         if (present(stat)) stat = HSD_STAT_TYPE_ERROR
      72              :       end select
      73              :     else
      74              :       ! No path separator - remove directly from the root table
      75            4 :       child_name = path
      76            4 :       call table%remove_child_by_name(child_name, local_stat, case_insensitive)
      77            4 :       if (present(stat)) stat = local_stat
      78              :     end if
      79              : 
      80           22 :   end subroutine hsd_remove_child
      81              : 
      82              :   !> Get the type of a value at the given path
      83              :   !>
      84              :   !> Returns one of: VALUE_TYPE_NONE (not found or is table), VALUE_TYPE_STRING,
      85              :   !> VALUE_TYPE_INTEGER, VALUE_TYPE_REAL, VALUE_TYPE_LOGICAL, VALUE_TYPE_ARRAY,
      86              :   !> VALUE_TYPE_COMPLEX
      87           12 :   function hsd_get_type(table, path) result(val_type)
      88              :     type(hsd_table), intent(in), target :: table
      89              :     character(len=*), intent(in) :: path
      90              :     integer :: val_type
      91              : 
      92              :     class(hsd_node), pointer :: child
      93           12 :     integer :: local_stat
      94              : 
      95           12 :     val_type = VALUE_TYPE_NONE
      96           12 :     call hsd_get_child(table, path, child, local_stat)
      97              : 
      98           12 :     if (local_stat /= 0 .or. .not. associated(child)) return
      99              : 
     100              :     select type (child)
     101              :     type is (hsd_value)
     102            7 :       val_type = child%value_type
     103              :     end select
     104              : 
     105           19 :   end function hsd_get_type
     106              : 
     107              :   !> Check if the node at path is a table (container)
     108            7 :   function hsd_is_table(table, path) result(is_tbl)
     109              :     type(hsd_table), intent(in), target :: table
     110              :     character(len=*), intent(in) :: path
     111              :     logical :: is_tbl
     112              : 
     113              :     class(hsd_node), pointer :: child
     114            7 :     integer :: local_stat
     115              : 
     116            7 :     is_tbl = .false.
     117            7 :     call hsd_get_child(table, path, child, local_stat)
     118              : 
     119            7 :     if (local_stat /= 0 .or. .not. associated(child)) return
     120              : 
     121              :     select type (child)
     122              :     type is (hsd_table)
     123            3 :       is_tbl = .true.
     124              :     end select
     125              : 
     126           19 :   end function hsd_is_table
     127              : 
     128              :   !> Check if the node at path is a value (leaf)
     129            7 :   function hsd_is_value(table, path) result(is_val)
     130              :     type(hsd_table), intent(in), target :: table
     131              :     character(len=*), intent(in) :: path
     132              :     logical :: is_val
     133              : 
     134              :     class(hsd_node), pointer :: child
     135            7 :     integer :: local_stat
     136              : 
     137            7 :     is_val = .false.
     138            7 :     call hsd_get_child(table, path, child, local_stat)
     139              : 
     140            7 :     if (local_stat /= 0 .or. .not. associated(child)) return
     141              : 
     142              :     select type (child)
     143              :     type is (hsd_value)
     144            4 :       is_val = .true.
     145              :     end select
     146              : 
     147           14 :   end function hsd_is_value
     148              : 
     149              :   !> Check if the node at path contains array data
     150            1 :   function hsd_is_array(table, path) result(is_arr)
     151              :     type(hsd_table), intent(in), target :: table
     152              :     character(len=*), intent(in) :: path
     153              :     logical :: is_arr
     154              : 
     155            2 :     is_arr = (hsd_get_type(table, path) == VALUE_TYPE_ARRAY)
     156              : 
     157            8 :   end function hsd_is_array
     158              : 
     159              :   !> Get the number of children in a table at the given path
     160              :   !>
     161              :   !> Returns 0 if path not found or is not a table
     162           10 :   function hsd_child_count(table, path) result(count)
     163              :     type(hsd_table), intent(in), target :: table
     164              :     character(len=*), intent(in) :: path
     165              :     integer :: count
     166              : 
     167              :     class(hsd_node), pointer :: child
     168           10 :     integer :: local_stat
     169              : 
     170           10 :     count = 0
     171              : 
     172           10 :     if (len_trim(path) == 0) then
     173              :       ! Empty path means the root table itself
     174            3 :       count = table%num_children
     175            3 :       return
     176              :     end if
     177              : 
     178            7 :     call hsd_get_child(table, path, child, local_stat)
     179              : 
     180            7 :     if (local_stat /= 0 .or. .not. associated(child)) return
     181              : 
     182              :     select type (child)
     183              :     type is (hsd_table)
     184            5 :       count = child%num_children
     185              :     end select
     186              : 
     187           18 :   end function hsd_child_count
     188              : 
     189              :   !> Get the keys (child names) from a table at the given path
     190            9 :   subroutine hsd_get_keys(table, path, keys, stat)
     191              :     type(hsd_table), intent(in), target :: table
     192              :     character(len=*), intent(in) :: path
     193              :     character(len=:), allocatable, intent(out) :: keys(:)
     194              :     integer, intent(out), optional :: stat
     195              : 
     196              :     class(hsd_node), pointer :: child
     197            9 :     integer :: local_stat
     198              : 
     199            9 :     if (present(stat)) stat = HSD_STAT_OK
     200              : 
     201            9 :     if (len_trim(path) == 0) then
     202              :       ! Empty path means the root table itself
     203            2 :       call table%get_keys(keys)
     204            3 :       return
     205              :     end if
     206              : 
     207            7 :     call hsd_get_child(table, path, child, local_stat)
     208              : 
     209            7 :     if (local_stat /= 0 .or. .not. associated(child)) then
     210            1 :       allocate(character(len=1) :: keys(0))
     211            1 :       if (present(stat)) stat = HSD_STAT_NOT_FOUND
     212            1 :       return
     213              :     end if
     214              : 
     215              :     select type (child)
     216              :     type is (hsd_table)
     217            4 :       call child%get_keys(keys)
     218              :     class default
     219            2 :       allocate(character(len=1) :: keys(0))
     220            2 :       if (present(stat)) stat = HSD_STAT_TYPE_ERROR
     221              :     end select
     222              : 
     223           26 :   end subroutine hsd_get_keys
     224              : 
     225              :   !> Get a child node by path (using / as separator)
     226       100167 :   subroutine hsd_get_child(table, path, child, stat)
     227              :     type(hsd_table), intent(in), target :: table
     228              :     character(len=*), intent(in) :: path
     229              :     class(hsd_node), pointer, intent(out) :: child
     230              :     integer, intent(out), optional :: stat
     231              : 
     232       100167 :     child => null()
     233              :     ! stat will be overriden by subroutine below.
     234          167 :     if (present(stat)) stat = HSD_STAT_OK
     235              : 
     236              :     ! Delegate to recursive helper
     237       100167 :     call get_first_child_table(table, path, child, stat)
     238              : 
     239            9 :   end subroutine hsd_get_child
     240              : 
     241              :   !> Helper to navigate path and get child
     242       100184 :   recursive subroutine get_first_child_table(table, path, child, stat)
     243              :     type(hsd_table), intent(in), target :: table
     244              :     character(len=*), intent(in) :: path
     245              :     class(hsd_node), pointer, intent(out) :: child
     246              :     integer, intent(out), optional :: stat
     247              : 
     248       100184 :     character(len=:), allocatable :: remaining, segment
     249              :     class(hsd_node), pointer :: current
     250       100184 :     integer :: sep_pos
     251              : 
     252       100184 :     child => null()
     253       100184 :     remaining = path
     254              : 
     255              :     ! Get first segment
     256       100184 :     sep_pos = index(remaining, "/")
     257       100184 :     if (sep_pos > 0) then
     258           19 :       segment = remaining(1:sep_pos-1)
     259           19 :       remaining = remaining(sep_pos+1:)
     260              :     else
     261       100165 :       segment = remaining
     262       100165 :       remaining = ""
     263              :     end if
     264              : 
     265              :     ! Find child with this name
     266       100184 :     call table%get_child_by_name(segment, current, case_insensitive=.true.)
     267              : 
     268       100184 :     if (.not. associated(current)) then
     269           28 :       if (present(stat)) stat = HSD_STAT_NOT_FOUND
     270       100165 :       return
     271              :     end if
     272              : 
     273              :     ! If no more path, return this node
     274       100156 :     if (len_trim(remaining) == 0) then
     275       100137 :       child => current
     276       100137 :       if (present(stat)) stat = HSD_STAT_OK
     277       100137 :       return
     278              :     end if
     279              : 
     280              :     ! Otherwise, recurse into child table
     281              :     select type (current)
     282              :     type is (hsd_table)
     283           17 :       call get_first_child_table(current, remaining, child, stat)
     284              :     class default
     285            2 :       if (present(stat)) stat = HSD_STAT_NOT_FOUND
     286              :     end select
     287              : 
     288       300535 :   end subroutine get_first_child_table
     289              : 
     290              :   !> Get a table child by path
     291            4 :   subroutine hsd_get_table(table, path, child_table, stat)
     292              :     type(hsd_table), intent(in), target :: table
     293              :     character(len=*), intent(in) :: path
     294              :     type(hsd_table), pointer, intent(out) :: child_table
     295              :     integer, intent(out), optional :: stat
     296              : 
     297              :     class(hsd_node), pointer :: child
     298              : 
     299            4 :     child_table => null()
     300            4 :     call hsd_get_child(table, path, child, stat)
     301              : 
     302            4 :     if (associated(child)) then
     303              :       select type (child)
     304              :       type is (hsd_table)
     305            3 :         child_table => child
     306              :       class default
     307            0 :         if (present(stat)) stat = HSD_STAT_NOT_FOUND
     308              :       end select
     309              :     end if
     310              : 
     311            8 :   end subroutine hsd_get_table
     312              : 
     313              :   !> Get an attribute from a node at the given path
     314              :   !>
     315              :   !> Example: For `LatticeConstant [Angstrom] = 5.4`, the attribute is "Angstrom"
     316            7 :   subroutine hsd_get_attrib(table, path, attrib, stat)
     317              :     type(hsd_table), intent(in), target :: table
     318              :     character(len=*), intent(in) :: path
     319              :     character(len=:), allocatable, intent(out) :: attrib
     320              :     integer, intent(out), optional :: stat
     321              : 
     322              :     class(hsd_node), pointer :: child
     323            7 :     integer :: local_stat
     324              : 
     325            7 :     call hsd_get_child(table, path, child, local_stat)
     326              : 
     327            7 :     if (local_stat /= 0 .or. .not. associated(child)) then
     328            1 :       if (present(stat)) stat = HSD_STAT_NOT_FOUND
     329            1 :       attrib = ""
     330            1 :       return
     331              :     end if
     332              : 
     333              :     ! Node exists - return OK regardless of whether attribute is set
     334            6 :     if (allocated(child%attrib)) then
     335            4 :       attrib = child%attrib
     336              :     else
     337            2 :       attrib = ""
     338              :     end if
     339            6 :     if (present(stat)) stat = HSD_STAT_OK
     340              : 
     341           11 :   end subroutine hsd_get_attrib
     342              : 
     343              :   !> Check if a node at the given path has an attribute
     344            3 :   function hsd_has_attrib(table, path) result(has)
     345              :     type(hsd_table), intent(in), target :: table
     346              :     character(len=*), intent(in) :: path
     347              :     logical :: has
     348              : 
     349              :     class(hsd_node), pointer :: child
     350            3 :     integer :: local_stat
     351              : 
     352            3 :     has = .false.
     353            3 :     call hsd_get_child(table, path, child, local_stat)
     354              : 
     355            3 :     if (local_stat /= 0 .or. .not. associated(child)) return
     356              : 
     357            2 :     has = allocated(child%attrib)
     358              : 
     359           10 :   end function hsd_has_attrib
     360              : 
     361              :   !> Merge two HSD tables (overlay pattern)
     362              :   !>
     363              :   !> Values from `overlay` are merged into `base`. If a key exists in both,
     364              :   !> the value from `overlay` takes precedence (unless it's a table,
     365              :   !> in which case they are merged recursively).
     366           15 :   recursive subroutine hsd_merge(base, overlay, stat)
     367              :     type(hsd_table), intent(inout) :: base
     368              :     type(hsd_table), intent(in) :: overlay
     369              :     integer, intent(out), optional :: stat
     370              : 
     371              :     class(hsd_node), pointer :: overlay_child, base_child
     372           15 :     type(hsd_table) :: cloned_table
     373           15 :     type(hsd_value) :: cloned_value
     374           15 :     integer :: i, local_stat
     375              : 
     376           15 :     if (present(stat)) stat = HSD_STAT_OK
     377              : 
     378              :     ! Iterate over overlay children
     379           37 :     do i = 1, overlay%num_children
     380           22 :       call overlay%get_child(i, overlay_child)
     381           22 :       if (.not. associated(overlay_child)) cycle
     382           22 :       if (.not. allocated(overlay_child%name)) cycle
     383              : 
     384              :       ! Check if base has this child
     385           22 :       call base%get_child_by_name(overlay_child%name, base_child, case_insensitive=.true.)
     386              : 
     387           37 :       if (.not. associated(base_child)) then
     388              :         ! Child doesn't exist in base - clone and add it
     389              :         select type (overlay_child)
     390              :         type is (hsd_table)
     391            0 :           call clone_table(overlay_child, cloned_table)
     392            6 :           call base%add_child(cloned_table)
     393              :         type is (hsd_value)
     394            0 :           call clone_value(overlay_child, cloned_value)
     395           14 :           call base%add_child(cloned_value)
     396              :         end select
     397              :       else
     398              :         ! Child exists - handle based on type
     399              :         select type (overlay_child)
     400              :         type is (hsd_table)
     401              :           ! If both are tables, merge recursively
     402            6 :           select type (base_child)
     403              :           type is (hsd_table)
     404            4 :             call hsd_merge(base_child, overlay_child, local_stat)
     405            8 :             if (present(stat) .and. local_stat /= HSD_STAT_OK) stat = local_stat
     406              :           class default
     407              :             ! Base is not a table but overlay is - skip (could log warning)
     408              :           end select
     409              :         type is (hsd_value)
     410              :           ! Overlay value replaces base value
     411            6 :           select type (base_child)
     412              :           type is (hsd_value)
     413            0 :             call clone_value(overlay_child, cloned_value)
     414              :             ! Replace the value content
     415            5 :             base_child%value_type = cloned_value%value_type
     416            5 :             if (allocated(cloned_value%string_value)) then
     417            5 :               base_child%string_value = cloned_value%string_value
     418              :             end if
     419            5 :             base_child%int_value = cloned_value%int_value
     420            5 :             base_child%real_value = cloned_value%real_value
     421            5 :             base_child%logical_value = cloned_value%logical_value
     422            5 :             base_child%complex_value = cloned_value%complex_value
     423            5 :             if (allocated(cloned_value%raw_text)) then
     424            0 :               base_child%raw_text = cloned_value%raw_text
     425              :             end if
     426            5 :             if (allocated(cloned_value%int_array)) then
     427            0 :               if (allocated(base_child%int_array)) deallocate(base_child%int_array)
     428            0 :               allocate(base_child%int_array, source=cloned_value%int_array)
     429              :             end if
     430           10 :             if (allocated(cloned_value%real_array)) then
     431            0 :               if (allocated(base_child%real_array)) deallocate(base_child%real_array)
     432            0 :               allocate(base_child%real_array, source=cloned_value%real_array)
     433              :             end if
     434              :           class default
     435              :             ! Type mismatch - skip
     436              :           end select
     437              :         end select
     438              :       end if
     439              :     end do
     440              : 
     441           58 :   end subroutine hsd_merge
     442              : 
     443              :   !> Clone a table (deep copy)
     444      2100338 :   recursive subroutine clone_table(source, dest)
     445              :     type(hsd_table), intent(in) :: source
     446              :     type(hsd_table), intent(out) :: dest
     447              : 
     448              :     class(hsd_node), pointer :: child
     449        66811 :     type(hsd_table) :: cloned_subtable
     450        66811 :     type(hsd_value) :: cloned_value
     451        66811 :     integer :: i
     452              : 
     453        66811 :     call new_table(dest, name=source%name)
     454        66811 :     if (allocated(source%attrib)) dest%attrib = source%attrib
     455        66811 :     dest%line = source%line
     456              : 
     457       601223 :     do i = 1, source%num_children
     458       534412 :       call source%get_child(i, child)
     459       534412 :       if (.not. associated(child)) cycle
     460              : 
     461        66811 :       select type (child)
     462              :       type is (hsd_table)
     463            0 :         call clone_table(child, cloned_subtable)
     464       132808 :         call dest%add_child(cloned_subtable)
     465              :       type is (hsd_value)
     466            0 :         call clone_value(child, cloned_value)
     467       936016 :         call dest%add_child(cloned_value)
     468              :       end select
     469              :     end do
     470              : 
     471       332483 :   end subroutine clone_table
     472              : 
     473              :   !> Clone a value (deep copy)
     474       936040 :   subroutine clone_value(source, dest)
     475              :     type(hsd_value), intent(in) :: source
     476              :     type(hsd_value), intent(out) :: dest
     477              : 
     478       468020 :     call new_value(dest, name=source%name)
     479       468020 :     if (allocated(source%attrib)) dest%attrib = source%attrib
     480       468020 :     dest%line = source%line
     481       468020 :     dest%value_type = source%value_type
     482              : 
     483       468020 :     if (allocated(source%string_value)) dest%string_value = source%string_value
     484       468020 :     dest%int_value = source%int_value
     485       468020 :     dest%real_value = source%real_value
     486       468020 :     dest%logical_value = source%logical_value
     487       468020 :     dest%complex_value = source%complex_value
     488              : 
     489       468020 :     if (allocated(source%raw_text)) dest%raw_text = source%raw_text
     490       468020 :     if (allocated(source%int_array)) allocate(dest%int_array, source=source%int_array)
     491       468020 :     if (allocated(source%real_array)) allocate(dest%real_array, source=source%real_array)
     492       468020 :     if (allocated(source%logical_array)) allocate(dest%logical_array, source=source%logical_array)
     493       468020 :     if (allocated(source%string_array)) allocate(dest%string_array, source=source%string_array)
     494       468020 :     if (allocated(source%complex_array)) allocate(dest%complex_array, source=source%complex_array)
     495       468020 :     if (allocated(source%int_matrix)) allocate(dest%int_matrix, source=source%int_matrix)
     496       468020 :     if (allocated(source%real_matrix)) allocate(dest%real_matrix, source=source%real_matrix)
     497       468020 :     dest%nrows = source%nrows
     498       468020 :     dest%ncols = source%ncols
     499              : 
     500       468020 :   end subroutine clone_value
     501              : 
     502              :   !> Deep clone an entire HSD table tree
     503          404 :   subroutine hsd_clone(source, dest, stat)
     504              :     type(hsd_table), intent(in) :: source
     505              :     type(hsd_table), intent(out) :: dest
     506              :     integer, intent(out), optional :: stat
     507              : 
     508          404 :     call clone_table(source, dest)
     509          404 :     if (present(stat)) stat = HSD_STAT_OK
     510              : 
     511       468020 :   end subroutine hsd_clone
     512              : 
     513      1068957 : end module hsd_query
        

Generated by: LCOV version 2.0-1