package_xml.py
Go to the documentation of this file.
00001 from xml.dom.minidom import parse
00002 import collections
00003 import operator
00004 import re
00005 
00006 DEPEND_ORDERING = ['buildtool_depend', 'depend', 'build_depend', 'build_export_depend',
00007                    'run_depend', 'exec_depend', 'test_depend', 'doc_depend']
00008 
00009 ORDERING = ['name', 'version', 'description',
00010             ['maintainer', 'license', 'author', 'url']] + DEPEND_ORDERING + ['export']
00011 
00012 INDENT_PATTERN = re.compile('\n *')
00013 
00014 PEOPLE_TAGS = ['maintainer', 'author']
00015 
00016 
00017 def get_ordering_index(name, whiny=True):
00018     for i, o in enumerate(ORDERING):
00019         if type(o) == list:
00020             if name in o:
00021                 return i
00022         elif name == o:
00023             return i
00024     if name and whiny:
00025         print('\tUnsure of ordering for', name)
00026     return len(ORDERING)
00027 
00028 
00029 def get_package_tag_index(s, key='<package'):
00030     if key not in s:
00031         return 0
00032     return s.index(key)
00033 
00034 
00035 def count_trailing_spaces(s):
00036     c = 0
00037     while c < len(s) and s[-c - 1] == ' ':
00038         c += 1
00039     return c
00040 
00041 
00042 class PackageXML:
00043     def __init__(self, fn):
00044         self.fn = fn
00045         self.tree = parse(fn)
00046         self.root = self.tree.getElementsByTagName('package')[0]
00047         contents = open(fn).read()
00048         self.header = contents[:get_package_tag_index(contents)]
00049         self._name = None
00050         self._format = None
00051         self._std_tab = None
00052         self.changed = False
00053 
00054     @property
00055     def name(self):
00056         if self._name is not None:
00057             return self._name
00058         name_tags = self.root.getElementsByTagName('name')
00059         if not name_tags:
00060             return
00061         name_tag = name_tags[0]
00062         self._name = name_tag.childNodes[0].nodeValue
00063         return self._name
00064 
00065     @property
00066     def format(self):
00067         if self._format is not None:
00068             return self._format
00069         if not self.root.hasAttribute('format'):
00070             self._format = 1
00071         else:
00072             self._format = int(self.root.attributes['format'].value)
00073         return self._format
00074 
00075     @property
00076     def std_tab(self):
00077         if self._std_tab is not None:
00078             return self._std_tab
00079         tab_ct = collections.defaultdict(int)
00080         for c in self.root.childNodes:
00081             if c.nodeType == c.TEXT_NODE:
00082                 spaces = count_trailing_spaces(c.data)
00083                 tab_ct[spaces] += 1
00084         if len(tab_ct) == 0:
00085             self._std_tab = 4
00086         else:
00087             self._std_tab = max(tab_ct.iteritems(), key=operator.itemgetter(1))[0]
00088         return self._std_tab
00089 
00090     def get_packages_by_tag(self, tag):
00091         pkgs = []
00092         for el in self.root.getElementsByTagName(tag):
00093             pkgs.append(el.childNodes[0].nodeValue)
00094         return pkgs
00095 
00096     def get_packages(self, mode='build'):
00097         keys = []
00098         if mode == 'build':
00099             keys.append('build_depend')
00100         if self.format == 1 and mode == 'run':
00101             keys.append('run_depend')
00102         if self.format == 2 and mode != 'test':
00103             keys.append('depend')
00104             if mode == 'run':
00105                 keys.append('exec_depend')
00106         if mode == 'test':
00107             keys.append('test_depend')
00108         pkgs = []
00109         for key in keys:
00110             pkgs += self.get_packages_by_tag(key)
00111         return set(pkgs)
00112 
00113     def get_tab_element(self, tabs=1):
00114         return self.tree.createTextNode('\n' + ' ' * (self.std_tab * tabs))
00115 
00116     def get_child_indexes(self):
00117         """
00118            Return a dictionary where the keys are the types of nodes in the xml (build_depend, maintainer, etc)
00119            and the values are arrays marking the range of elements in the xml root that match that tag.
00120 
00121            For example, tags[build_depend] = [(5, 9), (11, 50)] means that elements [5, 9) and [11, 50) are
00122            either build_depend elements (or the strings between them)
00123         """
00124         tags = collections.defaultdict(list)
00125         i = 0
00126         current = None
00127         current_start = 0
00128         current_last = 0
00129         while i < len(self.root.childNodes):
00130             child = self.root.childNodes[i]
00131             if child.nodeType == child.TEXT_NODE:
00132                 i += 1
00133                 continue
00134 
00135             name = child.nodeName
00136             if name != current:
00137                 if current:
00138                     tags[current].append((current_start, current_last))
00139                 current_start = i
00140                 current = name
00141             current_last = i
00142             i += 1
00143         if current:
00144             tags[current].append((current_start, current_last))
00145         return dict(tags)
00146 
00147     def get_insertion_index(self, tag, tag_value=None):
00148         """ Returns the index where to insert a new element with the given tag type.
00149             If there are already elements of that type, then either insert after the last matching element,
00150             or if the list is alphabetized, insert it in the correct place alphabetically using the tag_value.
00151             Otherwise, look at the existing elements, and find ones that are supposed to come the closest
00152             before the given tag, and insert after them. If none found, add at the end.
00153         """
00154         indexes = self.get_child_indexes()
00155         if tag in indexes:
00156             if len(indexes[tag]) == 1 and tag in DEPEND_ORDERING:
00157                 start, end = indexes[tag][0]
00158                 sub_indexes = []
00159                 tag_values = []
00160                 my_index = start
00161                 value = None
00162                 for i in range(start, end):
00163                     child = self.root.childNodes[i]
00164                     if child.nodeType == child.TEXT_NODE:
00165                         continue
00166                     sub_indexes.append(i)
00167                     value = child.firstChild.data
00168                     tag_values.append(value)
00169                     if tag_value >= value:
00170                         my_index = i
00171                 if sorted(tag_values) == tag_values and tag_value <= value:
00172                     return my_index
00173             return indexes[tag][-1][1]  # last match, end index
00174 
00175         max_index = get_ordering_index(tag, whiny=False)
00176         best_tag = None
00177         best_index = None
00178         for tag in indexes:
00179             ni = get_ordering_index(tag, whiny=False)
00180             if ni >= max_index:
00181                 # This tag should appear after our tag
00182                 continue
00183 
00184             if best_tag is None or ni > best_index or indexes[tag][-1] > indexes[best_tag][-1]:
00185                 best_tag = tag
00186                 best_index = ni
00187 
00188         if best_tag is None:
00189             return len(self.root.childNodes)
00190         else:
00191             return indexes[best_tag][-1][1]
00192 
00193     def insert_new_tag(self, tag):
00194         if tag.tagName in DEPEND_ORDERING:
00195             value = tag.firstChild.data
00196         else:
00197             value = None
00198 
00199         index = self.get_insertion_index(tag.tagName, value)
00200         before = self.root.childNodes[:index + 1]
00201         after = self.root.childNodes[index + 1:]
00202         self.root.childNodes = before + [self.get_tab_element(), tag] + after
00203         self.changed = True
00204 
00205     def insert_new_tags(self, tags):
00206         for tag in tags:
00207             self.insert_new_tag(tag)
00208 
00209     def insert_new_tag_inside_another(self, parent, tag, depth=2):
00210         all_elements = []
00211         all_elements.append(self.get_tab_element(depth))
00212         all_elements.append(tag)
00213 
00214         if len(parent.childNodes) == 0:
00215             parent.childNodes = all_elements + [self.get_tab_element()]
00216         else:
00217             parent.childNodes = parent.childNodes[:-1] + all_elements + parent.childNodes[-1:]
00218         self.changed = True
00219 
00220     def insert_new_packages(self, tag, values):
00221         for pkg in sorted(values):
00222             print('\tInserting %s: %s' % (tag, pkg))
00223             node = self.tree.createElement(tag)
00224             node.appendChild(self.tree.createTextNode(pkg))
00225             self.insert_new_tag(node)
00226 
00227     def add_packages(self, build_depends, run_depends, test_depends=None, prefer_depend_tag=True):
00228         if self.format == 1:
00229             run_depends.update(build_depends)
00230         existing_build = self.get_packages('build')
00231         existing_run = self.get_packages('run')
00232         build_depends = build_depends - existing_build
00233         run_depends = run_depends - existing_run
00234         if self.format == 1:
00235             self.insert_new_packages('build_depend', build_depends)
00236             self.insert_new_packages('run_depend', run_depends)
00237         elif prefer_depend_tag:
00238             depend_tags = build_depends.union(run_depends)
00239 
00240             # Remove tags that overlap with new depends
00241             self.remove_dependencies('build_depend', existing_build.intersection(depend_tags))
00242             self.remove_dependencies('exec_depend', existing_run.intersection(depend_tags))
00243 
00244             # Insert depends
00245             self.insert_new_packages('depend', depend_tags)
00246         else:
00247             both = build_depends.intersection(run_depends)
00248             self.insert_new_packages('depend', both)
00249             self.insert_new_packages('build_depend', build_depends - both)
00250             self.insert_new_packages('exec_depend', build_depends - both - existing_run)
00251             self.insert_new_packages('exec_depend', run_depends - both)
00252 
00253         if test_depends is not None and len(test_depends) > 0:
00254             existing_test = self.get_packages('test')
00255             test_depends = set(test_depends) - existing_build - build_depends - existing_test
00256             self.insert_new_packages('test_depend', test_depends)
00257 
00258     def remove_element(self, element):
00259         """ Remove the given element AND the text element before it if it is just an indentation """
00260         parent = element.parentNode
00261         index = parent.childNodes.index(element)
00262         if index > 0:
00263             previous = parent.childNodes[index - 1]
00264             if previous.nodeType == previous.TEXT_NODE and INDENT_PATTERN.match(previous.nodeValue):
00265                 parent.removeChild(previous)
00266         parent.removeChild(element)
00267         self.changed = True
00268 
00269     def remove_dependencies(self, name, pkgs, quiet=False):
00270         for el in self.root.getElementsByTagName(name):
00271             pkg = el.childNodes[0].nodeValue
00272             if pkg in pkgs:
00273                 if not quiet:
00274                     print('\tRemoving %s %s' % (name, pkg))
00275                 self.remove_element(el)
00276 
00277     def get_elements_by_tags(self, tags):
00278         elements = []
00279         for tag in tags:
00280             elements += self.root.getElementsByTagName(tag)
00281         return elements
00282 
00283     def get_people(self):
00284         people = []
00285         for el in self.get_elements_by_tags(PEOPLE_TAGS):
00286             name = el.childNodes[0].nodeValue
00287             email = el.getAttribute('email')
00288             people.append((name, email))
00289         return people
00290 
00291     def update_people(self, target_name, target_email=None, search_name=None, search_email=None):
00292         for el in self.get_elements_by_tags(PEOPLE_TAGS):
00293             name = el.childNodes[0].nodeValue
00294             email = el.getAttribute('email') if el.hasAttribute('email') else ''
00295             if (search_name is None or name == search_name) and (search_email is None or email == search_email):
00296                 el.childNodes[0].nodeValue = target_name
00297                 if target_email:
00298                     el.setAttribute('email', target_email)
00299                 print('\tReplacing %s %s/%s with %s/%s' % (el.nodeName, name, email, target_name, target_email))
00300                 self.changed = True
00301 
00302     def get_license_element(self):
00303         els = self.root.getElementsByTagName('license')
00304         if len(els) == 0:
00305             return None
00306         return els[0]
00307 
00308     def get_license(self):
00309         el = self.get_license_element()
00310         return el.childNodes[0].nodeValue
00311 
00312     def set_license(self, license):
00313         el = self.get_license_element()
00314         if license != el.childNodes[0].nodeValue:
00315             el.childNodes[0].nodeValue = license
00316             self.changed = True
00317 
00318     def is_metapackage(self):
00319         for node in self.root.getElementsByTagName('export'):
00320             for child in node.childNodes:
00321                 if child.nodeType == child.ELEMENT_NODE:
00322                     if child.nodeName == 'metapackage':
00323                         return True
00324         return False
00325 
00326     def get_plugin_xmls(self):
00327         """ Returns a mapping from the package name to a list of the relative path(s) for the plugin xml(s) """
00328         xmls = collections.defaultdict(list)
00329         export = self.root.getElementsByTagName('export')
00330         if len(export) == 0:
00331             return xmls
00332         for ex in export:
00333             for n in ex.childNodes:
00334                 if n.nodeType == self.root.ELEMENT_NODE:
00335                     plugin = n.getAttribute('plugin').replace('${prefix}/', '')
00336                     xmls[n.nodeName].append(plugin)
00337         return xmls
00338 
00339     def add_plugin_export(self, pkg_name, xml_path):
00340         """ Adds the plugin configuration if not found. Adds export tag as needed.
00341             Returns the export tag it was added to."""
00342         export_tags = self.root.getElementsByTagName('export')
00343         if len(export_tags) == 0:
00344             export_tag = self.tree.createElement('export')
00345             self.insert_new_tag(export_tag)
00346             export_tags = [export_tag]
00347 
00348         attr = '${prefix}/' + xml_path
00349         for ex_tag in export_tags:
00350             for tag in ex_tag.childNodes:
00351                 if tag.nodeName != pkg_name:
00352                     continue
00353                 plugin = tag.attributes.get('plugin')
00354                 if plugin and plugin.value == attr:
00355                     return
00356 
00357         ex_el = export_tags[0]
00358         pe = self.tree.createElement(pkg_name)
00359         pe.setAttribute('plugin', attr)
00360         self.insert_new_tag_inside_another(ex_el, pe)
00361         return ex_el
00362 
00363     def write(self, new_fn=None):
00364         if new_fn is None:
00365             new_fn = self.fn
00366 
00367         if new_fn == self.fn and not self.changed:
00368             return
00369 
00370         s = self.tree.toxml(self.tree.encoding)
00371         index = get_package_tag_index(s)
00372         s = self.header + s[index:]
00373 
00374         with open(new_fn, 'w') as f:
00375             f.write(s.encode('UTF-8'))
00376             f.write('\n')


ros_introspection
Author(s):
autogenerated on Wed Jun 19 2019 19:21:34