33 from __future__
import print_function, division
42 from roslaunch
import substitution_args
43 from rospkg.common
import ResourceNotFound
44 from copy
import deepcopy
45 from .color
import warning, error, message
46 from .xmlutils
import *
47 from .cli
import process_args
56 substitution_args_context = {}
64 Push a new filename to the filestack. 65 Instead of directly modifying filestack, a deep-copy is created and modified, 66 while the old filestack is returned. 67 This allows to store the filestack that was active when a macro or property is defined 71 filestack = deepcopy(filestack)
72 filestack.append(filename)
82 Prepend the dirname of the currently processed file 83 if filename_spec is not yet absolute 85 if not os.path.isabs(filename_spec):
86 parent_filename = filestack[-1]
87 basedir = os.path.dirname(parent_filename)
if parent_filename
else '.' 88 return os.path.join(basedir, filename_spec)
96 raise XacroException(
"yaml support not available; install python-yaml")
107 all_includes.append(filename)
114 global_symbols = {
'__builtins__': {k: __builtins__[k]
for k
in [
'list',
'dict',
'map',
'str',
'float',
'int',
'True',
'False',
'min',
'max',
'round']}}
116 global_symbols.update(math.__dict__)
118 global_symbols.update(dict(load_yaml=load_yaml))
123 XacroException allows to wrap another exception (exc) and to augment 124 its error message: prefixing with msg and suffixing with suffix. 125 str(e) finally prints: msg str(exc) suffix 127 def __init__(self, msg=None, suffix=None, exc=None, macro=None):
128 super(XacroException, self).
__init__(msg)
131 self.
macros = []
if macro
is None else [macro]
135 return ' '.join([str(e)
for e
in items
if e
not in [
'',
'None']])
146 warning(
"deprecated: xacro tags should be prepended with 'xacro' xml namespace.")
147 message(
"""Use the following script to fix incorrect usage: 148 find . -iname "*.xacro" | xargs sed -i 's#<\([/]\\?\)\(if\|unless\|include\|arg\|property\|macro\|insert_block\)#<\\1xacro:\\2#g'""")
150 print(file=sys.stderr)
154 allow_non_prefixed_tags =
True 159 Check whether tagName starts with xacro prefix. If not, issue a warning. 161 :return: True if tagName is accepted as xacro tag 162 False if tagName doesn't start with xacro prefix, but the prefix is required 164 if tag_name.startswith(
'xacro:'):
167 if allow_non_prefixed_tags:
169 return allow_non_prefixed_tags
184 return substitution_args.resolve_args(s, context=substitution_args_context, resolve_anon=
False)
185 except substitution_args.ArgException
as e:
187 except ResourceNotFound
as e:
207 if isinstance(value, _basestr):
211 evaluated = ast.literal_eval(value.strip())
214 if not isinstance(evaluated, (list, dict, tuple)):
227 self.recursive.append(key)
229 self.unevaluated.remove(key)
230 self.recursive.remove(key)
233 value = self.
table[key]
234 if (verbosity > 2
and self.
parent is None)
or verbosity > 3:
235 print(
"{indent}use {key}: {value} ({loc})".format(
236 indent=self.
depth*
' ', key=key, value=value, loc=filestack[-1]), file=sys.stderr)
242 if key
in self.
table:
250 if do_check_order
and key
in self.
used and key
not in self.
redefined:
253 if key
in global_symbols:
254 warning(
"redefining global property: %s" % key)
258 self.
table[key] = value
259 if unevaluated
and isinstance(value, _basestr):
261 self.unevaluated.add(key)
264 self.unevaluated.remove(key)
265 if (verbosity > 2
and self.
parent is None)
or verbosity > 3:
266 print(
"{indent}set {key}: {value} ({loc})".format(
267 indent=self.
depth*
' ', key=key, value=value, loc=filestack[-1]), file=sys.stderr)
270 self.
_setitem(key, value, unevaluated=
True)
274 key
in self.
table or \
279 if isinstance(self.
parent, Table):
293 return self.__getitem__(item)
297 super(PropertyNameSpace, self).
__init__(parent)
301 super(MacroNameSpace, self).
__init__(*args, **kwargs)
309 self.__dict__.update(other.__dict__)
312 for k, v
in kwargs.items():
313 self.__setattr__(k, len(self.
res))
314 self.res.append(re.compile(v))
329 for i
in range(len(self.
res)):
330 m = self.
res[i].match(self.
str)
332 self.
top = (i, m.group(0))
333 self.
str = self.
str[m.end():]
340 include_no_matches_msg =
"""Include tag's filename spec \"{}\" matched no files.""" 346 if elt.tagName
not in [
'xacro:include',
'include']:
350 if elt.tagName ==
'include':
355 if elt.childNodes
and not (len(elt.childNodes) == 1
and 356 elt.childNodes[0].nodeType == elt.TEXT_NODE):
368 except XacroException
as e:
369 if e.exc
and isinstance(e.exc, NameError)
and symbols
is None:
370 raise XacroException(
'variable filename is supported with --inorder option only')
374 if re.search(
'[*[?]+', filename_spec):
376 filenames = sorted(glob.glob(filename_spec))
377 if len(filenames) == 0:
378 warning(include_no_matches_msg.format(filename_spec))
381 filenames = [filename_spec]
383 for filename
in filenames:
385 all_includes.append(filename)
390 """import all namespace declarations into parent""" 391 for name, value
in attributes.items():
392 if name.startswith(
'xmlns:'):
393 oldAttr = parent.getAttributeNode(name)
394 if oldAttr
and oldAttr.value != value:
395 warning(
"inconsistent namespace redefinitions for {name}:" 396 "\n old: {old}\n new: {new} ({new_file})".format(
397 name=name, old=oldAttr.value, new=value,
398 new_file=filestack[-1]))
400 parent.setAttribute(name, value)
405 filename_spec, namespace_spec =
check_attrs(elt, [
'filename'], [
'ns'])
408 namespace_spec =
eval_text(namespace_spec, symbols)
414 raise XacroException(
'namespaces are supported with --inorder option only')
419 include =
parse(
None, filename).documentElement
422 func(include, macros, symbols)
423 included.append(include)
449 Checks whether name is a valid property or macro identifier. 450 With python-based evaluation, we need to avoid name clashes with python keywords. 454 root = ast.parse(name)
456 if isinstance(root, ast.Module)
and \
457 len(root.body) == 1
and isinstance(root.body[0], ast.Expr)
and \
458 isinstance(root.body[0].value, ast.Name)
and root.body[0].value.id == name:
466 re_macro_arg = re.compile(
r'''\s*([^\s:=]+?):?=(\^\|?)?((?:(?:'[^']*')?[^\s'"]*?)*)(?:\s+|$)(.*)''')
470 parse the first param spec from a macro parameter string s 471 accepting the following syntax: <param>[:=|=][^|]<default> 472 :param s: param spec string 473 :return: param, (forward, default), rest-of-string 474 forward will be either param or None (depending on whether ^ was specified) 475 default will be the default string or None 476 If there is no default spec at all, the middle pair will be replaced by None 478 m = re_macro_arg.match(s)
481 param, forward, default, rest = m.groups()
482 if not default: default =
None 483 return param, (param
if forward
else None, default), rest
486 result = s.lstrip().split(
None, 1)
487 return result[0],
None, result[1]
if len(result) > 1
else '' 491 assert(elt.tagName
in [
'macro',
'xacro:macro'])
494 name, params =
check_attrs(elt, [
'name'], [
'params'])
496 warning(
"deprecated use of macro name 'call'; xacro:call became a new keyword")
497 if name.find(
'.') != -1:
498 warning(
"macro names must not contain '.': %s" % name)
500 if not name.startswith(
'xacro:'):
501 name =
'xacro:' + name
504 macro = macros.get(name,
Macro())
506 macro.history.append(filestack)
511 macro.defaultmap = {}
514 macro.params.append(param)
515 if value
is not None:
516 macro.defaultmap[param] = value
527 if elt.tagName
in [
'macro',
'xacro:macro'] \
537 assert(elt.tagName
in [
'property',
'xacro:property'])
540 name, value, default, scope =
check_attrs(elt, [
'name'], [
'value',
'default',
'scope'])
542 raise XacroException(
'Property names must be valid python identifiers: ' + name)
543 if value
is not None and default
is not None:
544 raise XacroException(
'Property cannot define both a default and a value: ' + name)
546 if default
is not None:
547 if scope
is not None:
548 warning(
"%s: default property value can only be defined on local scope" % name)
549 if name
not in table:
561 if scope
and scope ==
'global':
562 target_table = table.root()
564 elif scope
and scope ==
'parent':
566 target_table = table.parent
569 warning(
"%s: no parent scope at global scope " % name)
575 if not unevaluated
and isinstance(value, _basestr):
578 target_table._setitem(name, value, unevaluated=unevaluated)
586 if elt.tagName
in [
'property',
'xacro:property'] \
588 if "default" in elt.attributes.keys():
589 raise XacroException(
'default property value supported with --inorder option only')
598 EXPR=
r"\$\{[^\}]*\}",
599 EXTENSION=
r"\$\([^\)]*\)",
600 TEXT=
r"([^\$]|\$[^{(]|\$$)+")
607 return eval(
eval_text(s, symbols), global_symbols, symbols)
608 except Exception
as e:
611 suffix=os.linesep +
"when evaluating expression '%s'" % s)
613 def handle_extension(s):
620 if lex.peek()[0] == lex.EXPR:
621 results.append(handle_expr(lex.next()[1][2:-1]))
622 elif lex.peek()[0] == lex.EXTENSION:
623 results.append(handle_extension(lex.next()[1][2:-1]))
624 elif lex.peek()[0] == lex.TEXT:
625 results.append(lex.next()[1])
626 elif lex.peek()[0] == lex.DOLLAR_DOLLAR_BRACE:
627 results.append(lex.next()[1][1:])
629 if len(results) == 1:
633 return ''.join(map(str, results))
637 if forward_variable
is None:
640 return symbols[forward_variable]
642 if default
is not None:
645 raise XacroException(
"Undefined property to forward: " + forward_variable, macro=macro)
651 raise XacroException(
"xacro:call is missing the 'macro' attribute")
655 node.removeAttribute(
'macro')
660 raise XacroException(
"unknown macro name '%s' in xacro:call" % name)
665 namespaces = fullname.split(
'.')
666 name = namespaces.pop(-1)
668 if namespaces
and namespaces[0].startswith(
'xacro:'):
669 namespaces[0] = namespaces[0].replace(
'xacro:',
'')
670 name =
'xacro:' + name
672 def _resolve(namespaces, name, macros):
674 for ns
in namespaces:
680 if allow_non_prefixed_tags
and not name.startswith(
'xacro:'):
681 return _resolve([],
'xacro:' + name, macros)
684 m = _resolve([], fullname, macros)
686 elif namespaces:
return _resolve(namespaces, name, macros)\
692 body = m.body.cloneNode(deep=
True)
694 except (KeyError, TypeError, AttributeError):
696 if node.tagName ==
'xacro:call':
701 scoped =
Table(symbols)
703 for name, value
in node.attributes.items():
704 if name
not in params:
705 raise XacroException(
"Invalid parameter \"%s\"" % str(name), macro=m)
707 scoped._setitem(name,
eval_text(value, symbols), unevaluated=
False)
708 node.setAttribute(name,
"")
715 for param
in params[:]:
720 scoped[param] = block
723 if block
is not None:
724 raise XacroException(
"Unused block \"%s\"" % block.tagName, macro=m)
727 for param
in params[:]:
729 if param[0] ==
'*':
continue 732 name, default = m.defaultmap.get(param, (
None,
None))
733 if name
is not None or default
is not None:
734 scoped._setitem(param,
eval_default_arg(name, default, symbols, m), unevaluated=
False)
738 raise XacroException(
"Undefined parameters [%s]" %
",".join(params), macro=m)
742 except Exception
as e:
744 if hasattr(e,
'macros'):
758 Return a boolean value that corresponds to the given Xacro condition value. 759 Values "true", "1" and "1.0" are supposed to be True. 760 Values "false", "0" and "0.0" are supposed to be False. 761 All other values raise an exception. 763 :param value: The value to be evaluated. The value has to already be evaluated by Xacro. 764 :param condition: The original condition text in the XML. 765 :return: The corresponding boolean value, or a Python expression that, converted to boolean, corresponds to it. 766 :raises ValueError: If the condition value is incorrect. 769 if isinstance(value, _basestr):
770 if value ==
'true':
return True 771 elif value ==
'false':
return False 772 else:
return ast.literal_eval(value)
776 raise XacroException(
"Xacro conditional \"%s\" evaluated to \"%s\", " 777 "which is not a boolean expression." % (condition, value))
780 _empty_text_node = xml.dom.minidom.getDOMImplementation().createDocument(
None,
"dummy",
None).createTextNode(
'\n\n')
782 """remove consecutive comments in front of the xacro-specific node""" 783 next = node.nextSibling
784 previous = node.previousSibling
786 if previous.nodeType == xml.dom.Node.TEXT_NODE
and \
787 previous.data.isspace()
and previous.data.count(
'\n') <= 1:
788 previous = previous.previousSibling
790 if previous
and previous.nodeType == xml.dom.Node.COMMENT_NODE:
792 previous = previous.previousSibling
793 node.parentNode.removeChild(comment)
797 if next
and _empty_text_node != next: node.parentNode.insertBefore(_empty_text_node, next)
802 """Recursively evaluate node, expanding macros, replacing properties, and evaluating expressions""" 804 for name, value
in node.attributes.items():
806 node.setAttribute(name, result)
808 node = node.firstChild
810 next = node.nextSibling
811 if node.nodeType == xml.dom.Node.ELEMENT_NODE:
812 if node.tagName
in [
'insert_block',
'xacro:insert_block'] \
816 if (
"**" + name)
in symbols:
818 block = symbols[
'**' + name]
820 elif (
"*" + name)
in symbols:
822 block = symbols[
'*' + name]
828 block = block.cloneNode(deep=
True)
836 elif node.tagName
in [
'property',
'xacro:property'] \
840 elif node.tagName
in [
'macro',
'xacro:macro'] \
844 elif node.tagName
in [
'arg',
'xacro:arg'] \
846 name, default =
check_attrs(node, [
'name',
'default'], [])
847 if name
not in substitution_args_context[
'arg']:
848 substitution_args_context[
'arg'][name] =
eval_text(default, symbols)
853 elif node.tagName ==
'xacro:element':
858 node.removeAttribute(
'xacro:name')
859 node.nodeName = node.tagName = name
862 elif node.tagName ==
'xacro:attribute':
867 node.parentNode.setAttribute(name, value)
870 elif node.tagName
in [
'if',
'xacro:if',
'unless',
'xacro:unless'] \
875 if node.tagName
in [
'unless',
'xacro:unless']:
889 if node.tagName.startswith(
"xacro:"):
895 elif node.nodeType == xml.dom.Node.TEXT_NODE:
896 node.data = str(
eval_text(node.data, symbols))
903 Parse input or filename into a DOM tree. 904 If inp is None, open filename and load from there. 905 Otherwise, parse inp, either as string or file object. 906 If inp is already a DOM tree, this function is a noop. 907 :return:xml.dom.minidom.Document 908 :raise: xml.parsers.expat.ExpatError 913 inp = f = open(filename)
920 if isinstance(inp, _basestr):
921 return xml.dom.minidom.parseString(inp)
922 elif hasattr(inp,
'read'):
923 return xml.dom.minidom.parse(inp)
932 in_order=
False, just_deps=
False, just_includes=
False,
933 mappings=
None, xacro_ns=
True, **kwargs):
934 global verbosity, do_check_order
935 verbosity = kwargs.get(
'verbosity', verbosity)
936 do_check_order = kwargs.get(
'do_check_order', do_check_order)
939 if mappings
is not None:
940 substitution_args_context[
'arg'] = mappings
942 global allow_non_prefixed_tags
943 allow_non_prefixed_tags = xacro_ns
950 if (just_deps
or just_includes)
and not in_order:
962 eval_all(doc.documentElement, macros, symbols)
965 substitution_args_context[
'arg'] = {}
967 if do_check_order
and symbols.redefined:
968 warning(
"Document is incompatible to --inorder processing.")
969 warning(
"The following properties were redefined after usage:")
970 for k, v
in symbols.redefined.iteritems():
971 message(k,
"redefined in", v, color=
'yellow')
975 if output_filename
is None:
978 dir_name = os.path.dirname(output_filename)
981 os.makedirs(dir_name)
988 return open(output_filename,
'w')
994 macros = getattr(err,
'macros', [])
if err
else []
995 msg =
'when instantiating macro:' 997 name = m.body.getAttribute(
'name')
998 location =
'(%s)' % m.history[-1][-1]
999 print(msg, name, location, file=file)
1000 msg =
'instantiated from:' 1002 msg =
'in file:' if macros
else 'when processing file:' 1003 for f
in reversed(filestack):
1004 if f
is None: f =
'string' 1005 print(msg, f, file=file)
1006 msg =
'included from:' 1009 """main processing pipeline""" 1013 doc =
parse(
None, input_file_name)
1018 banner = [xml.dom.minidom.Comment(c)
for c
in 1019 [
" %s " % (
'=' * 83),
1020 " | This document was autogenerated by xacro from %-30s | " % input_file_name,
1021 " | EDITING THIS FILE BY HAND IS NOT RECOMMENDED %-30s | " %
"",
1022 " %s " % (
'=' * 83)]]
1023 first = doc.firstChild
1024 for comment
in banner:
1025 doc.insertBefore(comment, first)
1031 if opts.in_order ==
False and not opts.just_includes:
1032 warning(
"xacro: Traditional processing is deprecated. Switch to --inorder processing!")
1033 message(
"To check for compatibility of your document, use option --check-order.", color=
'yellow')
1034 message(
"For more infos, see http://wiki.ros.org/xacro#Processing_Order", color=
'yellow')
1043 except xml.parsers.expat.ExpatError
as e:
1044 error(
"XML parsing error: %s" % str(e), alt_text=
None)
1047 print(file=sys.stderr)
1048 print(
"Check that:", file=sys.stderr)
1049 print(
" - Your XML is well-formed", file=sys.stderr)
1050 print(
" - You have the xacro xmlns declaration:",
1051 "xmlns:xacro=\"http://www.ros.org/wiki/xacro\"", file=sys.stderr)
1054 except Exception
as e:
1056 if not msg: msg = repr(e)
1061 print(file=sys.stderr)
1068 out.write(
" ".join(set(all_includes)))
1073 out.write(doc.toprettyxml(indent=
' '))
def __init__(self, args, kwargs)
def _setitem(self, key, value, unevaluated)
def check_deprecated_tag(tag_name)
def process_file(input_file_name, kwargs)
def remove_previous_comments(node)
def parse(inp, filename=None)
def process_includes(elt, macros=None, symbols=None)
def grab_properties(elt, table)
def __init__(self, msg=None, suffix=None, exc=None, macro=None)
def eval_text(text, symbols)
def __setitem__(self, key, value)
def handle_dynamic_macro_call(node, macros, symbols)
def next_sibling_element(node)
def process_doc(doc, in_order=False, just_deps=False, just_includes=False, mappings=None, xacro_ns=True, kwargs)
def handle_macro_call(node, macros, symbols)
def get_boolean_value(value, condition)
def abs_filename_spec(filename_spec)
def open_output(output_filename)
def resolve_macro(fullname, macros)
def process_include(elt, macros, symbols, func)
def restore_filestack(oldstack)
def grab_property(elt, table)
def __init__(self, args, kwargs)
def first_child_element(elt)
def import_xml_namespaces(parent, attributes)
def grab_macro(elt, macros)
def reqd_attrs(tag, attrs)
def print_location(filestack, err=None, file=sys.stderr)
def message(msg, args, kwargs)
def __getattr__(self, item)
def eval_default_arg(forward_variable, default, symbols, macro)
def replace_node(node, by, content_only=False)
def __contains__(self, key)
def warning(args, kwargs)
def process_args(argv, require_input=True)
def __init__(self, parent=None)
def grab_macros(elt, macros)
def eval_all(node, macros, symbols)
def check_attrs(tag, required, optional)
def __getitem__(self, key)
def __init__(self, parent=None)
def deprecated_tag(_issued=[False])
def get_include_files(filename_spec, symbols)