__init__.py
Go to the documentation of this file.
1 # Copyright (c) 2015, Open Source Robotics Foundation, Inc.
2 # Copyright (c) 2013, Willow Garage, Inc.
3 # All rights reserved.
4 #
5 # Redistribution and use in source and binary forms, with or without
6 # modification, are permitted provided that the following conditions are met:
7 #
8 # * Redistributions of source code must retain the above copyright
9 # notice, this list of conditions and the following disclaimer.
10 # * Redistributions in binary form must reproduce the above copyright
11 # notice, this list of conditions and the following disclaimer in the
12 # documentation and/or other materials provided with the distribution.
13 # * Neither the name of the Open Source Robotics Foundation, Inc.
14 # nor the names of its contributors may be used to endorse or promote
15 # products derived from this software without specific prior
16 # written permission.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22 # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 # POSSIBILITY OF SUCH DAMAGE.
29 
30 # Authors: Stuart Glaser, William Woodall, Robert Haschke
31 # Maintainer: Morgan Quigley <morgan@osrfoundation.org>
32 
33 from __future__ import print_function, division
34 
35 import ast
36 import glob
37 import math
38 import os
39 import re
40 import sys
41 import xml.dom.minidom
42 
43 from copy import deepcopy
44 from .cli import process_args
45 from .color import error, message, warning
46 from .xmlutils import opt_attrs, reqd_attrs, first_child_element, \
47  next_sibling_element, replace_node
48 
49 
50 try: # python 2
51  _basestr = basestring
52  encoding = {'encoding': 'utf-8'}
53 except NameError: # python 3
54  _basestr = str
55  unicode = str
56  encoding = {}
57 
58 # Dictionary of substitution args
59 substitution_args_context = {}
60 
61 
62 # Stack of currently processed files / macros
63 filestack = None
64 macrostack = None
65 
66 
67 def init_stacks(file):
68  global filestack
69  global macrostack
70  filestack = [file]
71  macrostack = []
72 
73 
74 def abs_filename_spec(filename_spec):
75  """
76  Prepend the dirname of the currently processed file
77  if filename_spec is not yet absolute
78  """
79  if not os.path.isabs(filename_spec):
80  parent_filename = filestack[-1]
81  basedir = os.path.dirname(parent_filename) if parent_filename else '.'
82  return os.path.join(basedir, filename_spec)
83  return filename_spec
84 
85 
86 class YamlListWrapper(list):
87  """Wrapper class for yaml lists to allow recursive inheritance of wrapper property"""
88  @staticmethod
89  def wrap(item):
90  """This static method, used by both YamlListWrapper and YamlDictWrapper,
91  dispatches to the correct wrapper class depending on the type of yaml item"""
92  if isinstance(item, dict):
93  return YamlDictWrapper(item)
94  elif isinstance(item, list):
95  return YamlListWrapper(item)
96  else: # scalar
97  return item
98 
99  def __getitem__(self, idx):
100  return YamlListWrapper.wrap(super(YamlListWrapper, self).__getitem__(idx))
101 
102  def __iter__(self):
103  for item in super(YamlListWrapper, self).__iter__():
104  yield YamlListWrapper.wrap(item)
105 
106 
107 class YamlDictWrapper(dict):
108  """Wrapper class providing dotted access to dict items"""
109  def __getattr__(self, item):
110  try:
111  return YamlListWrapper.wrap(super(YamlDictWrapper, self).__getitem__(item))
112  except KeyError:
113  raise AttributeError("The yaml dictionary has no key '{}'".format(item))
114 
115  __getitem__ = __getattr__
116 
117 
118 def construct_angle_radians(loader, node):
119  """utility function to construct radian values from yaml"""
120  value = loader.construct_scalar(node)
121  try:
122  return float(safe_eval(value, _global_symbols))
123  except SyntaxError:
124  raise XacroException("invalid expression: %s" % value)
125 
126 
127 def construct_angle_degrees(loader, node):
128  """utility function for converting degrees into radians from yaml"""
129  return math.radians(construct_angle_radians(loader, node))
130 
131 
132 def load_yaml(filename):
133  try:
134  import yaml
135  yaml.SafeLoader.add_constructor(u'!radians', construct_angle_radians)
136  yaml.SafeLoader.add_constructor(u'!degrees', construct_angle_degrees)
137  except Exception:
138  raise XacroException("yaml support not available; install python-yaml")
139 
140  filename = abs_filename_spec(filename)
141  f = open(filename)
142  filestack.append(filename)
143  try:
144  return YamlListWrapper.wrap(yaml.safe_load(f))
145  finally:
146  f.close()
147  filestack.pop()
148  global all_includes
149  all_includes.append(filename)
150 
151 
152 def tokenize(s, sep=',; ', skip_empty=True):
153  results = re.split('[{}]'.format(sep), s)
154  if skip_empty:
155  return [item for item in results if item]
156  else:
157  return results
158 
159 
160 # create global symbols dictionary
161 # taking simple security measures to forbid access to __builtins__
162 # only the very few symbols explicitly listed are allowed
163 # for discussion, see: http://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html
165  result = dict()
166 
167  def deprecate(f, msg):
168  def wrapper(*args, **kwargs):
169  warning(msg)
170  return f(*args, **kwargs)
171 
172  return wrapper if msg else f
173 
174  def expose(*args, **kwargs):
175  # Extract args from kwargs
176  source, ns, deprecate_msg = (kwargs.pop(key, None) for key in ['source', 'ns', 'deprecate_msg'])
177 
178  addons = dict()
179  if source is not None:
180  addons.update([(key, source[key]) for key in args]) # Add list of symbol names from source
181  else:
182  addons.update(*args) # Add from list of (key, value) pairs
183  addons.update(**kwargs) # Add key=value arguments
184 
185  if ns is not None: # Wrap dict into a namespace
186  try: # Retrieve namespace target dict
187  target = result[ns]
188  except KeyError: # or create if not existing yet
189  target = MacroNameSpace()
190  result.update([(ns, target)])
191  target.update(addons) # Populate target dict
192 
193  if deprecate_msg is not None: # Also import directly, but with deprecation warning
194  result.update([(key, deprecate(f, deprecate_msg.format(name=key, ns=ns))) for key, f in addons.items()])
195  else:
196  result.update(addons) # Import directly
197 
198  deprecate_msg = 'Using {name}() directly is deprecated. Use {ns}.{name}() instead.'
199  # This is the list of symbols we have exposed for years now. Continue exposing them directly
200  expose('list', 'dict', 'map', 'len', 'str', 'float', 'int', 'True', 'False', 'min', 'max', 'round',
201  source=__builtins__)
202  # These few were only recently added. The should move into python namespace, but (with a deprecation msg) stay global for now
203  expose('sorted', 'range', source=__builtins__, ns='python', deprecate_msg=deprecate_msg)
204  # Expose all builtin symbols into the python namespace. Thus the stay accessible if the global symbol was overriden
205  expose('list', 'dict', 'map', 'len', 'str', 'float', 'int', 'True', 'False', 'min', 'max', 'round',
206  'all', 'any', 'complex', 'divmod', 'enumerate', 'filter', 'frozenset', 'hash', 'isinstance', 'issubclass',
207  'ord', 'repr', 'reversed', 'slice', 'set', 'sum', 'tuple', 'type', 'zip', source=__builtins__, ns='python')
208 
209  # Expose all math symbols and functions into namespace math (and directly for backwards compatibility -- w/o deprecation)
210  expose([(k, v) for k, v in math.__dict__.items() if not k.startswith('_')], ns='math', deprecate_msg='')
211 
212  # Expose load_yaml, abs_filename, and dotify into namespace xacro (and directly with deprecation)
213  expose(load_yaml=load_yaml, abs_filename=abs_filename_spec, dotify=YamlDictWrapper,
214  ns='xacro', deprecate_msg=deprecate_msg)
215  expose(arg=lambda name: substitution_args_context['arg'][name], ns='xacro')
216 
217  def message_adapter(f):
218  def wrapper(*args, **kwargs):
219  location = kwargs.pop('print_location', f.__name__ in ['warning', 'error'])
220  kwargs.pop('file', None) # Don't forward a file argument
221  f(*args, **kwargs)
222  if location:
223  print_location()
224  return '' # Return empty string instead of None
225  return wrapper
226 
227  def fatal(*args):
228  raise XacroException(' '.join(map(str, args)))
229 
230  # Expose xacro's message functions
231  expose([(f.__name__, message_adapter(f)) for f in [message, warning, error, print_location]], ns='xacro')
232  expose(fatal=fatal, tokenize=tokenize, ns='xacro')
233 
234  return result
235 
236 
237 def safe_eval(expr, globals, locals=None):
238  code = compile(expr.strip(), "<expression>", "eval")
239  invalid_names = [n for n in code.co_names if n.startswith("__")]
240  if invalid_names:
241  raise XacroException("Use of invalid name(s): ", ', '.join(invalid_names))
242  globals.update(__builtins__= {}) # disable default builtins
243  return eval(code, globals, locals)
244 
245 
246 class XacroException(Exception):
247  """
248  XacroException allows to wrap another exception (exc) and to augment
249  its error message: prefixing with msg and suffixing with suffix.
250  str(e) finally prints: msg str(exc) suffix
251  """
252 
253  def __init__(self, msg=None, suffix=None, exc=None, macro=None):
254  super(XacroException, self).__init__(msg)
255  self.suffix = suffix
256  self.exc = exc
257  self.macros = [] if macro is None else [macro]
258 
259  def __str__(self):
260  items = [super(XacroException, self).__str__(), self.exc, self.suffix]
261  return ' '.join([s for s in [unicode(e) for e in items] if s not in ['', 'None']])
262 
263 
264 verbosity = 1
265 
266 
267 def check_attrs(tag, required, optional):
268  """
269  Helper routine to fetch required and optional attributes
270  and complain about any additional attributes.
271  :param tag (xml.dom.Element): DOM element node
272  :param required [str]: list of required attributes
273  :param optional [str]: list of optional attributes
274  """
275  result = reqd_attrs(tag, required)
276  result.extend(opt_attrs(tag, optional))
277  allowed = required + optional
278  extra = [a for a in tag.attributes.keys() if a not in allowed and not a.startswith("xmlns:")]
279  if extra:
280  warning("%s: unknown attribute(s): %s" % (tag.nodeName, ', '.join(extra)))
281  if verbosity > 0:
282  print_location()
283  return result
284 
285 
286 # deprecate non-namespaced use of xacro tags (issues #41, #59, #60)
287 def deprecated_tag(tag_name=None, _issued=[False]):
288  if _issued[0]:
289  return
290 
291  _issued[0] = True
292  warning("Deprecated: xacro tag '{}' w/o 'xacro:' xml namespace prefix (will be forbidden in Noetic)".format(tag_name))
293  if verbosity > 0:
294  print_location()
295  message("""Use the following command to fix incorrect tag usage:
296 find . -iname "*.xacro" | xargs sed -i 's#<\([/]\\?\)\(if\|unless\|include\|arg\|property\|macro\|insert_block\)#<\\1xacro:\\2#g'""")
297  print(file=sys.stderr)
298 
299 
300 # require xacro namespace?
301 allow_non_prefixed_tags = True
302 
303 
304 def check_deprecated_tag(tag_name):
305  """
306  Check whether tagName starts with xacro prefix. If not, issue a warning.
307  :param tag_name:
308  :return: True if tagName is accepted as xacro tag
309  False if tagName doesn't start with xacro prefix, but the prefix is required
310  """
311  if tag_name.startswith('xacro:'):
312  return True
313  else:
314  if allow_non_prefixed_tags:
315  deprecated_tag(tag_name)
316  return allow_non_prefixed_tags
317 
318 
319 class Macro(object):
320  def __init__(self):
321  self.body = None # original xml.dom.Node
322  self.params = [] # parsed parameter names
323  self.defaultmap = {} # default parameter values
324  self.history = [] # definition history
325 
326 
328  if s == '$(cwd)':
329  return os.getcwd()
330  try:
331  from roslaunch.substitution_args import resolve_args, ArgException
332  from rospkg.common import ResourceNotFound
333  return resolve_args(s, context=substitution_args_context, resolve_anon=False)
334  except ImportError as e:
335  raise XacroException("substitution args not supported: ", exc=e)
336  except ArgException as e:
337  raise XacroException("Undefined substitution argument", exc=e)
338  except ResourceNotFound as e:
339  raise XacroException("resource not found:", exc=e)
340 
341 
342 do_check_order = False
343 
344 
345 class Table(object):
346  def __init__(self, parent=None):
347  self.parent = parent
348  self.table = {}
349  self.unevaluated = set() # set of unevaluated variables
350  self.recursive = [] # list of currently resolved vars (to resolve recursive definitions)
351  # the following variables are for debugging / checking only
352  self.depth = self.parent.depth + 1 if self.parent else 0
353  if do_check_order:
354  # this is for smooth transition from deprecated to in-order processing
355  self.used = set() # set of used properties
356  self.redefined = dict() # set of properties redefined after usage
357 
358  @staticmethod
359  def _eval_literal(value):
360  if isinstance(value, _basestr):
361  # remove single quotes from escaped string
362  if len(value) >= 2 and value[0] == "'" and value[-1] == "'":
363  return value[1:-1]
364  # try to evaluate as number literal or boolean
365  # this is needed to handle numbers in property definitions as numbers, not strings
366  for f in [int, float, lambda x: get_boolean_value(x, None)]: # order of types is important!
367  try:
368  return f(value)
369  except:
370  pass
371  return value
372 
373  def _resolve_(self, key):
374  # lazy evaluation
375  if key in self.unevaluated:
376  if key in self.recursive:
377  raise XacroException('circular variable definition: {}\n'
378  'Consider disabling lazy evaluation via lazy_eval="false"'
379  .format(" -> ".join(self.recursive + [key])))
380  self.recursive.append(key)
381  self.table[key] = self._eval_literal(eval_text(self.table[key], self))
382  self.unevaluated.remove(key)
383  self.recursive.remove(key)
384 
385  # return evaluated result
386  value = self.table[key]
387  if (verbosity > 2 and self.parent is None) or verbosity > 3:
388  print("{indent}use {key}: {value} ({loc})".format(
389  indent=self.depth*' ', key=key, value=value, loc=filestack[-1]), file=sys.stderr)
390  if do_check_order:
391  self.used.add(key)
392  return value
393 
394  def __getitem__(self, key):
395  if key in self.table:
396  return self._resolve_(key)
397  elif self.parent:
398  return self.parent[key]
399  else:
400  raise KeyError(key)
401 
402  def _setitem(self, key, value, unevaluated):
403  if do_check_order and key in self.used and key not in self.redefined:
404  self.redefined[key] = filestack[-1]
405 
406  if key in _global_symbols:
407  warning("redefining global symbol: %s" % key)
408  print_location()
409 
410  value = self._eval_literal(value)
411  self.table[key] = value
412  if unevaluated and isinstance(value, _basestr):
413  # literal evaluation failed: re-evaluate lazily at first access
414  self.unevaluated.add(key)
415  elif key in self.unevaluated:
416  # all other types cannot be evaluated
417  self.unevaluated.remove(key)
418  if (verbosity > 2 and self.parent is None) or verbosity > 3:
419  print("{indent}set {key}: {value} ({loc})".format(
420  indent=self.depth*' ', key=key, value=value, loc=filestack[-1]), file=sys.stderr)
421 
422  def __setitem__(self, key, value):
423  self._setitem(key, value, unevaluated=True)
424 
425  def __delitem__(self, key):
426  # Remove all items up to root
427  p = self
428  while p is not None:
429  p.table.pop(key, None)
430  p = p.parent
431 
432  def __contains__(self, key):
433  return \
434  key in self.table or \
435  (self.parent and key in self.parent)
436 
437  def __str__(self):
438  s = unicode(self.table)
439  if isinstance(self.parent, Table):
440  s += "\n parent: "
441  s += unicode(self.parent)
442  return s
443 
444  def root(self):
445  p = self
446  while p.parent:
447  p = p.parent
448  return p
449 
450 
451 class NameSpace(object):
452  # dot access (namespace.property) is forwarded to getitem()
453  def __getattr__(self, item):
454  try:
455  return self.__getitem__(item)
456  except KeyError:
457  raise NameError("name '{}' is not defined".format(item))
458 
459 
461  def __init__(self, parent=None):
462  super(PropertyNameSpace, self).__init__(parent)
463 
464 
466  def __init__(self, *args, **kwargs):
467  super(MacroNameSpace, self).__init__(*args, **kwargs)
468 
469 
470 class QuickLexer(object):
471  def __init__(self, *args, **kwargs):
472  if args:
473  # copy attributes + variables from other instance
474  other = args[0]
475  self.__dict__.update(other.__dict__)
476  else:
477  self.res = []
478  for k, v in kwargs.items():
479  self.__setattr__(k, len(self.res))
480  self.res.append(re.compile(v))
481  self.str = ""
482  self.top = None
483 
484  def lex(self, str):
485  self.str = str
486  self.top = None
487  self.next()
488 
489  def peek(self):
490  return self.top
491 
492  def next(self):
493  result = self.top
494  self.top = None
495  if not self.str: # empty string
496  return result
497  for i in range(len(self.res)):
498  m = self.res[i].match(self.str)
499  if m:
500  self.top = (i, m.group(0))
501  self.str = self.str[m.end():]
502  return result
503  raise XacroException('invalid expression: ' + self.str)
504 
505 
506 all_includes = []
507 include_no_matches_msg = """Include tag's filename spec \"{}\" matched no files."""
508 
509 
510 def is_include(elt):
511  # Xacro should not use plain 'include' tags but only namespaced ones. Causes conflicts with
512  # other XML elements including Gazebo's <gazebo> extensions
513  if elt.tagName not in ['xacro:include', 'include']:
514  return False
515 
516  # Temporary fix for ROS Hydro and the xacro include scope problem
517  if elt.tagName == 'include':
518  # check if there is any element within the <include> tag. mostly we are concerned
519  # with Gazebo's <uri> element, but it could be anything. also, make sure the child
520  # nodes aren't just a single Text node, which is still considered a deprecated
521  # instance
522  if elt.childNodes and not (len(elt.childNodes) == 1 and
523  elt.childNodes[0].nodeType == elt.TEXT_NODE):
524  # this is not intended to be a xacro element, so we can ignore it
525  return False
526  else:
527  # throw a deprecated warning
528  return check_deprecated_tag(elt.tagName)
529  return True
530 
531 
532 def get_include_files(filename_spec, symbols):
533  try:
534  filename_spec = abs_filename_spec(eval_text(filename_spec, symbols))
535  except XacroException as e:
536  if e.exc and isinstance(e.exc, NameError) and symbols is None:
537  raise XacroException('variable filename is supported with in-order option only')
538  else:
539  raise
540 
541  if re.search('[*[?]+', filename_spec):
542  # Globbing behaviour
543  filenames = sorted(glob.glob(filename_spec))
544  if len(filenames) == 0:
545  warning(include_no_matches_msg.format(filename_spec))
546  else:
547  # Default behaviour
548  filenames = [filename_spec]
549 
550  for filename in filenames:
551  global all_includes
552  all_includes.append(filename)
553  yield filename
554 
555 
556 def import_xml_namespaces(parent, attributes):
557  """import all namespace declarations into parent"""
558  for name, value in attributes.items():
559  if name.startswith('xmlns:'):
560  oldAttr = parent.getAttributeNode(name)
561  if oldAttr and oldAttr.value != value:
562  warning("inconsistent namespace redefinitions for {name}:"
563  "\n old: {old}\n new: {new} ({new_file})".format(
564  name=name, old=oldAttr.value, new=value,
565  new_file=filestack[-1]))
566  else:
567  parent.setAttribute(name, value)
568 
569 
570 def process_include(elt, macros, symbols, func):
571  included = []
572  filename_spec, namespace_spec = check_attrs(elt, ['filename'], ['ns'])
573  if namespace_spec:
574  try:
575  namespace_spec = eval_text(namespace_spec, symbols)
576  macros[namespace_spec] = ns_macros = MacroNameSpace()
577  symbols[namespace_spec] = ns_symbols = PropertyNameSpace(parent=symbols)
578  except TypeError:
579  raise XacroException('namespaces are supported with in-order option only')
580  else:
581  ns_macros = macros
582  ns_symbols = symbols
583 
584  if first_child_element(elt):
585  warning("Child elements of a <xacro:include> tag are ignored")
586  if verbosity > 0:
587  print_location()
588 
589  for filename in get_include_files(filename_spec, symbols):
590  # extend filestack
591  filestack.append(filename)
592  include = parse(None, filename).documentElement
593 
594  # recursive call to func
595  func(include, ns_macros, ns_symbols)
596  included.append(include)
597  import_xml_namespaces(elt.parentNode, include.attributes)
598 
599  # restore filestack
600  filestack.pop()
601 
602  remove_previous_comments(elt)
603  # replace the include tag with the nodes of the included file(s)
604  replace_node(elt, by=included, content_only=True)
605 
606 
607 # @throws XacroException if a parsing error occurs with an included document
608 def process_includes(elt, macros=None, symbols=None):
609  elt = first_child_element(elt)
610  while elt:
611  next = next_sibling_element(elt)
612  if is_include(elt):
613  process_include(elt, macros, symbols, process_includes)
614  else:
615  process_includes(elt)
616 
617  elt = next
618 
619 
620 def is_valid_name(name):
621  """
622  Checks whether name is a valid property or macro identifier.
623  With python-based evaluation, we need to avoid name clashes with python keywords.
624  """
625  # Resulting AST of simple identifier is <Module [<Expr <Name "foo">>]>
626  try:
627  root = ast.parse(name)
628 
629  if isinstance(root, ast.Module) and \
630  len(root.body) == 1 and isinstance(root.body[0], ast.Expr) and \
631  isinstance(root.body[0].value, ast.Name) and root.body[0].value.id == name:
632  return True
633  except SyntaxError:
634  pass
635 
636  return False
637 
638 
639 default_value = '''\$\{.*?\}|\$\(.*?\)|(?:'.*?'|\".*?\"|[^\s'\"]+)+|'''
640 re_macro_arg = re.compile(r'^\s*([^\s:=]+?)\s*:?=\s*(\^\|?)?(' + default_value + ')(?:\s+|$)(.*)')
641 # space( param )( := )( ^| )( default )( space )(rest)
642 
643 
645  """
646  parse the first param spec from a macro parameter string s
647  accepting the following syntax: <param>[:=|=][^|]<default>
648  :param s: param spec string
649  :return: param, (forward, default), rest-of-string
650  forward will be either param or None (depending on whether ^ was specified)
651  default will be the default string or None
652  If there is no default spec at all, the middle pair will be replaced by None
653  """
654  m = re_macro_arg.match(s)
655  if m:
656  # there is a default value specified for param
657  param, forward, default, rest = m.groups()
658  if not default:
659  default = None
660  return param, (param if forward else None, default), rest
661  else:
662  # there is no default specified at all
663  result = s.lstrip().split(None, 1)
664  return result[0], None, result[1] if len(result) > 1 else ''
665 
666 
667 def grab_macro(elt, macros):
668  assert(elt.tagName in ['macro', 'xacro:macro'])
669  remove_previous_comments(elt)
670 
671  name, params = check_attrs(elt, ['name'], ['params'])
672  if name == 'call':
673  warning("deprecated use of 'call' as macro name: xacro:call became a keyword")
674  if name.find('.') != -1:
675  raise XacroException("macro names must not contain '.' (reserved for namespaces): %s" % name)
676  if name.startswith('xacro:'):
677  warning("macro names must not contain prefix 'xacro:': %s" % name)
678  name = name[6:] # drop 'xacro:' prefix
679 
680  # fetch existing or create new macro definition
681  macro = macros.get(name, Macro())
682  # append current filestack to history
683  macro.history.append(deepcopy(filestack))
684  macro.body = elt
685 
686  # parse params and their defaults
687  macro.params = []
688  macro.defaultmap = {}
689  while params:
690  param, value, params = parse_macro_arg(params)
691  macro.params.append(param)
692  if value is not None:
693  macro.defaultmap[param] = value # parameter with default
694 
695  macros[name] = macro
696  replace_node(elt, by=None)
697 
698 
699 # Fill the dictionary { macro_name => macro_xml_block }
700 def grab_macros(elt, macros):
701  elt = first_child_element(elt)
702  while elt:
703  next = next_sibling_element(elt)
704  if elt.tagName in ['macro', 'xacro:macro'] \
705  and check_deprecated_tag(elt.tagName):
706  grab_macro(elt, macros)
707  else:
708  grab_macros(elt, macros)
709 
710  elt = next
711 
712 
713 def grab_property(elt, table):
714  assert(elt.tagName in ['property', 'xacro:property'])
715  remove_previous_comments(elt)
716 
717  name, value, default, remove, scope, lazy_eval = \
718  check_attrs(elt, ['name'], ['value', 'default', 'remove', 'scope', 'lazy_eval'])
719  name = eval_text(name, table) # Allow name to be evaluated from expression
720  if not is_valid_name(name):
721  raise XacroException('Property names must be valid python identifiers: ' + name)
722  if name.startswith('__'):
723  raise XacroException('Property names must not start with double underscore:' + name)
724  remove = get_boolean_value(eval_text(remove or 'false', table), remove)
725  if sum([value is not None, default is not None, remove]) > 1:
726  raise XacroException('Property attributes default, value, and remove are mutually exclusive: ' + name)
727 
728  if remove and name in table:
729  del table[name]
730  replace_node(elt, by=None)
731  return
732 
733  if default is not None:
734  if scope is not None:
735  warning("%s: default property value can only be defined on local scope" % name)
736  if name not in table:
737  value = default
738  else:
739  replace_node(elt, by=None)
740  return
741 
742  if value is None:
743  name = '**' + name
744  value = elt # debug
745 
746  replace_node(elt, by=None)
747 
748  # We use lazy evaluation by default
749  lazy_eval = get_boolean_value(eval_text(lazy_eval or 'true', table), lazy_eval)
750 
751  if scope and scope == 'global':
752  target_table = table.root()
753  lazy_eval = False
754  elif scope and scope == 'parent':
755  if table.parent is not None:
756  target_table = table.parent
757  lazy_eval = False
758  if not isinstance(table, PropertyNameSpace): # in macro scope
759  # ... skip all namespaces to reach caller's scope
760  while isinstance(target_table, PropertyNameSpace):
761  target_table = target_table.parent
762  else:
763  warning("%s: no parent scope at global scope " % name)
764  return # cannot store the value, no reason to evaluate it
765  else:
766  target_table = table
767 
768  if not lazy_eval and isinstance(value, _basestr):
769  value = eval_text(value, table) # greedily eval value
770 
771  target_table._setitem(name, value, unevaluated=lazy_eval)
772 
773 
774 # Fill the table of the properties
775 def grab_properties(elt, table):
776  elt = first_child_element(elt)
777  while elt:
778  next = next_sibling_element(elt)
779  if elt.tagName in ['property', 'xacro:property'] \
780  and check_deprecated_tag(elt.tagName):
781  for name in ['default', 'remove']:
782  if name in elt.attributes.keys():
783  raise XacroException('Property attribute {} supported with in-order option only'.format(name))
784  grab_property(elt, table)
785  else:
786  grab_properties(elt, table)
787 
788  elt = next
789 
790 
791 LEXER = QuickLexer(DOLLAR_DOLLAR_BRACE=r"^\$\$+(\{|\()", # multiple $ in a row, followed by { or (
792  EXPR=r"^\$\{[^\}]*\}", # stuff starting with ${
793  EXTENSION=r"^\$\([^\)]*\)", # stuff starting with $(
794  TEXT=r"[^$]+|\$[^{($]+|\$$") # any text w/o $ or $ following any chars except {($ or single $
795 
796 
797 # evaluate text and return typed value
798 def eval_text(text, symbols):
799  def handle_expr(s):
800  try:
801  return safe_eval(eval_text(s, symbols), _global_symbols, symbols)
802  except Exception as e:
803  # re-raise as XacroException to add more context
804  raise XacroException(exc=e,
805  suffix=os.linesep + "when evaluating expression '%s'" % s)
806 
807  def handle_extension(s):
808  return eval_extension("$(%s)" % eval_text(s, symbols))
809 
810  results = []
811  lex = QuickLexer(LEXER)
812  lex.lex(text)
813  while lex.peek():
814  id = lex.peek()[0]
815  if id == lex.EXPR:
816  results.append(handle_expr(lex.next()[1][2:-1]))
817  elif id == lex.EXTENSION:
818  results.append(handle_extension(lex.next()[1][2:-1]))
819  elif id == lex.TEXT:
820  results.append(lex.next()[1])
821  elif id == lex.DOLLAR_DOLLAR_BRACE:
822  results.append(lex.next()[1][1:])
823  # return single element as is, i.e. typed
824  if len(results) == 1:
825  return results[0]
826  # otherwise join elements to a string
827  else:
828  return ''.join(map(unicode, results))
829 
830 
831 def eval_default_arg(forward_variable, default, symbols, macro):
832  if forward_variable is None:
833  return eval_text(default, symbols)
834  try:
835  return symbols[forward_variable]
836  except KeyError:
837  if default is not None:
838  return eval_text(default, symbols)
839  else:
840  raise XacroException("Undefined property to forward: " + forward_variable, macro=macro)
841 
842 
843 def handle_dynamic_macro_call(node, macros, symbols):
844  name, = reqd_attrs(node, ['macro'])
845  if not name:
846  raise XacroException("xacro:call is missing the 'macro' attribute")
847  name = unicode(eval_text(name, symbols))
848 
849  # remove 'macro' attribute and rename tag with resolved macro name
850  node.removeAttribute('macro')
851  node.tagName = 'xacro:' + name
852  # forward to handle_macro_call
853  try:
854  return handle_macro_call(node, macros, symbols)
855  except KeyError:
856  raise XacroException("unknown macro name '%s' in xacro:call" % name)
857 
858 
859 def resolve_macro(fullname, macros, symbols):
860  # split name into namespaces and real name
861  namespaces = fullname.split('.')
862  name = namespaces.pop(-1)
863 
864  def _resolve(namespaces, name, macros, symbols):
865  # traverse namespaces to actual macros+symbols dicts
866  for ns in namespaces:
867  macros = macros[ns]
868  symbols = symbols[ns]
869  return macros, symbols, macros[name]
870 
871  # try fullname and (namespaces, name) in this order
872  try:
873  return _resolve([], fullname, macros, symbols)
874  except KeyError:
875  if namespaces:
876  return _resolve(namespaces, name, macros, symbols)
877  else:
878  raise
879 
880 
881 def handle_macro_call(node, macros, symbols):
882  if node.tagName.startswith('xacro:'):
883  name = node.tagName[6:] # strip off 'xacro:' prefix
884  elif allow_non_prefixed_tags:
885  name = node.tagName
886  else: # require prefixed macro names
887  return False
888 
889  try:
890  macros, symbols, m = resolve_macro(name, macros, symbols)
891  if name is node.tagName: # no xacro prefix provided?
892  deprecated_tag(name)
893  body = m.body.cloneNode(deep=True)
894 
895  except KeyError:
896  # TODO If deprecation runs out, this test should be moved up front
897  if node.tagName == 'xacro:call':
898  return handle_dynamic_macro_call(node, macros, symbols)
899  return False # no macro
900 
901  macrostack.append(m)
902 
903  # Expand the macro
904  scoped_symbols = Table(symbols) # new local name space for macro evaluation
905  params = m.params[:] # deep copy macro's params list
906  for name, value in node.attributes.items():
907  if name not in params:
908  raise XacroException("Invalid parameter \"%s\"" % unicode(name), macro=m)
909  params.remove(name)
910  scoped_symbols._setitem(name, eval_text(value, symbols), unevaluated=False)
911  node.setAttribute(name, "") # suppress second evaluation in eval_all()
912 
913  # Evaluate block parameters in node
914  eval_all(node, macros, symbols)
915 
916  # Fetch block parameters, in order
917  block = first_child_element(node)
918  for param in params[:]:
919  if param[0] == '*':
920  if not block:
921  raise XacroException("Not enough blocks", macro=m)
922  params.remove(param)
923  scoped_symbols[param] = block
924  block = next_sibling_element(block)
925 
926  if block is not None:
927  raise XacroException("Unused block \"%s\"" % block.tagName, macro=m)
928 
929  # Try to load defaults for any remaining non-block parameters
930  for param in params[:]:
931  # block parameters are not supported for defaults
932  if param[0] == '*':
933  continue
934 
935  # get default
936  name, default = m.defaultmap.get(param, (None, None))
937  if name is not None or default is not None:
938  scoped_symbols._setitem(param, eval_default_arg(name, default, symbols, m), unevaluated=False)
939  params.remove(param)
940 
941  if params:
942  raise XacroException("Undefined parameters [%s]" % ",".join(params), macro=m)
943 
944  eval_all(body, macros, scoped_symbols)
945 
946  # Remove any comments directly before the macro call
947  remove_previous_comments(node)
948  # Lift all namespace attributes from the expanded body node to node's parent
949  import_xml_namespaces(node.parentNode, body.attributes)
950  # Replaces the macro node with the expansion
951  replace_node(node, by=body, content_only=True)
952 
953  macrostack.pop()
954  return True
955 
956 
957 def get_boolean_value(value, condition):
958  """
959  Return a boolean value that corresponds to the given Xacro condition value.
960  Values "true", "1" and "1.0" are supposed to be True.
961  Values "false", "0" and "0.0" are supposed to be False.
962  All other values raise an exception.
963 
964  :param value: The value to be evaluated. The value has to already be evaluated by Xacro.
965  :param condition: The original condition text in the XML.
966  :return: The corresponding boolean value, or a Python expression that, converted to boolean, corresponds to it.
967  :raises ValueError: If the condition value is incorrect.
968  """
969  try:
970  if isinstance(value, _basestr):
971  if value == 'true' or value == 'True':
972  return True
973  elif value == 'false' or value == 'False':
974  return False
975  else:
976  return bool(int(value))
977  else:
978  return bool(value)
979  except Exception:
980  raise XacroException("Xacro conditional \"%s\" evaluated to \"%s\", "
981  "which is not a boolean expression." % (condition, value))
982 
983 
984 _empty_text_node = xml.dom.minidom.getDOMImplementation().createDocument(None, "dummy", None).createTextNode('\n\n')
985 
986 
987 def remove_previous_comments(node):
988  """remove consecutive comments in front of the xacro-specific node"""
989  next = node.nextSibling
990  previous = node.previousSibling
991  while previous:
992  if previous.nodeType == xml.dom.Node.TEXT_NODE and \
993  previous.data.isspace() and previous.data.count('\n') <= 1:
994  previous = previous.previousSibling # skip a single empty text node (max 1 newline)
995 
996  if previous and previous.nodeType == xml.dom.Node.COMMENT_NODE:
997  comment = previous
998  previous = previous.previousSibling
999  node.parentNode.removeChild(comment)
1000  else:
1001  # insert empty text node to stop removing of comments in future calls
1002  # actually this moves the singleton instance to the new location
1003  if next and _empty_text_node != next:
1004  node.parentNode.insertBefore(_empty_text_node, next)
1005  return
1006 
1007 
1008 def eval_all(node, macros, symbols):
1009  """Recursively evaluate node, expanding macros, replacing properties, and evaluating expressions"""
1010  # evaluate the attributes
1011  for name, value in node.attributes.items():
1012  if name.startswith('xacro:'): # remove xacro:* attributes
1013  node.removeAttribute(name)
1014  else:
1015  result = unicode(eval_text(value, symbols))
1016  node.setAttribute(name, result)
1017 
1018  # remove xacro namespace definition
1019  try:
1020  node.removeAttribute('xmlns:xacro')
1021  except xml.dom.NotFoundErr:
1022  pass
1023 
1024  node = node.firstChild
1025  eval_comments = False
1026  while node:
1027  next = node.nextSibling
1028  if node.nodeType == xml.dom.Node.ELEMENT_NODE:
1029  eval_comments = False # any tag automatically disables comment evaluation
1030  if node.tagName in ['insert_block', 'xacro:insert_block'] \
1031  and check_deprecated_tag(node.tagName):
1032  name, = check_attrs(node, ['name'], [])
1033 
1034  if ("**" + name) in symbols:
1035  # Multi-block
1036  block = symbols['**' + name]
1037  content_only = True
1038  elif ("*" + name) in symbols:
1039  # Single block
1040  block = symbols['*' + name]
1041  content_only = False
1042  else:
1043  raise XacroException("Undefined block \"%s\"" % name)
1044 
1045  # cloning block allows to insert the same block multiple times
1046  block = block.cloneNode(deep=True)
1047  # recursively evaluate block
1048  eval_all(block, macros, symbols)
1049  replace_node(node, by=block, content_only=content_only)
1050 
1051  elif is_include(node):
1052  process_include(node, macros, symbols, eval_all)
1053 
1054  elif node.tagName in ['property', 'xacro:property'] \
1055  and check_deprecated_tag(node.tagName):
1056  grab_property(node, symbols)
1057 
1058  elif node.tagName in ['macro', 'xacro:macro'] \
1059  and check_deprecated_tag(node.tagName):
1060  grab_macro(node, macros)
1061 
1062  elif node.tagName in ['arg', 'xacro:arg'] \
1063  and check_deprecated_tag(node.tagName):
1064  name, default = check_attrs(node, ['name', 'default'], [])
1065  if name not in substitution_args_context['arg']:
1066  substitution_args_context['arg'][name] = unicode(eval_text(default, symbols))
1067 
1068  remove_previous_comments(node)
1069  replace_node(node, by=None)
1070 
1071  elif node.tagName == 'xacro:element':
1072  name = eval_text(*reqd_attrs(node, ['xacro:name']), symbols=symbols)
1073  if not name:
1074  raise XacroException("xacro:element: empty name")
1075 
1076  node.removeAttribute('xacro:name')
1077  node.nodeName = node.tagName = name
1078  continue # re-process the node with new tagName
1079 
1080  elif node.tagName == 'xacro:attribute':
1081  name, value = [eval_text(a, symbols) for a in reqd_attrs(node, ['name', 'value'])]
1082  if not name:
1083  raise XacroException("xacro:attribute: empty name")
1084 
1085  node.parentNode.setAttribute(name, value)
1086  replace_node(node, by=None)
1087 
1088  elif node.tagName in ['if', 'xacro:if', 'unless', 'xacro:unless'] \
1089  and check_deprecated_tag(node.tagName):
1090  remove_previous_comments(node)
1091  cond, = check_attrs(node, ['value'], [])
1092  keep = get_boolean_value(eval_text(cond, symbols), cond)
1093  if node.tagName in ['unless', 'xacro:unless']:
1094  keep = not keep
1095 
1096  if keep:
1097  eval_all(node, macros, symbols)
1098  replace_node(node, by=node, content_only=True)
1099  else:
1100  replace_node(node, by=None)
1101 
1102  elif handle_macro_call(node, macros, symbols):
1103  pass # handle_macro_call does all the work of expanding the macro
1104 
1105  else:
1106  # these are the non-xacro tags
1107  if node.tagName.startswith("xacro:"):
1108  raise XacroException("unknown macro name: %s" % node.tagName[6:])
1109 
1110  eval_all(node, macros, symbols)
1111 
1112  elif node.nodeType == xml.dom.Node.TEXT_NODE:
1113  node.data = unicode(eval_text(node.data, symbols))
1114  if node.data.strip():
1115  eval_comments = False # non-empty text disables comment evaluation
1116 
1117  elif node.nodeType == xml.dom.Node.COMMENT_NODE:
1118  if "xacro:eval-comments" in node.data:
1119  eval_comments = "xacro:eval-comments:off" not in node.data
1120  replace_node(node, by=None) # drop this comment
1121  elif eval_comments:
1122  node.data = unicode(eval_text(node.data, symbols))
1123  else:
1124  pass # leave comment as is
1125 
1126  node = next
1127 
1128 
1129 def parse(inp, filename=None):
1130  """
1131  Parse input or filename into a DOM tree.
1132  If inp is None, open filename and load from there.
1133  Otherwise, parse inp, either as string or file object.
1134  If inp is already a DOM tree, this function is a noop.
1135  :return:xml.dom.minidom.Document
1136  :raise: xml.parsers.expat.ExpatError
1137  """
1138  f = None
1139  if inp is None:
1140  try:
1141  inp = f = open(filename)
1142  except IOError as e:
1143  # do not report currently processed file as "in file ..."
1144  filestack.pop()
1145  raise XacroException(e.strerror + ": " + e.filename, exc=e)
1146 
1147  try:
1148  if isinstance(inp, _basestr):
1149  return xml.dom.minidom.parseString(inp)
1150  elif hasattr(inp, 'read'):
1151  return xml.dom.minidom.parse(inp)
1152  return inp
1153 
1154  finally:
1155  if f:
1156  f.close()
1157 
1158 
1159 def process_doc(doc,
1160  in_order=True, just_deps=False, just_includes=False,
1161  mappings=None, xacro_ns=True, **kwargs):
1162  global verbosity, do_check_order
1163  verbosity = kwargs.get('verbosity', verbosity)
1164  do_check_order = kwargs.get('do_check_order', do_check_order)
1165 
1166  # set substitution args
1167  if mappings is None:
1168  mappings = {}
1169  substitution_args_context['arg'] = mappings
1170 
1171  global allow_non_prefixed_tags
1172  allow_non_prefixed_tags = xacro_ns
1173 
1174  # if not yet defined: initialize filestack
1175  if not filestack:
1176  init_stacks(None)
1177 
1178  # inorder processing requires to process the whole document for deps too
1179  # because filenames might be specified via properties or macro parameters
1180  if (just_deps or just_includes) and not in_order:
1181  process_includes(doc.documentElement)
1182  return
1183 
1184  macros = {}
1185  symbols = Table()
1186  if not in_order:
1187  # process includes, macros, and properties before evaluating stuff
1188  process_includes(doc.documentElement)
1189  grab_macros(doc, macros)
1190  grab_properties(doc, symbols)
1191 
1192  # apply xacro:targetNamespace as global xmlns (if defined)
1193  targetNS = doc.documentElement.getAttribute('xacro:targetNamespace')
1194  if targetNS:
1195  doc.documentElement.removeAttribute('xacro:targetNamespace')
1196  doc.documentElement.setAttribute('xmlns', targetNS)
1197 
1198  eval_all(doc.documentElement, macros, symbols)
1199 
1200  # reset substitution args
1201  substitution_args_context['arg'] = {}
1202 
1203  if do_check_order and symbols.redefined:
1204  warning("Document is incompatible to in-order processing.")
1205  warning("The following properties were redefined after usage:")
1206  for k, v in symbols.redefined.items():
1207  message(k, "redefined in", v, color='yellow')
1208 
1209 
1210 def open_output(output_filename):
1211  if output_filename is None:
1212  return sys.stdout
1213  else:
1214  dir_name = os.path.dirname(output_filename)
1215  if dir_name:
1216  try:
1217  os.makedirs(dir_name)
1218  except os.error:
1219  # errors occur when dir_name exists or creation failed
1220  # ignore error here; opening of file will fail if directory is still missing
1221  pass
1222 
1223  try:
1224  return open(output_filename, 'w')
1225  except IOError as e:
1226  raise XacroException("Failed to open output:", exc=e)
1227 
1228 
1229 def print_location():
1230  msg = 'when instantiating macro:'
1231  for m in reversed(macrostack or []):
1232  name = m.body.getAttribute('name')
1233  location = '({file})'.format(file = m.history[-1][-1] or '???')
1234  print(msg, name, location, file=sys.stderr)
1235  msg = 'instantiated from:'
1236 
1237  msg = 'in file:' if macrostack else 'when processing file:'
1238  for f in reversed(filestack or []):
1239  if f is None:
1240  f = 'string'
1241  print(msg, f, file=sys.stderr)
1242  msg = 'included from:'
1243 
1244 
1245 def process_file(input_file_name, **kwargs):
1246  """main processing pipeline"""
1247  # initialize file stack for error-reporting
1248  init_stacks(input_file_name)
1249  # parse the document into a xml.dom tree
1250  doc = parse(None, input_file_name)
1251  # perform macro replacement
1252  process_doc(doc, **kwargs)
1253 
1254  # add xacro auto-generated banner
1255  banner = [xml.dom.minidom.Comment(c) for c in
1256  [" %s " % ('=' * 83),
1257  " | This document was autogenerated by xacro from %-30s | " % input_file_name,
1258  " | EDITING THIS FILE BY HAND IS NOT RECOMMENDED %-30s | " % "",
1259  " %s " % ('=' * 83)]]
1260  first = doc.firstChild
1261  for comment in banner:
1262  doc.insertBefore(comment, first)
1263 
1264  return doc
1265 
1266 
1267 _global_symbols = create_global_symbols()
1268 
1269 
1270 def main():
1271  opts, input_file_name = process_args(sys.argv[1:])
1272  try:
1273  # open and process file
1274  doc = process_file(input_file_name, **vars(opts))
1275  # open the output file
1276  out = open_output(opts.output)
1277 
1278  # error handling
1279  except xml.parsers.expat.ExpatError as e:
1280  error("XML parsing error: %s" % unicode(e), alt_text=None)
1281  if verbosity > 0:
1282  print_location()
1283  print(file=sys.stderr) # add empty separator line before error
1284  print("Check that:", file=sys.stderr)
1285  print(" - Your XML is well-formed", file=sys.stderr)
1286  print(" - You have the xacro xmlns declaration:",
1287  "xmlns:xacro=\"http://www.ros.org/wiki/xacro\"", file=sys.stderr)
1288  sys.exit(2) # indicate failure, but don't print stack trace on XML errors
1289 
1290  except Exception as e:
1291  msg = unicode(e)
1292  if not msg:
1293  msg = repr(e)
1294  error(msg)
1295  if verbosity > 0:
1296  print_location()
1297  if verbosity > 1:
1298  print(file=sys.stderr) # add empty separator line before error
1299  raise # create stack trace
1300  else:
1301  sys.exit(2) # gracefully exit with error condition
1302 
1303  # special output mode
1304  if opts.just_deps:
1305  out.write(" ".join(set(all_includes)))
1306  print()
1307  return
1308 
1309  # write output
1310  out.write(doc.toprettyxml(indent=' ', **encoding))
1311  print()
1312  # only close output file, but not stdout
1313  if opts.output:
1314  out.close()
def __init__(self)
Definition: __init__.py:320
def __init__(self, args, kwargs)
Definition: __init__.py:466
def _setitem(self, key, value, unevaluated)
Definition: __init__.py:402
def check_deprecated_tag(tag_name)
Definition: __init__.py:304
def process_includes(elt, macros=None, symbols=None)
Definition: __init__.py:608
def grab_properties(elt, table)
Definition: __init__.py:775
def __init__(self, msg=None, suffix=None, exc=None, macro=None)
Definition: __init__.py:253
def __setitem__(self, key, value)
Definition: __init__.py:422
def load_yaml(filename)
Definition: __init__.py:132
def __str__(self)
Definition: __init__.py:259
def construct_angle_radians(loader, node)
Definition: __init__.py:118
def __str__(self)
Definition: __init__.py:437
def next_sibling_element(node)
Definition: xmlutils.py:43
def _eval_literal(value)
Definition: __init__.py:359
def abs_filename_spec(filename_spec)
Definition: __init__.py:74
def init_stacks(file)
Definition: __init__.py:67
def __getattr__(self, item)
Definition: __init__.py:109
def process_include(elt, macros, symbols, func)
Definition: __init__.py:570
def peek(self)
Definition: __init__.py:489
def grab_property(elt, table)
Definition: __init__.py:713
def __init__(self, args, kwargs)
Definition: __init__.py:471
def construct_angle_degrees(loader, node)
Definition: __init__.py:127
def first_child_element(elt)
Definition: xmlutils.py:36
def is_valid_name(name)
Definition: __init__.py:620
def tokenize(s, sep=', skip_empty=True)
Definition: __init__.py:152
def create_global_symbols()
Definition: __init__.py:164
def import_xml_namespaces(parent, attributes)
Definition: __init__.py:556
def grab_macro(elt, macros)
Definition: __init__.py:667
def safe_eval(expr, globals, locals=None)
Definition: __init__.py:237
def root(self)
Definition: __init__.py:444
def error(args, kwargs)
Definition: color.py:68
def next(self)
Definition: __init__.py:492
def reqd_attrs(tag, attrs)
Definition: xmlutils.py:96
def __delitem__(self, key)
Definition: __init__.py:425
def message(msg, args, kwargs)
Definition: color.py:55
def __getattr__(self, item)
Definition: __init__.py:453
def replace_node(node, by, content_only=False)
Definition: xmlutils.py:50
def is_include(elt)
Definition: __init__.py:510
def __contains__(self, key)
Definition: __init__.py:432
def warning(args, kwargs)
Definition: color.py:62
def process_args(argv, require_input=True)
Definition: cli.py:65
def __init__(self, parent=None)
Definition: __init__.py:346
def __getitem__(self, idx)
Definition: __init__.py:99
def grab_macros(elt, macros)
Definition: __init__.py:700
def deprecated_tag(tag_name=None, _issued=[False])
Definition: __init__.py:287
def check_attrs(tag, required, optional)
Definition: __init__.py:267
def parse_macro_arg(s)
Definition: __init__.py:644
def __getitem__(self, key)
Definition: __init__.py:394
def __init__(self, parent=None)
Definition: __init__.py:461
def lex(self, str)
Definition: __init__.py:484
unicode
Definition: __init__.py:55
def get_include_files(filename_spec, symbols)
Definition: __init__.py:532
def __iter__(self)
Definition: __init__.py:102
def opt_attrs(tag, attrs)
Definition: xmlutils.py:87
def _resolve_(self, key)
Definition: __init__.py:373
def eval_extension(s)
Definition: __init__.py:327


xacro
Author(s): Stuart Glaser, William Woodall, Robert Haschke
autogenerated on Thu May 25 2023 02:45:08