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
37 """
38 Internal library for processing 'manifest' files, i.e. manifest.xml and stack.xml.
39 For external code apis, see L{roslib.manifest} and L{roslib.stack_manifest}.
40 """
41
42 import sys
43 import os
44 import xml.dom
45 import xml.dom.minidom as dom
46
47 import roslib.exceptions
48
49
50 REQUIRED = ['author', 'license']
51 ALLOWXHTML = ['description']
52 OPTIONAL = ['logo', 'url', 'brief', 'description', 'status',
53 'notes', 'depend', 'rosdep', 'export', 'review',
54 'versioncontrol', 'platform', 'version', 'rosbuild2',
55 'catkin']
56 VALID = REQUIRED + OPTIONAL
57
59
61 return [t for t in n.childNodes if t.nodeType == t.ELEMENT_NODE and t.tagName == name]
62
64 """
65 Validator for optional elements.
66 @raise ManifestException: if validation fails
67 """
68 def check(n, filename):
69 n = get_nodes_by_name(n, name)
70 if len(n) > 1 and not merge_multiple:
71 raise ManifestException("Invalid manifest file: must have a single '%s' element"%name)
72 if n:
73 values = []
74 for child in n:
75 if allowXHTML:
76 values.append(''.join([x.toxml() for x in child.childNodes]))
77 else:
78 values.append(_get_text(child.childNodes).strip())
79 return ', '.join(values)
80 return check
81
83 """
84 Validator for required elements.
85 @raise ManifestException: if validation fails
86 """
87 def check(n, filename):
88 n = get_nodes_by_name(n, name)
89 if not n:
90
91 return ''
92 if len(n) != 1 and not merge_multiple:
93 raise ManifestException("Invalid manifest file: must have only one '%s' element"%name)
94 values = []
95 for child in n:
96 if allowXHTML:
97 values.append(''.join([x.toxml() for x in child.childNodes]))
98 else:
99 values.append(_get_text(child.childNodes).strip())
100 return ', '.join(values)
101 return check
102
115 return check
116
118 """
119 Validator for manifest depends.
120 @raise ManifestException: if validation fails
121 """
122 def check(n, filename):
123 nodes = get_nodes_by_name(n, name)
124
125
126
127
128
129 depends = [e.attributes for e in nodes if 'thirdparty' not in e.attributes.keys()]
130 try:
131 packages = [d['package'].value for d in depends]
132 except KeyError:
133 raise ManifestException("Invalid manifest file: depends is missing 'package' attribute")
134
135 return [Depend(p) for p in packages]
136 return check
137
139 """
140 Validator for stack depends.
141 @raise ManifestException: if validation fails
142 """
143 def check(n, filename):
144 nodes = get_nodes_by_name(n, name)
145 depends = [e.attributes for e in nodes]
146 packages = [d['stack'].value for d in depends]
147 return [StackDepend(p) for p in packages]
148 return check
149
151 """
152 Validator for stack rosdeps.
153 @raise ManifestException: if validation fails
154 """
155 def check(n, filename):
156 nodes = get_nodes_by_name(n, name)
157 rosdeps = [e.attributes for e in nodes]
158 names = [d['name'].value for d in rosdeps]
159 return [ROSDep(n) for n in names]
160 return check
161
163 attrs = {}
164 for k in node.attributes.keys():
165 attrs[k] = node.attributes.get(k).value
166 return attrs
167
169 def check(n, filename):
170 ret_val = []
171 for e in get_nodes_by_name(n, name):
172 elements = [c for c in e.childNodes if c.nodeType == c.ELEMENT_NODE]
173 ret_val.extend([Export(t.tagName, _attrs(t), _get_text(t.childNodes)) for t in elements])
174 return ret_val
175 return check
176
178 def check(n, filename):
179 e = get_nodes_by_name(n, name)
180 if not e:
181 return None
182
183 return VersionControl(e[0].attributes['type'].value, e[0].attributes['url'].value)
184 return check
185
186 -def check(name, merge_multiple=False):
205
207 """
208 Manifest 'export' tag
209 """
210
212 """
213 Create new export instance.
214 @param tag: name of the XML tag
215 @type tag: str
216 @param attrs: dictionary of XML attributes for this export tag
217 @type attrs: dict
218 @param str: string value contained by tag, if any
219 @type str: str
220 """
221 self.tag = tag
222 self.attrs = attrs
223 self.str = str
224
225 - def get(self, attr):
226 """
227 @return: value of attribute or None if attribute not set
228 @rtype: str
229 """
230 return self.attrs.get(attr, None)
232 """
233 @return: export instance represented as manifest XML
234 @rtype: str
235 """
236 attrs = ' '.join([' %s="%s"'%(k,v) for k,v in self.attrs.items()])
237 if self.str:
238 return '<%s%s>%s</%s>'%(self.tag, attrs, self.str, self.tag)
239 else:
240 return '<%s%s />'%(self.tag, attrs)
241
286
288 """
289 Manifest 'depend' tag
290 """
291 __slots__ = ['package']
292
294 """
295 Create new depend instance.
296 @param package: package name. must be non-empty
297 @type package: str
298 """
299 if not package:
300 raise ValueError("bad 'package' attribute")
301 self.package = package
311 """
312 @return: depend instance represented as manifest XML
313 @rtype: str
314 """
315 return '<depend package="%s" />'%self.package
316
318 """
319 Stack Manifest 'depend' tag
320 """
321 __slots__ = ['stack', 'annotation']
322
324 """
325 @param stack: stack name. must be non-empty
326 @type stack: str
327 """
328 if not stack:
329 raise ValueError("bad 'stack' attribute")
330 self.stack = stack
331 self.annotation = None
332
342 """
343 @return: stack depend instance represented as stack manifest XML
344 @rtype: str
345 """
346 if self.annotation:
347 return '<depend stack="%s" /> <!-- %s -->'%(self.stack, self.annotation)
348 else:
349 return '<depend stack="%s" />'%self.stack
350
352 """
353 Manifest 'rosdep' tag
354 """
355 __slots__ = ['name',]
356
358 """
359 Create new rosdep instance.
360 @param name: dependency name. Must be non-empty.
361 @type name: str
362 """
363 if not name:
364 raise ValueError("bad 'name' attribute")
365 self.name = name
367 """
368 @return: rosdep instance represented as manifest XML
369 @rtype: str
370 """
371 return '<rosdep name="%s" />'%self.name
372
374 """
375 Manifest 'versioncontrol' tag
376 """
377 __slots__ = ['type', 'url']
378
380 """
381 @param type_: version control type (e.g. 'svn'). must be non empty
382 @type type_: str
383 @param url: URL associated with version control. must be non empty
384 @type url: str
385 """
386 def is_string_type(obj):
387 try:
388 return isinstance(obj, basestring)
389 except NameError:
390 return isinstance(obj, str)
391
392 if not type_ or not is_string_type(type_):
393 raise ValueError("bad 'type' attribute")
394 if not url is None and not is_string_type(url):
395 raise ValueError("bad 'url' attribute")
396 self.type = type_
397 self.url = url
399 """
400 @return: versioncontrol instance represented as manifest XML
401 @rtype: str
402 """
403 if self.url:
404 return '<versioncontrol type="%s" url="%s" />'%(self.type, self.url)
405 else:
406 return '<versioncontrol type="%s" />'%self.type
407
409 """
410 Object representation of a ROS manifest file
411 """
412 __slots__ = ['description', 'brief', \
413 'author', 'license', 'license_url', 'url', \
414 'depends', 'rosdeps','platforms',\
415 'logo', 'exports', 'version',\
416 'versioncontrol', 'status', 'notes',\
417 'unknown_tags',\
418 '_type']
420 self.description = self.brief = self.author = \
421 self.license = self.license_url = \
422 self.url = self.logo = self.status = \
423 self.version = self.notes = ''
424 self.depends = []
425 self.rosdeps = []
426 self.exports = []
427 self.platforms = []
428 self._type = _type
429
430
431 self.unknown_tags = []
432
436 """
437 @return: exports that match the specified tag and attribute, e.g. 'python', 'path'
438 @rtype: [L{Export}]
439 """
440 return [e.get(attr) for e in self.exports if e.tag == tag if e.get(attr) is not None]
442 """
443 @return: Manifest instance as ROS XML manifest
444 @rtype: str
445 """
446 if not self.brief:
447 desc = " <description>%s</description>"%self.description
448 else:
449 desc = ' <description brief="%s">%s</description>'%(self.brief, self.description)
450 author = " <author>%s</author>"%self.author
451 if self.license_url:
452 license = ' <license url="%s">%s</license>'%(self.license_url, self.license)
453 else:
454 license = " <license>%s</license>"%self.license
455 versioncontrol = url = logo = exports = version = ""
456 if self.url:
457 url = " <url>%s</url>"%self.url
458 if self.version:
459 version = " <version>%s</version>"%self.version
460 if self.logo:
461 logo = " <logo>%s</logo>"%self.logo
462 depends = '\n'.join([" %s"%d.xml() for d in self.depends])
463 rosdeps = '\n'.join([" %s"%rd.xml() for rd in self.rosdeps])
464 platforms = '\n'.join([" %s"%p.xml() for p in self.platforms])
465 if self.exports:
466 exports = ' <export>\n' + '\n'.join([" %s"%e.xml() for e in self.exports]) + ' </export>'
467 if self.versioncontrol:
468 versioncontrol = " %s"%self.versioncontrol.xml()
469 if self.status or self.notes:
470 review = ' <review status="%s" notes="%s" />'%(self.status, self.notes)
471
472
473 fields = filter(lambda x: x,
474 [desc, author, license, review, url, logo, depends,
475 rosdeps, platforms, exports, versioncontrol, version])
476 return "<%s>\n"%self._type + "\n".join(fields) + "\n</%s>"%self._type
477
478 -def _get_text(nodes):
479 """
480 DOM utility routine for getting contents of text nodes
481 """
482 return "".join([n.data for n in nodes if n.nodeType == n.TEXT_NODE])
483
485 """
486 Parse manifest file (package, stack)
487 @param m: field to populate
488 @type m: L{_Manifest}
489 @param file: manifest.xml file path
490 @type file: str
491 @return: return m, populated with parsed fields
492 @rtype: L{_Manifest}
493 """
494 if not file:
495 raise ValueError("Missing manifest file argument")
496 if not os.path.isfile(file):
497 raise ValueError("Invalid/non-existent manifest file: %s"%file)
498 with open(file, 'r') as f:
499 text = f.read()
500 try:
501 return parse(m, text, file)
502 except ManifestException as e:
503 raise ManifestException("Invalid manifest file [%s]: %s"%(os.path.abspath(file), e))
504
505 -def parse(m, string, filename='string'):
506 """
507 Parse manifest.xml string contents
508 @param string: manifest.xml contents
509 @type string: str
510 @param m: field to populate
511 @type m: L{_Manifest}
512 @return: return m, populated with parsed fields
513 @rtype: L{_Manifest}
514 """
515 try:
516 d = dom.parseString(string)
517 except Exception as e:
518 raise ManifestException("invalid XML: %s"%e)
519
520 p = get_nodes_by_name(d, m._type)
521 if len(p) != 1:
522 raise ManifestException("manifest must have a single '%s' element"%m._type)
523 p = p[0]
524 m.description = check('description')(p, filename)
525 m.brief = ''
526 try:
527 tag = get_nodes_by_name(p, 'description')[0]
528 m.brief = tag.getAttribute('brief') or ''
529 except:
530
531 pass
532
533 if m._type == 'package':
534 m.depends = check_depends('depend')(p, filename)
535 elif m._type == 'stack':
536 m.depends = check_stack_depends('depend')(p, filename)
537 elif m._type == 'app':
538
539 pass
540 m.rosdeps = check('rosdep')(p, filename)
541 m.platforms = check('platform')(p, filename)
542 m.exports = check('export')(p, filename)
543 m.versioncontrol = check('versioncontrol')(p,filename)
544 m.license = check('license')(p, filename)
545 m.license_url = ''
546 try:
547 tag = get_nodes_by_name(p, 'license')[0]
548 m.license_url = tag.getAttribute('url') or ''
549 except:
550 pass
551
552 m.status='unreviewed'
553 try:
554 tag = get_nodes_by_name(p, 'review')[0]
555 m.status=tag.getAttribute('status') or ''
556 except:
557 pass
558
559 m.notes=''
560 try:
561 tag = get_nodes_by_name(p, 'review')[0]
562 m.notes=tag.getAttribute('notes') or ''
563 except:
564 pass
565
566 m.author = check('author', True)(p, filename)
567 m.url = check('url')(p, filename)
568 m.version = check('version')(p, filename)
569 m.logo = check('logo')(p, filename)
570
571
572 if m._type == 'stack':
573 if m.exports:
574 raise ManifestException("stack manifests are not allowed to have exports")
575 if m.rosdeps:
576 raise ManifestException("stack manifests are not allowed to have rosdeps")
577
578
579 m.unknown_tags = [e for e in p.childNodes if e.nodeType == e.ELEMENT_NODE and e.tagName not in VALID]
580 return m
581