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