00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033 from __future__ import print_function
00034
00035 import getopt
00036 import glob
00037 import os
00038 import re
00039 import string
00040 import sys
00041 import xml
00042
00043 from xml.dom.minidom import parse
00044
00045 from roslaunch import substitution_args
00046 from rosgraph.names import load_mappings
00047
00048 try:
00049 _basestr = basestring
00050 except NameError:
00051 _basestr = str
00052
00053
00054 substitution_args_context = {}
00055
00056
00057 class XacroException(Exception):
00058 pass
00059
00060
00061 def isnumber(x):
00062 return hasattr(x, '__int__')
00063
00064
00065 def eval_extension(str):
00066 return substitution_args.resolve_args(str, context=substitution_args_context, resolve_anon=False)
00067
00068
00069
00070
00071 def fixed_writexml(self, writer, indent="", addindent="", newl=""):
00072
00073
00074
00075 writer.write(indent + "<" + self.tagName)
00076
00077 attrs = self._get_attributes()
00078 a_names = list(attrs.keys())
00079 a_names.sort()
00080
00081 for a_name in a_names:
00082 writer.write(" %s=\"" % a_name)
00083 xml.dom.minidom._write_data(writer, attrs[a_name].value)
00084 writer.write("\"")
00085 if self.childNodes:
00086 if len(self.childNodes) == 1 \
00087 and self.childNodes[0].nodeType == xml.dom.minidom.Node.TEXT_NODE:
00088 writer.write(">")
00089 self.childNodes[0].writexml(writer, "", "", "")
00090 writer.write("</%s>%s" % (self.tagName, newl))
00091 return
00092 writer.write(">%s" % (newl))
00093 for node in self.childNodes:
00094 if node.nodeType is not xml.dom.minidom.Node.TEXT_NODE:
00095 node.writexml(writer, indent + addindent, addindent, newl)
00096
00097 writer.write("%s</%s>%s" % (indent, self.tagName, newl))
00098 else:
00099 writer.write("/>%s" % (newl))
00100
00101 xml.dom.minidom.Element.writexml = fixed_writexml
00102
00103
00104 class Table:
00105 def __init__(self, parent=None):
00106 self.parent = parent
00107 self.table = {}
00108
00109 def __getitem__(self, key):
00110 if key in self.table:
00111 return self.table[key]
00112 elif self.parent:
00113 return self.parent[key]
00114 else:
00115 raise KeyError(key)
00116
00117 def __setitem__(self, key, value):
00118 self.table[key] = value
00119
00120 def __contains__(self, key):
00121 return \
00122 key in self.table or \
00123 (self.parent and key in self.parent)
00124
00125
00126 class QuickLexer(object):
00127 def __init__(self, **res):
00128 self.str = ""
00129 self.top = None
00130 self.res = []
00131 for k, v in res.items():
00132 self.__setattr__(k, len(self.res))
00133 self.res.append(v)
00134
00135 def lex(self, str):
00136 self.str = str
00137 self.top = None
00138 self.next()
00139
00140 def peek(self):
00141 return self.top
00142
00143 def next(self):
00144 result = self.top
00145 self.top = None
00146 for i in range(len(self.res)):
00147 m = re.match(self.res[i], self.str)
00148 if m:
00149 self.top = (i, m.group(0))
00150 self.str = self.str[m.end():]
00151 break
00152 return result
00153
00154
00155 def first_child_element(elt):
00156 c = elt.firstChild
00157 while c:
00158 if c.nodeType == xml.dom.Node.ELEMENT_NODE:
00159 return c
00160 c = c.nextSibling
00161 return None
00162
00163
00164 def next_sibling_element(elt):
00165 c = elt.nextSibling
00166 while c:
00167 if c.nodeType == xml.dom.Node.ELEMENT_NODE:
00168 return c
00169 c = c.nextSibling
00170 return None
00171
00172
00173
00174 def next_element(elt):
00175 child = first_child_element(elt)
00176 if child:
00177 return child
00178 while elt and elt.nodeType == xml.dom.Node.ELEMENT_NODE:
00179 next = next_sibling_element(elt)
00180 if next:
00181 return next
00182 elt = elt.parentNode
00183 return None
00184
00185
00186
00187 def next_node(node):
00188 if node.firstChild:
00189 return node.firstChild
00190 while node:
00191 if node.nextSibling:
00192 return node.nextSibling
00193 node = node.parentNode
00194 return None
00195
00196
00197 def child_elements(elt):
00198 c = elt.firstChild
00199 while c:
00200 if c.nodeType == xml.dom.Node.ELEMENT_NODE:
00201 yield c
00202 c = c.nextSibling
00203
00204 all_includes = []
00205
00206
00207 deprecated_include_msg = """DEPRECATED IN HYDRO:
00208 The <include> tag should be prepended with 'xacro' if that is the intended use
00209 of it, such as <xacro:include ...>. Use the following script to fix incorrect
00210 xacro includes:
00211 sed -i 's/<include/<xacro:include/g' `find . -iname *.xacro`"""
00212
00213 include_no_matches_msg = """Include tag filename spec \"{}\" matched no files."""
00214
00215
00216
00217 def process_includes(doc, base_dir):
00218 namespaces = {}
00219 previous = doc.documentElement
00220 elt = next_element(previous)
00221 while elt:
00222
00223
00224 is_include = False
00225 if elt.tagName == 'xacro:include' or elt.tagName == 'include':
00226
00227 is_include = True
00228
00229 if elt.tagName == 'include':
00230
00231
00232
00233
00234
00235 if elt.childNodes and not (len(elt.childNodes) == 1 and
00236 elt.childNodes[0].nodeType == elt.TEXT_NODE):
00237
00238 is_include = False
00239 else:
00240
00241 print(deprecated_include_msg, file=sys.stderr)
00242
00243
00244 if is_include:
00245 filename_spec = eval_text(elt.getAttribute('filename'), {})
00246 if not os.path.isabs(filename_spec):
00247 filename_spec = os.path.join(base_dir, filename_spec)
00248
00249 if re.search('[*[?]+', filename_spec):
00250
00251 filenames = sorted(glob.glob(filename_spec))
00252 if len(filenames) == 0:
00253 print(include_no_matches_msg.format(filename_spec), file=sys.stderr)
00254 else:
00255
00256 filenames = [filename_spec]
00257
00258 for filename in filenames:
00259 global all_includes
00260 all_includes.append(filename)
00261 try:
00262 with open(filename) as f:
00263 try:
00264 included = parse(f)
00265 except Exception as e:
00266 raise XacroException(
00267 "included file \"%s\" generated an error during XML parsing: %s"
00268 % (filename, str(e)))
00269 except IOError as e:
00270 raise XacroException("included file \"%s\" could not be opened: %s" % (filename, str(e)))
00271
00272
00273 for c in child_elements(included.documentElement):
00274 elt.parentNode.insertBefore(c.cloneNode(1), elt)
00275
00276
00277 for name, value in included.documentElement.attributes.items():
00278 if name.startswith('xmlns:'):
00279 namespaces[name] = value
00280
00281 elt.parentNode.removeChild(elt)
00282 elt = None
00283 else:
00284 previous = elt
00285
00286 elt = next_element(previous)
00287
00288
00289 for k, v in namespaces.items():
00290 doc.documentElement.setAttribute(k, v)
00291
00292
00293
00294 def grab_macros(doc):
00295 macros = {}
00296
00297 previous = doc.documentElement
00298 elt = next_element(previous)
00299 while elt:
00300 if elt.tagName == 'macro' or elt.tagName == 'xacro:macro':
00301 name = elt.getAttribute('name')
00302
00303 macros[name] = elt
00304 macros['xacro:' + name] = elt
00305
00306 elt.parentNode.removeChild(elt)
00307 elt = None
00308 else:
00309 previous = elt
00310
00311 elt = next_element(previous)
00312 return macros
00313
00314
00315
00316 def grab_properties(doc):
00317 table = Table()
00318
00319 previous = doc.documentElement
00320 elt = next_element(previous)
00321 while elt:
00322 if elt.tagName == 'property' or elt.tagName == 'xacro:property':
00323 name = elt.getAttribute('name')
00324 value = None
00325
00326 if elt.hasAttribute('value'):
00327 value = elt.getAttribute('value')
00328 else:
00329 name = '**' + name
00330 value = elt
00331
00332 bad = string.whitespace + "${}"
00333 has_bad = False
00334 for b in bad:
00335 if b in name:
00336 has_bad = True
00337 break
00338
00339 if has_bad:
00340 sys.stderr.write('Property names may not have whitespace, ' +
00341 '"{", "}", or "$" : "' + name + '"')
00342 else:
00343 table[name] = value
00344
00345 elt.parentNode.removeChild(elt)
00346 elt = None
00347 else:
00348 previous = elt
00349
00350 elt = next_element(previous)
00351 return table
00352
00353
00354 def eat_ignore(lex):
00355 while lex.peek() and lex.peek()[0] == lex.IGNORE:
00356 lex.next()
00357
00358
00359 def eval_lit(lex, symbols):
00360 eat_ignore(lex)
00361 if lex.peek()[0] == lex.NUMBER:
00362 return float(lex.next()[1])
00363 if lex.peek()[0] == lex.SYMBOL:
00364 try:
00365 value = symbols[lex.next()[1]]
00366 except KeyError as ex:
00367 raise XacroException("Property wasn't defined: %s" % str(ex))
00368 if not (isnumber(value) or isinstance(value, _basestr)):
00369 raise XacroException("WTF2")
00370 try:
00371 return int(value)
00372 except:
00373 try:
00374 return float(value)
00375 except:
00376 return value
00377 raise XacroException("Bad literal")
00378
00379
00380 def eval_factor(lex, symbols):
00381 eat_ignore(lex)
00382
00383 neg = 1
00384 if lex.peek()[1] == '-':
00385 lex.next()
00386 neg = -1
00387
00388 if lex.peek()[0] in [lex.NUMBER, lex.SYMBOL]:
00389 return neg * eval_lit(lex, symbols)
00390 if lex.peek()[0] == lex.LPAREN:
00391 lex.next()
00392 eat_ignore(lex)
00393 result = eval_expr(lex, symbols)
00394 eat_ignore(lex)
00395 if lex.next()[0] != lex.RPAREN:
00396 raise XacroException("Unmatched left paren")
00397 eat_ignore(lex)
00398 return neg * result
00399
00400 raise XacroException("Misplaced operator")
00401
00402
00403 def eval_term(lex, symbols):
00404 eat_ignore(lex)
00405
00406 result = 0
00407 if lex.peek()[0] in [lex.NUMBER, lex.SYMBOL, lex.LPAREN] \
00408 or lex.peek()[1] == '-':
00409 result = eval_factor(lex, symbols)
00410
00411 eat_ignore(lex)
00412 while lex.peek() and lex.peek()[1] in ['*', '/']:
00413 op = lex.next()[1]
00414 n = eval_factor(lex, symbols)
00415
00416 if op == '*':
00417 result = float(result) * float(n)
00418 elif op == '/':
00419 result = float(result) / float(n)
00420 else:
00421 raise XacroException("WTF")
00422 eat_ignore(lex)
00423 return result
00424
00425
00426 def eval_expr(lex, symbols):
00427 eat_ignore(lex)
00428
00429 op = None
00430 if lex.peek()[0] == lex.OP:
00431 op = lex.next()[1]
00432 if not op in ['+', '-']:
00433 raise XacroException("Invalid operation. Must be '+' or '-'")
00434
00435 result = eval_term(lex, symbols)
00436 if op == '-':
00437 result = -float(result)
00438
00439 eat_ignore(lex)
00440 while lex.peek() and lex.peek()[1] in ['+', '-']:
00441 op = lex.next()[1]
00442 n = eval_term(lex, symbols)
00443
00444 if op == '+':
00445 result = float(result) + float(n)
00446 if op == '-':
00447 result = float(result) - float(n)
00448 eat_ignore(lex)
00449 return result
00450
00451
00452 def eval_text(text, symbols):
00453 def handle_expr(s):
00454 lex = QuickLexer(IGNORE=r"\s+",
00455 NUMBER=r"(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?",
00456 SYMBOL=r"[a-zA-Z_]\w*",
00457 OP=r"[\+\-\*/^]",
00458 LPAREN=r"\(",
00459 RPAREN=r"\)")
00460 lex.lex(s)
00461 return eval_expr(lex, symbols)
00462
00463 def handle_extension(s):
00464 return eval_extension("$(%s)" % s)
00465
00466 results = []
00467 lex = QuickLexer(DOLLAR_DOLLAR_BRACE=r"\$\$+\{",
00468 EXPR=r"\$\{[^\}]*\}",
00469 EXTENSION=r"\$\([^\)]*\)",
00470 TEXT=r"([^\$]|\$[^{(]|\$$)+")
00471 lex.lex(text)
00472 while lex.peek():
00473 if lex.peek()[0] == lex.EXPR:
00474 results.append(handle_expr(lex.next()[1][2:-1]))
00475 elif lex.peek()[0] == lex.EXTENSION:
00476 results.append(handle_extension(lex.next()[1][2:-1]))
00477 elif lex.peek()[0] == lex.TEXT:
00478 results.append(lex.next()[1])
00479 elif lex.peek()[0] == lex.DOLLAR_DOLLAR_BRACE:
00480 results.append(lex.next()[1][1:])
00481 return ''.join(map(str, results))
00482
00483
00484
00485 def eval_all(root, macros, symbols):
00486
00487 for at in root.attributes.items():
00488 result = eval_text(at[1], symbols)
00489 root.setAttribute(at[0], result)
00490
00491 previous = root
00492 node = next_node(previous)
00493 while node:
00494 if node.nodeType == xml.dom.Node.ELEMENT_NODE:
00495 if node.tagName in macros:
00496 body = macros[node.tagName].cloneNode(deep=True)
00497 params = body.getAttribute('params').split()
00498
00499
00500 scoped = Table(symbols)
00501 for name, value in node.attributes.items():
00502 if not name in params:
00503 raise XacroException("Invalid parameter \"%s\" while expanding macro \"%s\"" %
00504 (str(name), str(node.tagName)))
00505 params.remove(name)
00506 scoped[name] = eval_text(value, symbols)
00507
00508
00509 cloned = node.cloneNode(deep=True)
00510 eval_all(cloned, macros, symbols)
00511 block = cloned.firstChild
00512 for param in params[:]:
00513 if param[0] == '*':
00514 while block and block.nodeType != xml.dom.Node.ELEMENT_NODE:
00515 block = block.nextSibling
00516 if not block:
00517 raise XacroException("Not enough blocks while evaluating macro %s" % str(node.tagName))
00518 params.remove(param)
00519 scoped[param] = block
00520 block = block.nextSibling
00521
00522 if params:
00523 raise XacroException("Some parameters were not set for macro %s" %
00524 str(node.tagName))
00525 eval_all(body, macros, scoped)
00526
00527
00528 for e in list(child_elements(body)):
00529 node.parentNode.insertBefore(e, node)
00530 node.parentNode.removeChild(node)
00531
00532 node = None
00533 elif node.tagName == 'insert_block' or node.tagName == 'xacro:insert_block':
00534 name = node.getAttribute('name')
00535
00536 if ("**" + name) in symbols:
00537
00538 block = symbols['**' + name]
00539
00540 for e in list(child_elements(block)):
00541 node.parentNode.insertBefore(e.cloneNode(deep=True), node)
00542 node.parentNode.removeChild(node)
00543 elif ("*" + name) in symbols:
00544
00545 block = symbols['*' + name]
00546
00547 node.parentNode.insertBefore(block.cloneNode(deep=True), node)
00548 node.parentNode.removeChild(node)
00549 else:
00550 raise XacroException("Block \"%s\" was never declared" % name)
00551
00552 node = None
00553 elif node.tagName == 'if' or node.tagName == 'xacro:if':
00554 value = eval_text(node.getAttribute('value'), symbols)
00555 try:
00556 value = int(float(value))
00557 except ValueError:
00558 pass
00559 if value == 1 or value == 'true':
00560 for e in list(child_elements(node)):
00561 cloned = node.cloneNode(deep=True)
00562 eval_all(cloned, macros, symbols)
00563 node.parentNode.insertBefore(e, node)
00564 elif value != 0 and value != 'false':
00565 raise XacroException("""\
00566 Xacro conditional evaluated to \"%s\". Acceptable evaluations are one of [\"1\",\"true\",\"0\",\"false\"]\
00567 """ % value)
00568 node.parentNode.removeChild(node)
00569 elif node.tagName == 'unless' or node.tagName == 'xacro:unless':
00570 value = eval_text(node.getAttribute('value'), symbols)
00571 if value == 0 or value == 'false':
00572 for e in list(child_elements(node)):
00573 cloned = node.cloneNode(deep=True)
00574 eval_all(cloned, macros, symbols)
00575 node.parentNode.insertBefore(e, node)
00576 elif value != 1 and value != 'true':
00577 raise XacroException("""\
00578 Xacro conditional evaluated to \"%s\". Acceptable evaluations are one of [\"1\",\"true\",\"0\",\"false\"]\
00579 """ % value)
00580 node.parentNode.removeChild(node)
00581 else:
00582
00583 for at in node.attributes.items():
00584 result = eval_text(at[1], symbols)
00585 node.setAttribute(at[0], result)
00586 previous = node
00587 elif node.nodeType == xml.dom.Node.TEXT_NODE:
00588 node.data = eval_text(node.data, symbols)
00589 previous = node
00590 else:
00591 previous = node
00592
00593 node = next_node(previous)
00594 return macros
00595
00596
00597
00598 def eval_self_contained(doc):
00599 macros = grab_macros(doc)
00600 symbols = grab_properties(doc)
00601 eval_all(doc.documentElement, macros, symbols)
00602
00603
00604 def print_usage(exit_code=0):
00605 print("Usage: %s [-o <output>] <input>" % 'xacro.py')
00606 print(" %s --deps Prints dependencies" % 'xacro.py')
00607 print(" %s --includes Only evalutes includes" % 'xacro.py')
00608 sys.exit(exit_code)
00609
00610
00611 def set_substitution_args_context(context={}):
00612 substitution_args_context['arg'] = context
00613
00614
00615 def main():
00616 try:
00617 opts, args = getopt.gnu_getopt(sys.argv[1:], "ho:", ['deps', 'includes'])
00618 except getopt.GetoptError as err:
00619 print(str(err))
00620 print_usage(2)
00621
00622 just_deps = False
00623 just_includes = False
00624
00625 output = sys.stdout
00626 for o, a in opts:
00627 if o == '-h':
00628 print_usage(0)
00629 elif o == '-o':
00630 output = open(a, 'w')
00631 elif o == '--deps':
00632 just_deps = True
00633 elif o == '--includes':
00634 just_includes = True
00635
00636 if len(args) < 1:
00637 print("No input given")
00638 print_usage(2)
00639
00640
00641 set_substitution_args_context(load_mappings(sys.argv))
00642
00643 f = open(args[0])
00644 doc = None
00645 try:
00646 doc = parse(f)
00647 except xml.parsers.expat.ExpatError:
00648 sys.stderr.write("Expat parsing error. Check that:\n")
00649 sys.stderr.write(" - Your XML is correctly formed\n")
00650 sys.stderr.write(" - You have the xacro xmlns declaration: " +
00651 "xmlns:xacro=\"http://www.ros.org/wiki/xacro\"\n")
00652 sys.stderr.write("\n")
00653 raise
00654 finally:
00655 f.close()
00656
00657 process_includes(doc, os.path.dirname(args[0]))
00658 if just_deps:
00659 for inc in all_includes:
00660 sys.stdout.write(inc + " ")
00661 sys.stdout.write("\n")
00662 elif just_includes:
00663 doc.writexml(output)
00664 print()
00665 else:
00666 eval_self_contained(doc)
00667 banner = [xml.dom.minidom.Comment(c) for c in
00668 [" %s " % ('=' * 83),
00669 " | This document was autogenerated by xacro from %-30s | " % args[0],
00670 " | EDITING THIS FILE BY HAND IS NOT RECOMMENDED %-30s | " % "",
00671 " %s " % ('=' * 83)]]
00672 first = doc.firstChild
00673 for comment in banner:
00674 doc.insertBefore(comment, first)
00675
00676 output.write(doc.toprettyxml(indent=' '))
00677 print()