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

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