Streaming parser
The streaming parser reads a document once, from the start to the end, and calls a handler of yours for each event: an element starts, some text, an element ends, ... Only the chunk being read is kept (with a comment or PI being read, whole), so a file may be any size, and your handler decides what to keep.
module pressure_handler
!< A handler summing the values of every <pressure> of a results file, in one pass.
use foxy
implicit none
private
public :: pressure_sum
type, extends(xml_handler) :: pressure_sum
character(len=:), allocatable :: text !< Text of the current <pressure>, collected in pieces.
logical :: is_inside = .false.
real(R8P) :: total = 0._R8P
integer :: values_number = 0
contains
procedure :: start_element => on_start
procedure :: characters => on_text
procedure :: end_element => on_end
endtype pressure_sum
contains
subroutine on_start(self, name, attributes)
class(pressure_sum), intent(inout) :: self
character(*), intent(in) :: name
type(xml_attribute), intent(in) :: attributes(:)
self%is_inside = name=='pressure'
if (self%is_inside) self%text = ''
endsubroutine on_start
subroutine on_text(self, text) ! the text may come in pieces: collect it
class(pressure_sum), intent(inout) :: self
character(*), intent(in) :: text
if (self%is_inside) self%text = self%text//text
endsubroutine on_text
subroutine on_end(self, name) ! the text is complete: convert it
class(pressure_sum), intent(inout) :: self
character(*), intent(in) :: name
real(R8P), allocatable :: values(:)
integer(I4P) :: iostat
character(len=:), allocatable :: iomsg
if (name/='pressure') return
call to_values(self%text, values, iostat, iomsg)
if (iostat==0) then
self%total = self%total + sum(values)
self%values_number = self%values_number + size(values)
endif
self%is_inside = .false.
endsubroutine on_end
endmodule pressure_handler
program stream_handler
use foxy
use pressure_handler, only : pressure_sum
implicit none
type(pressure_sum) :: summer
integer(I4P) :: iostat
call summer%parse(filename='results.xml', iostat=iostat) ! written by the tutorial, chapter 5
print '(A,I0,A,F8.3,A,I0)', 'values: ', summer%values_number, ', sum: ', summer%total, ', iostat: ', iostat
endprogram stream_handler$ stream_handler
values: 9, sum: 2.811, iostat: 0The handler
Extend xml_handler and override the callbacks you need: each one does nothing by default. The callbacks are ordinary procedures: they may print, write files and keep any state in your type.
| Callback | Called for |
|---|---|
start_element(name, attributes) | a start tag; attributes is an array of xml_attribute (name, value), in source order, values normalized and decoded as xml_file%attribute returns them |
end_element(name) | an end tag, and right after the start of a self-closing tag |
characters(text) | character data, decoded (references expanded, line ends as LF); the text between two tags may come in several pieces, never splitting a reference or a UTF-8 character; the blanks between tags (layout) are reported too |
cdata(text) | the content of a CDATA section, literal but for its line ends, as LF (maybe in pieces) |
comment(text) | a comment, without <!-- and --> |
processing_instruction(target, data) | a PI <?target data?>, the XML declaration included |
doctype(text) | a <!DOCTYPE ...>, as in the source |
problem(message) | a problem of malformed input, as line N: ...; parsing goes on |
During a callback, self%level is the depth of the current element (1 for the root) and self%pos the byte position of the event in the source.
Parsing
| Method | Does |
|---|---|
parse([string=], [filename=], [chunk_size=], [sanitize_attributes_value=], [iostat=], [iomsg=]) | reads the source in chunks (default 1 MiB) and calls the callbacks in source order; iostat is 0, the I/O error of a file that cannot be read, or XML_MALFORMED with the first problem in iomsg, as xml_file%parse |
stop_parsing() | called from a callback: no more callbacks, the rest of the source is not read, parse returns (with the problems met so far) |
Elements are balanced: an element closed implicitly by malformed input, or still open at the end of the source, gets its end_element (unless you call stop_parsing: then no more callbacks come). Problems come in source order among the other events; a problem inside character data may come between two pieces of that text.
Numbers in pieces
A number may be split between two characters calls (0.3 then 33): collect the text of an element and convert it in end_element, as the example does, with to_value or to_values (the conversion of the typed accessors, see Typed values). Their iostat and iomsg are not optional, and iomsg is an allocatable string: call to_values(text, values, iostat, iomsg).
When to use it
| Need | Use |
|---|---|
| query, edit and save a document that fits in memory | xml_file |
| look up tags of a large file, read a few contents | xml_index |
| process every element of a file of any size once (sum, filter, convert) | xml_handler |
Names are given as written (prefix included): namespaces are not resolved by the streaming parser.
Next: Namespaces.