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