GNDStk: GNDS Toolkit

INTRODUCTION & PRIMER

Introduction

Description

Los Alamos National Laboratory’s GNDS Toolkit, or GNDStk, has been designed first and foremost to provide a powerful, intuitive, and flexible C++ language API for interacting with Generalized Nuclear Database Structure data.

We begin by providing basic and cleanly-designed classes in which GNDS data are stored. Next, we support a robust and flexible I/O system for reading from, and writing to, both the XML and JSON file formats. Support for more file formats is anticipated in the future, as GNDS becomes more widely used.

While GNDStk is one library, from which you can use any functionality you wish to at any time, we consider it conceptually to consist of roughly three major parts: basic constructs and I/O; a “core” interface, and a higher-level interface that will also be equipped with Python bindings for users who wish to take advantage of them. Let’s say a bit more about all of these elements.

BASICS

Here we have the basic requisite data structures and functions, as well as flexible and easy-to-use GNDS file I/O capabilities. Along with these also come, of course, the numerous and sundry utilities needed for their implementation. Some of the utilities, e.g. those for generating diagnostic messages such as warnings and errors, may be of value in their own right to our users. We’ll therefore provide some documentation of how selected utility constructs work, without distracting us from our focus on GNDStk’s major, most interesting capabilities.

CORE INTERFACE

The heart of GNDStk lies in its Core Interface. Consider this interface to include the basics as described above, while adding to them a powerful, flexible, and highly user-programmable suite of data query and creation capabilities that can be used to great effect by themselves if you wish – given some knowledge of the GNDS hierarchy’s internal structure – and also for creating higher-level interfaces like our own.

Our Core Interface allows for version-independent access to all data in any GNDS file, including functionality for reading, writing, and modification.

We support both a more-traditional C++ API design, in which users can interact with classes and functions in the usual fashion (largely through the Basics as described above), as well as a powerful and easily extensible “query system” for retrieving or creating GNDS content. The query system is, in particular, quite intentionally designed to enable you to integrate GNDStk’s capabilities easily into virtually any other code in which you might wish to use it – code that utilizes entirely your own data structures, perhaps, or those of any other library or libraries with which you may be working.

HIGH-LEVEL INTERFACE

While still a work-in-progress at the time of this writing, GNDStk’s High-Level Interface will be comprised of several elements.

First, we’ll provide one or more C++ base classes that are designed to provide value to high-level derived classes that one might wish to create, individually or en masse, to represent GNDS data structures. Note that, here, our use of the word derived refers to derived classes in C++ – not, say, to nuclear data that were derived in some sense from other nuclear data.

Notably, and with support from the Core Interface, proper handling will be made available for capturing the concepts of a required field, an optional field, and an optional, with default field in a GNDS data structure. Here, and thinking in the language of XML, field may mean something from an XML element’s attributes (we’ll prefer the non-XML-specific term metadata in GNDStk), or from its nested XML elements – child nodes, in our preferred terminology.

An additional note regarding terminology: We may occasionally write element to mean GNDS data as it would appear in an XML element, if and when the term works well in the narrative, but do so with the understanding that GNDS data need not, of course, originate from an XML source, or be intended for an XML destination.

We’ll also design GNDS version-specific collections of high-level classes that represent important data structures for the GNDS version in question. Assuming that the GNDS specifications don’t change a great deal between releases, we’ll expect to see substantial overlap, across our version-specific collections, of these classes. GNDStk will, naturally, handle such issues efficiently, and will do so in a manner that’s entirely transparent to users. We’ll focus on making our capabilities work well, so that you can focus similarly on yours.

Finally, but no less significantly for many of GNDStk’s intended beneficiaries, we’ll provide a suite of Python bindings to much of our C++ functionality. That will include, certainly, the major classes in our version-specific interfaces, and perhaps also – where reasonable and possible, given differences between Python and C++ as well as their respective limitations - to selected fabulous and action-packed lower-level Core Interface constructs as well.

Background

The Generalized Nuclear Database Structure, or GNDS, started at Lawrence Livermore National Laboratory as an effort to update their ENDL format. Realizing that they could also modernize the ENDF format for nuclear data, and make this modernization useful and available to everyone, they evolved GNDS to contain evaluated data, processed data, and application data. GNDS has become an international standard as part of the OECD/NEA.

Acknowledgements

The author wishes to thank several individuals for the support and ideas that they provided throughout this endeavor. An introduction to GNDS, to the broader NJOY21 project, and to the need for quality software for working with the GNDS format, was provided by Nuclear Data team leader Jeremy Conlin. Materials and Physical Data (XCP-5) group leader Patrick Talou tirelessly ensured financial support for the project, and positive encouragement for its personnel. Nathan Gibson constructed the build system currently in use not only for GNDStk but for many other elements of NJOY21. Finally, Wim Haeck was a source of copious and invaluable comments, ideas, and discussions, without which GNDStk wouldn’t be what it is today.

Martin Staley, Los Alamos National Laboratory, February 2021

Building GNDStk

We designed GNDStk with the hope that it will prove to be straightforward to download, to install, and to use. GNDStk resides on Github, and uses CMake as its primary build system.

The following description is based on commands that work for us, on a Linux system. Adapt our instructions as necessary for your own platform.

Download

Enter the directory in which you’d like GNDStk to reside. This might be your root user directory, e.g. /home/yourname/, or perhaps, say, in a dedicated directory that you use for your projects.

Download:

git clone https://github.com/njoy/GNDStk.git

Enter directory:

cd GNDStk

At this point you can, if you wish, check out a specific branch of GNDStk. As is typical in git repositories, our main branch is called master. So, if you wish:

Check out a branch:

git checkout master

With master that isn’t necessary, but you can replace master with something else.

Build & Test

Some people prefer, as you may, to create a build directory in which to build the project. (Doing as much certainly helps to keep a project’s base directory more free of clutter.) Continuing from the GNDStk directory, where we left off in the Downloading narrative above:

Make and enter a build directory:

mkdir build
cd build

Now run cmake itself, being sure to point it one level up, to where the CMakeLists.txt file resides, if you’re indeed down in build.

Run cmake:

cmake ..

The good news is that the above command should download GNDStk’s dependencies – the modest number of outside C++ libraries on which it depends – automatically. The bad news is that the download may, for the same reason, take quite some time. Resist the temptation to terminate the command, perhaps believing that your computer has hung, and consider starting cmake .. before lunch hour if you have a slow Internet connection. The main culprit appears to be the “nlohmann json” library, https://github.com/nlohmann/json. An excellent library, by all accounts, and invaluable as the workhorse for GNDStk’s JSON capabilities; but responsible, at the time of the writing, for over 400MB – about 95% – of the entire dependencies directory that the above command creates.

Finally, the Makefile that the above cmake command should have created, can be used to build GNDStk’s test suite.

Build GNDStk’s test suite:

make

Or, if you have for instance six processor cores available, then

Multicore build:

make -j 6

will no doubt run far faster.

GNDStk was carefully designed to not be one of those infamous C++ libraries that triggers hours-long, even many-minutes-long, compilations, leaving beleaguered users wondering if they could more quickly find the data they’re looking for by loading a file into an editor, finding the data of interest, and cutting-and-pasting in their own code. The above make in fact compiles several codes, comprising our entire, substantial GNDStk test suite with broad coverage across all of its considerable capabilities. Even so, we hope and believe that you won’t have the need to report to us that a multicore build took more than a minute or two, at most, on a modern and well-oiled home or office machine.

You can then invoke:

Run GNDStk’s tests:

make test

to run all of the tests. We hope that you will, at this point, have the same pleasant experience that we do when we invoke make test on our master GNDStk branch: a report that 100% tests passed.

Summary

Here’s a summary of the commands described above, from downloading GNDS from our repository, through building and running its full suite of tests:

# Get GNDStk
git clone https://github.com/njoy/GNDStk.git
cd GNDStk
git checkout master

# Cmake; may take some time
mkdir build
cd build
cmake ..

# Make and run test suite
make
# ...or make -j 6
make test

Your Own Application

Let’s outline how you can interface your own application code with GNDStk, using CMake.

First, you should have downloaded the GNDStk repository as described above. Building and running its test suite isn’t a prerequisite for our present purposes, but certainly wouldn’t hurt. Any problems you might encounter in that process would no doubt show themselves again, in some form, here.

Now assume you have some directory, call it MyApp, for your application, with the following file structure:

MyApp/
   CMakeLists.txt
   dependencies/
      GNDStk/
   src/
      app.cpp

GNDStk/ is the cloned GNDStk repository. (If you downloaded it elsewhere and don’t want a duplicate, then perhaps make it a symlink here , a.k.a. a shortcut, to the cloned repo.) Next, for our simple illustration here, let app.cpp be a single C++ source file that contains all of your code to be used with GNDStk. The remaining structure is typical for applications that use CMake.

A working CMakeLists.txt for the above is as follows:


cmake_minimum_required( VERSION 3.14 )
project( app LANGUAGES CXX )

set( CMAKE_CXX_STANDARD 17 )
set( CMAKE_CXX_STANDARD_REQUIRED YES )

add_subdirectory(dependencies/GNDStk)

add_executable(app src/app.cpp)
target_link_libraries(app PUBLIC GNDStk)

And, the simplest possible GNDStk-aware app.cpp would be:


#include "GNDStk.hpp"

int main()
{
}

Finally, building app should be as simple as this:

cd MyApp  # <== If you're not there already
mkdir build
cd build
cmake ..
make

If all went well, app.cpp should have been compiled into an executable file called app.

Alternative: Bash Script

An important goal for us is that GNDStk be accessible, with as minimally intrusive a build process as possible, to a wide variety of researchers.

If you’re using libraries other than GNDStk, they may impose their own build systems – possibly ones you like, possibly ones you don’t, but ones you’re stuck with, regardless, for better or for worse. You may, on the other hand, be using your preferred and well-liked build system. Independent of what you may or may not be working with in that respect, we want GNDStk to impose as little additional complexity as it can.

In the above spirit, and if you’re using a Linux or Linux-based machine, you may find that the contents of the following simple shell script can be adapted easily to your needs:

#!/bin/bash

# Specify your base GNDStk directory (as cloned from github) here.
GNDSTKDIR=/path/to/your/downloaded/GNDStk

# Example compilation command. -std=c++17, and the -Is, are needed.
COMPILE="
   g++
  -std=c++17
  -I$GNDSTKDIR/src
  -I$GNDSTKDIR/build/_deps/pugixml-adapter-src/src/src
  -I$GNDSTKDIR/build/_deps/json-src/include
  -I$GNDSTKDIR/build/_deps/json-src/include/nlohmann
  -I$GNDSTKDIR/build/_deps/log-src/src
  -I$GNDSTKDIR/build/_deps/spdlog-src/include
  -Wall -Wextra -Wpedantic"

# pugixml.cpp is the only C++ source file, other than your own,
# that needs to be compiled. We'll arrange to build it *once*.
if [ ! -f "pugixml.o" ]; then
$COMPILE \
   $GNDSTKDIR/build/_deps/pugixml-adapter-src/src/src/pugixml.cpp \
  -c -o pugixml.o
fi

# Compile your own C++ application, linking with the .o from above.
$COMPILE app.cpp pugixml.o -o app

Begin, as you can see, by specifying the base GNDStk directory you cloned. The script immediately uses this value to create a simple compilation command, in this case one that uses g++ as its C++ compiler. Next, the script checks to see if a certain .cpp file, from one of GNDStk’s dependencies, has been compiled. If it hasn’t been, yet, then it is now. Finally, another compilation command builds your own application – illustrated in this simple example as a single C++ source file called app.cpp. Consider trying this first with the minimal app.cpp that was shown in the section on CMake builds.

You’re welcome to adapt our script, or its contents, as may be necessary or helpful within your own build regime.

Some caveats. Use of the sample bash script assumes that you’ve downloaded GNDStk, and run cmake .. (and in a build directory), as outlined earlier. You can easily adjust the script if, for whatever reason, you configured things in a different manner. Realize, however, that the cmake .. in some form, or steps that created the same effect, must have happened in order for GNDStk’s dependencies to have been downloaded into the _deps directory that makes several appearances throughout the script’s compilation command.

Be aware also that the script reflects dependencies, and their locations in directories, that are correct at the time of this writing. While we intend to update these instructions if and when we make relevant changes to GNDStk, it’s possible that some detective work may prove to be necessary if we drop the documentation ball after dependencies do, for whatever reason, change. If, for instance, we decide to explore someday one of those deep mysteries of the universe that regularly visits our world through computers, such as why pugixml.cpp ended up in src/src/ rather than just in src/, then it’s possible, even as much as we try to behave, that we’ll make a quick change to our own make system’s actions without updating these instructions for a simple script in an entirely timely manner.

Header-Only Library

GNDStk, proper, is a C++ header-only library. You can find plenty of information online if you’re unfamiliar with the concept. In our opinion, header-only libraries provide a multitude of advantages, such as making builds far less complex than they’d otherwise be; and their disadvantages, generally distilling down to some variation of “builds can take longer,” are straightforward to mitigate with careful design. We designed GNDStk carefully.

Our library does, however, have one dependency, pugixml (https://pugixml.org/), that has a single C++ source (not header) file. That’s why our sample shell script, if you read that section, needed to compile one .cpp file, other than your own, directly.

We mention our library’s header-only nature not so as to conclude this chapter with any particular profound point, but largely for informational purposes. If you’re unfamiliar with the header-only concept, or with how to write or to use such libraries in C++, then you might find it helpful – or more importantly, fun – to learn more. With respect to GNDStk, knowing that it’s formulated in this fashion may allow you, in one way or another, to make the best use of GNDStk in your own build system, and in your own application.

Tutorial

Basics + Core Interface

Let’s begin with some very minimal GNDStk-based example codes, explain our arrangement of major C++ namespaces, and then move on to more-interesting and useful examples.

Minimal GNDStk-Aware Code

Here’s the most minimal GNDStk “application”, albeit one that doesn’t do anything:

#include "GNDStk.hpp"

int main()
{
}

The takeaway: to use GNDStk, you should #include its one primary header file, GNDStk.hpp. In contrast to the manner in which some C++ libraries are designed, GNDStk provides just this one main header file for user consumption. That header, in turn, includes all of GNDStk’s other headers, and in the correct manner with respect to namespaces and such.

Put another way, GNDStk is not designed, as some libraries are, so that you selectively choose what headers to #include. When we use the C++ Standard Library, for instance, we’ll pick and choose: include iostream, most likely, and perhaps vector, and any number of additional specific chosen headers.

The entire C++ Standard library is very large, of course, and such selectivity is important so that compile times are kept under control. GNDStk is much smaller, and we’ve judged that compilation times aren’t significantly impacted by putting forth our simple, easily followed rule: just #include GNDStk.hpp – nothing more, nothing less – in whichever of your own source files need it.

Namespace Hierarchy

GNDStk, like many C++ libraries, places its various constructs into a moderate number of C++ namespaces. For simplicity’s sake, omitting details that most users won’t care about, consider that GNDStk’s namespace hierarchy looks something like this:

// Outer namespaces that surround everything else.
namespace njoy {
namespace GNDStk {

   // Namespace for basic, generic "GNDS query objects". There are
   // other sets of query objects; don't worry about those for now.
   namespace basic {

      // Query objects specific for GNDS metadata.
      namespace meta {
      }

      // Query objects specific for GNDS child nodes.
      namespace child {
      }

      // Bring in meta:: and child:: above.
      using namespace meta;
      using namespace child;

      // Bring in common:: below.
      using namespace common;
   }

   // Some query objects that we wish to be shared across basic::
   // as well as other query-object namespaces that we don't list here.
   namespace common {
   }

   // Logging capabilities, including, for example, support for errors,
   // warnings, and informational messages.
   namespace log {
   }

   // Our "core" namespace; see discussion.
   namespace core {
      // Bring in GNDStk:: itself, and basic:: above
      using namespace GNDStk;
      using namespace basic;
   }

}
}

A few things are happening here, so bear with us.

For starters, everything is in njoy::GNDStk::. That’s easy enough to understand. Notably, however: considering that a typical C++ library will often begin, at global scope, with a namespace that matches the library’s name, it’s laughably easy to forget the njoy:: part. We’ve made that mistake several times, ourselves. We have the njoy:: only because GNDStk is just one element of Los Alamos National Laboratory’s broader NJOY suite of software projects. If – well, when – you ever forget the njoy::, a modern C++ compiler may suggest, in its initial error message (before its flood of spurious ones), that you probably meant the GNDStk:: in njoy::.

Fundamentally important in GNDStk are its “query objects”. Think of these as small modules of information that facilitate a concise notation for getting (or setting) GNDS data. (Much more on all of this later.) For now, suffice it to say that the query objects of most interest to users are our basic ones in – drum roll, please – basic::. Within basic::, query objects are further placed into meta:: or child::, depending on whether they’re designed for getting and setting metadata, or for getting and setting child nodes. Names for our query objects equate, except in certain rare cases, to the GNDS names of the metadata and child nodes with which they interact. For example, a GNDS label metadatum is called label in GNDStk’s query objects, and a GNDS styles node is called styles.

Strangely – at first glance – basic:: has using directives for its own meta:: and child:: sub-namespaces! Why not place the contents of those directly into basic:: to begin with, and dispense with the sub-namespaces altogether? It turns out, in fact, that the GNDS standard has a small amount of overlap between its names for metadata, and its names for nodes. Two examples are parity and spin. If, for example, you look through the currently available XML-format GNDS files, you’ll see XML spin="something" metadata, and also XML <spin> elements. Our arrangement for basic::, meta:: and child:: is such that if you’re using namespace basic, you can dispense with a meta:: or child:: prefix where names are unique (styles, label, and most other names), or prefix appropriately in the occasional cases where they aren’t: meta::spin for spin metadata, child::spin for spin nodes, and so forth.

basic:: is one of two namespaces (at the time of this writing) into which we’ve placed full sets of query objects for GNDS metadata and child nodes. (Don’t worry, for now, about the other set. We may even remove it, as other capabilities of GNDStk have made it less worthwhile to have than it once was.) A third namespace, common::, contains a small handful (not a complete set) of query objects that are intended for use with both of the two full sets. In addition to using its own meta:: and child:: sub-namespaces for the reasons we described above, basic:: also brings in the contents of common::, so that no common:: prefix is needed when you’re using namespace basic.

A namespace log:: also exists in GNDStk. We’ll discuss it elsewhere, but mention it here only because (1) you may occasionally find its contents to be useful for your own purposes; and (2) it serves, in contrast to the other namespaces being discussed here, as an example of something that isn’t included automatically by our core:: namespace. We don’t consider it to be useful enough, for the average user, to justify cluttering core:: with its contents. If and when you need it, log:: is short, and easy to type.

Core Interface

That brings us, finally, to the core:: namespace that we called out, in our example code, as being precisely what we suggest that most users bring in:

using namespace njoy::GNDStk::core;

core:: is little more than this:

namespace core {
   using namespace GNDStk;
   using namespace basic;
}

So, core:: brings in basic::, which as we saw above brings in its own meta:: and child:: sub-namespaces, as well as the (modest but useful) content in common::. On top of that, core:: actually brings in GNDStk:: (that is, njoy::GNDStk::), even though core:: itself in inside of njoy::GNDStk::! (The language does allow that.)

All things considered, then, the single directive using namespace njoy::GNDStk::core brings in all content from:

njoy::GNDStk::
njoy::GNDStk::basic::
njoy::GNDStk::basic::meta::
njoy::GNDStk::basic::child::
njoy::GNDStk::common::

with the single caveat we spoke of already in regards to basic:: – that in the rare but occasional cases of overlap (meta::spin vs. child::spin, for instance, or meta::parity vs. child::parity), you must disambiguate. And the compiler will tell you as much, as it’ll be an error until you do.

The combined content of the above-listed namespaces constitute what we consider to be a good set of core GNDStk capabilities. Hence, our motivation for creating a core:: namespace that brings all of them into your code, together, via the one convenient directive that we’ve recommended.

You can consider the phrases core namespace and core interface to be essentially interchangeable. Which term we use, and where, depends on whether we’re referring to the namespace in particular, or to the functionality it exposes.

We’ll note, finally, that having (and recommending) our core:: namespace is helpful from the standpoint of software maintainability. If we decide at some future time that GNDStk needs a refactor, and/or a rearrangement of its functionality into a different overarching namespace scheme, we anticipate being able to update the contents of core:: in such a way that the codes that use it – like, we hope, yours – will need few if any changes, even if the GNDStk constructs that the codes employ have been moved to entirely new or different locations.

Read and Write GNDS

Read XML

Here’s a simple code that reads the XML format GNDS file named n-094_Pu_239.xml:

#include "GNDStk.hpp"
using namespace njoy::GNDStk::core;

int main()
{
   Tree pu239("n-094_Pu_239.xml");
}

Obviously, but worth a reminder, this assumes that the file resides right there, in the directory from which this code is run. If it doesn’t, include a suitable absolute or relative path in the file name string. We, and no doubt everyone reading this, have probably made this mistake often enough over the years.

Tree is GNDStk’s data structure for holding an entire GNDS hierarchy, a.k.a. GNDS tree. GNDS is, indeed, a tree-like structure, and that’s reflected in the name of our C++ class. Once loaded, you’ll be able to do great things – most likely, in this particular example, data queries from an existing GNDS file – with object, pu239 here, into which the GNDS data in the loaded file were placed.

A large collection of XML-format GNDS files can be downloaded from here:

That’s where we got our example’s n-094_Pu_239.xml, and many other GNDS files. At around 24MB in size, it’s one of the larger GNDS files from the above site, but it isn’t among the absolute largest. We’ll often use it in our examples; its modest size (by today’s standards) should still allow for fast reading, and we believe that its contents make for good examples. The same, of course, could probably be said about any GNDS file, depending on what data are of interest.

While we didn’t say so directly, a C++ programmer will have realized what the above code tells us: that Tree has a constructor from a character string (in fact, from a std::string), and for which the behavior is: “interpret the string as the name of a GNDS file, and load the file.” You could write this instead:

#include "GNDStk.hpp"
using namespace njoy::GNDStk::core;

int main()
{
   Tree pu239;
   pu239.read("n-094_Pu_239.xml");
}

which is more explicit in its purpose, but slightly less concise. (And the earlier, direct-constructed Tree could be const, if that matters.)

Files, Streams, Types

In terms of what we saw above, Tree has four similar constructors. (And has several additional completely different constructors that are outside the scope of the present discussion).

The first argument is either a file name, or a C++ std::istream from which to read the “file.” The second argument allows you to stipulate the file format explicitly, and can either be something from GNDStk’s FileType enumerator:

enum class FileType {
   null, // Default, automagick, etc.
   tree, // <== DON't use this for reading; just writing
   // Generally use one of these:
   xml,  XML  = xml,
   json, JSON = json,
   hdf5, HDF5 = hdf5
};

or can be a direct string: "xml", etc. A direct string is shorter and slightly easier to type – but, if mistyped, would lead to a run-time error, not a compile-time error, if that matters to you in this simple context.

HDF5 is not supported at this time! Just XML and JSON.

You should seldom, if ever, need to provide the second argument. Absent the second argument, GNDStk determines the file type automatically, and we doubt that you’ll have any objections to that. If you do choose provide the second argument, then it, not GNDStk’s automatic file type determination, will be used, but you’ll see a warning if your directly-given value contradicts GNDStk’s automatic determination, which it still performs for diagnostic purposes. Of course, if you try to force reading in one format, and the file’s actual format is something else, you’ll soon be seeing a flood of errors, not mere warnings, as we attempt to read the file pursuant to the (incorrect) forced format.

GNDStk uses the “file magic number,” not the file name, to determine file type automatically. The file magic number really means the first byte, or bytes, of the file. XML files always begin with a < character. HDF files (not supported yet) begin with ASCII 137 and a few other specific bytes. If the first byte is neither of those values, then GNDStk assumes JSON format.

A nice thing about using the file magic number, not the file name, is that it works for std::istream, for which a “file name” isn’t even available. Moreover, it tells us what’s actually in the file or the stream, independent of what any name might imply. If you provide an XML file but call it something.JSON, then that would be a rather strange thing to do, but GNDStk will correctly determine the actual type – XML – and thus read the file correctly. In cases like that, GNDStk will do an additional good deed: it’ll warn you that the file’s name contradicts the file’s type as implied by the file magic number.

Read & Write XML

Here’s a simple example in which we read our trusty example GNDS XML file, then write it back out to another XML file:

#include "GNDStk.hpp"
using namespace njoy::GNDStk::core;

int main()
{
   Tree pu239;
   pu239.read("n-094_Pu_239.xml");
   indent = 2; // <== not necessary; just for illustration
   pu239.write("pu239");
}

You probably already guessed, correctly, that if Tree has a read that reads, then it probably has a write that writes. We’ve endeavored to make GNDStk’s design as intuitive and consistent as reasonably possible.

We wrote earlier that GNDStk uses the file magic number, not the file name, to determine the file type when reading. When writing, there is no file magic number – the file to be written doesn’t exist, yet, or if it does, then its present contents are meaningless because the file is about to be replaced.

When you call write, therefore, GNDStk does use the file name to determine what format in which to write, except that you can provide that second argument again – a value from our FileType enumerator, or a string like "xml" or "json" – to specify the type you want directly. As it does for read, GNDStk makes some consistency checks. If you write, for instance,

pu239.write("pu239.xml", "json");

then GNDStk will write file pu239.xml in JSON format, as you asked for in the second argument, but will warn that the file extension is inconsistent with the format you asked for.

What if the file name extension isn’t given, or isn’t recognized, and a format isn’t forced with a second argument? That is, what if we wrote, for example, pu239.write("pu239")? In that case, write writes the Tree into a simple output format that we created largely for debugging purposes. You probably won’t have much use for this format, and we don’t provide the ability to read from it, but you’ll no doubt notice the problem quickly and be able to fix it.

In the above code, what’s indent all about? We didn’t really need to clutter the example by including it, as it isn’t required at all, but we wanted to illustrate something minor but perhaps of interest. indent is one of a small handful of useful “global variables” (not truly global, but in namespace njoy::GNDStk::) that GNDStk provides to you for fun and profit. Fun, at least. For XML and JSON output files, as well as for a few other things throughout GNDStk’s vast array of features, indent tells how many spaces you’d like indentation to be. GNDStk’s default is 3 spaces, which this author happens to prefer. In the example, we’re saying (before the write, of course) that we’d like 2 spaces to be used. At present, behavior is undefined if you give a negative number, and of course the output will look ridiculous if you give a huge number. Most people prefer 2-5 spaces for indentation. In case you’re wondering, GNDStk has no facility for using tabs – an evil creation, quite arguably – for this purpose.

Finally, we note that write can write to a std::ostream, not just a file, in much the same way that read can read from a std::istream, not just a file. (Always remember: ostream for writes, istream for reads.) Bear in mind, again, that with output a file magic number isn’t available, and if you use std::ostream, then a file name, from which we might guess the format, isn’t available either. So, you’ll specifically want to give the second argument – "xml", say, or "json" – if you write to a std::ostream.

More Reads & Writes

We hope that GNDStk’s basic facilities for reading and writing GNDS files are clear enough at this point, but we’ll provide a few more examples nonetheless. A simple XML to JSON conversion can be done like this:

#include "GNDStk.hpp"
using namespace njoy::GNDStk::core;

int main()
{
   Tree pu239;
   pu239.read("n-094_Pu_239.xml");
   pu239.write("n-094_Pu_239.json");
}

Here’s a more compact version of the same thing:

#include "GNDStk.hpp"
using namespace njoy::GNDStk::core;

int main()
{
   Tree("n-094_Pu_239.xml").write("n-094_Pu_239.json");
}

Just as we can write JSON, we can read it, too. If we’ve produced the output .json file as with the above example, we can read it thus:

#include "GNDStk.hpp"
using namespace njoy::GNDStk::core;

int main()
{
   Tree pu239("n-094_Pu_239.json");
}

Here, as you can see , we’ve returned to using a “read by constructor,” as in our original XML example, instead of employing a read call. It’s just more concise, in our opinion. Of course, you’ll use – and should – whichever variation you prefer.

Read, Write, Compare

We’ll wrap up our set of read/write examples with a code that reads our favorite GNDS XML file, writes it to a JSON file, independently reads the JSON back into another Tree object, and then also compares the new Tree to the original:

#include "GNDStk.hpp"
using namespace njoy::GNDStk::core;

int main()
{
   // Read from XML
   Tree FromXML;
   FromXML.read("n-094_Pu_239.xml");

   // Write to JSON
   FromXML.write("test.json");

   // Read back from JSON
   Tree FromJSON;
   FromJSON.read("test.json");

   // Compare
   assert(FromXML == FromJSON);
}

Several remarks are in order here.

The comparison operator for Tree compares the two GNDS trees in an order-agnostic manner. GNDS fundamentally provides data in two places: nodes (think XML “elements”) in its overall tree structure, and metadata (think XML “attributes”). The GNDS standard does not, however, consider ordering to be important. One tree node’s child nodes or metadata, anywhere or everywhere throughout the entire tree structure, could be reordered arbitrarily, but if each remains equivalent – in the same respect that we consider two mathematical sets to be equivalent – then the two GNDS trees are to be considered equivalent. So it is with our Tree comparison.

Interestingly enough, the above example’s FromXML and FromJSON objects will, in fact, have quite different orderings, across the board, of child nodes and metadata! Why is this the case? GNDStk makes use of an external library called pugixml for reading and writing XML files, and an external library nlohmann/json, on Github, for reading and writing JSON files:

It turns out that pugixml preserves the existing ordering of XML elements and attributes when it reads a file, while nlohmann/json lexicographically sorts the JSON name/value pairs by name. The latter library’s behavior could be considered unfortunate if we’d rather see ordering preserved; while the former library’s behavior could be considered unfortunate if we wish to fully respect GNDS’ “no-ordering” rule and discourage the creation of code that might inadvertently depend on data ordering. In any event, our operator== for Tree respects order-independent equivalence, as it should.

What’s assert about? If you’re not familiar with assert, it’s actually a longstanding and quite useful macro that C++ inherited from the C language. assert simply checks that its argument is true, and causes the code to exit immediately, with an error message that says something along the lines of “assertion failure …”, if it isn’t true.

This documentation uses assert throughout its examples. It’s great for that purpose: an expression like assert(foo) can be read, simply and concisely, as: “we’re asserting to you, the reader, that foo is true here.” In the above code, for instance, we’re telling you directly that FromXML and FromJSON equal each other when we’re at the assert line.

GNDStk’s test suite, by the way, makes use of the CATCH library, described online as a “test framework for unit-tests.” CATCH, on the one hand, has far more macros and other testing machinery than we get with just assert by itself. However, a philosophy we’re keeping in mind with GNDStk is that we don’t want to require potential users to learn more than they need to. A system like CATCH, while more powerful, can obscure, to the uninitiated user, where even a basic construct like a main() is located, or how different source files are fitted together to create a group of tests.

Therefore, for your benefit throughout this manual, we’ll keep things simple. We’ll provide complete, working, compile-able codes except where it’s obvious that something is just a code fragment; and employ the simple but clear assert where we wish to draw attention to the fact that the asserted expression is true.

Regarding JSON Files

The specifications for Version 1.9 of the GNDS standard can be found here:

which, at the time of this writing, is the latest available standard. Section 2.4 of the downloadable PDF document discusses limitations of some “meta-languages” (roughly speaking, file formats) such as JSON, in comparison with what XML is able to represent.

Here are three points quoted directly from the document:

1. for meta-languages that do not support attributes, either group all attributes together under a child node called attributes or convert each attribute to a child node and add a suffix like _attr to the node name.

2. for meta-languages that do not support multiple child nodes with the same name, add a unique suffix to each node name. For example, if multiple reaction elements appear in the file, they become reaction0, reaction1, etc. To avoid parsing strings to determine the original node name, a nodeName attribute (or child node) should also be added indicating the original unmodified node name.

3. for meta-languages that do not preserve the order of child elements, an attribute or child node with the (0-based) index should be added to the node. For example, in HDF5 the attribute nodeIndex could be added to each child in a group.

For (1), GNDStk does the first suggested action: it groups all of a node’s attributes under a child node called attributes. We consider that to be cleaner than using an _attr suffix.

For (2), GNDStk does exactly as illustrated: multiple elements of the same name are suffixed with 0, 1, etc. And, then, a JSON name/value pair with the name nodeName, as suggested, is created in order to preserve the original unsuffixed element’s name.

For (3), GNDStk does nothing in particular right now. Our understanding of GNDS is that it’s designed so that elements – nodes – can appear in any order. Here’s a small XML fragment taken directly from the n-094_Pu_239.xml example GNDS file that we’ve been using for our examples:

<axes>
   <axis index="1" label="energy_in" unit="eV"/>
   <axis index="0" label="radius" unit="fm"/>
</axes>

Those axis child nodes already contain a 0-based index attribute, so perhaps the specification’s admonishment #3 is something we can consider to have been satisfied already by whomever has created an existing, valid GNDS file (so that no further treatment is required); or something that we must satisfy if we are to create our own valid GNDS files.

The GNDS document then puts forth the following example XML fragment – slightly reformatted here for clarity, and with a proper XML declaration node (the first line) added for completeness:

<?xml version="1.0" encoding="UTF-8"?>
<employees>
  <employee>
     <name first="Doc" last="Jones"/>
  </employee>
  <employee>
     <name first="Grumpy" last="Smith"/>
  </employee>
  <employee>
    <name first="Happy" last="Earp"/>
  </employee>
</employees>

A viable JSON equivalent is then suggested.

Calling the above XML file employees.xml, let’s bring forth our tried-and-true GNDStk methodology for converting from XML to JSON:

#include "GNDStk.hpp"
using namespace njoy::GNDStk::core;

int main()
{
   Tree("employees.xml").write("employees.json");
}

and see what happens. Here’s exactly the output JSON file that the above code, applied to the sample XML, creates:

{
   "employees": {
      "employee0": {
         "name": {
            "attributes": {
               "first": "Doc",
               "last": "Jones"
            }
         },
         "nodeName": "employee"
      },
      "employee1": {
         "name": {
            "attributes": {
               "first": "Grumpy",
               "last": "Smith"
            }
         },
         "nodeName": "employee"
      },
      "employee2": {
         "name": {
            "attributes": {
               "first": "Happy",
               "last": "Earp"
            }
         },
         "nodeName": "employee"
      }
   }
}

This illustrates how GNDStk creates JSON files, consistent with the suggestions in the GNDS specification.

If you try the above code, on the given input, you’ll see that GNDStk prints two warnings. The same warning twice, actually: once during input, and once during output. The warning tells us that <employees> – the top-level node of the above XML document – is not recognized as a valid GNDS top-level node. (Valid GNDS top-level nodes, per the standard, are reactionSuite, covarianceSuite, PoPs, thermalScattering, and fissionFragmentData.) It’s just a warning, not an error, so don’t worry about it for now.

Naturally, GNDStk reverses the modifications when we read from a JSON file into our internal format. Specifically: values in an attributes block are transformed into metadata in the enclosing node, and values from nodeName name/value pairs replace index-suffixed names.

At this time, GNDStk provides no other options, such as the _attr suffix that the GNDS specification suggested as a possibility, for handling JSON. Neither can it read JSONs that may have been created in a different manner. We’re not aware, at the time of this writing, of the existence any official JSON-format GNDS files. If and when such files come into existence, and if such files use a different scheme than we do for addressing the issues described above, then we’ll provide capabilities at least for reading those files, and perhaps for writing them in that manner as well.

Data Structure “Direct”

In this section, we’ll talk about some of the basic internal constructs of some of GNDStk’s classes: more importantly, Tree and Node; less importantly, XML and JSON. We’ll describe member data – with an important message to users first – and also some of the member functions that you’re likely to find useful. For now, here, we won’t cover the plethora of member functions that support our “smart query system.” Those need their own dedicated, and detailed, discussion.

About Direct Access

Right away, we strongly suggest that most users avoid direct access of member data in these classes! An exception is the name string in Tree and Node, which you might well wish to access. It’s rather inoculous. Other member data, at this time, consists of containers for metadata and child nodes.

In most cases, we hope you’ll prefer to use GNDStk’s rich variety of powerful higher-level capabilities, in our “smart query system,” for pulling data from, or pushing data to, the metadata and child-node containers. (We’re talking right now about capabilities that are still in our core interface – not in our “high-level” interface that provides classes tailored to specific versions of the GNDS standard.) The basics of our “smart query system” are described in an upcoming section of this document.

Our query system was designed precisely so that you’ll have something much more concise and powerful than you will by directly accessing the containers in question. And, most likely, also safer to use, insofar as working directly with the internals of data structures, especially those that were designed by other people, invariably runs some risks. C++ containers aren’t rocket science (and, besides, some GNDStk users may well be rocket scientists), so we do in fact provide public access to these structures, in the interest of supporting users who are comfortable and capable with the C++ language.

Finally, we believe that if you understand the basic internal data format, then you may find the behavior of the higher-level capabilities, and our motivation for creating them, to be more clear.

Tree vs. Node

We’ve already seen Tree in some examples. It’s the class to use when you want to read or write an entire GNDS tree. Tree is derived from another important class: Node. At the time of this writing, Tree contains no additional data beyond what it gets from Node. It does, however, contain some additional member functions, and it makes some slight changes to some of the member functions that otherwise gets from its Node base.

Here’s a short sketch of our arrangement:

class Node
{
   using metaPair = std::pair<std::string,std::string>;
   using childPtr = std::unique_ptr<Node>;
public:
   std::string name;
   std::vector<metaPair> metadata;
   std::vector<childPtr> children;
   // constructors, member functions, ...
};

class Tree : public Node
{
   // a few additional and/or different member functions
};

The GNDS standard is essentially a tree structure, and this is reflected in our classes, with Tree being intended for the top-level (root) node, and Node for all others.

Some readers may realize, correctly, that a typical tree structure’s top-level node could be treated in exactly the same way as all of its other nodes. One doesn’t generally need different data types for a tree’s root node and its other nodes, including leaves. Roughly speaking, tree nodes all “look the same,” with similar contents as well as relationships to their child nodes.

That’s all true, and it could be described as the theoretical/mathematical view of tree structures. From a practical/engineering standpoint, some utility can sometimes be had in treating a top-level node differently from the others. That’s the reasoning for Tree versus Node.

As a derived class, Tree automatically inherits most of its functionality from Node, as we want it to. In a handful of respects, however, Tree will reflect the fact that it’s there to represent an entire GNDS hierarchy, not just a portion thereof. For example, it tries to ensure that the top-level GNDS node isn’t any valid GNDS node, but one of the few that’s valid as a top-level GNDS node. (GNDStk, it turns out, emits a warning, but not an error, if you try to write a Tree that doesn’t have a top-level GNDS node with a valid name.)

One could also imagine extra functionality that a Tree, but not a Node, could be equipped with. In the typical case that a GNDS tree is read from a file, for example, we could have the Tree structure store the file name. Then, perhaps, we could equip Tree with a member function like overwrite() or rewrite() that would replace the original file (say, after a user has made changes that they wished to make to the GNDS data) without requiring that the file name be repeated. (Analogy: a image-editing GUI that provides, in its File menu, an item like Overwrite <original.jpg>, in addition to a Save As... and an Export.) GNDStk does not, at the time of this writing, provide this particular capability. By making Tree different from Node, however, we allow for the possibility of such things being added, painlessly, at a later time.

Content Preservation

An important initial design decision that we made for our Tree and Node classes is that they faithfully represent precisely the content from any GNDS file we may read into them. The fundamental motivation here is simple: data evaluators work hard to create good data, and we don’t want to take any actions that might, in any way, change or lose anything.

Consider, as a simple example, this small fragment of content from near the beginning of our favorite n-094_Pu_239.xml example GNDS file:

<mass>
  <double label="eval" value="1.00866491574" unit="amu"/>
</mass>

We could probably all agree that the label "eval" and unit "amu" should be stored as strings. But what about the value "1.00866491574"? We could store it as a double, if we’re presumptuous enough to assume that a user intends to use it as a double – not a float, say, or a long double. We’d also be assuming, there, that a user doesn’t mind the expensive of presumptively “floating-point” content from GNDS files being converted en masse from the original XML character strings to floating-points, regardless of which GNDS content the user might actually access. On top of that, we’d be glossing over the various complexities that can (and do) arise when decimal representations of floating-point numbers are converted to internal binary floating-points, and back again. (The “back again” part is especially relevant if someone plans, say, to read a GNDS file, add new data and/or fix old data in selected areas, and then write the entire GNDS file back out again.)

Instead of making wild assumptions, we’ll opt instead to preserve original content – that is, to respect precisely what exists in a GNDS file to begin with.

To this end, all individual data, regardless of what they may appear to be (string, floating-point, integer, single character, etc.), are stored as strings. More precisely, as C++ std::strings. Node names ("mass") are stored as strings. Metadata key/value pairs are stored as C++ std::pairs of strings; think {"label","eval"}. Even the content in GNDS values nodes, like this one (the first in n-094_Pu_239.xml):

<values>
  2500 8.9172 2550 8.9155 2650 8.9139 ...
  ... 28500 8.4901 29500 8.4741 3e4 8.4659
</values>

are stored, in a Node, as long strings. (We could reasonably split out such thing into std::vector<std::string>s, too, but decided to not even do that. To perform such a split everywhere, automatically, would take time, and a user might not even intend to access any specific portion of GNDS data.)

No worries, though: our core interface, and especially the smart query system that we’ve spoken of, has plenty of functionality for serving its internal strings to you as floating-points, for instance; or for re-forming long strings, like the ones just described, into vectors of strings, or vectors of floating-points, or vectors of just about anything you may wish to create. When we speak of content preservation, then, we’re saying that an input text file – XML or JSON, for now – is factored into its underlying tree structure, but with its individual meaningful parts (neglecting, as usual, whitespace) still stored as text, with no modifications.

A given user’s application code will almost certainly have its own internal classes that contain GNDS data, or data computed from GNDS data, in ways that work well for the user’s application. Someone may also have classes specifically intended to mirror the content in various GNDS nodes, just in a different way. (GNDStk’s own “high-level interface” will provide precisely such classes.) Such classes can certainly make assumptions we didn’t want GNDStk to make – like, for example, that we do want double for that numerical value above. Or, for that matter, that perhaps the unit, "amu" above should be an entry in some C++ enumerator for allowable units – no longer a string at all. We’re happy to report that our core interface, and in particular our smart query system, is designed to help you interact well, and easily, with GNDStk’s internal string storage.

We’ll write more about the above considerations elsewhere. For now, let’s return to the main point of this chapter, and describe GNDStk’s two major classes that store GNDS data.

Node

We’ll write first about Node (for general nodes), because Tree (for the root node only) derives from Node. Recall that the member data in Node looks like this:

class Node
{
   using metaPair = std::pair<std::string,std::string>;
   using childPtr = std::unique_ptr<Node>;
public:
   std::string name;
   std::vector<metaPair> metadata;
   std::vector<childPtr> children;
   // constructors, member functions, ...
};

In short, inlining the metaPair and childPtr types and omitting the std:: prefix for brevity:

// Node's data members
string name;
vector< pair<string,string> > metadata;
vector< unique_ptr<Node> > children;

The above evinces a simple tree structure that’s entirely sufficient for representing the contents of any GNDS node.

Let’s provide a short but concrete example. Here’s some XML content from near the top of the n-094_Pu_239.xml GNDS file:

<evaluated label="eval" date="2017-12-01" library="ENDF/B" version="8.0.5">
  <temperature value="0.0" unit="K"/>
  <projectileEnergyDomain min="1e-05" max="20000000.0" unit="eV"/>
</evaluated>

Here, an outer evaluated node (XML “element”) contains four metadata key/value pairs (XML “attributes”) and two child elements. The first child element, temperature, contains two metadata pairs but no further child nodes. The second child element, projectileEnergyDomain, contains three metadata pairs but no further child nodes.

At the risk of continuing a narrative of statements that are no doubt obvious, here’s precisely how the above evaluated node is represented in a Node:

name: "evaluated"

metadata[0]: {"label", "eval"}
metadata[1]: {"date", "2017-12-01"}
metadata[2]: {"library", "ENDF/B"}
metadata[3]: {"version", "8.0.5"}

children[0]: pointer to another Node, with:

   name: "temperature"

   metadata[0]: {"value", "0.0"}
   metadata[1]: {"unit", "K"}

children[1]: pointer to another Node, with:

   name: "projectileEnergyDomain"

   metadata[0]: {"min", "1e-05"}
   metadata[1]: {"max", "20000000.0"}
   metadata[2]: {"unit", "eV"}

Here, {"foo", "bar"} is a C++ std::pair<std::string,std::string>, and is thus accessible in the customary manner: .first for the "foo" and .second for the "bar".

We use C++ std::unique_ptr<Node>s for the pointers to child nodes.

Pointers about Pointers

A couple of early users asked us about the motivation for using pointers, so we’ll briefly address, here, the concerns that they raised, in case other users wonder the same things.

One person wondered why children is a vector of pointers – not a vector of Nodes, which would appear at least to be simpler. Of course, a Node can’t directly contain another Node – C++ wouldn’t allow it – but could indeed contain a vector of Nodes. (C++ vectors themselves involve pointers, so pointers are still involved, they’re just not explicit.)

Without delving into a discussion that’s well beyond the scope of this document, we’ll say only that implementing a Node’s children as a vector of Nodes would likely wreak havoc on efficiency, both in space (memory) and in time, when objects like Tree and Node are being read from a file or otherwise created or modified. Considerable memory fragmentation could also come about.

Another user wanted to write code that copied some of children's pointers. The attempt to do so was stymied due to std::unique_ptr's intentional lack of a copy constructor, as unique_ptr is designed to be the exclusive “owner” of the object to which it points. GNDStk uses unique_ptr quite intentionally, precisely to deal with the ownership issue cleanly and clearly while also benefitting from unique_ptr's automatic handling of an object’s memory footprint.

A code shouldn’t attempt to take any actions that would break the ownership rules unique_ptr manifests, and a C++ compiler will say so loudly if one tries. Anyone who really wishes to make their own pointer – say, a raw pointer – to an object to which one of our unique_ptrs already refers, can always dereference the unique_ptr (giving a reference to a const or non-const Node, and effectively losing the unique_ptr aspect), then take the address to get a pointer again: basically &(*uptr), where uptr is a unique_ptr in one of our children vectors. (Do not, of course, delete the Node through such a pointer; leave its management to the original unique_ptr!) We recommend that anyone who does this, or anything similar, be sufficiently familiar with the C++ language, as well as justifiably confident that there isn’t a better way to accomplish the goal at hand.

Tree

Tree derives from Node, so what we’ve already spoken about, in terms of member data, still applies. Some additional points are in order, however, owing to Tree's status as the root node in our internal representation of a GNDS hierarchy.

Direct-Access Examples
XML and JSON

Smart Query System

GNDS Creation

node ctors tree ctors add()s

Advanced Examples

Largely continue query system discussion. Not sure about “advanced examples” characterization.

BASIC CONSTRUCTS

Primary Classes

Tree

Node

XML

JSON

Node: Major Capabilities

Query

Add Data

Functions

foo

bar

etc

Reading & Writing

Miscellaneous Utilities

Global Flags

Diagnostics

Notes
Warnings
Errors
Context

Other

CORE INTERFACE

Motivation

Query System, Part 1

Meta & Child

Meta Class
Child Class

Operators

Query Metadata

Node.meta(string)
1const string &meta ( const string &key ) const;
2      string &meta ( const string &key );
Node.meta(Meta)
1const   string  &meta ( const Meta<          void            > &kwd ) const;
2        string  &meta ( const Meta<          void            > &kwd );
3          TYPE   meta ( const Meta<          TYPE,  CONVERTER> &kwd ) const;
4optional <TYPE>  meta ( const Meta<optional <TYPE>, CONVERTER> &kwd ) const;
5Defaulted<TYPE>  meta ( const Meta<Defaulted<TYPE>, CONVERTER> &kwd ) const;
6          TYPE   meta ( const Meta<variant  <Ts...>,CONVERTER> &kwd ) const;
Node(Meta)
1decltype(auto) operator()( const Meta<TYPE,CONVERTER> &kwd ) const;
2decltype(auto) operator()( const Meta<TYPE,CONVERTER> &kwd );

Query Child Nodes

Node.one(string)
1const Node &one( const string &key, const FILTER &filter ) const;
2      Node &one( const string &key, const FILTER &filter );
3const Node &one( const string &key                       ) const;
4      Node &one( const string &key                       );
Node.many(string)
1CONTAINER<Node> many( const string &key, const FILTER &filter ) const;
2CONTAINER<Node> many( const string &key                       ) const;
Node.child(Child)
 1    const Node &child( const Child<void,             one,  void,      FILTER> &kwd ) const;
 2          Node &child( const Child<void,             one,  void,      FILTER> &kwd );
 3CONTAINER<Node> child( const Child<void,             many, void,      FILTER> &kwd ) const;
 4          TYPE  child( const Child<TYPE,             one,  CONVERTER, FILTER> &kwd ) const;
 5 optional<TYPE> child( const Child<optional <TYPE>,  one,  CONVERTER, FILTER> &kwd ) const;
 6Defaulted<TYPE> child( const Child<Defaulted<TYPE>,  one,  CONVERTER, FILTER> &kwd ) const;
 7          TYPE  child( const Child<variant  <Ts...>, one,  CONVERTER, FILTER> &kwd ) const;
 8CONTAINER<TYPE> child( const Child<TYPE,             many, CONVERTER, FILTER> &kwd ) const;
 9CONTAINER<TYPE> child( const Child<optional <TYPE>,  many, CONVERTER, FILTER> &kwd ) const;
10CONTAINER<TYPE> child( const Child<Defaulted<TYPE>,  many, CONVERTER, FILTER> &kwd ) const;
11CONTAINER<TYPE> child( const Child<variant  <Ts...>, many, CONVERTER, FILTER> &kwd ) const;
Node(Child)
1decltype(auto) operator()( const Child<TYPE,ALLOW,CONVERTER,FILTER> &kwd                          ) const;
2decltype(auto) operator()( const Child<TYPE,ALLOW,CONVERTER,FILTER> &kwd                          );
3decltype(auto) operator()( const Child<TYPE,ALLOW,CONVERTER,FILTER> &kwd, const string label      ) const;
4decltype(auto) operator()( const Child<TYPE,ALLOW,CONVERTER,FILTER> &kwd, const string label      );
5decltype(auto) operator()( const Child<TYPE,ALLOW,CONVERTER,FILTER> &kwd, const regex  labelRegex ) const;
6decltype(auto) operator()( const Child<TYPE,ALLOW,CONVERTER,FILTER> &kwd, const regex  labelRegex );

Query System, Part 2

Sequence Queries

Node.operator(…)
 1decltype(auto) operator()( const Child<TYPE,ALLOW,CONVERTER,FILTER> &kwd,                          KEYWORDS &&...kwds ) const;
 2decltype(auto) operator()( const Child<TYPE,ALLOW,CONVERTER,FILTER> &kwd,                          KEYWORDS &&...kwds );
 3decltype(auto) operator()( const Child<TYPE,ALLOW,CONVERTER,FILTER> &kwd, const string label,      KEYWORDS &&...kwds ) const;
 4decltype(auto) operator()( const Child<TYPE,ALLOW,CONVERTER,FILTER> &kwd, const string label,      KEYWORDS &&...kwds );
 5decltype(auto) operator()( const Child<TYPE,ALLOW,CONVERTER,FILTER> &kwd, const char *const label, KEYWORDS &&...kwds ) const;
 6decltype(auto) operator()( const Child<TYPE,ALLOW,CONVERTER,FILTER> &kwd, const char *const label, KEYWORDS &&...kwds );
 7decltype(auto) operator()( const Child<TYPE,ALLOW,CONVERTER,FILTER> &kwd, const regex labelRegex,  KEYWORDS &&...kwds ) const;
 8decltype(auto) operator()( const Child<TYPE,ALLOW,CONVERTER,FILTER> &kwd, const regex labelRegex,  KEYWORDS &&...kwds );
 9decltype(auto) operator()( const Child<TYPE,ALLOW,CONVERTER,FILTER> &kwd, const pair<Child,label>, KEYWORDS &&...kwds ) const;
10decltype(auto) operator()( const Child<TYPE,ALLOW,CONVERTER,FILTER> &kwd, const pair<Child,label>, KEYWORDS &&...kwds );

Multi-Queries

Node.operator( | )
1auto operator()( const KeywordTup<Ks...> &kwds ) const;
2auto operator()( const KeywordTup<Ks...> &kwds );

Conversion & Filters

Creating Data

Direct

Using “Query” Objects

Metadata

Need a legend for things like this….

 1// string, value
 2metaPair &add( const string &key, const           T  &val, const CONVERTER &converter = CONVERTER{} );
 3metaPair &add( const string &key, const Defaulted<T> &def, const CONVERTER &converter = CONVERTER{} );
 4
 5// Meta<void>, value
 6metaPair &add( const Meta<void> &kwd, const T &val = T{}, const CONVERTER &converter = CONVERTER{} );
 7
 8// Meta<TYPE>, value
 9metaPair &add( const Meta<TYPE,CONVERTER> &kwd, const           T  &val = T{} );
10metaPair &add( const Meta<TYPE,CONVERTER> &kwd, const Defaulted<T> &def );
11
12// Meta<optional>, value
13metaPair &add( const Meta<optional<TYPE>,CONVERTER> &kwd, const           T  &val = T{} );
14bool      add( const Meta<optional<TYPE>,CONVERTER> &kwd, const optional <T> &opt );
15bool      add( const Meta<optional<TYPE>,CONVERTER> &kwd, const Defaulted<T> &def );
16
17// Meta<Defaulted>, value
18metaPair &add( const Meta<Defaulted<TYPE>,CONVERTER> &kwd, const           T  &val = T{} );
19bool      add( const Meta<Defaulted<TYPE>,CONVERTER> &kwd, const optional <T> &opt );
20bool      add( const Meta<Defaulted<TYPE>,CONVERTER> &kwd, const Defaulted<T> &def );
Children
 1// string
 2Node &add( const string &name = "" );
 3
 4// value
 5Node &add( const           T  &val );
 6Node &add( const Defaulted<T> &def );
 7
 8// Child<void>, value
 9Node &add( const Child<void,ALLOW,void,FILTER> &kwd, const T &val = T{} );
10
11// Child<TYPE>, value
12Node &add( const Child<TYPE,ALLOW,CONVERTER,FILTER> &kwd, const           T  &val = T{} );
13Node &add( const Child<TYPE,ALLOW,CONVERTER,FILTER> &kwd, const Defaulted<T> &def       );
14
15// Child<optional>, value
16Node &add( const Child<optional<TYPE>,ALLOW,CONVERTER,FILTER> &kwd, const           T  &val = T{} );
17bool  add( const Child<optional<TYPE>,ALLOW,CONVERTER,FILTER> &kwd, const optional <T> &opt       );
18bool  add( const Child<optional<TYPE>,ALLOW,CONVERTER,FILTER> &kwd, const Defaulted<T> &def       );
19
20// Child<Defaulted>, value
21Node &add( const Child<Defaulted<TYPE>,ALLOW,CONVERTER,FILTER> &kwd, const           T  &val = T{} );
22bool  add( const Child<Defaulted<TYPE>,ALLOW,CONVERTER,FILTER> &kwd, const optional <T> &opt       );
23bool  add( const Child<Defaulted<TYPE>,ALLOW,CONVERTER,FILTER> &kwd, const Defaulted<T> &def       );
24
25// Child<*> w/allow::many, container
26void add( const Child<TYPE,allow::many,CONVERTER,FILTER> &kwd, const CONTAINER<T,Args...> &container );

Conversion Scheme

Advanced Topics

HIGH-LEVEL INTERFACE

Component Base

Motivation

Capabilities

Usage Requirements

Main Structures

Examples

Field Concepts

Required

Optional

Defaulted

C++ Version-Specific

GNDS v1.9

GNDS v2.0

Python Bindings

REFERENCE

Core Classes

Tree

Node

XML

JSON

Meta

Child

KeywordTup

Node: Major Capabilities

meta()

one() and many()

child()

operator()

operator[]

MetaRef & ChildRef

Meta & Child Operators

convert()

Tree/XML/JSON

For Metadata

For Child Nodes

Canned Keywords

For Metadata

For Child Nodes

Special cases

High-Level Support

High-Level Interface

GNDS Version 1.9

GNDS Version 2.0

Miscellaneous

INDEX