$search
00001 #! /usr/bin/env python 00002 # Copyright (c) 2008, Willow Garage, Inc. 00003 # All rights reserved. 00004 # 00005 # Redistribution and use in source and binary forms, with or without 00006 # modification, are permitted provided that the following conditions are met: 00007 # 00008 # * Redistributions of source code must retain the above copyright 00009 # notice, this list of conditions and the following disclaimer. 00010 # * Redistributions in binary form must reproduce the above copyright 00011 # notice, this list of conditions and the following disclaimer in the 00012 # documentation and/or other materials provided with the distribution. 00013 # * Neither the name of the Willow Garage, Inc. nor the names of its 00014 # contributors may be used to endorse or promote products derived from 00015 # this software without specific prior written permission. 00016 # 00017 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 00018 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 00019 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 00020 # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 00021 # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 00022 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 00023 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 00024 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 00025 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 00026 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 00027 # POSSIBILITY OF SUCH DAMAGE. 00028 00029 # Author: Stuart Glaser 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 # dual electric/fuerte compatibility 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 # Better pretty printing of xml 00056 # Taken from http://ronrothman.com/public/leftbraned/xml-dom-minidom-toprettyxml-and-silly-whitespace/ 00057 def fixed_writexml(self, writer, indent="", addindent="", newl=""): 00058 # indent = current indentation 00059 # addindent = indentation to add to higher levels 00060 # newl = newline string 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: # 3: 00081 node.writexml(writer,indent+addindent,addindent,newl) 00082 #node.writexml(writer,indent+addindent,addindent,newl) 00083 writer.write("%s</%s>%s" % (indent,self.tagName,newl)) 00084 else: 00085 writer.write("/>%s"%(newl)) 00086 # replace minidom's function with ours 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 # Pre-order traversal of the elements 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 # Pre-order traversal of all the nodes 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 ## @throws XacroException if a parsing error occurs with an included document 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 # Replaces the include tag with the elements of the included file 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 # Grabs all the declared namespaces of the included document 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 # Makes sure the final document declares all the namespaces of the included documents. 00228 for k,v in namespaces.items(): 00229 doc.documentElement.setAttribute(k, v) 00230 00231 # Returns a dictionary: { macro_name => macro_xml_block } 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 # Returns a Table of the properties 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 #debug 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 #sys.stderr.write("Could not find symbol: %s\n" % str(ex)) 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 # Expands macros, replaces properties, and evaluates expressions 00417 def eval_all(root, macros, symbols): 00418 # Evaluates the attributes for the root node 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 # Expands the macro 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 # Pulls out the block arguments, in order 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 # Replaces the macro node with the expansion 00460 for e in list(child_elements(body)): # Ew 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 # Multi-block 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 # Single block 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 # Evals the attributes 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 # Expands everything except includes 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 #doc.writexml(output, newl = "\n") 00575 print 00576 00577