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