XML processing

The configuration concept (Chapter System configuration and Section Component configuration) of the framework relies on XML syntax. Hence, there is the need to process XML-formed data. For parsing XML data, the Xml_node and the accompanying Xml_attribute utilities are provided. Those utilities operate directly on a text buffer that contains XML data. There is no conversion step into an internal representation. This approach alleviates the need for allocating any meta data while extracting information from XML data. The XML parser is stateless.

Vice versa, the Xml_generator serves as a utility for generating XML formatted data. The scope of an XML node is represented by a lambda function. Hence, nested XML nodes can be created by nested lambda functions, which makes the structure of the XML data immediately apparent in the C++ source code. As for the Xml_node the Xml_generator does not use any internal intermediate representation of the XML data. No dynamic memory allocations are needed while generating XML-formatted output.

A typical component imports parts of its internal state from XML input, most prominently its configuration. This import is not a one-off operation but may occur multiple times during the lifetime of the component. Hence, the component is faced with the challenge of updating its internal data model from potentially changing XML input. The List_model provides a convenient and robust formalism to implement such partial model updates.

XML parsing

Genode's XML parser consists of the two classes Xml_node and Xml_attribute. Its primary use case is the provisioning of configuration information to low-level components. Consequently, it takes the following considerations into account:

Low complexity

Because the parser is implicitly used in most components, it must not be complex to keep its footprint on the trusted computing base as small as possible.

Free-standing

The parser must be able to operate without external dependencies such as a C runtime. Otherwise, each Genode-based system would inherit such dependencies.

No dynamic memory allocations

The parser should not dynamically allocate memory to be usable in resource multiplexers and runtime environments where no anonymous memory allocations are allowed (Section Component-local heap partitioning).

Robustness

The parser must be robust in the sense that it must not contain buffer overflows, infinite loops, memory corruptions, or any other defect that may make the program crash.

Other possible goals like expressive error messages, the support for more general use cases, and even the adherence to standards are deliberately subordinated. Given its low complexity, the XML parser cannot satisfy components that need advanced XML processing such as validating XML data against a DTD or schema, mutating XML nodes, or using different character encodings. In such cases, component developers may consider the use of ported 3rd-party XML parser.

Genode::Xml_attribute

Genode::Xml_node

Genode::Xml_unquoted

XML generation

Genode::Xml_generator

XML-based data models

List-based data model created and updated from XML

The List_model defined at base/include/util/list_model.h stores a component-internal representation of XML-node content. It keeps XML sub-nodes of a matching type in an internal list of C++ objects of type ELEM. An ELEM type carries two methods matches and type_matches that define the relation of the elements to XML nodes. E.g.,

 struct Item : List_model<Item>::Element
 {
   static bool type_matches(Xml_node const &);

   bool matches(Xml_node const &) const;
   ...
 };

The class function type_matches returns true if the specified XML node matches the Item type. It can thereby be used to control the creation of ELEM nodes by responding to specific XML tags while ignoring unrelated XML tags.

The matches method returns true if the concrete element instance matches the given XML node. It is used to correlate existing ELEM objects with new versions of XML nodes to update the ELEM objects.

The functor arguments create_fn, destroy_fn, and update_fn for the update_from_xml method define how objects are created, destructed, and updated. E.g.,

 _list_model.update_from_xml(node,

   [&] (Xml_node const &node) -> Item & {
     return *new (alloc) Item(node); },

   [&] (Item &item) { destroy(alloc, &item); },

   [&] (Item &item, Xml_node const &node) { item.update(node); }
 );

The elements are ordered according to the order of XML nodes.

The list model is a container owning the elements. Before destructing a list model, its elements must be removed by calling update_from_xml with an Xml_node("<empty/>") as argument, which results in the call of destroy_fn for each element.

Genode::List_model

XML buffering

In some situations, it is convenient to keep a verbatim copy of XML input as part of the internal data model. As the Xml_node is merely a light-weight pointer into XML data, it cannot be stored when the underlying XML data is updated dynamically. Instead, the XML input must be copied into the internal data model. The Buffered_xml utility takes care of the backing-store allocation and copying of an existing Xml_node.

Genode::Buffered_xml