{"slug": "foxy-v0-8-0-is-out", "title": "FoXy v0.8.0 is out", "summary": "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.", "body_md": "Fortran XML for poor people\n\nFoXy 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.\n\nFive types, one for each way of working with a document:\n\n| Type | For | \n|---|---|\n| `xml_file` | a whole document in memory (a DOM): parse, query, edit, save | \n| `xml_index` | a file too large to hold: tags and attributes, contents read on demand | \n| `xml_handler` | a document of any size read once: your callbacks, nothing stored | \n| `xml_writer` | a document of any size, written element by element to a file, a unit or memory | \n| `xml_tag` | a single element: build it, parse it, write it | \n\nThe common case: a solver reads its settings from XML. No string juggling, no `read(..., *)`:\n\n```\nuse foxy\ntype(xml_file) :: doc\nreal(R8P)      :: dt\ninteger(I4P)   :: steps\n\ncall doc%parse(filename='case.xml')\ncall doc%get_content(id=doc%find(path='case/time/dt'), value=dt)\ncall doc%get_content(id=doc%find(path='case/time/steps'), value=steps)\n```\n\n`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.\n\n```\nids = doc%select('case/boundary[@type=\"wall\"]')                 ! attribute value\nids = doc%select('//*[@unit]')                                  ! any name, at any depth\nids = doc%select('/case/boundary[2]')                           ! the second boundary child\nids = doc%select('//boundary[@side~=\"^(east|west)$\"]/velocity') ! regular expression\n```\n\n`/`, `//`, `*`, `[@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.\n\n```\ntype(xml_writer) :: w\n\ncall w%open()\ncall w%start('cell')\ncall w%attribute('dt', 0.1_R8P)                        ! the shortest text that reads back: 0.1\ncall w%attribute('extent', [0_I4P, 63_I4P, 0_I4P, 31_I4P])\ncall w%start('volume')\ncall w%text(1._R8P/3._R8P)\ncall w%end('volume')\ncall w%start('center')\ncall w%text([0.5_R8P, 0.25_R8P, 0._R8P], fmt='(F5.3)')\ncall w%end('center')\ncall w%close()\n<cell dt=\"0.1\" extent=\"0 63 0 31\">\n  <volume>0.3333333333333333</volume>\n  <center>0.500 0.250 0.000</center>\n</cell>\n```\n\n`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`.\n\n```\ntype, extends(xml_handler) :: pressure_sum\n   ...\n   contains\n      procedure :: start_element => on_start\n      procedure :: characters    => on_text\n      procedure :: end_element   => on_end\nendtype pressure_sum\n\ncall summer%parse(filename='results.xml', iostat=iostat)\n```\n\nExtend `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.\n\nFor 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).\n\nUTF-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.\n\nWhat I use it for, and what it is designed for:\n\n`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 loading them.\nPerformance: 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).\n\nKnown limits: entities declared in a DTD are not expanded, and there is no validation against a DTD or schema.\n\n43 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.\n\nThe documentation has a [tutorial](https://fortran-foss-programmers.github.io/FoXy/manual/tutorial/01-first-document) that builds the input and output of a small solver in nine chapters, a [cookbook](https://fortran-foss-programmers.github.io/FoXy/manual/cookbook), and an [Upgrading](https://fortran-foss-programmers.github.io/FoXy/guide/upgrading) page listing the behaviour changes release by release.\n\nFoXy 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).\n\nFeedback, issues, and contributions are welcome.\n\nStefano", "url": "https://wpnews.pro/news/foxy-v0-8-0-is-out", "canonical_source": "https://fortran-lang.discourse.group/t/foxy-v0-8-0-is-out/11273#post_1", "published_at": "2026-11-03 17:14:13+00:00", "updated_at": "2026-11-03 17:21:35.692374+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["FoXy", "Fortran", "VTKFortran", "StringiFor"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/foxy-v0-8-0-is-out", "markdown": "https://wpnews.pro/news/foxy-v0-8-0-is-out.md", "text": "https://wpnews.pro/news/foxy-v0-8-0-is-out.txt", "jsonld": "https://wpnews.pro/news/foxy-v0-8-0-is-out.jsonld"}}