Howto:Create animation XML files from Nasal: Difference between revisions
m (→Background) |
m (Switch to the {{forum url}} template for all forum links.) |
||
(14 intermediate revisions by 4 users not shown) | |||
Line 1: | Line 1: | ||
Objective: Use [[Nasal]] scripting to dynamically create an XML file by using the setprop() API call to set up your XML file's PropertyList-encoded structure and then writing the result to the file system using the write_properties() wrapper found in $FG_ROOT/Nasal/io.nas. | Objective: Use [[Nasal]] scripting to dynamically create an XML file by using the setprop() API call to set up your XML file's PropertyList-encoded structure and then writing the result to the file system using the write_properties() wrapper found in $FG_ROOT/Nasal/io.nas. | ||
There are two wrappers provided: read_properties() and write_properties. These are wrappers for the corresponding fgcommands | There are two wrappers provided: read_properties() and write_properties. These are wrappers for the corresponding fgcommands loadxml ({{readme file|commands|line=133}}) and save-xml ({{readme file|commands|line=139}}). | ||
So, you don't need to look into the low level XML processing routines, you can directly use these PropertyList-wrappers - these allow you to directly dump a property tree sub branch to an XML file. So you can create your structure dynamically using setprop() and at the end use the write_properties() wrapper to write the resulting XML file to disk. That should save you some time. | So, you don't need to look into the low level XML processing routines, you can directly use these PropertyList-wrappers - these allow you to directly dump a property tree sub branch to an XML file. So you can create your structure dynamically using setprop() and at the end use the write_properties() wrapper to write the resulting XML file to disk. That should save you some time. | ||
{{IO Restrictions}} | |||
== Background == | == Background == | ||
Line 13: | Line 12: | ||
== Getting started == | == Getting started == | ||
The following piece of Nasal creates a new XML file: | The following piece of Nasal creates a new XML file, in the standard FlightGear [[PropertyList XML File|PropertyList]] encoded form: | ||
<syntaxhighlight lang="php"> | <syntaxhighlight lang="php"> | ||
Line 58: | Line 57: | ||
var filename="xmltest.xml"; | var filename="xmltest.xml"; | ||
setprop(location~"path", "vsd.ac"); | setprop(location~"path", "vsd.ac"); | ||
setprop(location~"animation/type", "select"); | setprop(location~"animation[0]/type", "select"); | ||
setprop(location~"animation/object-name", "vsd"); | setprop(location~"animation[0]/object-name", "vsd"); | ||
setprop(location~"animation/condition/greather-than-equals/property", "systems/electrical/outputs/efis"); | setprop(location~"animation[0]/condition/greather-than-equals/property", "systems/electrical/outputs/efis"); | ||
setprop(location~"animation/condition/greather-than-equals/value", "9"); | setprop(location~"animation[0]/condition/greather-than-equals/value", "9"); | ||
io.write_properties(filename, location); | io.write_properties(filename, location); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Note that multiple setprop() calls like this would obviously overwrite the previous animation, so you would need to use explicit indices here: | |||
* animation[0] | |||
* animation[1] | |||
* animation[2] | |||
This also holds true for any child nodes (once you have several identically named nodes): | |||
* object-name[0] | |||
* object-name[1] | |||
* object-name[2] | |||
* object-name[3] | |||
So, it is always a good idea to explicitly add the index, too. | |||
This is identical to how the property tree internally works already: /sim/foo is equivalent to /sim/foo[0] | |||
If you don't understand how this works, please take a look at: {{readme file|xmlpanel|line=162}} | |||
If you want this to be even easier, you could use the props.nas module, because its methods have support for adding indices automatically. | |||
Next, you could add a handful of helpers (or even a class wrapping animations) to make things a bit easier. | Next, you could add a handful of helpers (or even a class wrapping animations) to make things a bit easier. | ||
Anybody interested in coming up with an OOP wrapper for this, should look into using the props.nas module available in $FG_ROOT/Nasal. | Anybody interested in coming up with an OOP wrapper for this, should look into using the props.nas module available in $FG_ROOT/Nasal. | ||
== | If you already have some prefabricated XML file that you'd like to continue using, then you could also just automate the creation of certain animations and save them in a separate file. This is supported by including a PropertyList file in another file using the "include" attribute: | ||
<syntaxhighlight lang="xml"> | |||
<PropertyList> | |||
<animation include="vsd.xml"/> | |||
</PropertyList> | |||
</syntaxhighlight> | |||
<syntaxhighlight lang="xml"> | |||
<?xml version="1.0"?> | |||
<PropertyList> | |||
<foo include="someotherfile.xml"/> | |||
</PropertyList> | |||
</syntaxhighlight> | |||
<syntaxhighlight lang="xml"> | |||
<?xml version="1.0"?> | |||
<PropertyList> | |||
<bla>data</bla> | |||
</PropertyList> | |||
</syntaxhighlight> | |||
This would result in /foo/bla=data | |||
Some additional info is available in $FG_ROOT/Docs: | |||
* {{readme file|xmlpanel|line=162}} | |||
* {{readme file|xmlpanel|line=191}} | |||
There is also support for customizing included files (overriding certain properties with custom defaults): {{readme file|xmlpanel|line=245}} | |||
== External links == | |||
* {{forum url|t=15226}} | |||
* {{forum url|t=15214}} | |||
* {{forum url|t=15200}} | |||
[[Category:Nasal howto]] | |||
[[Category:Nasal]] |
Latest revision as of 16:38, 7 June 2019
Objective: Use Nasal scripting to dynamically create an XML file by using the setprop() API call to set up your XML file's PropertyList-encoded structure and then writing the result to the file system using the write_properties() wrapper found in $FG_ROOT/Nasal/io.nas.
There are two wrappers provided: read_properties() and write_properties. These are wrappers for the corresponding fgcommands loadxml ($FG_ROOT/Docs/README.commands#l133) and save-xml ($FG_ROOT/Docs/README.commands#l139).
So, you don't need to look into the low level XML processing routines, you can directly use these PropertyList-wrappers - these allow you to directly dump a property tree sub branch to an XML file. So you can create your structure dynamically using setprop() and at the end use the write_properties() wrapper to write the resulting XML file to disk. That should save you some time.
Note All FlightGear disk I/O handled via Nasal scripting and/or fgcommands, is subject to access validation via IOrules. This includes the SGPath bindings in FlightGear 2.99+
However, unlike $FG_ROOT, $FG_HOME is generally accessible for writing, consider this example:
# 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
Background
In general, this shouldn't really be necessary - because XML files are meant to be created and edited manually. On the other hand, due to the lack of a simple 2D drawing API accessible from Nasal, more and more complex "glass cockpit"-style instruments are getting implemented using the XML-based approach. These files (often more than 100-200 kbytes in size) contain fairly repetitive markup that could (and should) be created procedurally in an automated fashion. So, the approach suggested here is to use a separate Nasal script to create the required XML markup dynamically, rather than having to copy/paste and customize hundreds of XML animations manually using a text editor.
Getting started
The following piece of Nasal creates a new XML file, in the standard FlightGear PropertyList encoded form:
var location= "/temp/test/foo"; # location in the global FG property tree
var filename="test.xml"; # location in the local file system (will be OVERWRITTEN/DELETED!)
setprop(location, "hello world");
io.write_properties(filename, location);
The created output is:
<?xml version="1.0"?>
<PropertyList>
<temp>
<test>
<foo>hello world</foo>
</test>
</temp>
Once you understand how this works, you can dynamically create PropertyList-encoded XML files for your instruments. Consider the following XML file containing a single animation:
<path>vsd.ac</path>
<animation>
<type>select</type>
<object-name>vsd</object-name>
<condition>
<greater-than-equals>
<property>systems/electrical/outputs/efis</property>
<value>9</value>
</greater-than-equals>
</condition>
</animation>
Now, to create this snippet of XML procedurally, you can use this piece of Nasal:
var location = "/temp/test/";
var filename="xmltest.xml";
setprop(location~"path", "vsd.ac");
setprop(location~"animation[0]/type", "select");
setprop(location~"animation[0]/object-name", "vsd");
setprop(location~"animation[0]/condition/greather-than-equals/property", "systems/electrical/outputs/efis");
setprop(location~"animation[0]/condition/greather-than-equals/value", "9");
io.write_properties(filename, location);
Note that multiple setprop() calls like this would obviously overwrite the previous animation, so you would need to use explicit indices here:
- animation[0]
- animation[1]
- animation[2]
This also holds true for any child nodes (once you have several identically named nodes):
- object-name[0]
- object-name[1]
- object-name[2]
- object-name[3]
So, it is always a good idea to explicitly add the index, too.
This is identical to how the property tree internally works already: /sim/foo is equivalent to /sim/foo[0] If you don't understand how this works, please take a look at: $FG_ROOT/Docs/README.xmlpanel#l162
If you want this to be even easier, you could use the props.nas module, because its methods have support for adding indices automatically.
Next, you could add a handful of helpers (or even a class wrapping animations) to make things a bit easier. Anybody interested in coming up with an OOP wrapper for this, should look into using the props.nas module available in $FG_ROOT/Nasal.
If you already have some prefabricated XML file that you'd like to continue using, then you could also just automate the creation of certain animations and save them in a separate file. This is supported by including a PropertyList file in another file using the "include" attribute:
<PropertyList>
<animation include="vsd.xml"/>
</PropertyList>
<?xml version="1.0"?>
<PropertyList>
<foo include="someotherfile.xml"/>
</PropertyList>
<?xml version="1.0"?>
<PropertyList>
<bla>data</bla>
</PropertyList>
This would result in /foo/bla=data
Some additional info is available in $FG_ROOT/Docs:
There is also support for customizing included files (overriding certain properties with custom defaults): $FG_ROOT/Docs/README.xmlpanel#l245