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