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