xacro_standalone.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705
  1. #! /usr/bin/env python
  2. # Copyright (c) 2013, Willow Garage, Inc.
  3. # Copyright (c) 2014, Open Source Robotics Foundation, Inc.
  4. # All rights reserved.
  5. #
  6. # Redistribution and use in source and binary forms, with or without
  7. # modification, are permitted provided that the following conditions are met:
  8. #
  9. # * Redistributions of source code must retain the above copyright
  10. # notice, this list of conditions and the following disclaimer.
  11. # * Redistributions in binary form must reproduce the above copyright
  12. # notice, this list of conditions and the following disclaimer in the
  13. # documentation and/or other materials provided with the distribution.
  14. # * Neither the name of the Open Source Robotics Foundation, Inc.
  15. # nor the names of its contributors may be used to endorse or promote
  16. # products derived from this software without specific prior
  17. # written permission.
  18. #
  19. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  20. # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  21. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  22. # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  23. # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  24. # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  25. # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  26. # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  27. # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  28. # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  29. # POSSIBILITY OF SUCH DAMAGE.
  30. # Author: Stuart Glaser
  31. # Maintainer: William Woodall <[email protected]>
  32. from __future__ import print_function
  33. import getopt
  34. import glob
  35. import os
  36. import re
  37. import string
  38. import sys
  39. import xml
  40. from xml.dom.minidom import parse
  41. try:
  42. _basestr = basestring
  43. except NameError:
  44. _basestr = str
  45. # Dictionary of subtitution args
  46. substitution_args_context = {}
  47. class XacroException(Exception):
  48. pass
  49. def isnumber(x):
  50. return hasattr(x, '__int__')
  51. # Better pretty printing of xml
  52. # Taken from http://ronrothman.com/public/leftbraned/xml-dom-minidom-toprettyxml-and-silly-whitespace/
  53. def fixed_writexml(self, writer, indent="", addindent="", newl=""):
  54. # indent = current indentation
  55. # addindent = indentation to add to higher levels
  56. # newl = newline string
  57. writer.write(indent + "<" + self.tagName)
  58. attrs = self._get_attributes()
  59. a_names = list(attrs.keys())
  60. a_names.sort()
  61. for a_name in a_names:
  62. writer.write(" %s=\"" % a_name)
  63. xml.dom.minidom._write_data(writer, attrs[a_name].value)
  64. writer.write("\"")
  65. if self.childNodes:
  66. if len(self.childNodes) == 1 \
  67. and self.childNodes[0].nodeType == xml.dom.minidom.Node.TEXT_NODE:
  68. writer.write(">")
  69. self.childNodes[0].writexml(writer, "", "", "")
  70. writer.write("</%s>%s" % (self.tagName, newl))
  71. return
  72. writer.write(">%s" % (newl))
  73. for node in self.childNodes:
  74. # skip whitespace-only text nodes
  75. if node.nodeType == xml.dom.minidom.Node.TEXT_NODE and \
  76. not node.data.strip():
  77. continue
  78. node.writexml(writer, indent + addindent, addindent, newl)
  79. writer.write("%s</%s>%s" % (indent, self.tagName, newl))
  80. else:
  81. writer.write("/>%s" % (newl))
  82. # replace minidom's function with ours
  83. xml.dom.minidom.Element.writexml = fixed_writexml
  84. class Table:
  85. def __init__(self, parent=None):
  86. self.parent = parent
  87. self.table = {}
  88. def __getitem__(self, key):
  89. if key in self.table:
  90. return self.table[key]
  91. elif self.parent:
  92. return self.parent[key]
  93. else:
  94. raise KeyError(key)
  95. def __setitem__(self, key, value):
  96. self.table[key] = value
  97. def __contains__(self, key):
  98. return \
  99. key in self.table or \
  100. (self.parent and key in self.parent)
  101. class QuickLexer(object):
  102. def __init__(self, **res):
  103. self.str = ""
  104. self.top = None
  105. self.res = []
  106. for k, v in res.items():
  107. self.__setattr__(k, len(self.res))
  108. self.res.append(v)
  109. def lex(self, str):
  110. self.str = str
  111. self.top = None
  112. self.next()
  113. def peek(self):
  114. return self.top
  115. def next(self):
  116. result = self.top
  117. self.top = None
  118. for i in range(len(self.res)):
  119. m = re.match(self.res[i], self.str)
  120. if m:
  121. self.top = (i, m.group(0))
  122. self.str = self.str[m.end():]
  123. break
  124. return result
  125. def first_child_element(elt):
  126. c = elt.firstChild
  127. while c:
  128. if c.nodeType == xml.dom.Node.ELEMENT_NODE:
  129. return c
  130. c = c.nextSibling
  131. return None
  132. def next_sibling_element(elt):
  133. c = elt.nextSibling
  134. while c:
  135. if c.nodeType == xml.dom.Node.ELEMENT_NODE:
  136. return c
  137. c = c.nextSibling
  138. return None
  139. # Pre-order traversal of the elements
  140. def next_element(elt):
  141. child = first_child_element(elt)
  142. if child:
  143. return child
  144. while elt and elt.nodeType == xml.dom.Node.ELEMENT_NODE:
  145. next = next_sibling_element(elt)
  146. if next:
  147. return next
  148. elt = elt.parentNode
  149. return None
  150. # Pre-order traversal of all the nodes
  151. def next_node(node):
  152. if node.firstChild:
  153. return node.firstChild
  154. while node:
  155. if node.nextSibling:
  156. return node.nextSibling
  157. node = node.parentNode
  158. return None
  159. def child_nodes(elt):
  160. c = elt.firstChild
  161. while c:
  162. yield c
  163. c = c.nextSibling
  164. all_includes = []
  165. # Deprecated message for <include> tags that don't have <xacro:include> prepended:
  166. deprecated_include_msg = """DEPRECATED IN HYDRO:
  167. The <include> tag should be prepended with 'xacro' if that is the intended use
  168. of it, such as <xacro:include ...>. Use the following script to fix incorrect
  169. xacro includes:
  170. sed -i 's/<include/<xacro:include/g' `find . -iname *.xacro`"""
  171. include_no_matches_msg = """Include tag filename spec \"{}\" matched no files."""
  172. ## @throws XacroException if a parsing error occurs with an included document
  173. def process_includes(doc, base_dir):
  174. namespaces = {}
  175. previous = doc.documentElement
  176. elt = next_element(previous)
  177. while elt:
  178. # Xacro should not use plain 'include' tags but only namespaced ones. Causes conflicts with
  179. # other XML elements including Gazebo's <gazebo> extensions
  180. is_include = False
  181. if elt.tagName == 'xacro:include' or elt.tagName == 'include':
  182. is_include = True
  183. # Temporary fix for ROS Hydro and the xacro include scope problem
  184. if elt.tagName == 'include':
  185. # check if there is any element within the <include> tag. mostly we are concerned
  186. # with Gazebo's <uri> element, but it could be anything. also, make sure the child
  187. # nodes aren't just a single Text node, which is still considered a deprecated
  188. # instance
  189. if elt.childNodes and not (len(elt.childNodes) == 1 and
  190. elt.childNodes[0].nodeType == elt.TEXT_NODE):
  191. # this is not intended to be a xacro element, so we can ignore it
  192. is_include = False
  193. else:
  194. # throw a deprecated warning
  195. print(deprecated_include_msg, file=sys.stderr)
  196. # Process current element depending on previous conditions
  197. if is_include:
  198. filename_spec = eval_text(elt.getAttribute('filename'), {})
  199. if not os.path.isabs(filename_spec):
  200. filename_spec = os.path.join(base_dir, filename_spec)
  201. if re.search('[*[?]+', filename_spec):
  202. # Globbing behaviour
  203. filenames = sorted(glob.glob(filename_spec))
  204. if len(filenames) == 0:
  205. print(include_no_matches_msg.format(filename_spec), file=sys.stderr)
  206. else:
  207. # Default behaviour
  208. filenames = [filename_spec]
  209. for filename in filenames:
  210. global all_includes
  211. all_includes.append(filename)
  212. try:
  213. with open(filename) as f:
  214. try:
  215. included = parse(f)
  216. except Exception as e:
  217. raise XacroException(
  218. "included file \"%s\" generated an error during XML parsing: %s"
  219. % (filename, str(e)))
  220. except IOError as e:
  221. raise XacroException("included file \"%s\" could not be opened: %s" % (filename, str(e)))
  222. # Replaces the include tag with the elements of the included file
  223. for c in child_nodes(included.documentElement):
  224. elt.parentNode.insertBefore(c.cloneNode(deep=True), elt)
  225. # Grabs all the declared namespaces of the included document
  226. for name, value in included.documentElement.attributes.items():
  227. if name.startswith('xmlns:'):
  228. namespaces[name] = value
  229. elt.parentNode.removeChild(elt)
  230. elt = None
  231. else:
  232. previous = elt
  233. elt = next_element(previous)
  234. # Makes sure the final document declares all the namespaces of the included documents.
  235. for k, v in namespaces.items():
  236. doc.documentElement.setAttribute(k, v)
  237. # Returns a dictionary: { macro_name => macro_xml_block }
  238. def grab_macros(doc):
  239. macros = {}
  240. previous = doc.documentElement
  241. elt = next_element(previous)
  242. while elt:
  243. if elt.tagName == 'macro' or elt.tagName == 'xacro:macro':
  244. name = elt.getAttribute('name')
  245. macros[name] = elt
  246. macros['xacro:' + name] = elt
  247. elt.parentNode.removeChild(elt)
  248. elt = None
  249. else:
  250. previous = elt
  251. elt = next_element(previous)
  252. return macros
  253. # Returns a Table of the properties
  254. def grab_properties(doc):
  255. table = Table()
  256. previous = doc.documentElement
  257. elt = next_element(previous)
  258. while elt:
  259. if elt.tagName == 'property' or elt.tagName == 'xacro:property':
  260. name = elt.getAttribute('name')
  261. value = None
  262. if elt.hasAttribute('value'):
  263. value = elt.getAttribute('value')
  264. else:
  265. name = '**' + name
  266. value = elt # debug
  267. bad = string.whitespace + "${}"
  268. has_bad = False
  269. for b in bad:
  270. if b in name:
  271. has_bad = True
  272. break
  273. if has_bad:
  274. sys.stderr.write('Property names may not have whitespace, ' +
  275. '"{", "}", or "$" : "' + name + '"')
  276. else:
  277. table[name] = value
  278. elt.parentNode.removeChild(elt)
  279. elt = None
  280. else:
  281. previous = elt
  282. elt = next_element(previous)
  283. return table
  284. def eat_ignore(lex):
  285. while lex.peek() and lex.peek()[0] == lex.IGNORE:
  286. lex.next()
  287. def eval_lit(lex, symbols):
  288. eat_ignore(lex)
  289. if lex.peek()[0] == lex.NUMBER:
  290. return float(lex.next()[1])
  291. if lex.peek()[0] == lex.SYMBOL:
  292. try:
  293. key = lex.next()[1]
  294. value = symbols[key]
  295. except KeyError as ex:
  296. raise XacroException("Property wasn't defined: %s" % str(ex))
  297. if not (isnumber(value) or isinstance(value, _basestr)):
  298. if value is None:
  299. raise XacroException("Property %s recursively used" % key)
  300. raise XacroException("WTF2")
  301. try:
  302. return int(value)
  303. except:
  304. try:
  305. return float(value)
  306. except:
  307. # prevent infinite recursion
  308. symbols[key] = None
  309. result = eval_text(value, symbols)
  310. # restore old entry
  311. symbols[key] = value
  312. return result
  313. raise XacroException("Bad literal")
  314. def eval_factor(lex, symbols):
  315. eat_ignore(lex)
  316. neg = 1
  317. if lex.peek()[1] == '-':
  318. lex.next()
  319. neg = -1
  320. if lex.peek()[0] in [lex.NUMBER, lex.SYMBOL]:
  321. return neg * eval_lit(lex, symbols)
  322. if lex.peek()[0] == lex.LPAREN:
  323. lex.next()
  324. eat_ignore(lex)
  325. result = eval_expr(lex, symbols)
  326. eat_ignore(lex)
  327. if lex.next()[0] != lex.RPAREN:
  328. raise XacroException("Unmatched left paren")
  329. eat_ignore(lex)
  330. return neg * result
  331. raise XacroException("Misplaced operator")
  332. def eval_term(lex, symbols):
  333. eat_ignore(lex)
  334. result = 0
  335. if lex.peek()[0] in [lex.NUMBER, lex.SYMBOL, lex.LPAREN] \
  336. or lex.peek()[1] == '-':
  337. result = eval_factor(lex, symbols)
  338. eat_ignore(lex)
  339. while lex.peek() and lex.peek()[1] in ['*', '/']:
  340. op = lex.next()[1]
  341. n = eval_factor(lex, symbols)
  342. if op == '*':
  343. result = float(result) * float(n)
  344. elif op == '/':
  345. result = float(result) / float(n)
  346. else:
  347. raise XacroException("WTF")
  348. eat_ignore(lex)
  349. return result
  350. def eval_expr(lex, symbols):
  351. eat_ignore(lex)
  352. op = None
  353. if lex.peek()[0] == lex.OP:
  354. op = lex.next()[1]
  355. if not op in ['+', '-']:
  356. raise XacroException("Invalid operation. Must be '+' or '-'")
  357. result = eval_term(lex, symbols)
  358. if op == '-':
  359. result = -float(result)
  360. eat_ignore(lex)
  361. while lex.peek() and lex.peek()[1] in ['+', '-']:
  362. op = lex.next()[1]
  363. n = eval_term(lex, symbols)
  364. if op == '+':
  365. result = float(result) + float(n)
  366. if op == '-':
  367. result = float(result) - float(n)
  368. eat_ignore(lex)
  369. return result
  370. def eval_text(text, symbols):
  371. def handle_expr(s):
  372. lex = QuickLexer(IGNORE=r"\s+",
  373. NUMBER=r"(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?",
  374. SYMBOL=r"[a-zA-Z_]\w*",
  375. OP=r"[\+\-\*/^]",
  376. LPAREN=r"\(",
  377. RPAREN=r"\)")
  378. lex.lex(s)
  379. return eval_expr(lex, symbols)
  380. def handle_extension(s):
  381. return ("$(%s)" % s)
  382. results = []
  383. lex = QuickLexer(DOLLAR_DOLLAR_BRACE=r"\$\$+\{",
  384. EXPR=r"\$\{[^\}]*\}",
  385. EXTENSION=r"\$\([^\)]*\)",
  386. TEXT=r"([^\$]|\$[^{(]|\$$)+")
  387. lex.lex(text)
  388. while lex.peek():
  389. if lex.peek()[0] == lex.EXPR:
  390. results.append(handle_expr(lex.next()[1][2:-1]))
  391. elif lex.peek()[0] == lex.EXTENSION:
  392. results.append(handle_extension(lex.next()[1][2:-1]))
  393. elif lex.peek()[0] == lex.TEXT:
  394. results.append(lex.next()[1])
  395. elif lex.peek()[0] == lex.DOLLAR_DOLLAR_BRACE:
  396. results.append(lex.next()[1][1:])
  397. return ''.join(map(str, results))
  398. # Expands macros, replaces properties, and evaluates expressions
  399. def eval_all(root, macros, symbols):
  400. # Evaluates the attributes for the root node
  401. for at in root.attributes.items():
  402. result = eval_text(at[1], symbols)
  403. root.setAttribute(at[0], result)
  404. previous = root
  405. node = next_node(previous)
  406. while node:
  407. if node.nodeType == xml.dom.Node.ELEMENT_NODE:
  408. if node.tagName in macros:
  409. body = macros[node.tagName].cloneNode(deep=True)
  410. params = body.getAttribute('params').split()
  411. # Parse default values for any parameters
  412. defaultmap = {}
  413. for param in params[:]:
  414. splitParam = param.split(':=')
  415. if len(splitParam) == 2:
  416. defaultmap[splitParam[0]] = splitParam[1]
  417. params.remove(param)
  418. params.append(splitParam[0])
  419. elif len(splitParam) != 1:
  420. raise XacroException("Invalid parameter definition")
  421. # Expands the macro
  422. scoped = Table(symbols)
  423. for name, value in node.attributes.items():
  424. if not name in params:
  425. raise XacroException("Invalid parameter \"%s\" while expanding macro \"%s\"" %
  426. (str(name), str(node.tagName)))
  427. params.remove(name)
  428. scoped[name] = eval_text(value, symbols)
  429. # Pulls out the block arguments, in order
  430. cloned = node.cloneNode(deep=True)
  431. eval_all(cloned, macros, symbols)
  432. block = cloned.firstChild
  433. for param in params[:]:
  434. if param[0] == '*':
  435. while block and block.nodeType != xml.dom.Node.ELEMENT_NODE:
  436. block = block.nextSibling
  437. if not block:
  438. raise XacroException("Not enough blocks while evaluating macro %s" % str(node.tagName))
  439. params.remove(param)
  440. scoped[param] = block
  441. block = block.nextSibling
  442. # Try to load defaults for any remaining non-block parameters
  443. for param in params[:]:
  444. if param[0] != '*' and param in defaultmap:
  445. scoped[param] = defaultmap[param]
  446. params.remove(param)
  447. if params:
  448. raise XacroException("Parameters [%s] were not set for macro %s" %
  449. (",".join(params), str(node.tagName)))
  450. eval_all(body, macros, scoped)
  451. # Replaces the macro node with the expansion
  452. for e in list(child_nodes(body)): # Ew
  453. node.parentNode.insertBefore(e, node)
  454. node.parentNode.removeChild(node)
  455. node = None
  456. elif node.tagName == 'arg' or node.tagName == 'xacro:arg':
  457. name = node.getAttribute('name')
  458. if not name:
  459. raise XacroException("Argument name missing")
  460. default = node.getAttribute('default')
  461. if default and name not in substitution_args_context['arg']:
  462. substitution_args_context['arg'][name] = default
  463. node.parentNode.removeChild(node)
  464. node = None
  465. elif node.tagName == 'insert_block' or node.tagName == 'xacro:insert_block':
  466. name = node.getAttribute('name')
  467. if ("**" + name) in symbols:
  468. # Multi-block
  469. block = symbols['**' + name]
  470. for e in list(child_nodes(block)):
  471. node.parentNode.insertBefore(e.cloneNode(deep=True), node)
  472. node.parentNode.removeChild(node)
  473. elif ("*" + name) in symbols:
  474. # Single block
  475. block = symbols['*' + name]
  476. node.parentNode.insertBefore(block.cloneNode(deep=True), node)
  477. node.parentNode.removeChild(node)
  478. else:
  479. raise XacroException("Block \"%s\" was never declared" % name)
  480. node = None
  481. elif node.tagName in ['if', 'xacro:if', 'unless', 'xacro:unless']:
  482. value = eval_text(node.getAttribute('value'), symbols)
  483. try:
  484. if value == 'true': keep = True
  485. elif value == 'false': keep = False
  486. else: keep = float(value)
  487. except ValueError:
  488. raise XacroException("Xacro conditional evaluated to \"%s\". Acceptable evaluations are one of [\"1\",\"true\",\"0\",\"false\"]" % value)
  489. if node.tagName in ['unless', 'xacro:unless']: keep = not keep
  490. if keep:
  491. for e in list(child_nodes(node)):
  492. node.parentNode.insertBefore(e.cloneNode(deep=True), node)
  493. node.parentNode.removeChild(node)
  494. else:
  495. # Evals the attributes
  496. for at in node.attributes.items():
  497. result = eval_text(at[1], symbols)
  498. node.setAttribute(at[0], result)
  499. previous = node
  500. elif node.nodeType == xml.dom.Node.TEXT_NODE:
  501. node.data = eval_text(node.data, symbols)
  502. previous = node
  503. else:
  504. previous = node
  505. node = next_node(previous)
  506. return macros
  507. # Expands everything except includes
  508. def eval_self_contained(doc):
  509. macros = grab_macros(doc)
  510. symbols = grab_properties(doc)
  511. eval_all(doc.documentElement, macros, symbols)
  512. def print_usage(exit_code=0):
  513. print("Usage: %s [-o <output>] <input>" % 'xacro.py')
  514. print(" %s --deps Prints dependencies" % 'xacro.py')
  515. print(" %s --includes Only evalutes includes" % 'xacro.py')
  516. sys.exit(exit_code)
  517. def set_substitution_args_context(context={}):
  518. substitution_args_context['arg'] = context
  519. def open_output(output_filename):
  520. if output_filename is None:
  521. return sys.stdout
  522. else:
  523. return open(output_filename, 'w')
  524. def main():
  525. try:
  526. opts, args = getopt.gnu_getopt(sys.argv[1:], "ho:", ['deps', 'includes'])
  527. except getopt.GetoptError as err:
  528. print(str(err))
  529. print_usage(2)
  530. just_deps = False
  531. just_includes = False
  532. output_filename = None
  533. for o, a in opts:
  534. if o == '-h':
  535. print_usage(0)
  536. elif o == '-o':
  537. output_filename = a
  538. elif o == '--deps':
  539. just_deps = True
  540. elif o == '--includes':
  541. just_includes = True
  542. if len(args) < 1:
  543. print("No input given")
  544. print_usage(2)
  545. # Process substitution args
  546. # set_substitution_args_context(load_mappings(sys.argv))
  547. set_substitution_args_context((sys.argv))
  548. f = open(args[0])
  549. doc = None
  550. try:
  551. doc = parse(f)
  552. except xml.parsers.expat.ExpatError:
  553. sys.stderr.write("Expat parsing error. Check that:\n")
  554. sys.stderr.write(" - Your XML is correctly formed\n")
  555. sys.stderr.write(" - You have the xacro xmlns declaration: " +
  556. "xmlns:xacro=\"http://www.ros.org/wiki/xacro\"\n")
  557. sys.stderr.write("\n")
  558. raise
  559. finally:
  560. f.close()
  561. process_includes(doc, os.path.dirname(args[0]))
  562. if just_deps:
  563. for inc in all_includes:
  564. sys.stdout.write(inc + " ")
  565. sys.stdout.write("\n")
  566. elif just_includes:
  567. doc.writexml(open_output(output_filename))
  568. print()
  569. else:
  570. eval_self_contained(doc)
  571. banner = [xml.dom.minidom.Comment(c) for c in
  572. [" %s " % ('=' * 83),
  573. " | This document was autogenerated by xacro from %-30s | " % args[0],
  574. " | EDITING THIS FILE BY HAND IS NOT RECOMMENDED %-30s | " % "",
  575. " %s " % ('=' * 83)]]
  576. first = doc.firstChild
  577. for comment in banner:
  578. doc.insertBefore(comment, first)
  579. open_output(output_filename).write(doc.toprettyxml(indent=' '))
  580. print()
  581. if __name__ == '__main__':
  582. main()