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