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_handlerand overrides the callbacks it needs: herestart_element,charactersandend_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_elementgets the attributes of the element, decoded: the step of each snapshot is read from there, withto_value(the conversion ofget_attribute, for any text you hold).- The handler also knows where it is:
levelis the depth of the current element (1 for the root),posthe byte of the event in the source. charactersmay get the text of an element in pieces (a long data array comes in many): collect it and convert it inend_element, here withto_values, the conversion ofget_content.parsereads the file in chunks and calls the callbacks in source order;stop_parsingends 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.333What you learned
Extend xml_handler, override the callbacks you need, parse once: any file size, your own state. Reference: Streaming parser.
Next: 8. Namespaces.