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
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')| Method | Returns |
|---|---|
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; withparent, only its direct children.path:/-separated names; the first one matches at any depth (withparent: among its children), each next one is a direct child of the previous; every candidate is explored, so repeated parents are fine (case/boundarygives the four boundaries). If bothnameandpathare passed,pathis 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
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]"| Method | Returns |
|---|---|
select(expression, [namespaces=], [iostat=], [iomsg=]) | the IDs of the tags selected by expression, in document order |
expression is a small subset of XPath:
| Syntax | Selects |
|---|---|
a/b | the b children of the a tags |
a//b | the b descendants of the a tags, at any depth |
/a | the root tags named a |
a or //a | the a tags at any depth (as find_all(path=)) |
* | any name |
name, {uri}local | names 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 firstbchild of each parent, not the firstbof 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,iomsgsaying what is wrong and at which column; withoutiostat, nothing is reported. selectis not pure (it compiles the regular expressions);findandfind_allare the pure lookups. Each step sweeps the tags once at most, so a selection costs O(steps × tags).xml_indexselects the same way.
The tree
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| Method | Returns |
|---|---|
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:
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_treeText and attributes
| Method | Returns |
|---|---|
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>')).
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 = 1Next: Typed values.