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', 'notes', 'depend', 'rosdep', 'export', 'review', 'versioncontrol', 'platform']
53 VALID = REQUIRED + OPTIONAL
54
56
58 return [t for t in n.childNodes if t.nodeType == t.ELEMENT_NODE and t.tagName == name]
59
60
61
62
63
65 """
66 Validator for optional elements.
67 @raise ManifestException: if validation fails
68 """
69 def check(n, filename):
70 n = n.getElementsByTagName(name)
71 if len(n) > 1:
72 raise ManifestException("Invalid manifest file: must have a single '%s' element"%name)
73 if n:
74 if allowXHTML:
75 return ''.join([x.toxml() for x in n[0].childNodes])
76 return _get_text(n[0].childNodes).strip()
77 return check
78
80 """
81 Validator for required elements.
82 @raise ManifestException: if validation fails
83 """
84 def check(n, filename):
85 n = n.getElementsByTagName(name)
86 if not n:
87 print >> sys.stderr, "Invalid manifest file[%s]: missing required '%s' element"%(filename, name)
88 return ''
89 if len(n) != 1:
90 raise ManifestException("Invalid manifest file: must have only one '%s' element"%name)
91 if allowXHTML:
92 return ''.join([x.toxml() for x in n[0].childNodes])
93 return _get_text(n[0].childNodes).strip()
94 return check
95
108 return check
109
111 """
112 Validator for manifest depends.
113 @raise ManifestException: if validation fails
114 """
115 def check(n, filename):
116 nodes = get_nodes_by_name(n, name)
117 depends = [e.attributes for e in nodes if 'thirdparty' not in e.attributes.keys()]
118 packages = [d['package'].value for d in depends]
119 return [Depend(p) for p in packages]
120 return check
121
123 """
124 Validator for stack depends.
125 @raise ManifestException: if validation fails
126 """
127 def check(n, filename):
128 nodes = get_nodes_by_name(n, name)
129 depends = [e.attributes for e in nodes if 'thirdparty' not in e.attributes.keys()]
130 packages = [d['stack'].value for d in depends]
131 return [StackDepend(p) for p in packages]
132 return check
133
135 """
136 Validator for stack rosdeps.
137 @raise ManifestException: if validation fails
138 """
139 def check(n, filename):
140 nodes = get_nodes_by_name(n, name)
141 rosdeps = [e.attributes for e in nodes]
142 names = [d['name'].value for d in rosdeps]
143 return [ROSDep(n) for n in names]
144 return check
145
147 attrs = {}
148 for k in node.attributes.keys():
149 attrs[k] = node.attributes.get(k).value
150 return attrs
151
153 def check(n, filename):
154 ret_val = []
155 for e in n.getElementsByTagName(name):
156 elements = [c for c in e.childNodes if c.nodeType == c.ELEMENT_NODE]
157 ret_val.extend([Export(t.tagName, _attrs(t), _get_text(t.childNodes)) for t in elements])
158 return ret_val
159 return check
160
162 def check(n, filename):
163 e = n.getElementsByTagName(name)
164 if not e:
165 return None
166
167 return VersionControl(e[0].attributes['type'].value, e[0].attributes['url'].value)
168 return check
169
189
191 """
192 Manifest 'export' tag
193 """
194
196 """
197 Create new export instance.
198 @param tag: name of the XML tag
199 @type tag: str
200 @param attrs: dictionary of XML attributes for this export tag
201 @type attrs: dict
202 @param str: string value contained by tag, if any
203 @type str: str
204 """
205 self.tag = tag
206 self.attrs = attrs
207 self.str = str
208
209 - def get(self, attr):
210 """
211 @return: value of attribute or None if attribute not set
212 @rtype: str
213 """
214 return self.attrs.get(attr, None)
216 """
217 @return: export instance represented as manifest XML
218 @rtype: str
219 """
220 attrs = ' '.join([' %s="%s"'%(k,v) for k,v in self.attrs.iteritems()])
221 if self.str:
222 return '<%s%s>%s</%s>'%(self.tag, attrs, self.str, self.tag)
223 else:
224 return '<%s%s />'%(self.tag, attrs)
225
272
274 """
275 Manifest 'depend' tag
276 """
277 __slots__ = ['package']
278
280 """
281 Create new depend instance.
282 @param package: package name. must be non-empty
283 @type package: str
284 """
285 if not package or not isinstance(package, basestring):
286 raise ValueError("bad 'package' attribute")
287 self.package = package
297 """
298 @return: depend instance represented as manifest XML
299 @rtype: str
300 """
301 return '<depend package="%s" />'%self.package
302
304 """
305 Stack Manifest 'depend' tag
306 """
307 __slots__ = ['stack', 'annotation']
308
310 """
311 @param stack: stack name. must be non-empty
312 @type stack: str
313 """
314 if not stack or not isinstance(stack, basestring):
315 raise ValueError("bad 'stack' attribute")
316 self.stack = stack
317 self.annotation = None
318
328 """
329 @return: stack depend instance represented as stack manifest XML
330 @rtype: str
331 """
332 if self.annotation:
333 return '<depend stack="%s" /> <!-- %s -->'%(self.stack, self.annotation)
334 else:
335 return '<depend stack="%s" />'%self.stack
336
338 """
339 Manifest 'rosdep' tag
340 """
341 __slots__ = ['name',]
342
344 """
345 Create new rosdep instance.
346 @param name: dependency name. Must be non-empty.
347 @type name: str
348 """
349 if not name or not isinstance(name, basestring):
350 raise ValueError("bad 'name' attribute")
351 self.name = name
353 """
354 @return: rosdep instance represented as manifest XML
355 @rtype: str
356 """
357 return '<rosdep name="%s" />'%self.name
358
360 """
361 Manifest 'versioncontrol' tag
362 """
363 __slots__ = ['type', 'url']
364
366 """
367 @param type_: version control type (e.g. 'svn'). must be non empty
368 @type type_: str
369 @param url: URL associated with version control. must be non empty
370 @type url: str
371 """
372 if not type_ or not isinstance(type_, basestring):
373 raise ValueError("bad 'type' attribute")
374 if not url is None and not isinstance(url, basestring):
375 raise ValueError("bad 'url' attribute")
376 self.type = type_
377 self.url = url
379 """
380 @return: versioncontrol instance represented as manifest XML
381 @rtype: str
382 """
383 if self.url:
384 return '<versioncontrol type="%s" url="%s" />'%(self.type, self.url)
385 else:
386 return '<versioncontrol type="%s" />'%self.type
387
389 """
390 Object representation of a ROS manifest file
391 """
392 __slots__ = ['description', 'brief', \
393 'author', 'license', 'license_url', 'url', \
394 'depends', 'rosdeps','platforms',\
395 'logo', 'exports',\
396 'versioncontrol', 'status', 'notes',\
397 'unknown_tags',\
398 '_type']
400 self.description = self.brief = self.author = \
401 self.license = self.license_url = \
402 self.url = self.logo = self.status = self.notes = ''
403 self.depends = []
404 self.rosdeps = []
405 self.exports = []
406 self.platforms = []
407 self._type = _type
408
409
410 self.unknown_tags = []
411
415 """
416 @return: exports that match the specified tag and attribute, e.g. 'python', 'path'
417 @rtype: [L{Export}]
418 """
419 return [e.get(attr) for e in self.exports if e.tag == tag if e.get(attr) is not None]
421 """
422 @return: Manifest instance as ROS XML manifest
423 @rtype: str
424 """
425 if not self.brief:
426 desc = " <description>%s</description>"%self.description
427 else:
428 desc = ' <description brief="%s">%s</description>'%(self.brief, self.description)
429 author = " <author>%s</author>"%self.author
430 if self.license_url:
431 license = ' <license url="%s">%s</license>'%(self.license_url, self.license)
432 else:
433 license = " <license>%s</license>"%self.license
434 versioncontrol = url = logo = exports = ""
435 if self.url:
436 url = " <url>%s</url>"%self.url
437 if self.logo:
438 logo = " <logo>%s</logo>"%self.logo
439 depends = '\n'.join([" %s"%d.xml() for d in self.depends])
440 rosdeps = '\n'.join([" %s"%rd.xml() for rd in self.rosdeps])
441 platforms = '\n'.join([" %s"%p.xml() for p in self.platforms])
442 if self.exports:
443 exports = ' <export>\n' + '\n'.join([" %s"%e.xml() for e in self.exports]) + ' </export>'
444 if self.versioncontrol:
445 versioncontrol = " %s"%self.versioncontrol.xml()
446 if self.status or self.notes:
447 review = ' <review status="%s" notes="%s" />'%(self.status, self.notes)
448
449
450 fields = filter(lambda x: x, [desc, author, license, review, url, logo, depends, rosdeps, platforms, exports, versioncontrol])
451 return "<%s>\n"%self._type + "\n".join(fields) + "\n</%s>"%self._type
452
453 -def _get_text(nodes):
454 """
455 DOM utility routine for getting contents of text nodes
456 """
457 return "".join([n.data for n in nodes if n.nodeType == n.TEXT_NODE])
458
460 """
461 Parse manifest file (package, stack)
462 @param m: field to populate
463 @type m: L{_Manifest}
464 @param file: manifest.xml file path
465 @type file: str
466 @return: return m, populated with parsed fields
467 @rtype: L{_Manifest}
468 """
469 if not file:
470 raise ValueError("Missing manifest file argument")
471 if not os.path.isfile(file):
472 raise ValueError("Invalid/non-existent manifest file: %s"%file)
473 f = open(file, 'r')
474 try:
475 text = f.read()
476 finally:
477 f.close()
478 try:
479 return parse(m, text, file)
480 except ManifestException, e:
481 raise ManifestException("Invalid manifest file [%s]: %s"%(os.path.abspath(file), e))
482
483 -def parse(m, string, filename='string'):
484 """
485 Parse manifest.xml string contents
486 @param string: manifest.xml contents
487 @type string: str
488 @param m: field to populate
489 @type m: L{_Manifest}
490 @return: return m, populated with parsed fields
491 @rtype: L{_Manifest}
492 """
493 try:
494 d = dom.parseString(string)
495 except Exception, e:
496 raise ManifestException("invalid XML: %s"%e)
497 p = d.getElementsByTagName(m._type)
498 if len(p) != 1:
499 raise ManifestException("manifest must have a single '%s' element"%m._type)
500 p = p[0]
501 m.description = check('description')(p, filename)
502 m.brief = ''
503 try:
504 tag = p.getElementsByTagName('description')[0]
505 m.brief = tag.getAttribute('brief') or ''
506 except:
507
508 pass
509
510 if m._type == 'package':
511 m.depends = check_depends('depend')(p, filename)
512 elif m._type == 'stack':
513 m.depends = check_stack_depends('depend')(p, filename)
514 elif m._type == 'app':
515
516 pass
517 m.rosdeps = check('rosdep')(p, filename)
518 m.platforms = check('platform')(p, filename)
519 m.exports = check('export')(p, filename)
520 m.versioncontrol = check('versioncontrol')(p,filename)
521 m.license = check('license')(p, filename)
522 m.license_url = ''
523 try:
524 tag = p.getElementsByTagName('license')[0]
525 m.license_url = tag.getAttribute('url') or ''
526 except:
527 pass
528
529 m.status='unreviewed'
530 try:
531 tag = p.getElementsByTagName('review')[0]
532 m.status=tag.getAttribute('status') or ''
533 except:
534 pass
535
536 m.notes=''
537 try:
538 tag = p.getElementsByTagName('review')[0]
539 m.notes=tag.getAttribute('notes') or ''
540 except:
541 pass
542
543 m.author = check('author')(p, filename)
544 m.url = check('url')(p, filename)
545 m.logo = check('logo')(p, filename)
546
547
548 if m._type == 'stack':
549 if m.exports:
550 raise ManifestException("stack manifests are not allowed to have exports")
551 if m.rosdeps:
552 raise ManifestException("stack manifests are not allowed to have rosdeps")
553
554
555 m.unknown_tags = [e for e in p.childNodes if e.nodeType == e.ELEMENT_NODE and e.tagName not in VALID]
556 return m
557