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(None, 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 if not name.startswith('xacro:'):
00499 name = 'xacro:' + name
00500
00501
00502 macro = macros.get(name, Macro())
00503
00504 macro.history.append(filestack)
00505 macro.body = elt
00506
00507
00508 macro.params = []
00509 macro.defaultmap = {}
00510 while params:
00511 param, value, params = parse_macro_arg(params)
00512 macro.params.append(param)
00513 if value is not None:
00514 macro.defaultmap[param] = value
00515
00516 macros[name] = macro
00517 replace_node(elt, by=None)
00518
00519
00520
00521 def grab_macros(elt, macros):
00522 elt = first_child_element(elt)
00523 while elt:
00524 next = next_sibling_element(elt)
00525 if elt.tagName in ['macro', 'xacro:macro'] \
00526 and check_deprecated_tag(elt.tagName):
00527 grab_macro(elt, macros)
00528 else:
00529 grab_macros(elt, macros)
00530
00531 elt = next
00532
00533
00534 def grab_property(elt, table):
00535 assert(elt.tagName in ['property', 'xacro:property'])
00536 remove_previous_comments(elt)
00537
00538 name, value, scope = check_attrs(elt, ['name'], ['value', 'scope'])
00539 if not is_valid_name(name):
00540 raise XacroException('Property names must be valid python identifiers: ' + name)
00541
00542 if value is None:
00543 name = '**' + name
00544 value = elt
00545
00546 if scope and scope == 'global':
00547 target_table = table.root()
00548 unevaluated = False
00549 elif scope and scope == 'parent':
00550 if table.parent:
00551 target_table = table.parent
00552 else:
00553 warning("%s: no parent scope at global scope " % name)
00554 unevaluated = False
00555 else:
00556 target_table = table
00557 unevaluated = True
00558
00559 if not unevaluated and isinstance(value, _basestr):
00560 value = eval_text(value, table)
00561
00562 target_table._setitem(name, value, unevaluated=unevaluated)
00563 replace_node(elt, by=None)
00564
00565
00566
00567 def grab_properties(elt, table):
00568 elt = first_child_element(elt)
00569 while elt:
00570 next = next_sibling_element(elt)
00571 if elt.tagName in ['property', 'xacro:property'] \
00572 and check_deprecated_tag(elt.tagName):
00573 grab_property(elt, table)
00574 else:
00575 grab_properties(elt, table)
00576
00577 elt = next
00578
00579
00580 LEXER = QuickLexer(DOLLAR_DOLLAR_BRACE=r"\$\$+\{",
00581 EXPR=r"\$\{[^\}]*\}",
00582 EXTENSION=r"\$\([^\)]*\)",
00583 TEXT=r"([^\$]|\$[^{(]|\$$)+")
00584
00585
00586
00587 def eval_text(text, symbols):
00588 def handle_expr(s):
00589 try:
00590 return eval(eval_text(s, symbols), global_symbols, symbols)
00591 except Exception as e:
00592
00593 raise XacroException(exc=e,
00594 suffix=os.linesep + "when evaluating expression '%s'" % s)
00595
00596 def handle_extension(s):
00597 return eval_extension("$(%s)" % eval_text(s, symbols))
00598
00599 results = []
00600 lex = QuickLexer(LEXER)
00601 lex.lex(text)
00602 while lex.peek():
00603 if lex.peek()[0] == lex.EXPR:
00604 results.append(handle_expr(lex.next()[1][2:-1]))
00605 elif lex.peek()[0] == lex.EXTENSION:
00606 results.append(handle_extension(lex.next()[1][2:-1]))
00607 elif lex.peek()[0] == lex.TEXT:
00608 results.append(lex.next()[1])
00609 elif lex.peek()[0] == lex.DOLLAR_DOLLAR_BRACE:
00610 results.append(lex.next()[1][1:])
00611
00612 if len(results) == 1:
00613 return results[0]
00614
00615 else:
00616 return ''.join(map(str, results))
00617
00618
00619 def eval_default_arg(forward_variable, default, symbols, macro):
00620 if forward_variable is None:
00621 return eval_text(default, symbols)
00622 try:
00623 return symbols[forward_variable]
00624 except KeyError:
00625 if default is not None:
00626 return eval_text(default, symbols)
00627 else:
00628 raise XacroException("Undefined property to forward: " + forward_variable, macro=macro)
00629
00630
00631 def handle_dynamic_macro_call(node, macros, symbols):
00632 name, = reqd_attrs(node, ['macro'])
00633 if not name:
00634 raise XacroException("xacro:call is missing the 'macro' attribute")
00635 name = str(eval_text(name, symbols))
00636
00637
00638 node.removeAttribute('macro')
00639 node.tagName = name
00640
00641 result = handle_macro_call(node, macros, symbols)
00642 if not result:
00643 raise XacroException("unknown macro name '%s' in xacro:call" % name)
00644 return True
00645
00646 def resolve_macro(fullname, macros):
00647
00648 namespaces = fullname.split('.')
00649 name = namespaces.pop(-1)
00650
00651 if namespaces and namespaces[0].startswith('xacro:'):
00652 namespaces[0] = namespaces[0].replace('xacro:', '')
00653 name = 'xacro:' + name
00654
00655 def _resolve(namespaces, name, macros):
00656
00657 for ns in namespaces:
00658 macros = macros[ns]
00659 try:
00660 return macros[name]
00661 except KeyError:
00662
00663 if allow_non_prefixed_tags and not name.startswith('xacro:'):
00664 return _resolve([], 'xacro:' + name, macros)
00665
00666
00667 m = _resolve([], fullname, macros)
00668 if m: return m
00669 elif namespaces: return _resolve(namespaces, name, macros)\
00670
00671
00672 def handle_macro_call(node, macros, symbols):
00673 try:
00674 m = resolve_macro(node.tagName, macros)
00675 body = m.body.cloneNode(deep=True)
00676
00677 except (KeyError, TypeError, AttributeError):
00678
00679 if node.tagName == 'xacro:call':
00680 return handle_dynamic_macro_call(node, macros, symbols)
00681 return False
00682
00683
00684 scoped = Table(symbols)
00685 params = m.params[:]
00686 for name, value in node.attributes.items():
00687 if name not in params:
00688 raise XacroException("Invalid parameter \"%s\"" % str(name), macro=m)
00689 params.remove(name)
00690 scoped._setitem(name, eval_text(value, symbols), unevaluated=False)
00691 node.setAttribute(name, "")
00692
00693
00694 eval_all(node, macros, symbols)
00695
00696
00697 block = first_child_element(node)
00698 for param in params[:]:
00699 if param[0] == '*':
00700 if not block:
00701 raise XacroException("Not enough blocks", macro=m)
00702 params.remove(param)
00703 scoped[param] = block
00704 block = next_sibling_element(block)
00705
00706 if block is not None:
00707 raise XacroException("Unused block \"%s\"" % block.tagName, macro=m)
00708
00709
00710 for param in params[:]:
00711
00712 if param[0] == '*': continue
00713
00714
00715 name, default = m.defaultmap.get(param, (None,None))
00716 if name is not None or default is not None:
00717 scoped._setitem(param, eval_default_arg(name, default, symbols, m), unevaluated=False)
00718 params.remove(param)
00719
00720 if params:
00721 raise XacroException("Undefined parameters [%s]" % ",".join(params), macro=m)
00722
00723 try:
00724 eval_all(body, macros, scoped)
00725 except Exception as e:
00726
00727 if hasattr(e, 'macros'):
00728 e.macros.append(m)
00729 else:
00730 e.macros = [m]
00731 raise
00732
00733
00734 remove_previous_comments(node)
00735 replace_node(node, by=body, content_only=True)
00736 return True
00737
00738
00739 def get_boolean_value(value, condition):
00740 """
00741 Return a boolean value that corresponds to the given Xacro condition value.
00742 Values "true", "1" and "1.0" are supposed to be True.
00743 Values "false", "0" and "0.0" are supposed to be False.
00744 All other values raise an exception.
00745
00746 :param value: The value to be evaluated. The value has to already be evaluated by Xacro.
00747 :param condition: The original condition text in the XML.
00748 :return: The corresponding boolean value, or a Python expression that, converted to boolean, corresponds to it.
00749 :raises ValueError: If the condition value is incorrect.
00750 """
00751 try:
00752 if isinstance(value, _basestr):
00753 if value == 'true': return True
00754 elif value == 'false': return False
00755 else: return ast.literal_eval(value)
00756 else:
00757 return bool(value)
00758 except:
00759 raise XacroException("Xacro conditional \"%s\" evaluated to \"%s\", "
00760 "which is not a boolean expression." % (condition, value))
00761
00762
00763 _empty_text_node = xml.dom.minidom.getDOMImplementation().createDocument(None, "dummy", None).createTextNode('\n\n')
00764 def remove_previous_comments(node):
00765 """remove consecutive comments in front of the xacro-specific node"""
00766 next = node.nextSibling
00767 previous = node.previousSibling
00768 while previous:
00769 if previous.nodeType == xml.dom.Node.TEXT_NODE and \
00770 previous.data.isspace() and previous.data.count('\n') <= 1:
00771 previous = previous.previousSibling
00772
00773 if previous and previous.nodeType == xml.dom.Node.COMMENT_NODE:
00774 comment = previous
00775 previous = previous.previousSibling
00776 node.parentNode.removeChild(comment)
00777 else:
00778
00779
00780 if next and _empty_text_node != next: node.parentNode.insertBefore(_empty_text_node, next)
00781 return
00782
00783
00784 def eval_all(node, macros, symbols):
00785 """Recursively evaluate node, expanding macros, replacing properties, and evaluating expressions"""
00786
00787 for name, value in node.attributes.items():
00788 result = str(eval_text(value, symbols))
00789 node.setAttribute(name, result)
00790
00791 node = node.firstChild
00792 while node:
00793 next = node.nextSibling
00794 if node.nodeType == xml.dom.Node.ELEMENT_NODE:
00795 if node.tagName in ['insert_block', 'xacro:insert_block'] \
00796 and check_deprecated_tag(node.tagName):
00797 name, = check_attrs(node, ['name'], [])
00798
00799 if ("**" + name) in symbols:
00800
00801 block = symbols['**' + name]
00802 content_only = True
00803 elif ("*" + name) in symbols:
00804
00805 block = symbols['*' + name]
00806 content_only = False
00807 else:
00808 raise XacroException("Undefined block \"%s\"" % name)
00809
00810
00811 block = block.cloneNode(deep=True)
00812
00813 eval_all(block, macros, symbols)
00814 replace_node(node, by=block, content_only=content_only)
00815
00816 elif is_include(node):
00817 process_include(node, macros, symbols, eval_all)
00818
00819 elif node.tagName in ['property', 'xacro:property'] \
00820 and check_deprecated_tag(node.tagName):
00821 grab_property(node, symbols)
00822
00823 elif node.tagName in ['macro', 'xacro:macro'] \
00824 and check_deprecated_tag(node.tagName):
00825 grab_macro(node, macros)
00826
00827 elif node.tagName in ['arg', 'xacro:arg'] \
00828 and check_deprecated_tag(node.tagName):
00829 name, default = check_attrs(node, ['name', 'default'], [])
00830 if name not in substitution_args_context['arg']:
00831 substitution_args_context['arg'][name] = eval_text(default, symbols)
00832
00833 remove_previous_comments(node)
00834 replace_node(node, by=None)
00835
00836 elif node.tagName == 'xacro:element':
00837 name = eval_text(*reqd_attrs(node, ['xacro:name']), symbols=symbols)
00838 if not name:
00839 raise XacroException("xacro:element: empty name")
00840
00841 node.removeAttribute('xacro:name')
00842 node.nodeName = node.tagName = name
00843 continue
00844
00845 elif node.tagName == 'xacro:attribute':
00846 name, value = [eval_text(a, symbols) for a in reqd_attrs(node, ['name', 'value'])]
00847 if not name:
00848 raise XacroException("xacro:attribute: empty name")
00849
00850 node.parentNode.setAttribute(name, value)
00851 replace_node(node, by=None)
00852
00853 elif node.tagName in ['if', 'xacro:if', 'unless', 'xacro:unless'] \
00854 and check_deprecated_tag(node.tagName):
00855 remove_previous_comments(node)
00856 cond, = check_attrs(node, ['value'], [])
00857 keep = get_boolean_value(eval_text(cond, symbols), cond)
00858 if node.tagName in ['unless', 'xacro:unless']:
00859 keep = not keep
00860
00861 if keep:
00862 eval_all(node, macros, symbols)
00863 replace_node(node, by=node, content_only=True)
00864 else:
00865 replace_node(node, by=None)
00866
00867 elif handle_macro_call(node, macros, symbols):
00868 pass
00869
00870 else:
00871
00872 if node.tagName.startswith("xacro:"):
00873 raise XacroException("unknown macro name: %s" % node.tagName)
00874
00875 eval_all(node, macros, symbols)
00876
00877
00878 elif node.nodeType == xml.dom.Node.TEXT_NODE:
00879 node.data = str(eval_text(node.data, symbols))
00880
00881 node = next
00882
00883
00884 def parse(inp, filename=None):
00885 """
00886 Parse input or filename into a DOM tree.
00887 If inp is None, open filename and load from there.
00888 Otherwise, parse inp, either as string or file object.
00889 If inp is already a DOM tree, this function is a noop.
00890 :return:xml.dom.minidom.Document
00891 :raise: xml.parsers.expat.ExpatError
00892 """
00893 f = None
00894 if inp is None:
00895 try:
00896 inp = f = open(filename)
00897 except IOError as e:
00898
00899 filestack.pop()
00900 raise XacroException(e.strerror + ": " + e.filename)
00901
00902 try:
00903 if isinstance(inp, _basestr):
00904 return xml.dom.minidom.parseString(inp)
00905 elif isinstance(inp, file):
00906 return xml.dom.minidom.parse(inp)
00907 return inp
00908
00909 finally:
00910 if f:
00911 f.close()
00912
00913
00914 def process_doc(doc,
00915 in_order=False, just_deps=False, just_includes=False,
00916 mappings=None, xacro_ns=True, **kwargs):
00917 global verbosity, do_check_order
00918 verbosity = kwargs.get('verbosity', verbosity)
00919 do_check_order = kwargs.get('do_check_order', do_check_order)
00920
00921
00922 if mappings is not None:
00923 substitution_args_context['arg'] = mappings
00924
00925 global allow_non_prefixed_tags
00926 allow_non_prefixed_tags = xacro_ns
00927
00928
00929 if not filestack: restore_filestack([None])
00930
00931
00932
00933 if (just_deps or just_includes) and not in_order:
00934 process_includes(doc.documentElement)
00935 return
00936
00937 macros = {}
00938 symbols = Table()
00939 if not in_order:
00940
00941 process_includes(doc.documentElement)
00942 grab_macros(doc, macros)
00943 grab_properties(doc, symbols)
00944
00945 eval_all(doc.documentElement, macros, symbols)
00946
00947
00948 substitution_args_context['arg'] = {}
00949
00950 if do_check_order and symbols.redefined:
00951 warning("Document is incompatible to --inorder processing.")
00952 warning("The following properties were redefined after usage:")
00953 for k, v in symbols.redefined.iteritems():
00954 message(k, "redefined in", v, color='yellow')
00955
00956
00957 def open_output(output_filename):
00958 if output_filename is None:
00959 return sys.stdout
00960 else:
00961 dir_name = os.path.dirname(output_filename)
00962 if dir_name:
00963 try:
00964 os.makedirs(dir_name)
00965 except os.error:
00966
00967
00968 pass
00969
00970 try:
00971 return open(output_filename, 'w')
00972 except IOError as e:
00973 raise XacroException("Failed to open output:", exc=e)
00974
00975
00976 def print_location(filestack, err=None, file=sys.stderr):
00977 macros = getattr(err, 'macros', []) if err else []
00978 msg = 'when instantiating macro:'
00979 for m in macros:
00980 name = m.body.getAttribute('name')
00981 location = '(%s)' % m.history[-1][-1]
00982 print(msg, name, location, file=file)
00983 msg = 'instantiated from:'
00984
00985 msg = 'in file:' if macros else 'when processing file:'
00986 for f in reversed(filestack):
00987 if f is None: f = 'string'
00988 print(msg, f, file=file)
00989 msg = 'included from:'
00990
00991 def process_file(input_file_name, **kwargs):
00992 """main processing pipeline"""
00993
00994 restore_filestack([input_file_name])
00995
00996 doc = parse(None, input_file_name)
00997
00998 process_doc(doc, **kwargs)
00999
01000
01001 banner = [xml.dom.minidom.Comment(c) for c in
01002 [" %s " % ('=' * 83),
01003 " | This document was autogenerated by xacro from %-30s | " % input_file_name,
01004 " | EDITING THIS FILE BY HAND IS NOT RECOMMENDED %-30s | " % "",
01005 " %s " % ('=' * 83)]]
01006 first = doc.firstChild
01007 for comment in banner:
01008 doc.insertBefore(comment, first)
01009
01010 return doc
01011
01012 def main():
01013 opts, input_file_name = process_args(sys.argv[1:])
01014 if opts.in_order == False:
01015 warning("xacro: Traditional processing is deprecated. Switch to --inorder processing!")
01016 message("To check for compatibility of your document, use option --check-order.", color='yellow')
01017 message("For more infos, see http://wiki.ros.org/xacro#Processing_Order", color='yellow')
01018
01019 try:
01020
01021 doc = process_file(input_file_name, **vars(opts))
01022
01023 out = open_output(opts.output)
01024
01025
01026 except xml.parsers.expat.ExpatError as e:
01027 error("XML parsing error: %s" % str(e), alt_text=None)
01028 if verbosity > 0:
01029 print_location(filestack, e)
01030 print(file=sys.stderr)
01031 print("Check that:", file=sys.stderr)
01032 print(" - Your XML is well-formed", file=sys.stderr)
01033 print(" - You have the xacro xmlns declaration:",
01034 "xmlns:xacro=\"http://www.ros.org/wiki/xacro\"", file=sys.stderr)
01035 sys.exit(2)
01036
01037 except Exception as e:
01038 msg = error(str(e))
01039 if not msg: msg = repr(e)
01040 error(msg)
01041 if verbosity > 0:
01042 print_location(filestack, e)
01043 if verbosity > 1:
01044 print(file=sys.stderr)
01045 raise
01046 else:
01047 sys.exit(2)
01048
01049
01050 if opts.just_deps:
01051 out.write(" ".join(set(all_includes)))
01052 print()
01053 return
01054
01055
01056 out.write(doc.toprettyxml(indent=' '))
01057 print()
01058
01059 if opts.output:
01060 out.close()