1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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
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
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
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
89 """
90 Decorator for evaluating whether or not tag function should run based on if/unless attributes
91 """
92 def call(*args, **kwds):
93
94 if ifunless_test(args[0], args[1], args[2]):
95 return f(*args, **kwds)
96 return call
97
103 """Error with the XML syntax (e.g. invalid attribute/value combinations)"""
104 pass
105
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
131 _is_default = {'true': True, 'false': False, 'never': False }
132
133 _assignable = {'true': True, 'false': True, 'never': False }
134
135
136
137
138 -class XmlLoader(loader.Loader):
139 """
140 Parser for roslaunch XML format. Loads parsed representation into ROSConfig model.
141 """
142
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
150 self.root_context = None
151 self.resolve_anon = resolve_anon
152
154 """
155 Wrapper around substitution_args.resolve_args to set common parameters
156 """
157
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
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
173
174
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
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
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
197
198
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
207 param = ns_join(ns or '', param or '')
208
209
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
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
238
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
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
347 pkg, node_type = self.reqd_attrs(tag, context, ('pkg', 'type'))
348
349
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
357 required, respawn = [_bool_attr(*rr) for rr in ((required, False, 'required'),\
358 (respawn, False, 'respawn'))]
359
360
361
362 remap_context = context.child('')
363
364
365
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
382
383 for p in itertools.chain(context.params, param_ns.params):
384 pkey = p.key
385 if is_private(pkey):
386
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
419 context = context.child(None)
420
421
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
429 name, address = self.reqd_attrs(tag, context, ('name', 'address'))
430
431
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
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
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
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
485
486 ENV_ATTRS = ('name', 'value')
487 @ifunless
488 - def _env_tag(self, tag, context, ros_config):
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
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
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
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
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
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
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
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
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
719
720
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