Skip to content

Querying ​

A parsed document is a flat array of tags in document order. A tag is known by its ID, 1 to tags_number(); 0 stands for "no tag" (and, for children and parent=, for the document itself, whose children are the root tags).

Finding tags ​

f90
dt = case_file%content('dt')               ! the text of the first tag named dt
print '(A)', 'dt    = '//dt
time = case_file%find(path='case/time')    ! a tag ID, 0 if there is none
print '(A,I0)', 'time  = tag ', time
print '(A)', 'steps = '//case_file%content(id=case_file%find(name='steps', parent=time))
print '(A)', 'name  = '//case_file%attribute(id=1, name='name')
MethodReturns
find([name=], [path=], [parent=], [namespaces=])the ID of the first match, in document order, 0 if none
find_all(...)the IDs of all the matches, in document order
  • name: the tags with that name, at any depth; with parent, only its direct children.
  • path: /-separated names; the first one matches at any depth (with parent: among its children), each next one is a direct child of the previous; every candidate is explored, so repeated parents are fine (case/boundary gives the four boundaries). If both name and path are passed, path is used.
  • namespaces: see Namespaces.

find stops at the first match of a name; the following path segments and parent= walk the children lists, so find(name=, parent=) in a loop costs the number of children, not of tags. children(id) reads the children list of the tag (its cost is the number of children); children(0), the root tags, sweeps all the tags.

Path selector ​

f90
program select_path
use foxy
implicit none
type(xml_file)            :: doc
integer(I4P), allocatable :: ids(:)
integer(I4P)              :: iostat, i
character(len=200)        :: iomsg

call doc%parse(filename='case.xml')
ids = doc%select('case/boundary[@type="wall"]')                ! attribute value
print '(A,*(1X,A))', 'walls:', (doc%attribute(ids(i), 'side'), i=1, size(ids))
ids = doc%select('//*[@unit]')                                 ! any name, at any depth
print '(A,*(1X,A))', 'with a unit:', (doc%name(ids(i)), i=1, size(ids))
ids = doc%select('/case/boundary[2]')                          ! the second boundary child
print '(A,*(1X,A))', 'second boundary:', (doc%attribute(ids(i), 'side'), i=1, size(ids))
ids = doc%select('//boundary[@side~="^(east|west)$"]/velocity') ! regular expression
print '(A,*(1X,A))', 'east/west velocity:', (doc%content(id=ids(i)), i=1, size(ids))
ids = doc%select('case/boundary[@type=wall]', iostat=iostat, iomsg=iomsg)
print '(A,L1,1X,A)', 'malformed: ', iostat==XML_MALFORMED, trim(iomsg)
endprogram select_path
$ select_path
walls: north south
with a unit: dt density viscosity
second boundary: east
east/west velocity: 1.0 0.0 0.0
malformed: T select: a quoted value expected at column 21 of "case/boundary[@type=wall]"
MethodReturns
select(expression, [namespaces=], [iostat=], [iomsg=])the IDs of the tags selected by expression, in document order

expression is a small subset of XPath:

SyntaxSelects
a/bthe b children of the a tags
a//bthe b descendants of the a tags, at any depth
/athe root tags named a
a or //athe a tags at any depth (as find_all(path=))
*any name
name, {uri}localnames matched as in find_all (with namespaces= for prefixes)
[@k]the tags having the attribute k
[@k="v"] (or 'v')the tags whose attribute k is v (decoded, as attribute returns it)
[@k~="re"]the tags whose attribute k matches the regular expression re, anywhere
[n]the n-th (from 1) among its siblings, counting the tags kept by the previous predicates
  • Predicates follow the step they filter, in order: boundary[@type="wall"][1] is the first wall of each parent, boundary[1][@type="wall"] the first boundary, if it is a wall. As in XPath, //b[1] is the first b child of each parent, not the first b of the document.
  • Spaces are allowed around the predicate tokens, not inside names. A quoted value cannot hold its own quote.
  • A malformed expression (or regular expression) selects nothing and returns iostat=XML_MALFORMED, iomsg saying what is wrong and at which column; without iostat, nothing is reported.
  • select is not pure (it compiles the regular expressions); find and find_all are the pure lookups. Each step sweeps the tags once at most, so a selection costs O(steps × tags). xml_index selects the same way.

The tree ​

f90
recursive subroutine outline(id, depth)
!< Print a tag, its attributes and, indented, its children.
integer(I4P), intent(in)      :: id, depth
character(len=:), allocatable :: line
integer(I4P), allocatable     :: kids(:)
integer(I4P)                  :: a, k

line = repeat('  ', depth)//case_file%name(id)
do a=1, case_file%attributes_number(id)            ! the attributes, without knowing their names
   line = line//' '//case_file%attribute_name(id, a)//'='//case_file%attribute(id, case_file%attribute_name(id, a))
enddo
if (len(case_file%content(id=id))>0) line = line//': '//case_file%content(id=id)
print '(A)', line
kids = case_file%children(id)
do k=1, size(kids)
   call outline(id=kids(k), depth=depth+1)
enddo
endsubroutine outline
MethodReturns
tags_number()the number of tags
children(id)the IDs of the direct children (children(0): the root tags)
parent(id)the parent ID, 0 for a root tag
level(id)the depth, 1 for a root tag, 0 if the tag does not exist
name(id)the name, '' if the tag does not exist

A plain loop over the IDs visits the tree depth first, in document order:

f90
program walk_tree
use foxy
implicit none
type(xml_file) :: doc
integer(I4P)   :: t

call doc%parse(filename='case.xml')
do t=1, doc%tags_number()                 ! every tag, in document order
   print '(A)', repeat('. ', doc%level(t)-1)//doc%name(t)
enddo
endprogram walk_tree

Text and attributes ​

MethodReturns
content(name)the text of the first tag named name
content(id=)the text of tag id
attribute(id, name)the value of an attribute, '' if not defined
attributes_number(id)the number of attributes
attribute_name(id, a)the name of the a-th attribute, in source order

content returns the direct text of a tag: its text and the tails of its children, the children's own text excluded; references decoded, CDATA literal, comments and PIs removed, blanks at both ends removed. content(name) also finds a tag set as a content string (e.g. xml_tag(name='a', content='<b>1</b>')).

f90
program list_attributes
use foxy
implicit none
type(xml_file) :: doc
integer(I4P)   :: grid, a

call doc%parse(filename='case.xml')
grid = doc%find(name='grid')
do a=1, doc%attributes_number(grid)
   print '(A)', doc%attribute_name(grid, a)//' = '//doc%attribute(grid, doc%attribute_name(grid, a))
enddo
endprogram list_attributes
$ list_attributes
ni = 64
nj = 32
nk = 1

Next: Typed values.