00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033 from __future__ import print_function, division
00034
00035 import glob
00036 import os
00037 import re
00038 import sys
00039 import ast
00040 import math
00041
00042 from roslaunch import substitution_args
00043 from rospkg.common import ResourceNotFound
00044 from copy import deepcopy
00045 from .color import warning, error, message
00046 from .xmlutils import *
00047 from .cli import process_args
00048
00049
00050 try:
00051 _basestr = basestring
00052 except NameError:
00053 _basestr = str
00054
00055
00056 substitution_args_context = {}
00057
00058
00059
00060 filestack = []
00061
00062 def push_file(filename):
00063 """
00064 Push a new filename to the filestack.
00065 Instead of directly modifying filestack, a deep-copy is created and modified,
00066 while the old filestack is returned.
00067 This allows to store the filestack that was active when a macro or property is defined
00068 """
00069 global filestack
00070 oldstack = filestack
00071 filestack = deepcopy(filestack)
00072 filestack.append(filename)
00073 return oldstack
00074
00075 def restore_filestack(oldstack):
00076 global filestack
00077 filestack = oldstack
00078
00079
00080 def abs_filename_spec(filename_spec):
00081 """
00082 Prepend the dirname of the currently processed file
00083 if filename_spec is not yet absolute
00084 """
00085 if not os.path.isabs(filename_spec):
00086 parent_filename = filestack[-1]
00087 basedir = os.path.dirname(parent_filename) if parent_filename else '.'
00088 return os.path.join(basedir, filename_spec)
00089 return filename_spec
00090
00091
00092 def load_yaml(filename):
00093 try:
00094 import yaml
00095 except:
00096 raise XacroException("yaml support not available; install python-yaml")
00097
00098 filename = abs_filename_spec(filename)
00099 f = open(filename)
00100 oldstack = push_file(filename)
00101 try:
00102 return yaml.load(f)
00103 finally:
00104 f.close()
00105 restore_filestack(oldstack)
00106
00107
00108
00109
00110
00111
00112 global_symbols = {'__builtins__': {k: __builtins__[k] for k in ['list', 'dict', 'map', 'str', 'float', 'int']}}
00113
00114 global_symbols.update(math.__dict__)
00115
00116 global_symbols.update(dict(load_yaml=load_yaml))
00117
00118
00119 class XacroException(Exception):
00120 """
00121 XacroException allows to wrap another exception (exc) and to augment
00122 its error message: prefixing with msg and suffixing with suffix.
00123 str(e) finally prints: msg str(exc) suffix
00124 """
00125 def __init__(self, msg=None, suffix=None, exc=None, macro=None):
00126 super(XacroException, self).__init__(msg)
00127 self.suffix = suffix
00128 self.exc = exc
00129 self.macros = [] if macro is None else [macro]
00130
00131 def __str__(self):
00132 return ' '.join([str(item) for item in
00133 [self.message, self.exc, self.suffix] if item])
00134
00135
00136 verbosity = 1
00137
00138 def deprecated_tag(_issued=[False]):
00139 if _issued[0]:
00140 return
00141 _issued[0] = True
00142
00143 if verbosity > 0:
00144 warning("deprecated: xacro tags should be prepended with 'xacro' xml namespace.")
00145 message("""Use the following script to fix incorrect usage:
00146 find . -iname "*.xacro" | xargs sed -i 's#<\([/]\\?\)\(if\|unless\|include\|arg\|property\|macro\|insert_block\)#<\\1xacro:\\2#g'""")
00147 print_location(filestack)
00148 print(file=sys.stderr)
00149
00150
00151
00152 allow_non_prefixed_tags = True
00153
00154
00155 def check_deprecated_tag(tag_name):
00156 """
00157 Check whether tagName starts with xacro prefix. If not, issue a warning.
00158 :param tag_name:
00159 :return: True if tagName is accepted as xacro tag
00160 False if tagName doesn't start with xacro prefix, but the prefix is required
00161 """
00162 if tag_name.startswith('xacro:'):
00163 return True
00164 else:
00165 if allow_non_prefixed_tags:
00166 deprecated_tag()
00167 return allow_non_prefixed_tags
00168
00169
00170 class Macro(object):
00171 def __init__(self):
00172 self.body = None
00173 self.params = []
00174 self.defaultmap = {}
00175 self.history = []
00176
00177
00178 def eval_extension(s):
00179 if s == '$(cwd)':
00180 return os.getcwd()
00181 try:
00182 return substitution_args.resolve_args(s, context=substitution_args_context, resolve_anon=False)
00183 except substitution_args.ArgException as e:
00184 raise XacroException("Undefined substitution argument", exc=e)
00185 except ResourceNotFound as e:
00186 raise XacroException("resource not found:", exc=e)
00187
00188
00189 do_check_order=False
00190 class Table(object):
00191 def __init__(self, parent=None):
00192 self.parent = parent
00193 self.table = {}
00194 self.unevaluated = set()
00195 self.recursive = []
00196
00197 self.depth = self.parent.depth + 1 if self.parent else 0
00198 if do_check_order:
00199
00200 self.used = set()
00201 self.redefined = dict()
00202
00203 @staticmethod
00204 def _eval_literal(value):
00205 if isinstance(value, _basestr):
00206 try:
00207
00208
00209 evaluated = ast.literal_eval(value.strip())
00210
00211
00212 if not isinstance(evaluated, (list, dict, tuple)):
00213 return evaluated
00214 except:
00215 pass
00216
00217 return value
00218
00219 def _resolve_(self, key):
00220
00221 if key in self.unevaluated:
00222 if key in self.recursive:
00223 raise XacroException("recursive variable definition: %s" %
00224 " -> ".join(self.recursive + [key]))
00225 self.recursive.append(key)
00226 self.table[key] = self._eval_literal(eval_text(self.table[key], self))
00227 self.unevaluated.remove(key)
00228 self.recursive.remove(key)
00229
00230
00231 value = self.table[key]
00232 if (verbosity > 2 and self.parent is None) or verbosity > 3:
00233 print("{indent}use {key}: {value} ({loc})".format(
00234 indent=self.depth*' ', key=key, value=value, loc=filestack[-1]), file=sys.stderr)
00235 if do_check_order:
00236 self.used.add(key)
00237 return value
00238
00239 def __getitem__(self, key):
00240 if key in self.table:
00241 return self._resolve_(key)
00242 elif self.parent:
00243 return self.parent[key]
00244 else:
00245 raise KeyError(key)
00246
00247 def _setitem(self, key, value, unevaluated):
00248 if do_check_order and key in self.used and key not in self.redefined:
00249 self.redefined[key] = filestack[-1]
00250
00251 if key in global_symbols:
00252 warning("redefining global property: %s" % key)
00253 print_location(filestack)
00254
00255 value = self._eval_literal(value)
00256 self.table[key] = value
00257 if unevaluated and isinstance(value, _basestr):
00258
00259 self.unevaluated.add(key)
00260 elif key in self.unevaluated:
00261
00262 self.unevaluated.remove(key)
00263 if (verbosity > 2 and self.parent is None) or verbosity > 3:
00264 print("{indent}set {key}: {value} ({loc})".format(
00265 indent=self.depth*' ', key=key, value=value, loc=filestack[-1]), file=sys.stderr)
00266
00267 def __setitem__(self, key, value):
00268 self._setitem(key, value, unevaluated=True)
00269
00270 def __contains__(self, key):
00271 return \
00272 key in self.table or \
00273 (self.parent and key in self.parent)
00274
00275 def __str__(self):
00276 s = str(self.table)
00277 if isinstance(self.parent, Table):
00278 s += "\n parent: "
00279 s += str(self.parent)
00280 return s
00281
00282 def root(self):
00283 p = self
00284 while p.parent:
00285 p = p.parent
00286 return p
00287
00288 class NameSpace(object):
00289
00290 def __getattr__(self, item):
00291 return self.__getitem__(item)
00292
00293 class PropertyNameSpace(Table, NameSpace):
00294 def __init__(self, parent=None):
00295 super(PropertyNameSpace, self).__init__(parent)
00296
00297 class MacroNameSpace(dict, NameSpace):
00298 def __init__(self, *args, **kwargs):
00299 super(MacroNameSpace, self).__init__(*args, **kwargs)
00300
00301
00302 class QuickLexer(object):
00303 def __init__(self, *args, **kwargs):
00304 if args:
00305
00306 other = args[0]
00307 self.__dict__.update(other.__dict__)
00308 else:
00309 self.res = []
00310 for k, v in kwargs.items():
00311 self.__setattr__(k, len(self.res))
00312 self.res.append(re.compile(v))
00313 self.str = ""
00314 self.top = None
00315
00316 def lex(self, str):
00317 self.str = str
00318 self.top = None
00319 self.next()
00320
00321 def peek(self):
00322 return self.top
00323
00324 def next(self):
00325 result = self.top
00326 self.top = None
00327 for i in range(len(self.res)):
00328 m = self.res[i].match(self.str)
00329 if m:
00330 self.top = (i, m.group(0))
00331 self.str = self.str[m.end():]
00332 break
00333 return result
00334
00335
00336 all_includes = []
00337
00338 include_no_matches_msg = """Include tag's filename spec \"{}\" matched no files."""
00339
00340
00341 def is_include(elt):
00342
00343
00344 if elt.tagName not in ['xacro:include', 'include']:
00345 return False
00346
00347
00348 if elt.tagName == 'include':
00349
00350
00351
00352
00353 if elt.childNodes and not (len(elt.childNodes) == 1 and
00354 elt.childNodes[0].nodeType == elt.TEXT_NODE):
00355
00356 return False
00357 else:
00358
00359 return check_deprecated_tag(elt.tagName)
00360 return True
00361
00362
00363 def get_include_files(filename_spec, symbols):
00364 try:
00365 filename_spec = abs_filename_spec(eval_text(filename_spec, symbols))
00366 except XacroException as e:
00367 if e.exc and isinstance(e.exc, NameError) and symbols is None:
00368 raise XacroException('variable filename is supported with --inorder option only')
00369 else:
00370 raise
00371
00372 if re.search('[*[?]+', filename_spec):
00373
00374 filenames = sorted(glob.glob(filename_spec))
00375 if len(filenames) == 0:
00376 warning(include_no_matches_msg.format(filename_spec))
00377 else:
00378
00379 filenames = [filename_spec]
00380
00381 for filename in filenames:
00382 global all_includes
00383 all_includes.append(filename)
00384 yield filename
00385
00386
00387 def import_xml_namespaces(parent, attributes):
00388 """import all namespace declarations into parent"""
00389 for name, value in attributes.items():
00390 if name.startswith('xmlns:'):
00391 oldAttr = parent.getAttributeNode(name)
00392 if oldAttr and oldAttr.value != value:
00393 warning("inconsistent namespace redefinitions for {name}:"
00394 "\n old: {old}\n new: {new} ({new_file})".format(
00395 name=name, old=oldAttr.value, new=value,
00396 new_file=filestack[-1]))
00397 else:
00398 parent.setAttribute(name, value)
00399
00400
00401 def process_include(elt, macros, symbols, func):
00402 included = []
00403 filename_spec, namespace_spec = check_attrs(elt, ['filename'], ['ns'])
00404 if namespace_spec:
00405 try:
00406 namespace_spec = eval_text(namespace_spec, symbols)
00407 macros[namespace_spec] = ns_macros = MacroNameSpace()
00408 symbols[namespace_spec] = ns_symbols = PropertyNameSpace()
00409 macros = ns_macros
00410 symbols = ns_symbols
00411 except TypeError:
00412 raise XacroException('namespaces are supported with --inorder option only')
00413
00414 for filename in get_include_files(filename_spec, symbols):
00415
00416 oldstack = push_file(filename)
00417 include = parse(None, filename).documentElement
00418
00419
00420 func(include, macros, symbols)
00421 included.append(include)
00422 import_xml_namespaces(elt.parentNode, include.attributes)
00423
00424
00425 restore_filestack(oldstack)
00426
00427 remove_previous_comments(elt)
00428
00429 replace_node(elt, by=included, content_only=True)
00430
00431
00432
00433 def process_includes(elt, macros=None, symbols=None):
00434 elt = first_child_element(elt)
00435 while elt:
00436 next = next_sibling_element(elt)
00437 if is_include(elt):
00438 process_include(elt, macros, symbols, process_includes)
00439 else:
00440 process_includes(elt)
00441
00442 elt = next
00443
00444
00445 def is_valid_name(name):
00446 """
00447 Checks whether name is a valid property or macro identifier.
00448 With python-based evaluation, we need to avoid name clashes with python keywords.
00449 """
00450
00451 try:
00452 root = ast.parse(name)
00453
00454 if isinstance(root, ast.Module) and \
00455 len(root.body) == 1 and isinstance(root.body[0], ast.Expr) and \
00456 isinstance(root.body[0].value, ast.Name) and root.body[0].value.id == name:
00457 return True
00458 except SyntaxError:
00459 pass
00460
00461 return False
00462
00463
00464 re_macro_arg = re.compile(r'''\s*([^\s:=]+?):?=(\^\|?)?((?:(?:'[^']*')?[^\s'"]*?)*)(?:\s+|$)(.*)''')
00465
00466 def parse_macro_arg(s):
00467 """
00468 parse the first param spec from a macro parameter string s
00469 accepting the following syntax: <param>[:=|=][^|]<default>
00470 :param s: param spec string
00471 :return: param, (forward, default), rest-of-string
00472 forward will be either param or None (depending on whether ^ was specified)
00473 default will be the default string or None
00474 If there is no default spec at all, the middle pair will be replaced by None
00475 """
00476 m = re_macro_arg.match(s)
00477 if m:
00478
00479 param, forward, default, rest = m.groups()
00480 if not default: default = None
00481 return param, (param if forward else None, default), rest
00482 else:
00483
00484 result = s.lstrip(' ').split(' ', 1)
00485 return result[0], None, result[1] if len(result) > 1 else ''
00486
00487
00488 def grab_macro(elt, macros):
00489 assert(elt.tagName in ['macro', 'xacro:macro'])
00490 remove_previous_comments(elt)
00491
00492 name, params = check_attrs(elt, ['name'], ['params'])
00493 if name == 'call':
00494 warning("deprecated use of macro name 'call'; xacro:call became a new keyword")
00495 if name.find('.') != -1:
00496 warning("macro names must not contain '.': %s" % name)
00497
00498
00499 macro = macros.get(name, Macro())
00500
00501 macro.history.append(filestack)
00502 macro.body = elt
00503
00504
00505 macro.params = []
00506 macro.defaultmap = {}
00507 while params:
00508 param, value, params = parse_macro_arg(params)
00509 macro.params.append(param)
00510 if value is not None:
00511 macro.defaultmap[param] = value
00512
00513 macros[name] = macro
00514 replace_node(elt, by=None)
00515
00516
00517
00518 def grab_macros(elt, macros):
00519 elt = first_child_element(elt)
00520 while elt:
00521 next = next_sibling_element(elt)
00522 if elt.tagName in ['macro', 'xacro:macro'] \
00523 and check_deprecated_tag(elt.tagName):
00524 grab_macro(elt, macros)
00525 else:
00526 grab_macros(elt, macros)
00527
00528 elt = next
00529
00530
00531 def grab_property(elt, table):
00532 assert(elt.tagName in ['property', 'xacro:property'])
00533 remove_previous_comments(elt)
00534
00535 name, value, scope = check_attrs(elt, ['name'], ['value', 'scope'])
00536 if not is_valid_name(name):
00537 raise XacroException('Property names must be valid python identifiers: ' + name)
00538
00539 if value is None:
00540 name = '**' + name
00541 value = elt
00542
00543 if scope and scope == 'global':
00544 target_table = table.root()
00545 unevaluated = False
00546 elif scope and scope == 'parent':
00547 if table.parent:
00548 target_table = table.parent
00549 else:
00550 warning("%s: no parent scope at global scope " % name)
00551 unevaluated = False
00552 else:
00553 target_table = table
00554 unevaluated = True
00555
00556 if not unevaluated and isinstance(value, _basestr):
00557 value = eval_text(value, table)
00558
00559 target_table._setitem(name, value, unevaluated=unevaluated)
00560 replace_node(elt, by=None)
00561
00562
00563
00564 def grab_properties(elt, table):
00565 elt = first_child_element(elt)
00566 while elt:
00567 next = next_sibling_element(elt)
00568 if elt.tagName in ['property', 'xacro:property'] \
00569 and check_deprecated_tag(elt.tagName):
00570 grab_property(elt, table)
00571 else:
00572 grab_properties(elt, table)
00573
00574 elt = next
00575
00576
00577 LEXER = QuickLexer(DOLLAR_DOLLAR_BRACE=r"\$\$+\{",
00578 EXPR=r"\$\{[^\}]*\}",
00579 EXTENSION=r"\$\([^\)]*\)",
00580 TEXT=r"([^\$]|\$[^{(]|\$$)+")
00581
00582
00583
00584 def eval_text(text, symbols):
00585 def handle_expr(s):
00586 try:
00587 return eval(eval_text(s, symbols), global_symbols, symbols)
00588 except Exception as e:
00589
00590 raise XacroException(exc=e,
00591 suffix=os.linesep + "when evaluating expression '%s'" % s)
00592
00593 def handle_extension(s):
00594 return eval_extension("$(%s)" % eval_text(s, symbols))
00595
00596 results = []
00597 lex = QuickLexer(LEXER)
00598 lex.lex(text)
00599 while lex.peek():
00600 if lex.peek()[0] == lex.EXPR:
00601 results.append(handle_expr(lex.next()[1][2:-1]))
00602 elif lex.peek()[0] == lex.EXTENSION:
00603 results.append(handle_extension(lex.next()[1][2:-1]))
00604 elif lex.peek()[0] == lex.TEXT:
00605 results.append(lex.next()[1])
00606 elif lex.peek()[0] == lex.DOLLAR_DOLLAR_BRACE:
00607 results.append(lex.next()[1][1:])
00608
00609 if len(results) == 1:
00610 return results[0]
00611
00612 else:
00613 return ''.join(map(str, results))
00614
00615
00616 def eval_default_arg(forward_variable, default, symbols, macro):
00617 if forward_variable is None:
00618 return eval_text(default, symbols)
00619 try:
00620 return symbols[forward_variable]
00621 except KeyError:
00622 if default is not None:
00623 return eval_text(default, symbols)
00624 else:
00625 raise XacroException("Undefined property to forward: " + forward_variable, macro=macro)
00626
00627
00628 def handle_dynamic_macro_call(node, macros, symbols):
00629 name, = reqd_attrs(node, ['macro'])
00630 if not name:
00631 raise XacroException("xacro:call is missing the 'macro' attribute")
00632 name = str(eval_text(name, symbols))
00633
00634
00635 node.removeAttribute('macro')
00636 node.tagName = name
00637
00638 result = handle_macro_call(node, macros, symbols)
00639 if not result:
00640 raise XacroException("unknown macro name '%s' in xacro:call" % name)
00641 return True
00642
00643 def resolve_macro(fullname, macros):
00644
00645 namespaces = fullname.split('.')
00646 name = namespaces.pop(-1)
00647
00648 if namespaces and namespaces[0].startswith('xacro:'):
00649 namespaces[0] = namespaces[0].replace('xacro:', '')
00650 name = 'xacro:' + name
00651
00652 def _resolve(namespaces, name, macros):
00653
00654 for ns in namespaces:
00655 macros = macros[ns]
00656 try:
00657 return macros[name]
00658 except KeyError:
00659
00660 if name.startswith('xacro:'):
00661 return _resolve([], name.replace('xacro:',''), macros)
00662
00663
00664 m = _resolve([], fullname, macros)
00665 if m: return m
00666 elif namespaces: return _resolve(namespaces, name, macros)\
00667
00668
00669 def handle_macro_call(node, macros, symbols):
00670 try:
00671 m = resolve_macro(node.tagName, macros)
00672 body = m.body.cloneNode(deep=True)
00673
00674 except (KeyError, TypeError, AttributeError):
00675
00676 if node.tagName == 'xacro:call':
00677 return handle_dynamic_macro_call(node, macros, symbols)
00678 return False
00679
00680
00681 scoped = Table(symbols)
00682 params = m.params[:]
00683 for name, value in node.attributes.items():
00684 if name not in params:
00685 raise XacroException("Invalid parameter \"%s\"" % str(name), macro=m)
00686 params.remove(name)
00687 scoped._setitem(name, eval_text(value, symbols), unevaluated=False)
00688 node.setAttribute(name, "")
00689
00690
00691 eval_all(node, macros, symbols)
00692
00693
00694 block = first_child_element(node)
00695 for param in params[:]:
00696 if param[0] == '*':
00697 if not block:
00698 raise XacroException("Not enough blocks", macro=m)
00699 params.remove(param)
00700 scoped[param] = block
00701 block = next_sibling_element(block)
00702
00703 if block is not None:
00704 raise XacroException("Unused block \"%s\"" % block.tagName, macro=m)
00705
00706
00707 for param in params[:]:
00708
00709 if param[0] == '*': continue
00710
00711
00712 name, default = m.defaultmap.get(param, (None,None))
00713 if name is not None or default is not None:
00714 scoped._setitem(param, eval_default_arg(name, default, symbols, m), unevaluated=False)
00715 params.remove(param)
00716
00717 if params:
00718 raise XacroException("Undefined parameters [%s]" % ",".join(params), macro=m)
00719
00720 try:
00721 eval_all(body, macros, scoped)
00722 except Exception as e:
00723 if hasattr(e, 'macros'):
00724 e.macros.append(m)
00725 else:
00726 e.macros = [m]
00727 raise
00728
00729
00730 remove_previous_comments(node)
00731 replace_node(node, by=body, content_only=True)
00732 return True
00733
00734
00735 def get_boolean_value(value, condition):
00736 """
00737 Return a boolean value that corresponds to the given Xacro condition value.
00738 Values "true", "1" and "1.0" are supposed to be True.
00739 Values "false", "0" and "0.0" are supposed to be False.
00740 All other values raise an exception.
00741
00742 :param value: The value to be evaluated. The value has to already be evaluated by Xacro.
00743 :param condition: The original condition text in the XML.
00744 :return: The corresponding boolean value, or a Python expression that, converted to boolean, corresponds to it.
00745 :raises ValueError: If the condition value is incorrect.
00746 """
00747 try:
00748 if isinstance(value, _basestr):
00749 if value == 'true': return True
00750 elif value == 'false': return False
00751 else: return ast.literal_eval(value)
00752 else:
00753 return bool(value)
00754 except:
00755 raise XacroException("Xacro conditional \"%s\" evaluated to \"%s\", "
00756 "which is not a boolean expression." % (condition, value))
00757
00758
00759 _empty_text_node = xml.dom.minidom.getDOMImplementation().createDocument(None, "dummy", None).createTextNode('\n\n')
00760 def remove_previous_comments(node):
00761 """remove consecutive comments in front of the xacro-specific node"""
00762 next = node.nextSibling
00763 previous = node.previousSibling
00764 while previous:
00765 if previous.nodeType == xml.dom.Node.TEXT_NODE and \
00766 previous.data.isspace() and previous.data.count('\n') <= 1:
00767 previous = previous.previousSibling
00768
00769 if previous and previous.nodeType == xml.dom.Node.COMMENT_NODE:
00770 comment = previous
00771 previous = previous.previousSibling
00772 node.parentNode.removeChild(comment)
00773 else:
00774
00775
00776 if next: node.parentNode.insertBefore(_empty_text_node, next)
00777 return
00778
00779
00780 def eval_all(node, macros, symbols):
00781 """Recursively evaluate node, expanding macros, replacing properties, and evaluating expressions"""
00782
00783 for name, value in node.attributes.items():
00784 result = str(eval_text(value, symbols))
00785 node.setAttribute(name, result)
00786
00787 node = node.firstChild
00788 while node:
00789 next = node.nextSibling
00790 if node.nodeType == xml.dom.Node.ELEMENT_NODE:
00791 if node.tagName in ['insert_block', 'xacro:insert_block'] \
00792 and check_deprecated_tag(node.tagName):
00793 name, = check_attrs(node, ['name'], [])
00794
00795 if ("**" + name) in symbols:
00796
00797 block = symbols['**' + name]
00798 content_only = True
00799 elif ("*" + name) in symbols:
00800
00801 block = symbols['*' + name]
00802 content_only = False
00803 else:
00804 raise XacroException("Undefined block \"%s\"" % name)
00805
00806
00807 block = block.cloneNode(deep=True)
00808
00809 eval_all(block, macros, symbols)
00810 replace_node(node, by=block, content_only=content_only)
00811
00812 elif is_include(node):
00813 process_include(node, macros, symbols, eval_all)
00814
00815 elif node.tagName in ['property', 'xacro:property'] \
00816 and check_deprecated_tag(node.tagName):
00817 grab_property(node, symbols)
00818
00819 elif node.tagName in ['macro', 'xacro:macro'] \
00820 and check_deprecated_tag(node.tagName):
00821 grab_macro(node, macros)
00822
00823 elif node.tagName in ['arg', 'xacro:arg'] \
00824 and check_deprecated_tag(node.tagName):
00825 name, default = check_attrs(node, ['name', 'default'], [])
00826 if name not in substitution_args_context['arg']:
00827 substitution_args_context['arg'][name] = eval_text(default, symbols)
00828
00829 remove_previous_comments(node)
00830 replace_node(node, by=None)
00831
00832 elif node.tagName == 'xacro:element':
00833 name = eval_text(*reqd_attrs(node, ['xacro:name']), symbols=symbols)
00834 if not name:
00835 raise XacroException("xacro:element: empty name")
00836
00837 node.removeAttribute('xacro:name')
00838 node.nodeName = node.tagName = name
00839 continue
00840
00841 elif node.tagName == 'xacro:attribute':
00842 name, value = [eval_text(a, symbols) for a in reqd_attrs(node, ['name', 'value'])]
00843 if not name:
00844 raise XacroException("xacro:attribute: empty name")
00845
00846 node.parentNode.setAttribute(name, value)
00847 replace_node(node, by=None)
00848
00849 elif node.tagName in ['if', 'xacro:if', 'unless', 'xacro:unless'] \
00850 and check_deprecated_tag(node.tagName):
00851 remove_previous_comments(node)
00852 cond, = check_attrs(node, ['value'], [])
00853 keep = get_boolean_value(eval_text(cond, symbols), cond)
00854 if node.tagName in ['unless', 'xacro:unless']:
00855 keep = not keep
00856
00857 if keep:
00858 eval_all(node, macros, symbols)
00859 replace_node(node, by=node, content_only=True)
00860 else:
00861 replace_node(node, by=None)
00862
00863 elif handle_macro_call(node, macros, symbols):
00864 pass
00865
00866 else:
00867
00868 if node.tagName.startswith("xacro:"):
00869 raise XacroException("unknown macro name: %s" % node.tagName)
00870
00871 eval_all(node, macros, symbols)
00872
00873
00874 elif node.nodeType == xml.dom.Node.TEXT_NODE:
00875 node.data = str(eval_text(node.data, symbols))
00876
00877 node = next
00878
00879
00880 def parse(inp, filename=None):
00881 """
00882 Parse input or filename into a DOM tree.
00883 If inp is None, open filename and load from there.
00884 Otherwise, parse inp, either as string or file object.
00885 If inp is already a DOM tree, this function is a noop.
00886 :return:xml.dom.minidom.Document
00887 :raise: xml.parsers.expat.ExpatError
00888 """
00889 f = None
00890 if inp is None:
00891 try:
00892 inp = f = open(filename)
00893 except IOError as e:
00894
00895 filestack.pop()
00896 raise XacroException(e.strerror + ": " + e.filename)
00897
00898 try:
00899 if isinstance(inp, _basestr):
00900 return xml.dom.minidom.parseString(inp)
00901 elif isinstance(inp, file):
00902 return xml.dom.minidom.parse(inp)
00903 return inp
00904
00905 finally:
00906 if f:
00907 f.close()
00908
00909
00910 def process_doc(doc,
00911 in_order=False, just_deps=False, just_includes=False,
00912 mappings=None, xacro_ns=True, **kwargs):
00913 global verbosity, do_check_order
00914 verbosity = kwargs.get('verbosity', verbosity)
00915 do_check_order = kwargs.get('do_check_order', do_check_order)
00916
00917
00918 if mappings is not None:
00919 substitution_args_context['arg'] = mappings
00920
00921 global allow_non_prefixed_tags
00922 allow_non_prefixed_tags = xacro_ns
00923
00924
00925 if not filestack: restore_filestack([None])
00926
00927
00928
00929 if (just_deps or just_includes) and not in_order:
00930 process_includes(doc.documentElement)
00931 return
00932
00933 macros = {}
00934 symbols = Table()
00935 if not in_order:
00936
00937 process_includes(doc.documentElement)
00938 grab_macros(doc, macros)
00939 grab_properties(doc, symbols)
00940
00941 eval_all(doc.documentElement, macros, symbols)
00942
00943
00944 substitution_args_context['arg'] = {}
00945
00946 if do_check_order and symbols.redefined:
00947 warning("Document is incompatible to --inorder processing.")
00948 warning("The following properties were redefined after usage:")
00949 for k, v in symbols.redefined.iteritems():
00950 message(k, "redefined in", v, color='yellow')
00951
00952
00953 def open_output(output_filename):
00954 if output_filename is None:
00955 return sys.stdout
00956 else:
00957 dir_name = os.path.dirname(output_filename)
00958 if dir_name:
00959 try:
00960 os.makedirs(dir_name)
00961 except os.error:
00962
00963
00964 pass
00965
00966 try:
00967 return open(output_filename, 'w')
00968 except IOError as e:
00969 raise XacroException("Failed to open output:", exc=e)
00970
00971
00972 def print_location(filestack, err=None, file=sys.stderr):
00973 macros = getattr(err, 'macros', []) if err else []
00974 msg = 'when instantiating macro:'
00975 for m in macros:
00976 name = m.body.getAttribute('name')
00977 location = '(%s)' % m.history[-1][-1]
00978 print(msg, name, location, file=file)
00979 msg = 'instantiated from:'
00980
00981 msg = 'in file:' if macros else 'when processing file:'
00982 for f in reversed(filestack):
00983 if f is None: f = 'string'
00984 print(msg, f, file=file)
00985 msg = 'included from:'
00986
00987
00988 def main():
00989 opts, input_file = process_args(sys.argv[1:])
00990 if opts.in_order == False:
00991 warning("Traditional processing is deprecated. Switch to --inorder processing!")
00992 message("To check for compatibility of your document, use option --check-order.", color='yellow')
00993 message("For more infos, see http://wiki.ros.org/xacro#Processing_Order", color='yellow')
00994
00995 try:
00996 restore_filestack([input_file])
00997 doc = parse(None, input_file)
00998 process_doc(doc, **vars(opts))
00999 out = open_output(opts.output)
01000
01001 except xml.parsers.expat.ExpatError as e:
01002 error("XML parsing error: %s" % str(e), alt_text=None)
01003 if verbosity > 0:
01004 print_location(filestack, e)
01005 print(file=sys.stderr)
01006 print("Check that:", file=sys.stderr)
01007 print(" - Your XML is well-formed", file=sys.stderr)
01008 print(" - You have the xacro xmlns declaration:",
01009 "xmlns:xacro=\"http://www.ros.org/wiki/xacro\"", file=sys.stderr)
01010 sys.exit(2)
01011
01012 except Exception as e:
01013 error(str(e))
01014 if verbosity > 0:
01015 print_location(filestack, e)
01016 if verbosity > 1:
01017 print(file=sys.stderr)
01018 raise
01019 else:
01020 sys.exit(2)
01021
01022 if opts.just_deps:
01023 out.write(" ".join(set(all_includes)))
01024 print()
01025 return
01026 if opts.just_includes:
01027 out.write(doc.toprettyxml(indent=' '))
01028 print()
01029 return
01030
01031 banner = [xml.dom.minidom.Comment(c) for c in
01032 [" %s " % ('=' * 83),
01033 " | This document was autogenerated by xacro from %-30s | " % input_file,
01034 " | EDITING THIS FILE BY HAND IS NOT RECOMMENDED %-30s | " % "",
01035 " %s " % ('=' * 83)]]
01036 first = doc.firstChild
01037 for comment in banner:
01038 doc.insertBefore(comment, first)
01039
01040 out.write(doc.toprettyxml(indent=' '))
01041 print()
01042 if opts.output:
01043 out.close()