cd /news/developer-tools/foxy-v0-8-0-is-out · home › topics › developer-tools › article
[ARTICLE · art-163269] src=fortran-lang.discourse.group ↗ pub= topic=developer-tools verified=true sentiment=↑ positive

FoXy v0.8.0 is out

FoXy v0.8.0, a pure Fortran XML library with no C underneath, has been released, completing a rebuild that began at version 0.5 and was carried out with the help of an AI assistant. The release provides five types — xml_file for an in-memory DOM, xml_index for files too large to hold, xml_handler for streaming callbacks, xml_writer for element-by-element output, and xml_tag for single elements — and supports files of any size, including over 2 GiB. The library targets scientific code that reads case files as numbers and writes results metadata as it computes, returning typed errors such as XML_BAD_VALUE, XML_NOT_FOUND and XML_MALFORMED instead of requiring string parsing.

read4 min views2 publishedNov 3, 2026

Fortran XML for poor people

FoXy is a pure Fortran library to read, query, edit, and write XML, with no C underneath: use foxy and your code talks XML (hopefully). It started as a small parser for another poor people project (VTKFortran); the last four releases (0.5 to 0.8) rebuilt it (with the help of an AI assistant) into what scientific code needs from XML (at least in my mind, even though I prefer other kinds of I/O formats generally): case files read as numbers, results metadata written as you compute, and files of any size, including over 2 GiB.

Five types, one for each way of working with a document:

Type For
xml_file a whole document in memory (a DOM): parse, query, edit, save
xml_index a file too large to hold: tags and attributes, contents read on demand
xml_handler a document of any size read once: your callbacks, nothing stored
xml_writer a document of any size, written element by element to a file, a unit or memory
xml_tag a single element: build it, parse it, write it

The common case: a solver reads its settings from XML. No string juggling, no read(..., *):

use foxy
type(xml_file) :: doc
real(R8P)      :: dt
integer(I4P)   :: steps

call doc%parse(filename='case.xml')
call doc%get_content(id=doc%find(path='case/time/dt'), value=dt)
call doc%get_content(id=doc%find(path='case/time/steps'), value=steps)

values= into an allocatable array), with iostat/ iomsg. A value that is not a number gives XML_BAD_VALUE and leaves your variable unchanged; a missing tag or attribute gives XML_NOT_FOUND. 1e3 is not accepted as an integer.parse returns XML_MALFORMED with the first problem and its line: tag structure, names, duplicate attributes, a second root, text outside the root, malformed references, ill-formed UTF-8.

ids = doc%select('case/boundary[@type="wall"]')                 ! attribute value
ids = doc%select('//*[@unit]')                                  ! any name, at any depth
ids = doc%select('/case/boundary[2]')                           ! the second boundary child
ids = doc%select('//boundary[@side~="^(east|west)$"]/velocity') ! regular expression

/, //, *, [@a], [@a="v"], [@a~="re"], [n] among siblings. A malformed expression returns no IDs and the column of the problem in matching(re, ...), using the StringiFor engine (Python re syntax, linear time, no backtracking).find/ find_all: by prefix, by {uri}local, or with your own prefixes mapped to URIs.

type(xml_writer) :: w

call w%open()
call w%start('cell')
call w%attribute('dt', 0.1_R8P)                        ! the shortest text that reads back: 0.1
call w%attribute('extent', [0_I4P, 63_I4P, 0_I4P, 31_I4P])
call w%start('volume')
call w%text(1._R8P/3._R8P)
call w%end('volume')
call w%start('center')
call w%text([0.5_R8P, 0.25_R8P, 0._R8P], fmt='(F5.3)')
call w%end('center')
call w%close()
<cell dt="0.1" extent="0 63 0 31">
  <volume>0.3333333333333333</volume>
  <center>0.500 0.250 0.000</center>
</cell>

0.1, not 1.000000000000000E-001), or your own fmt=. A value too wide for the field is an error, not a row of asterisks.& < > " ', checks names, duplicate attributes, one root element, and PI targets. Errors are sticky and reported once, by close or set_content/ set_attribute with value=/ add_tag(parent=|before=|after=), set_name, delete_tag, then save.

type, extends(xml_handler) :: pressure_sum
   ...
   contains
      procedure :: start_element => on_start
      procedure :: characters    => on_text
      procedure :: end_element   => on_end
endtype pressure_sum

call summer%parse(filename='results.xml', iostat=iostat)

Extend xml_handler, override the callbacks you need (start and end tags, text, CDATA, comments, PIs, DOCTYPE, problems with their line), and the file is read in chunks with nothing stored; stop_parsing ends it early. Your callbacks are plain type-bound procedures: I/O and side effects are allowed.

For random access to files too large for a DOM, there is xml_index: it scans the tags and attributes only, and reads content from the file when you ask for it (stop_at= stops the scan early).

UTF-8, UTF-16, UTF-32 (byte order mark or not), ISO-8859-1, Windows-1252, and US-ASCII sources are detected (XML 1.0 Appendix F, then the declaration) and converted to UTF-8 piece by piece. save writes UTF-8 and declares it. An encoding FoXy does not read is reported, not guessed.

What I use it for, and what it is designed for:

xml_writer as the run goes, numbers formatted for you. VTKFortran writes its XML formats through FoXy.xml_index or xml_handler on files of hundreds of MB or over 2 GiB, without them. Performance: parsing is linear in the document size. A generated QuakeML-like catalogue of 100,000 tags (3 MB) is parsed into the DOM in about 0.11 s and written back by stringify in 0.03 s (gfortran 16, -O2, a desktop CPU; fobis build --mode benchmark-gnu reproduces it).

Known limits: entities declared in a DTD are not expanded, and there is no validation against a DTD or schema.

43 test programs, run with gfortran, nvfortran and ifx (debug builds with runtime checks included). Chunked parsing is checked against whole-string parsing down to 1-character chunks, the streaming events against the DOM on every test file, and the index against the DOM tag by tag. Every sample in the documentation is a program compiled and run to produce the output shown, and CI fails if the output changes.

The documentation has a tutorial that builds the input and output of a small solver in nine chapters, a cookbook, and an Upgrading page listing the behaviour changes release by release.

FoXy builds with FoBiS, fpm, or CMake, is tested with GNU gfortran, NVIDIA nvfortran, and Intel ifx, and is multi-licensed (GPL v3, BSD 2-Clause, BSD 3-Clause, MIT, select the best fit for you).

Feedback, issues, and contributions are welcome.

Stefano

── more in #developer-tools 4 stories · sorted by recency
── more on @foxy 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/foxy-v0-8-0-is-out] indexed:0 read:4min 2026-11-03 · —