Package roshlaunch :: Module xmlloader
[frames] | no frames]

Source Code for Module roshlaunch.xmlloader

  1  # Software License Agreement (BSD License) 
  2  # 
  3  # Copyright (c) 2008, Willow Garage, 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 
  8  # are met: 
  9  # 
 10  #  * Redistributions of source code must retain the above copyright 
 11  #    notice, this list of conditions and the following disclaimer. 
 12  #  * Redistributions in binary form must reproduce the above 
 13  #    copyright notice, this list of conditions and the following 
 14  #    disclaimer in the documentation and/or other materials provided 
 15  #    with the distribution. 
 16  #  * Neither the name of Willow Garage, Inc. nor the names of its 
 17  #    contributors may be used to endorse or promote products derived 
 18  #    from this software without specific prior written permission. 
 19  # 
 20  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 21  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 22  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 23  # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
 24  # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
 25  # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 26  # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 27  # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 28  # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
 29  # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
 30  # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 31  # POSSIBILITY OF SUCH DAMAGE. 
 32  # 
 33  # Revision $Id: xmlloader.py 10271 2010-07-07 00:12:00Z kwc $ 
 34   
 35  """ 
 36  Roslaunch XML file parser. 
 37  """ 
 38   
 39  from __future__ import with_statement 
 40   
 41  import itertools 
 42  import os 
 43  import sys 
 44   
 45  from xml.dom.minidom import parse, parseString 
 46  from xml.dom import Node as DomNode #avoid aliasing 
 47   
 48  from roslib.names import make_global_ns, ns_join, is_global, is_private, PRIV_NAME 
 49  import pdb; pdb.set_trace() 
 50  import roslaunch.substitution_args 
 51   
 52  from roslaunch.core import Param, Node, Test, Machine, RLException, get_ros_package_path 
 53  import roslaunch.loader 
 54   
 55  # use in our namespace 
 56  SubstitutionException = roslaunch.substitution_args.SubstitutionException 
 57  ArgException = roslaunch.substitution_args.ArgException 
 58   
 59  NS='ns' 
 60  CLEAR_PARAMS='clear_params' 
61 62 -def _get_text(tag):
63 buff = '' 64 for t in tag.childNodes: 65 if t.nodeType in [t.TEXT_NODE, t.CDATA_SECTION_NODE]: 66 buff += t.data 67 return buff
68
69 -def ifunless_test(obj, tag, context):
70 """ 71 @return True: if tag should be processed according to its if/unless attributes 72 """ 73 if_val, unless_val = obj.opt_attrs(tag, context, ['if', 'unless']) 74 if if_val is not None and unless_val is not None: 75 raise XmlParseException("cannot set both 'if' and 'unless' on the same tag") 76 if if_val is not None: 77 if_val = roslaunch.loader.convert_value(if_val, 'bool') 78 if if_val: 79 return True 80 elif unless_val is not None: 81 unless_val = roslaunch.loader.convert_value(unless_val, 'bool') 82 if not unless_val: 83 return True 84 else: 85 return True 86 return False
87
88 -def ifunless(f):
89 """ 90 Decorator for evaluating whether or not tag function should run based on if/unless attributes 91 """ 92 def call(*args, **kwds): 93 #TODO: logging, as well as check for verbose in kwds 94 if ifunless_test(args[0], args[1], args[2]): 95 return f(*args, **kwds)
96 return call 97
98 # This code has gotten a bit crufty as roslaunch has grown far beyond 99 # its original spec. It needs to be far more generic than it is in 100 # order to not replicate bugs in multiple places. 101 102 -class XmlParseException(RLException):
103 """Error with the XML syntax (e.g. invalid attribute/value combinations)""" 104 pass
105
106 -def _bool_attr(v, default, label):
107 """ 108 Validate boolean xml attribute. 109 @param v: parameter value or None if no value provided 110 @type v: any 111 @param default: default value 112 @type default: bool 113 @param label: parameter name/label 114 @type label: str 115 @return: boolean value for attribute 116 @rtype: bool 117 @raise XmlParseException: if v is not in correct range or is empty. 118 """ 119 if v is None: 120 return default 121 if v.lower() == 'true': 122 return True 123 elif v.lower() == 'false': 124 return False 125 elif not v: 126 raise XmlParseException("bool value for %s must be non-empty"%(label)) 127 else: 128 raise XmlParseException("invalid bool value for %s: %s"%(label, v))
129 130 # maps machine 'default' attribute to Machine default property 131 _is_default = {'true': True, 'false': False, 'never': False } 132 # maps machine 'default' attribute to Machine assignable property 133 _assignable = {'true': True, 'false': True, 'never': False }
134 135 # NOTE: code is currently in a semi-refactored state. I'm slowly 136 # migrating common routines into the Loader class in the hopes it will 137 # make it easier to write alternate loaders and also test. 138 -class XmlLoader(roslaunch.loader.Loader):
139 """ 140 Parser for roslaunch XML format. Loads parsed representation into ROSConfig model. 141 """ 142
143 - def __init__(self, resolve_anon=True):
144 """ 145 @param resolve_anon: If True (default), will resolve $(anon foo). If 146 false, will leave these args as-is. 147 @type resolve_anon: bool 148 """ 149 # store the root XmlContext so that outside code can access it 150 self.root_context = None 151 self.resolve_anon = resolve_anon
152
153 - def resolve_args(self, args, context):
154 """ 155 Wrapper around roslaunch.substitution_args.resolve_args to set common parameters 156 """ 157 # resolve_args gets called a lot, so we optimize by testing for dollar sign before resolving 158 if args and '$' in args: 159 return roslaunch.substitution_args.resolve_args(args, context=context.resolve_dict, resolve_anon=self.resolve_anon) 160 else: 161 return args
162
163 - def opt_attrs(self, tag, context, attrs):
164 """ 165 Helper routine for fetching and resolving optional tag attributes 166 @param tag DOM tag 167 @param context LoaderContext 168 @param attrs (str): list of attributes to resolve 169 """ 170 def tag_value(tag, a): 171 if tag.hasAttribute(a): 172 # getAttribute returns empty string for non-existent 173 # attributes, which makes it impossible to distinguish 174 # with actual empty values 175 return tag.getAttribute(a) 176 else: 177 return None
178 return [self.resolve_args(tag_value(tag,a), context) for a in attrs]
179
180 - def reqd_attrs(self, tag, context, attrs):
181 """ 182 Helper routine for fetching and resolving required tag attributes 183 @param tag: DOM tag 184 @param attrs: list of attributes to resolve 185 @type attrs: (str) 186 @raise KeyError: if required attribute is missing 187 """ 188 return [self.resolve_args(tag.attributes[a].value, context) for a in attrs]
189
190 - def _check_attrs(self, tag, context, ros_config, attrs):
191 tag_attrs = tag.attributes.keys() 192 for t_a in tag_attrs: 193 if not t_a in attrs and not t_a in ['if', 'unless']: 194 ros_config.add_config_error("[%s] unknown <%s> attribute '%s'"%(context.filename, tag.tagName, t_a))
195 #print >> sys.stderr, "WARNING: 196 197 # 'ns' attribute is now deprecated and is an alias for 198 # 'param'. 'param' is required if the value is a non-dictionary 199 # type 200 ROSPARAM_OPT_ATTRS = ('command', 'ns', 'file', 'param') 201 @ifunless
202 - def _rosparam_tag(self, tag, context, ros_config, verbose=True):
203 try: 204 cmd, ns, file, param = self.opt_attrs(tag, context, (XmlLoader.ROSPARAM_OPT_ATTRS)) 205 # ns atribute is a bit out-moded and is only left in for backwards compatibility 206 param = ns_join(ns or '', param or '') 207 208 # load is the default command 209 cmd = cmd or 'load' 210 211 self.load_rosparam(context, ros_config, cmd, param, file, _get_text(tag), verbose=verbose) 212 213 except ValueError, e: 214 raise roslaunch.loader.LoadException("error loading <rosparam> tag: \n\t"+str(e)+"\nXML is %s"%tag.toxml())
215 216 PARAM_ATTRS = ('name', 'value', 'type', 'value', 'textfile', 'binfile', 'command') 217 @ifunless
218 - def _param_tag(self, tag, context, ros_config, force_local=False, verbose=True):
219 """ 220 @param force_local: if True, param must be added to context instead of ros_config 221 @type force_local: bool 222 """ 223 try: 224 self._check_attrs(tag, context, ros_config, XmlLoader.PARAM_ATTRS) 225 226 # compute name and value 227 ptype = (tag.getAttribute('type') or 'auto').lower().strip() 228 229 vals = self.opt_attrs(tag, context, ('value', 'textfile', 'binfile', 'command')) 230 if len([v for v in vals if v is not None]) != 1: 231 raise XmlParseException( 232 "<param> tag must have one and only one of value/textfile/binfile.") 233 234 # compute name. if name is a tilde name, it is placed in 235 # the context. otherwise it is placed in the ros config. 236 name = self.resolve_args(tag.attributes['name'].value.strip(), context) 237 value = self.param_value(verbose, name, ptype, *vals) 238 239 if is_private(name) or force_local: 240 p = Param(name, value) 241 context.add_param(p) 242 else: 243 p = Param(ns_join(context.ns, name), value) 244 ros_config.add_param(Param(ns_join(context.ns, name), value), filename=context.filename, verbose=verbose) 245 return p 246 247 except KeyError, e: 248 raise XmlParseException( 249 "<param> tag is missing required attribute: %s. \n\nParam xml is %s"%(e, tag.toxml())) 250 except ValueError, e: 251 raise XmlParseException( 252 "Invalid <param> tag: %s. \n\nParam xml is %s"%(e, tag.toxml()))
253 254 ARG_ATTRS = ('name', 'value', 'default') 255 @ifunless
256 - def _arg_tag(self, tag, context, ros_config, verbose=True):
257 """ 258 Process an <arg> tag. 259 """ 260 try: 261 self._check_attrs(tag, context, ros_config, XmlLoader.ARG_ATTRS) 262 (name,) = self.reqd_attrs(tag, context, ('name',)) 263 value, default = self.opt_attrs(tag, context, ('value', 'default')) 264 265 if value is not None and default is not None: 266 raise XmlParseException( 267 "<arg> tag must have one and only one of value/default.") 268 269 context.add_arg(name, value=value, default=default) 270 271 except roslaunch.substitution_args.ArgException, e: 272 raise XmlParseException( 273 "arg '%s' is not defined. \n\nArg xml is %s"%(e, tag.toxml())) 274 except Exception, e: 275 raise XmlParseException( 276 "Invalid <arg> tag: %s. \n\nArg xml is %s"%(e, tag.toxml()))
277
278 - def _test_attrs(self, tag, context):
279 """ 280 Process attributes of <test> tag not present in <node> 281 @return: test_name, time_limit 282 @rtype: str, int 283 """ 284 for attr in ['respawn', 'output']: 285 if tag.hasAttribute(attr): 286 raise XmlParseException("<test> tags cannot have '%s' attribute"%attr) 287 288 test_name = self.resolve_args(tag.attributes['test-name'].value, context) 289 time_limit = self.resolve_args(tag.getAttribute('time-limit'), context) 290 retry = self.resolve_args(tag.getAttribute('retry'), context) 291 if time_limit: 292 try: 293 time_limit = float(time_limit) 294 except ValueError: 295 raise XmlParseException("'time-limit' must be a number: [%s]"%time_limit) 296 if time_limit <= 0.0: 297 raise XmlParseException("'time-limit' must be a positive number") 298 if retry: 299 try: 300 retry = int(retry) 301 except ValueError: 302 raise XmlParseException("'retry' must be a number: [%s]"%retry) 303 304 return test_name, time_limit, retry
305 306 NODE_ATTRS = ['pkg', 'type', 'machine', 'name', 'args', 'output', 'respawn', 'cwd', NS, CLEAR_PARAMS, 'launch-prefix', 'required'] 307 TEST_ATTRS = NODE_ATTRS + ['test-name','time-limit', 'retry'] 308 309 @ifunless
310 - def _node_tag(self, tag, context, ros_config, default_machine, is_test=False, verbose=True):
311 """ 312 Process XML <node> or <test> tag 313 @param tag: DOM node 314 @type tag: Node 315 @param context: namespace context 316 @type context: L{LoaderContext} 317 @param params: ROS parameter list 318 @type params: [L{Param}] 319 @param clear_params: list of ROS parameter names to clear before setting parameters 320 @type clear_params: [str] 321 @param default_machine: default machine to assign to node 322 @type default_machine: str 323 @param is_test: if set, will load as L{Test} object instead of L{Node} object 324 @type is_test: bool 325 """ 326 try: 327 if is_test: 328 self._check_attrs(tag, context, ros_config, XmlLoader.TEST_ATTRS) 329 (name,) = self.opt_attrs(tag, context, ('name',)) 330 test_name, time_limit, retry = self._test_attrs(tag, context) 331 if not name: 332 name = test_name 333 else: 334 self._check_attrs(tag, context, ros_config, XmlLoader.NODE_ATTRS) 335 (name,) = self.reqd_attrs(tag, context, ('name',)) 336 337 if not roslib.names.is_legal_name(name): 338 ros_config.add_config_error("WARN: illegal <node> name '%s'.\nhttp://ros.org/wiki/Names\nThis will likely cause problems with other ROS tools.\nNode xml is %s"%(name, tag.toxml())) 339 340 child_ns = self._ns_clear_params_attr('node', tag, context, ros_config, node_name=name) 341 param_ns = child_ns.child(name) 342 343 # required attributes 344 pkg, node_type = self.reqd_attrs(tag, context, ('pkg', 'type')) 345 346 # optional attributes 347 machine, args, output, respawn, cwd, launch_prefix, required = \ 348 self.opt_attrs(tag, context, ('machine', 'args', 'output', 'respawn', 'cwd', 'launch-prefix', 'required')) 349 if tag.hasAttribute('machine') and not len(machine.strip()): 350 raise XmlParseException("<node> 'machine' must be non-empty: [%s]"%machine) 351 if not machine and default_machine: 352 machine = default_machine.name 353 # validate respawn, required 354 required, respawn = [_bool_attr(*rr) for rr in ((required, False, 'required'),\ 355 (respawn, False, 'respawn'))] 356 357 # each node gets its own copy of <remap> arguments, which 358 # it inherits from its parent 359 remap_context = context.child('') 360 361 # nodes can have individual env args set in addition to 362 # the ROS-specific ones. 363 for t in [c for c in tag.childNodes if c.nodeType == DomNode.ELEMENT_NODE]: 364 tag_name = t.tagName.lower() 365 if tag_name == 'remap': 366 remap_context.add_remap(self._remap_tag(t, context, ros_config)) 367 elif tag_name == 'param': 368 self._param_tag(t, param_ns, ros_config, force_local=True, verbose=verbose) 369 elif tag_name == 'rosparam': 370 self._rosparam_tag(t, param_ns, ros_config, verbose=verbose) 371 elif tag_name == 'env': 372 self._env_tag(t, context, ros_config) 373 else: 374 ros_config.add_config_error("WARN: unrecognized '%s' tag in <node> tag. Node xml is %s"%(t.tagName, tag.toxml())) 375 376 # #1036 evaluate all ~params in context 377 # TODO: can we get rid of force_local (above), remove this for loop, and just rely on param_tag logic instead? 378 for p in itertools.chain(context.params, param_ns.params): 379 pkey = p.key 380 if is_private(pkey): 381 # strip leading ~, which is optional/inferred 382 pkey = pkey[1:] 383 pkey = param_ns.ns + pkey 384 ros_config.add_param(Param(pkey, p.value), verbose=verbose) 385 386 if not is_test: 387 return Node(pkg, node_type, name=name, namespace=child_ns.ns, machine_name=machine, 388 args=args, respawn=respawn, 389 remap_args=remap_context.remap_args(), env_args=context.env_args, 390 output=output, cwd=cwd, launch_prefix=launch_prefix, 391 required=required, filename=context.filename) 392 else: 393 return Test(test_name, pkg, node_type, name=name, namespace=child_ns.ns, 394 machine_name=machine, args=args, 395 remap_args=remap_context.remap_args(), env_args=context.env_args, 396 time_limit=time_limit, cwd=cwd, launch_prefix=launch_prefix, 397 retry=retry, filename=context.filename) 398 except KeyError, e: 399 raise XmlParseException( 400 "<%s> tag is missing required attribute: %s. Node xml is %s"%(tag.tagName, e, tag.toxml())) 401 except XmlParseException, e: 402 raise XmlParseException( 403 "Invalid <node> tag: %s. \n\nNode xml is %s"%(e, tag.toxml())) 404 except ValueError, e: 405 raise XmlParseException( 406 "Invalid <node> tag: %s. \n\nNode xml is %s"%(e, tag.toxml()))
407 408 MACHINE_ATTRS = ('name', 'address', 'ros-root', 'ros-package-path', 'ros-ip', 'ros-host-name', 409 'ssh-port', 'user', 'password', 'default', 'timeout') 410 @ifunless
411 - def _machine_tag(self, tag, context, ros_config, verbose=True):
412 try: 413 # clone context as <machine> tag sets up its own env args 414 context = context.child(None) 415 416 self._check_attrs(tag, context, ros_config, XmlLoader.MACHINE_ATTRS) 417 # required attributes 418 name, address = self.reqd_attrs(tag, context, ('name', 'address')) 419 420 # optional attributes 421 attrs = self.opt_attrs(tag, context, 422 ('ros-root', 'ros-package-path', 'ros-ip', 'ros-host-name', 423 'ssh-port', 'user', 'password', 'default', 'timeout')) 424 rosroot, ros_package_path, ros_ip, ros_host_name, \ 425 ssh_port, user, password, default, timeout = attrs 426 427 # DEPRECATED: remove in ROS 0.11 if possible 428 if ros_host_name and ros_ip: 429 raise XmlParseException("only one of 'ros-host-name' or 'ros-ip' may be set") 430 if ros_ip: 431 ros_config.add_config_error("WARN: ros-ip in <machine> tags is now deprecated. Use <env> tags instead") 432 if ros_host_name: 433 ros_config.add_config_error("WARN: ros-host-name in <machine> tags is now deprecated. Use <env> tags instead") 434 435 ros_host_name = ros_host_name or ros_ip #alias 436 437 if not ros_package_path: 438 # the default vale of ros_package_path is dependent on 439 # ros-root. If user has set ros-root they must also 440 # explicitly set ros-package-path. If neither is set, 441 # they default to the environment. 442 if rosroot: 443 ros_package_path = '' 444 else: 445 ros_package_path = get_ros_package_path() 446 if not rosroot: 447 try: 448 rosroot = os.environ['ROS_ROOT'] 449 except KeyError, e: 450 pass 451 ssh_port = int(ssh_port or '22') 452 453 # check for default switch 454 default = (default or 'false').lower() 455 try: 456 assignable = _assignable[default] 457 is_default = _is_default[default] 458 except KeyError, e: 459 raise XmlParseException("Invalid value for 'attribute': %s"%default) 460 461 # load env args 462 for t in [c for c in tag.childNodes if c.nodeType == DomNode.ELEMENT_NODE]: 463 if t.tagName == 'env': 464 self._env_tag(t, context, ros_config) 465 else: 466 ros_config.add_config_error("unrecognized '%s' tag in <%s> tag"%(t.tagName, tag.tagName)) 467 # cast timeout to float. make sure timeout wasn't an empty string or negative 468 if timeout: 469 try: 470 timeout = float(timeout) 471 except ValueError: 472 raise XmlParseException("'timeout' be a number: [%s]"%timeout) 473 elif timeout == '': 474 raise XmlParseException("'timeout' cannot be empty") 475 if timeout is not None and timeout <= 0.: 476 raise XmlParseException("'timeout' be a positive number: [%s]"%timeout) 477 478 #TODO: change Machine representation to use an environment 479 # dictionary instead of the umpteen ROS environment 480 # variable settings. 481 m = Machine(name, rosroot, ros_package_path, address, 482 ros_ip=ros_host_name, ssh_port=ssh_port, user=user, password=password, 483 assignable=assignable, env_args=context.env_args, timeout=timeout) 484 return (m, is_default) 485 except KeyError, e: 486 raise XmlParseException("<machine> tag is missing required attribute: %s"%e) 487 except SubstitutionException, e: 488 raise XmlParseException( 489 "%s. \n\nMachine xml is %s"%(e, tag.toxml())) 490 except RLException, e: 491 raise XmlParseException( 492 "%s. \n\nMachine xml is %s"%(e, tag.toxml()))
493 494 REMAP_ATTRS = ('from', 'to') 495 @ifunless
496 - def _remap_tag(self, tag, context, ros_config):
497 try: 498 self._check_attrs(tag, context, ros_config, XmlLoader.REMAP_ATTRS) 499 return self.reqd_attrs(tag, context, XmlLoader.REMAP_ATTRS) 500 except KeyError, e: 501 raise XmlParseException("<remap> tag is missing required from/to attributes: %s"%tag.toxml())
502 503 ENV_ATTRS = ('name', 'value') 504 @ifunless
505 - def _env_tag(self, tag, context, ros_config):
506 try: 507 self._check_attrs(tag, context, ros_config, XmlLoader.ENV_ATTRS) 508 self.load_env(context, ros_config, *self.reqd_attrs(tag, context, XmlLoader.ENV_ATTRS)) 509 except ValueError, e: 510 raise XmlParseException("Invalid <env> tag: %s. \nXML is %s"%(str(e), tag.toxml())) 511 except KeyError, e: 512 raise XmlParseException("<env> tag is missing required name/value attributes: %s"%tag.toxml())
513
514 - def _ns_clear_params_attr(self, tag_name, tag, context, ros_config, node_name=None, include_filename=None):
515 """ 516 Common processing routine for xml tags with NS and CLEAR_PARAMS attributes 517 518 @param tag: DOM Node 519 @type tag: Node 520 @param context: current namespace context 521 @type context: LoaderContext 522 @param clear_params: list of params to clear 523 @type clear_params: [str] 524 @param node_name: name of node (for use when tag_name == 'node') 525 @type node_name: str 526 @param include_filename: <include> filename if this is an <include> tag. If specified, context will use include rules. 527 @type include_filename: str 528 @return: loader context 529 @rtype: L{LoaderContext} 530 """ 531 if tag.hasAttribute(NS): 532 ns = self.resolve_args(tag.getAttribute(NS), context) 533 if not ns: 534 raise XmlParseException("<%s> tag has an empty '%s' attribute"%(tag_name, NS)) 535 else: 536 ns = None 537 if include_filename is not None: 538 child_ns = context.include_child(ns, include_filename) 539 else: 540 child_ns = context.child(ns) 541 clear_p = self.resolve_args(tag.getAttribute(CLEAR_PARAMS), context) 542 if clear_p: 543 clear_p = _bool_attr(clear_p, False, 'clear_params') 544 if clear_p: 545 if tag_name == 'node': 546 if not node_name: 547 raise XmlParseException("<%s> tag must have a 'name' attribute to use '%s' attribute"%(tag_name, CLEAR_PARAMS)) 548 # use make_global_ns to give trailing slash in order to be consistent with XmlContext.ns 549 ros_config.add_clear_param(make_global_ns(ns_join(child_ns.ns, node_name))) 550 else: 551 if not ns: 552 raise XmlParseException("'ns' attribute must be set in order to use 'clear_params'") 553 ros_config.add_clear_param(child_ns.ns) 554 return child_ns
555 556 @ifunless
557 - def _launch_tag(self, tag, ros_config, filename=None):
558 # #2499 559 deprecated = tag.getAttribute('deprecated') 560 if deprecated: 561 if filename: 562 ros_config.add_config_error("[%s] DEPRECATED: %s"%(filename, deprecated)) 563 else: 564 ros_config.add_config_error("Deprecation Warning: "+deprecated)
565 566 INCLUDE_ATTRS = ('file', NS, CLEAR_PARAMS) 567 @ifunless
568 - def _include_tag(self, tag, context, ros_config, default_machine, is_core, verbose):
569 self._check_attrs(tag, context, ros_config, XmlLoader.INCLUDE_ATTRS) 570 inc_filename = self.resolve_args(tag.attributes['file'].value, context) 571 572 child_ns = self._ns_clear_params_attr(tag.tagName, tag, context, ros_config, include_filename=inc_filename) 573 574 for t in [c for c in tag.childNodes if c.nodeType == DomNode.ELEMENT_NODE]: 575 tag_name = t.tagName.lower() 576 if tag_name == 'env': 577 self._env_tag(t, child_ns, ros_config) 578 elif tag_name == 'arg': 579 self._arg_tag(t, child_ns, ros_config, verbose=verbose) 580 else: 581 print >> sys.stderr, \ 582 "WARN: unrecognized '%s' tag in <%s> tag"%(t.tagName, tag.tagName) 583 584 # setup arg passing 585 roslaunch.loader.process_include_args(child_ns) 586 587 try: 588 launch = self._parse_launch(inc_filename, verbose=verbose) 589 self._launch_tag(launch, ros_config, filename=inc_filename) 590 default_machine = \ 591 self._recurse_load(ros_config, launch.childNodes, child_ns, \ 592 default_machine, is_core, verbose) 593 594 # check for unused args 595 roslaunch.loader.post_process_include_args(child_ns) 596 597 except ArgException, e: 598 raise XmlParseException("included file [%s] requires the '%s' arg to be set"%(inc_filename, str(e))) 599 except XmlParseException, e: 600 raise XmlParseException("while processing %s:\n%s"%(inc_filename, str(e))) 601 if verbose: 602 print "... done importing include file [%s]"%inc_filename 603 return default_machine
604 605 GROUP_ATTRS = (NS, CLEAR_PARAMS)
606 - def _recurse_load(self, ros_config, tags, context, default_machine, is_core, verbose):
607 """ 608 @return: new default machine for current context 609 @rtype: L{Machine} 610 """ 611 for tag in [t for t in tags if t.nodeType == DomNode.ELEMENT_NODE]: 612 name = tag.tagName 613 if name == 'group': 614 if ifunless_test(self, tag, context): 615 self._check_attrs(tag, context, ros_config, XmlLoader.GROUP_ATTRS) 616 child_ns = self._ns_clear_params_attr(name, tag, context, ros_config) 617 default_machine = \ 618 self._recurse_load(ros_config, tag.childNodes, child_ns, \ 619 default_machine, is_core, verbose) 620 elif name == 'node': 621 # clone the context so that nodes' env does not pollute global env 622 n = self._node_tag(tag, context.child(''), ros_config, default_machine, verbose=verbose) 623 if n is not None: 624 ros_config.add_node(n, core=is_core, verbose=verbose) 625 elif name == 'test': 626 # clone the context so that nodes' env does not pollute global env 627 t = self._node_tag(tag, context.child(''), ros_config, default_machine, is_test=True, verbose=verbose) 628 if t is not None: 629 ros_config.add_test(t, verbose=verbose) 630 elif name == 'param': 631 self._param_tag(tag, context, ros_config, verbose=verbose) 632 elif name == 'remap': 633 try: 634 r = self._remap_tag(tag, context, ros_config) 635 if r is not None: 636 context.add_remap(r) 637 except RLException, e: 638 raise XmlParseException("Invalid <remap> tag: %s.\nXML is %s"%(str(e), tag.toxml())) 639 elif name == 'machine': 640 val = self._machine_tag(tag, context, ros_config, verbose=verbose) 641 if val is not None: 642 (m, is_default) = val 643 if is_default: 644 default_machine = m 645 ros_config.add_machine(m, verbose=verbose) 646 elif name == 'rosparam': 647 self._rosparam_tag(tag, context, ros_config, verbose=verbose) 648 elif name == 'master': 649 pass #handled non-recursively 650 elif name == 'include': 651 val = self._include_tag(tag, context, ros_config, default_machine, is_core, verbose) 652 if val is not None: 653 default_machine = val 654 elif name == 'env': 655 self._env_tag(tag, context, ros_config) 656 elif name == 'arg': 657 self._arg_tag(tag, context, ros_config, verbose=verbose) 658 else: 659 ros_config.add_config_error("unrecognized tag "+tag.tagName) 660 return default_machine
661
662 - def _load_launch(self, launch, ros_config, is_core=False, filename=None, argv=None, verbose=True):
663 """ 664 subroutine of launch for loading XML DOM into config. Load_launch assumes that it is 665 creating the root XmlContext, and is thus affected by command-line arguments. 666 @param launch: DOM node of the root <launch> tag in the file 667 @type launch: L{Node} 668 @param ros_config: launch configuration to load XML file into 669 @type ros_config: L{ROSLaunchConfig} 670 @param is_core: (optional) if True, load file using ROS core rules. Default False. 671 @type is_core: bool 672 @param filename: (optional) name of file being loaded 673 @type filename: str 674 @param verbose: (optional) print verbose output. Default False. 675 @type verbose: bool 676 @param argv: (optional) command-line args. Default sys.argv. 677 """ 678 if argv is None: 679 argv = sys.argv 680 681 self._launch_tag(launch, ros_config, filename) 682 self.root_context = roslaunch.loader.LoaderContext('', filename) 683 roslaunch.loader.load_sysargs_into_context(self.root_context, argv) 684 685 if len(launch.getElementsByTagName('master')) > 0: 686 print >> sys.stderr, "WARNING: ignoring defunct <master /> tag" 687 self._recurse_load(ros_config, launch.childNodes, self.root_context, None, is_core, verbose)
688
689 - def _parse_launch(self, filename, verbose):
690 try: 691 if verbose: 692 print "... loading XML file [%s]"%filename 693 root = parse(filename).getElementsByTagName('launch') 694 except Exception, e: 695 raise XmlParseException("Invalid roslaunch XML syntax: %s"%e) 696 if len(root) != 1: 697 raise XmlParseException("Invalid roslaunch XML syntax: no root <launch> tag") 698 return root[0]
699
700 - def load(self, filename, ros_config, core=False, argv=None, verbose=True):
701 """ 702 load XML file into launch configuration 703 @param filename: XML config file to load 704 @type filename: str 705 @param ros_config: launch configuration to load XML file into 706 @type ros_config: L{ROSLaunchConfig} 707 @param core: if True, load file using ROS core rules 708 @type core: bool 709 @param argv: override command-line arguments (mainly for arg testing) 710 @type argv: [str] 711 """ 712 try: 713 launch = self._parse_launch(filename, verbose) 714 self._load_launch(launch, ros_config, is_core=core, filename=filename, argv=argv, verbose=verbose) 715 except ArgException, e: 716 raise XmlParseException("[%s] requires the '%s' arg to be set"%(filename, str(e))) 717 except SubstitutionException, e: 718 raise XmlParseException(str(e))
719
720 - def load_string(self, xml_text, ros_config, core=False, verbose=True):
721 """ 722 Load XML text into launch configuration 723 @param xml_text: XML configuration 724 @type xml_text: str 725 @param ros_config: launch configuration to load XML file into 726 @type ros_config: L{ROSLaunchConfig} 727 @param core: if True, load file using ROS core rules 728 @type core: bool 729 """ 730 try: 731 if verbose: 732 print "... loading XML" 733 root = parseString(xml_text).getElementsByTagName('launch') 734 except Exception, e: 735 import traceback 736 import logging 737 logging.getLogger('roslaunch').error("Invalid roslaunch XML syntax:\nstring[%s]\ntraceback[%s]"%(xml_text, traceback.format_exc())) 738 raise XmlParseException("Invalid roslaunch XML syntax: %s"%e) 739 740 if len(root) != 1: 741 raise XmlParseException("Invalid roslaunch XML syntax: no root <launch> tag") 742 self._load_launch(root[0], ros_config, core, filename='string', verbose=verbose)
743