Skip to content

7. Your own handler ​

The index of chapter 6 keeps the tags of a file and reads contents on demand. For a statistic over every snapshot, solver needs neither: it reads results.xml once, from the start to the end, with a handler of its own that the streaming parser calls for each element.

f90
module snapshot_handler
!< Tutorial, chapter 7: a handler of the results, in one pass.
use foxy
implicit none
private
public :: snapshot_stats

type, extends(xml_handler) :: snapshot_stats
   character(len=:), allocatable :: text               !< Text of the current <pressure>, collected in pieces.
   logical                       :: is_inside = .false. !< Inside a <pressure>.
   integer(I4P)                  :: step = 0             !< Step of the current snapshot.
   integer(I4P)                  :: last_step = huge(1)  !< Stop after the snapshot of this step.
   contains
      procedure :: start_element => on_start
      procedure :: characters    => on_text
      procedure :: end_element   => on_end
endtype snapshot_stats

contains
   subroutine on_start(self, name, attributes)
   class(snapshot_stats), intent(inout) :: self
   character(*),          intent(in)    :: name
   type(xml_attribute),   intent(in)    :: attributes(:)
   integer                              :: a
   integer(I4P)                         :: iostat
   character(len=:), allocatable        :: iomsg
   select case(name)
   case('snapshot')
      do a=1, size(attributes)                        ! attributes come with the start of the element
         if (attributes(a)%name=='step') call to_value(attributes(a)%value, self%step, iostat, iomsg)
      enddo
   case('pressure')
      self%is_inside = .true.
      self%text = ''
   endselect
   endsubroutine on_start

   subroutine on_text(self, text)                    ! the text may come in pieces: collect it
   class(snapshot_stats), intent(inout) :: self
   character(*),          intent(in)    :: text
   if (self%is_inside) self%text = self%text//text
   endsubroutine on_text

   subroutine on_end(self, name)
   class(snapshot_stats), intent(inout) :: self
   character(*),          intent(in)    :: name
   real(R8P), allocatable               :: p(:)
   integer(I4P)                         :: iostat
   character(len=:), allocatable        :: iomsg
   select case(name)
   case('pressure')                                   ! the text is complete: convert it
      self%is_inside = .false.
      call to_values(self%text, p, iostat, iomsg)
      if (iostat==0) print '(A,I3,A,F6.3,A,F6.3)', 'step ', self%step, ': mean p = ', sum(p)/size(p), ', max = ', maxval(p)
   case('snapshot')
      if (self%step>=self%last_step) call self%stop_parsing()   ! the rest of the file is not read
   endselect
   endsubroutine on_end
endmodule snapshot_handler

program solver
!< Tutorial, chapter 7: your own handler.
use foxy
use snapshot_handler, only : snapshot_stats
implicit none
type(snapshot_stats) :: stats
integer(I4P)         :: iostat

call stats%parse(filename='results.xml', iostat=iostat)        ! every snapshot, in one pass
print '(A,I0)', 'iostat = ', iostat
stats%last_step = 300
call stats%parse(filename='results.xml')                       ! up to the snapshot of step 300
endprogram solver
  • A handler is a type that extends xml_handler and overrides the callbacks it needs: here start_element, characters and end_element. The others (comments, PIs, problems, ...) do nothing by default.
  • The callbacks are ordinary procedures: they print, and keep their state (the current step, the text being collected) in the type.
  • start_element gets the attributes of the element, decoded: the step of each snapshot is read from there, with to_value (the conversion of get_attribute, for any text you hold).
  • The handler also knows where it is: level is the depth of the current element (1 for the root), pos the byte of the event in the source.
  • characters may get the text of an element in pieces (a long data array comes in many): collect it and convert it in end_element, here with to_values, the conversion of get_content.
  • parse reads the file in chunks and calls the callbacks in source order; stop_parsing ends the parse early, and the rest of the file is not read.

For one snapshot, the calls are:

Running it ​

$ solver
step 100: mean p =  0.611, max =  1.000
step 300: mean p =  0.204, max =  0.333
step 500: mean p =  0.122, max =  0.200
iostat = 0
step 100: mean p =  0.611, max =  1.000
step 300: mean p =  0.204, max =  0.333

What you learned

Extend xml_handler, override the callbacks you need, parse once: any file size, your own state. Reference: Streaming parser.

Next: 8. Namespaces.