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