PropertyList XML files
Most configuration files in FlightGear are PropertyList XML files using a subset of XML to encode lists and trees of properties as XML. PropertyList-XML files are the main mechanism to populate the FlightGear property tree (or a sub branch of it) from XML files, but also to serialize (save) property tree/branch state to an XML file. XML attributes are mainly used as meta-directives for the property tree.
For additional background information, you'll also want to refer to $FG_ROOT/Docs/README.introduction.
One advantage of the XML representation is its conceptual simplicity. Every element except for the root PropertyList container represents exactly one property, and every attribute represents metadata for the property associated with its element.
To work with PropertyList-XML files, you can use various means, such as:
- Nasal (props.nas, io.nas)
- C++ APIs (see links at the end of the navigation sidebar)
- Text editor: manually edit XML files
Right now, we use attributes for meta-information (data type, read/write flags, aliasing, and tracing). As long as we use attributes for meta-information and elements for data, we'll have a clean upgrade path; otherwise, we could end up with lots of reserved names (like you could not have a property named "alias", for example, because it would conflict with the attribute), and versioning problems (there's a new attribute named "foo", so any existing "foo" properties have to be renamed).
The current practice is a little verbose compared with a custom-designed, single-purpose XML format, but it generalizes nicely. Another alternative would be to use XML Namespaces, but (while I support them) those have their detractors even in the hard-core XML community, and I'm worried that they'll confuse people a bit in FlightGear.
A custom-designed XML format for panels, sound, animations, FDMs, etc. could be *much* less verbose, but then we'd be managing n different XML-based formats and parsing libraries.
Read and write access
# come up with a path and filename in $FG_HOME, inside the Export sub folder, file name is test.xml var filename = getprop("/sim/fg-home") ~ "/Export/test.xml"; # use the write_properties() helper in io.nas, which is a wrapper for the savexml fgcommand (see README.commands) io.write_properties( path: filename, prop: "/sim" );
This will dump the sub branch of the /sim property tree into $FG_HOME/Export/test.xml
For additional examples, see $FG_ROOT/Nasal/io.nas
To learn more about PropertyList processing via loadxml and savexml, please see $FG_ROOT/Docs/README.commands
Internally, all read/write access is validated via an API called fgValidatePath(), for details please see $FG_SRC/Main/util.cxx
The PropertyList format
The root element of each file is always named <PropertyList>. Tags are almost always found in pairs, with the closing tag having a slash prefixing the tag name, i.e </PropertyList>, just like in any other XML dialect. The exception is the tag representing an aliased property. In this case a slash is prepended to the closing angle bracket:
A minimal example of a complete property list encoded XML file, looks like this:
<?xml version="1.0" encoding="UTF-8"?> <PropertyList> </PropertyList>
Each node in a tree is specified using a single tag that can take attributes describing various things about it:
<?xml version="1.0" encoding="UTF-8"?> <PropertyList> <welcome type="string">Welcome to FlightGear! This is a string as a property</welcome> </PropertyList>
Property type attributes
Property typing is optional, all properties are by default string/unspecified and are transparently converted by the property tree, but it never hurts, especially when including spaces around numbers:
<?xml version="1.0" encoding="UTF-8"?> <PropertyList> <foo type="string">Hello</foo> <pi type="float"> 3.14 </pi> <boo type="bool">true</boo> </PropertyList>
Indexing happens implicitly for identically named properties:
<?xml version="1.0" encoding="UTF-8"?> <PropertyList> <foo>Hello</foo> <foo>Hello</foo> <foo>Hello</foo> </PropertyList>
so that the three foo tags become foo, foo, foo in the property tree, but you can also use explicit indexing by setting the n attribute of each tag:
<?xml version="1.0" encoding="UTF-8"?> <PropertyList> <foo n="0">Hello</foo> <foo n="1">Hello</foo> <foo n="2">Hello</foo> </PropertyList>
Though the above two examples are equivalent, we tend often to include "n" anyway, just to help people keep track of where they are. That's most important when there are a lot of subproperties, not in a simple list like the above.
To make property settings persistent in between FlightGear sessions, use the userarchive attribute:
<?xml version="1.0" encoding="UTF-8"?> <PropertyList> <foo userarchive="y">Hello</foo> </PropertyList>
Including external PropertyList files
We can also create new PropertyList files by subtyping existing ones using the include attribute. For example, if someone made a DC-3 model for JSBSim, we could subclass from the YASim config file like this:
<?xml version="1.0" encoding="UTF-8"?> <PropertyList include="dc3-yasim-set.xml"> <sim> <description>DC-3 (JSBSim).</description> <flight-model archive="y">jsb</flight-model> </sim> </PropertyList>
Emmanuel Baranger's models tend to use this technique lot and often have several XML files: the first (the -set.xml) contains very basic information, often just author and version number, and includes the next; the second is for FDM-specific properties (the -yasim-cnf or -jsbsim-cnf); and the last has FDM-independent things like flap settings (the -base.xml). This structuring allows for maximum reusability between FDMs and makes it easier for someone to come along and add another FDM to a model.
|Caution The 'new' vec*d types are not fully/propertly integrated with the underlying PropertyTree APIs yet, which means that some things may not work as expected, which is particularly important when dealing with files using those types (e.g. effects - see below for details).
Properties can alias other properties, in a similar way to symbolic links in Unix. When one of them changes the other one changes as well.
For example will
<?xml version="1.0" encoding="UTF-8"?> <PropertyList> <foo>46</foo> <bar alias="/foo"/> </PropertyList>
be functionally identical to
<?xml version="1.0" encoding="UTF-8"?> <PropertyList> <foo>46</foo> <bar>46</bar> </PropertyList>
except that bar and foo will always be identical.
A PropertyList file can begin with a property subtree that are aliased later in the file.
<?xml version="1.0" encoding="UTF-8"?> <PropertyList> <params> <comm-freq-prop>/radios/comm1/frequencies/selected</comm-freq-prop> <nav-freq-prop>/radios/nav1/frequencies/selected</comm-freq-prop> </params> <chunk> <type>number-value</type> <property alias="/params/nav-freq-prop"/> </chunk> </PropertyList>
Aliasing combined with inclusion
The combination of aliasing and inclusion allows for parameterization, which can be of great help both for debugging and mainenance. The above example could for example instead be split up into
<?xml version="1.0" encoding="UTF-8"?> <PropertyList> <params> <comm-freq-prop>/radios/comm1/frequencies/selected</comm-freq-prop> <nav-freq-prop>/radios/nav1/frequencies/selected</comm-freq-prop> </params> </PropertyList>
<?xml version="1.0" encoding="UTF-8"?> <PropertyList> <params include="MyParams.xml"/> <chunk> <type>number-value</type> <property alias="/params/nav-freq-prop"/> </chunk> </PropertyList>
The parameters can be overridden at inclusion
<?xml version="1.0" encoding="UTF-8"?> <PropertyList> <instrument include="../Instruments/navcomm.xml"> <params> <comm-freq-prop>/radios/comm1/frequencies/selected</comm-freq-prop> <nav-freq-prop>/radios/nav1/frequencies/selected</comm-freq-prop> </params> </instrument> </PropertyList>
<?xml version="1.0" encoding="UTF-8"?> <PropertyList> <instrument include="../Instruments/navcomm.xml"> <params> <comm-freq-prop>/radios/comm2/frequencies/selected</comm-freq-prop> <nav-freq-prop>/radios/nav2/frequencies/selected</comm-freq-prop> </params> </instrument> </PropertyList>
Whenever you need to embed text that isn't valid XML, because it may contain XML tokens, you need to wrap the whole section in a CDATA section, this is typically done with Nasal code, so that it doesn't need to escape XML entities:
<?xml version="1.0" encoding="UTF-8"?> <PropertyList> <nasal> <![CDATA[ # begin of code print("<!-- Hello World-->\n"); # end of code ]]> </nasal> </PropertyList>
Note how the CDATA section now contains tokens/characters that would otherwise make the XML markup invalid.
PropertyList-based configuration files
Not everything that is read from an XML file resides in the main property tree; some subsystems also use XML files for initial configuration information and are read into a temporary tree and subsequently discarded. The main property tree is meant to represent the shared state of the program, but when a subsystem happens to use an XML file to set up its internal state that information is ofteb of no use to the rest of the program and thus does not need to continually be in the main tree. Temporary trees are usually deleted as soon as the subsystem is set up (i.e. they exist for perhaps 0.1 sec). We could just as easily use another format for internal initialization, but since the XML support is already available, it was the easiest route.
The config files serve many different purposes; using the XML-based property-list format for all of them helps a lot, since it allows some common structure and reusable code among all the formats. Imagine if we had one file format for preferences, a different one for panels (say, with fixed-length fields), a different one for saving a flight (perhaps a binary format), another one for sound configuration (perhaps an INI file), a different one for top-level aircraft configuration (perhaps CSV), yet another one for configuring 3D models (perhaps embedded data strings in the 3D files themselves), etc. etc.
We have config files for totally different purposes, and the fact that they all use XML is simply a convenience for programmers and customizers. Here are some of the conventions that we've come up with so far, partly ad-hoc (all paths relative to $FG_ROOT):
- preferences.xml - the top-level default preferences
- joysticks.xml - default joystick bindings, included by preferences.xml
- keyboard.xml - default keyboard bindings, included by preferences.xml
- Aircraft/*-set.xml - aircraft-specific settings, overriding the defaults in preferences.xml (and joystick/keyboard.xml)
Basically, these are the main files in the base package that affect FlightGear's main property tree. Other files use the property-file format for convenience to populate various data structures, but they do not touch the main tree and are not accessible through the property browser or through the command-line --prop: option; it's just a coincidence that they also use the property-list format:
- materials.xml - define the materials (textures, colour, lighting) for use in the scenery
- HUDS/**/*.xml - configuration files to define the various heads-up displays
- Aircraft/*/*-sound.xml - configuration files to define sounds played for various aircraft
- Aircraft/*/Panels/*-panel.xml - configuration files to define 2D panels for various aircraft.
- Aircraft/*/Instruments/*.xml - configuration files for individual instruments included by the 2D panels.
- Aircraft/Instruments/*.xml - ditto
- Aircraft/*/Models/*.xml - animation files for a .ac file, oodles of <animation> nodes!
We also use some XML-based formats that do not (yet?) follow the property-list conventions, including the following:
- Aircraft/*/*.xml - JSBSim aero model config files
- Aircraft/Aircraft-yasim/*.xml - YASim aero model config files
- Engine/*.xml - JSBSim engine and thruster config files
YASim and JSBSim each uses its own XML format, which is different from the XML format used by the rest of FlightGear. For YASim, see $FG_ROOT/Aircraft-yasim/README.yasim in the base package; for JSBSim, see the documentation at http://jsbsim.sourceforge.net/. UIUC uses a non-XML config-file format.
The one advantage of Yasim's approach is efficiency -- Andy copies from the XML straight into the YASim data structures, without building up and tearing down an in-memory property tree first. For large-scale XML implementations, we *have* to do things that way -- the DOM and XSLT tend to break down catastrophically for large XML documents or high volume. That's why we designed the Simple API for XML (SAX).
Efficiency doesn't matter much for YASim, since it's a short file and we're reading it only once. If we're ever processing a lot of XML in the main loop, say, over a network connection or from large GIS databases, we'll need to go with a streaming approach like Andy used.
There are several places to look for properties; one is in the aircraft files, another is all Nasal files, and the last place (and often most useful!) is grepping (searching) through the C++ code. To determine how a property works and what it does often requires looking through any code that uses it. This is a part of FlightGear that we could certainly document better.
As for the relationship between XML and the Property Tree, in some cases in FG (most notably preferences.xml and each AIRCRAFT-set.xml file), the <PropertyList> format directly defines properties in FlightGear's global property tree. In other cases, like the animation files given by /sim/model/path, those do not define properties but the <PropertyList> format is used as a matter of convenience so that FG can parse all of its XML files using the same mechanism and to keep the fundamental structure the same. XML has a lot of different dialects, and having only one for FlightGear really makes it easier, especially since it is very intuitive
- Conditions – Sometimes things can be made conditional on other properties
- Expressions – Sometimes mathematical expressions can be used
- $FG_ROOT/Docs/README.xmlsyntax – XML in 15 minutes or less
- $FG_ROOT/Docs/README.xmlpanel – Contains valuable information on the PropertyList format