__init__.py
Go to the documentation of this file.
00001 # Copyright (c) 2015, Open Source Robotics Foundation, Inc.
00002 # Copyright (c) 2013, Willow Garage, Inc.
00003 # All rights reserved.
00004 #
00005 # Redistribution and use in source and binary forms, with or without
00006 # modification, are permitted provided that the following conditions are met:
00007 #
00008 #     * Redistributions of source code must retain the above copyright
00009 #       notice, this list of conditions and the following disclaimer.
00010 #     * Redistributions in binary form must reproduce the above copyright
00011 #       notice, this list of conditions and the following disclaimer in the
00012 #       documentation and/or other materials provided with the distribution.
00013 #     * Neither the name of the Open Source Robotics Foundation, Inc.
00014 #       nor the names of its contributors may be used to endorse or promote
00015 #       products derived from this software without specific prior
00016 #       written permission.
00017 #
00018 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
00019 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00020 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00021 # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
00022 # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
00023 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
00024 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
00025 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
00026 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
00027 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00028 # POSSIBILITY OF SUCH DAMAGE.
00029 
00030 # Authors: Stuart Glaser, William Woodall, Robert Haschke
00031 # Maintainer: Morgan Quigley <morgan@osrfoundation.org>
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 # Dictionary of substitution args
00056 substitution_args_context = {}
00057 
00058 
00059 # Stack of currently processed files
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 # global symbols dictionary
00109 # taking simple security measures to forbid access to __builtins__
00110 # only the very few symbols explicitly listed are allowed
00111 # for discussion, see: http://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html
00112 global_symbols = {'__builtins__': {k: __builtins__[k] for k in ['list', 'dict', 'map', 'str', 'float', 'int']}}
00113 # also define all math symbols and functions
00114 global_symbols.update(math.__dict__)
00115 # allow to import dicts from yaml
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 # deprecate non-namespaced use of xacro tags (issues #41, #59, #60)
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 # require xacro namespace?
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  # original xml.dom.Node
00173         self.params = []  # parsed parameter names
00174         self.defaultmap = {}  # default parameter values
00175         self.history = []  # definition 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()  # set of unevaluated variables
00195         self.recursive = []  # list of currently resolved vars (to resolve recursive definitions)
00196         # the following variables are for debugging / checking only
00197         self.depth = self.parent.depth + 1 if self.parent else 0
00198         if do_check_order:
00199             # this is for smooth transition from deprecated to --inorder processing
00200             self.used = set() # set of used properties
00201             self.redefined = dict() # set of properties redefined after usage
00202 
00203     @staticmethod
00204     def _eval_literal(value):
00205         if isinstance(value, _basestr):
00206             try:
00207                 # try to evaluate as literal, e.g. number, boolean, etc.
00208                 # this is needed to handle numbers in property definitions as numbers, not strings
00209                 evaluated = ast.literal_eval(value.strip())
00210                 # However, (simple) list, tuple, dict expressions will be evaluated as such too,
00211                 # which would break expected behaviour. Thus we only accept the evaluation otherwise.
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         # lazy evaluation
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         # return evaluated result
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             # literal evaluation failed: re-evaluate lazily at first access
00259             self.unevaluated.add(key)
00260         elif key in self.unevaluated:
00261             # all other types cannot be evaluated
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     # dot access (namespace.property) is forwarded to getitem()
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             # copy attributes + variables from other instance
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     # Xacro should not use plain 'include' tags but only namespaced ones. Causes conflicts with
00343     # other XML elements including Gazebo's <gazebo> extensions
00344     if elt.tagName not in ['xacro:include', 'include']:
00345         return False
00346 
00347     # Temporary fix for ROS Hydro and the xacro include scope problem
00348     if elt.tagName == 'include':
00349         # check if there is any element within the <include> tag. mostly we are concerned
00350         # with Gazebo's <uri> element, but it could be anything. also, make sure the child
00351         # nodes aren't just a single Text node, which is still considered a deprecated
00352         # instance
00353         if elt.childNodes and not (len(elt.childNodes) == 1 and
00354                                    elt.childNodes[0].nodeType == elt.TEXT_NODE):
00355             # this is not intended to be a xacro element, so we can ignore it
00356             return False
00357         else:
00358             # throw a deprecated warning
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         # Globbing behaviour
00374         filenames = sorted(glob.glob(filename_spec))
00375         if len(filenames) == 0:
00376             warning(include_no_matches_msg.format(filename_spec))
00377     else:
00378         # Default behaviour
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         # extend filestack
00416         oldstack = push_file(filename)
00417         include = parse(None, filename).documentElement
00418 
00419         # recursive call to func
00420         func(include, macros, symbols)
00421         included.append(include)
00422         import_xml_namespaces(elt.parentNode, include.attributes)
00423 
00424         # restore filestack
00425         restore_filestack(oldstack)
00426 
00427     remove_previous_comments(elt)
00428     # replace the include tag with the nodes of the included file(s)
00429     replace_node(elt, by=included, content_only=True)
00430 
00431 
00432 # @throws XacroException if a parsing error occurs with an included document
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     # Resulting AST of simple identifier is <Module [<Expr <Name "foo">>]>
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 #                           space   param    :=   ^|   <--      default      -->   space    rest
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         # there is a default value specified for param
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         # there is no default specified at all
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     # always have 'xacro:' namespace in macro name
00498     if not name.startswith('xacro:'):
00499         name = 'xacro:' + name
00500 
00501     # fetch existing or create new macro definition
00502     macro = macros.get(name, Macro())
00503     # append current filestack to history
00504     macro.history.append(filestack)
00505     macro.body = elt
00506 
00507     # parse params and their defaults
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  # parameter with default
00515 
00516     macros[name] = macro
00517     replace_node(elt, by=None)
00518 
00519 
00520 # Fill the dictionary { macro_name => macro_xml_block }
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  # debug
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 # Fill the table of the properties
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 # evaluate text and return typed value
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             # re-raise as XacroException to add more context
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     # return single element as is, i.e. typed
00612     if len(results) == 1:
00613         return results[0]
00614     # otherwise join elements to a string
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     # remove 'macro' attribute and rename tag with resolved macro name
00638     node.removeAttribute('macro')
00639     node.tagName = name
00640     # forward to handle_macro_call
00641     result = handle_macro_call(node, macros, symbols)
00642     if not result:  # we expect the call to succeed
00643         raise XacroException("unknown macro name '%s' in xacro:call" % name)
00644     return True
00645 
00646 def resolve_macro(fullname, macros):
00647     # split name into namespaces and real name
00648     namespaces = fullname.split('.')
00649     name = namespaces.pop(-1)
00650     # move xacro: prefix from first namespace to name
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         # traverse namespaces to actual macros dict
00657         for ns in namespaces:
00658             macros = macros[ns]
00659         try:
00660             return macros[name]
00661         except KeyError:
00662             # try with xacro: prefix as well
00663             if allow_non_prefixed_tags and not name.startswith('xacro:'):
00664                 return _resolve([], 'xacro:' + name, macros)
00665 
00666     # try fullname and (namespaces, name) in this order
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         # TODO If deprecation runs out, this test should be moved up front
00679         if node.tagName == 'xacro:call':
00680             return handle_dynamic_macro_call(node, macros, symbols)
00681         return False  # no macro
00682 
00683     # Expand the macro
00684     scoped = Table(symbols)  # new local name space for macro evaluation
00685     params = m.params[:]  # deep copy macro's params list
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, "")  # suppress second evaluation in eval_all()
00692 
00693     # Evaluate block parameters in node
00694     eval_all(node, macros, symbols)
00695 
00696     # Fetch block parameters, in order
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     # Try to load defaults for any remaining non-block parameters
00710     for param in params[:]:
00711         # block parameters are not supported for defaults
00712         if param[0] == '*': continue
00713 
00714         # get default
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         # fill in macro call history for nice error reporting
00727         if hasattr(e, 'macros'):
00728             e.macros.append(m)
00729         else:
00730             e.macros = [m]
00731         raise
00732 
00733     # Replaces the macro node with the expansion
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  # skip a single empty text node (max 1 newline)
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             # insert empty text node to stop removing of comments in future calls
00779             # actually this moves the singleton instance to the new location
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     # evaluate the attributes
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                     # Multi-block
00801                     block = symbols['**' + name]
00802                     content_only = True
00803                 elif ("*" + name) in symbols:
00804                     # Single block
00805                     block = symbols['*' + name]
00806                     content_only = False
00807                 else:
00808                     raise XacroException("Undefined block \"%s\"" % name)
00809 
00810                 # cloning block allows to insert the same block multiple times
00811                 block = block.cloneNode(deep=True)
00812                 # recursively evaluate block
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  # re-process the node with new tagName
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  # handle_macro_call does all the work of expanding the macro
00869 
00870             else:
00871                 # these are the non-xacro tags
00872                 if node.tagName.startswith("xacro:"):
00873                     raise XacroException("unknown macro name: %s" % node.tagName)
00874 
00875                 eval_all(node, macros, symbols)
00876 
00877         # TODO: Also evaluate content of COMMENT_NODEs?
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             # do not report currently processed file as "in file ..."
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     # set substitution args
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     # if not yet defined: initialize filestack
00929     if not filestack: restore_filestack([None])
00930 
00931     # inorder processing requires to process the whole document for deps too
00932     # because filenames might be specified via properties or macro parameters
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         # process includes, macros, and properties before evaluating stuff
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     # reset substitution args
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                 # errors occur when dir_name exists or creation failed
00967                 # ignore error here; opening of file will fail if directory is still missing
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     # initialize file stack for error-reporting
00994     restore_filestack([input_file_name])
00995     # parse the document into a xml.dom tree
00996     doc = parse(None, input_file_name)
00997     # perform macro replacement
00998     process_doc(doc, **kwargs)
00999 
01000     # add xacro auto-generated banner
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         # open and process file
01021         doc = process_file(input_file_name, **vars(opts))
01022         # open the output file
01023         out = open_output(opts.output)
01024 
01025     # error handling
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) # add empty separator line before error
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)  # indicate failure, but don't print stack trace on XML errors
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)  # add empty separator line before error
01045             raise  # create stack trace
01046         else:
01047             sys.exit(2)  # gracefully exit with error condition
01048 
01049     # special output mode
01050     if opts.just_deps:
01051         out.write(" ".join(set(all_includes)))
01052         print()
01053         return
01054 
01055     # write output
01056     out.write(doc.toprettyxml(indent='  '))
01057     print()
01058     # only close output file, but not stdout
01059     if opts.output:
01060         out.close()


xacro
Author(s): Stuart Glaser, William Woodall, Robert Haschke
autogenerated on Fri Jun 24 2016 04:05:50