Javascript parser and loader for processing the ROS Xacro file format.
NOTE This package uses expr-eval for expression parsing.
import fs from 'fs';
import { XacroParser } from 'xacro-parser';
import { JSDOM } from 'jsdom';
// XacroParser depends on the browser xml parser.
global.DOMParser = new JSDOM().window.DOMParser;
const parser = new XacroParser();
parser.workingPath = './path/to/directory/';
parser.getFileContents = path => {
return fs.readFile( path, { encoding: 'utf8' } );
};
const xacroContents = fs.readFileSync( './path/to/directory/file.xacro', { encoding: 'utf8' } );
parser.parse( xacroContents ).then( result => {
// xacro XML
} );
import fs from 'fs';
import { XacroParser } from 'xacro-parser';
fetch( './path/to/directory/file.xacro' )
.then( res => res.text() )
.then( xacroContents => {
const parser = new XacroParser();
parser.workingPath = './path/to/directory/';
parser.getFileContents = path => {
return fetch( path ).then( res => res.text() );
};
parser.parse( xacroContents ).then( result => {
// xacro XML
} );
} );
import { XacroLoader } from 'xacro-parser';
// The working path is extracted automatically.
// Only works in the browser.
const loader = new XacroLoader();
loader.load(
'../path/to/file.xacro',
result => {
// xacro XML
},
err => {
// parse error
} );
Xacro files from different versions of ROS require different options to be to be set. The differences are documented in the spec.
Options required for xacros created with a ROS version <= release 8.
parser.inOrder = false;
parser.requirePrefix = false;
parser.localProperties = false;
Options required for xacros created with a ROS version >= release 9.
parser.inOrder = true;
parser.requirePrefix = true;
parser.localProperties = true;
localProperties = true : boolean
Since ROS Jade
xacro scopes property definitions to the containing macro. Setting localProperties
to false disables this behavior.
requirePrefix = true : boolean
Since ROS Jade
xacro requires all tags be prefixed with "xacro:". Setting requirePrefix
to false disables this requirement.
inOrder = true : boolean
Since ROS Jade
xacro allows for in order processing, which allows variables to be used to define include paths and order-dependent property definitions. The equivalent of the --inorder
xacro command line flag.
workingPath = '' : string
The working directory to search for dependent files in when parsing include
tags. The path is required to end with '/'.
arguments = {} : Object
A map of argument names to values that will be substituted for $(arg name)
tags. These take precedence over any <xacro:arg>
defaults.
loader.arguments =
{
transmission_hw_interface: "hardware_interface/PositionJointInterface",
arm_x_separation: -0.4,
laser_visual: true,
};
rospackCommands = {} : ( ( command : String, ...args : Array<String> ) => String ) | Object
A map of rospack command stem to handling function that take all arguments as function parameters. An example implementation of the rospack find
command:
loader.rospackCommands =
{
find: function( pkg ) {
switch( pkg ) {
case 'valkyrie_description':
return '/absolute/path/to/valkyrie_description/';
case 'r2_description':
return '/absolute/path/to/r2_description/'
}
}
};
Alternatively a function can be provided to evaluate the command:
load.rospackCommands = ( command, ...args ) => {
if ( command === 'find' ) {
const [ pkg ] = args;
switch( pkg ) {
case 'valkyrie_description':
return '/absolute/path/to/valkyrie_description/';
case 'r2_description':
return '/absolute/path/to/r2_description/'
}
}
};
parse( contents : string ) : Promise<XMLDocument>
Parses the passed xacro contents using the options specified on the object and returns an xml document of the processed xacro file.
getFileContents( path : string ) : Promise<string>
And overrideable function that takes a file path and returns the contents of that file as a string. Used for loading a documents referenced in include
tags.
extends XacroParser
Extends XacroParse and implements getFileContents
to load from a server using fetch.
fetchOptions = {} : Object
load(
url : string,
onComplete : ( result : XMLDocument ) => void,
onError? : ( error : Error ) => void
) : void
parse(
url : string,
onComplete : ( result : XMLDocument ) => void,
onError? : ( error : Error ) => void
) : void
- Macro argument pass-through using
param:=^|default
is not supported #5. - Calling macros with a dynamic name using the
<xacro:call macro="${var}"/>
syntax is not supported #9. - Include tag namespaces are not supported #12.
- The official xacro parser supports using basically any Python syntax in the
${}
syntax which can't be easily supported in Javascript. Instead basic argument substitution and expression evaluation is supported using theexpr-eval
package which may not support all expression types. Please submit an issue if evaluation fails to work on a file.
While the documentation for the xacro format is relatively complete there are some features that cannot necessarily be well understood without looking at code or tests.
The xacro documentation on default parameters only mentions the param:=default
syntax. However, examples in the wild such as turtlebot_description appear to use param=default
. This parser supports both syntaxes.
The xacro:property
tags can have a scope
attribute on them that can take "global" and "parent" values, which adds the property to the global or parent scope respetively. Neither of these is the default, though. If the scope is not specified then the variable is only relevant to the macro scope.
The docs for the <xacro:macro params="*a *b" ... >
syntax makes it look like it's important that the name of the *
parameters be the same as the tag they are including or that they always reference the first element but this is not the case. Instead the first *
parameter refers the first one and the second one refers to the second element and so on.
Consider the following:
<xacro:macro name="test" params="*a *b">
<xacro:insert_block name="a"/>
<xacro:insert_block name="b"/>
</xacro:macro>
<test>
<xacro:if value="true">
<child1/>
</xacro:if>
<xacro:if value="true">
<child2/>
</xacro:if>
<child3/>
</test>
The macro "test" includes the first and second elements of the caller when generating the contents. The contents of the caller element are to be evaluated first before evaluating the macro, though, which means the if statements will be removed and test will be left with child1
and child3
before the elements are included in the test macro body.
When tracking properties the unevaluated expression itself is added to the property scope and evaluated when used in an attribute. However when a property is scoped locally (as in does not have a global or parent scope property) then it is evaluated immediately, as seen here.