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