package_xml.py
Go to the documentation of this file.
1 from xml.dom.minidom import parse
2 import collections
3 import operator
4 import re
5 
6 DEPEND_ORDERING = ['buildtool_depend', 'depend', 'build_depend', 'build_export_depend',
7  'run_depend', 'exec_depend', 'test_depend', 'doc_depend']
8 
9 ORDERING = ['name', 'version', 'description',
10  ['maintainer', 'license', 'author', 'url']] + DEPEND_ORDERING + ['export']
11 
12 INDENT_PATTERN = re.compile('\n *')
13 
14 PEOPLE_TAGS = ['maintainer', 'author']
15 
16 
17 def get_ordering_index(name, whiny=True):
18  for i, o in enumerate(ORDERING):
19  if type(o) == list:
20  if name in o:
21  return i
22  elif name == o:
23  return i
24  if name and whiny:
25  print('\tUnsure of ordering for', name)
26  return len(ORDERING)
27 
28 
29 def get_package_tag_index(s, key='<package'):
30  if key not in s:
31  return 0
32  return s.index(key)
33 
34 
36  c = 0
37  while c < len(s) and s[-c - 1] == ' ':
38  c += 1
39  return c
40 
41 
42 class PackageXML:
43  def __init__(self, fn):
44  self.fn = fn
45  self.tree = parse(fn)
46  self.root = self.tree.getElementsByTagName('package')[0]
47  contents = open(fn).read()
48  self.header = contents[:get_package_tag_index(contents)]
49  self._name = None
50  self._format = None
51  self._std_tab = None
52  self.changed = False
53 
54  @property
55  def name(self):
56  if self._name is not None:
57  return self._name
58  name_tags = self.root.getElementsByTagName('name')
59  if not name_tags:
60  return
61  name_tag = name_tags[0]
62  self._name = name_tag.childNodes[0].nodeValue
63  return self._name
64 
65  @property
66  def format(self):
67  if self._format is not None:
68  return self._format
69  if not self.root.hasAttribute('format'):
70  self._format = 1
71  else:
72  self._format = int(self.root.attributes['format'].value)
73  return self._format
74 
75  @property
76  def std_tab(self):
77  if self._std_tab is not None:
78  return self._std_tab
79  tab_ct = collections.defaultdict(int)
80  for c in self.root.childNodes:
81  if c.nodeType == c.TEXT_NODE:
82  spaces = count_trailing_spaces(c.data)
83  tab_ct[spaces] += 1
84  if len(tab_ct) == 0:
85  self._std_tab = 4
86  else:
87  self._std_tab = max(tab_ct.iteritems(), key=operator.itemgetter(1))[0]
88  return self._std_tab
89 
90  def get_packages_by_tag(self, tag):
91  pkgs = []
92  for el in self.root.getElementsByTagName(tag):
93  pkgs.append(el.childNodes[0].nodeValue)
94  return pkgs
95 
96  def get_packages(self, mode='build'):
97  keys = []
98  if mode == 'build':
99  keys.append('build_depend')
100  if self.format == 1 and mode == 'run':
101  keys.append('run_depend')
102  if self.format == 2 and mode != 'test':
103  keys.append('depend')
104  if mode == 'run':
105  keys.append('exec_depend')
106  if mode == 'test':
107  keys.append('test_depend')
108  pkgs = []
109  for key in keys:
110  pkgs += self.get_packages_by_tag(key)
111  return set(pkgs)
112 
113  def get_tab_element(self, tabs=1):
114  return self.tree.createTextNode('\n' + ' ' * (self.std_tab * tabs))
115 
116  def get_child_indexes(self):
117  """
118  Return a dictionary where the keys are the types of nodes in the xml (build_depend, maintainer, etc)
119  and the values are arrays marking the range of elements in the xml root that match that tag.
120 
121  For example, tags[build_depend] = [(5, 9), (11, 50)] means that elements [5, 9) and [11, 50) are
122  either build_depend elements (or the strings between them)
123  """
124  tags = collections.defaultdict(list)
125  i = 0
126  current = None
127  current_start = 0
128  current_last = 0
129  while i < len(self.root.childNodes):
130  child = self.root.childNodes[i]
131  if child.nodeType == child.TEXT_NODE:
132  i += 1
133  continue
134 
135  name = child.nodeName
136  if name != current:
137  if current:
138  tags[current].append((current_start, current_last))
139  current_start = i
140  current = name
141  current_last = i
142  i += 1
143  if current:
144  tags[current].append((current_start, current_last))
145  return dict(tags)
146 
147  def get_insertion_index(self, tag, tag_value=None):
148  """ Returns the index where to insert a new element with the given tag type.
149  If there are already elements of that type, then either insert after the last matching element,
150  or if the list is alphabetized, insert it in the correct place alphabetically using the tag_value.
151  Otherwise, look at the existing elements, and find ones that are supposed to come the closest
152  before the given tag, and insert after them. If none found, add at the end.
153  """
154  indexes = self.get_child_indexes()
155  if tag in indexes:
156  if len(indexes[tag]) == 1 and tag in DEPEND_ORDERING:
157  start, end = indexes[tag][0]
158  sub_indexes = []
159  tag_values = []
160  my_index = start
161  value = None
162  for i in range(start, end):
163  child = self.root.childNodes[i]
164  if child.nodeType == child.TEXT_NODE:
165  continue
166  sub_indexes.append(i)
167  value = child.firstChild.data
168  tag_values.append(value)
169  if tag_value >= value:
170  my_index = i
171  if sorted(tag_values) == tag_values and tag_value <= value:
172  return my_index
173  return indexes[tag][-1][1] # last match, end index
174 
175  max_index = get_ordering_index(tag, whiny=False)
176  best_tag = None
177  best_index = None
178  for tag in indexes:
179  ni = get_ordering_index(tag, whiny=False)
180  if ni >= max_index:
181  # This tag should appear after our tag
182  continue
183 
184  if best_tag is None or ni > best_index or indexes[tag][-1] > indexes[best_tag][-1]:
185  best_tag = tag
186  best_index = ni
187 
188  if best_tag is None:
189  return len(self.root.childNodes)
190  else:
191  return indexes[best_tag][-1][1]
192 
193  def insert_new_tag(self, tag):
194  if tag.tagName in DEPEND_ORDERING:
195  value = tag.firstChild.data
196  else:
197  value = None
198 
199  index = self.get_insertion_index(tag.tagName, value)
200  before = self.root.childNodes[:index + 1]
201  after = self.root.childNodes[index + 1:]
202  self.root.childNodes = before + [self.get_tab_element(), tag] + after
203  self.changed = True
204 
205  def insert_new_tags(self, tags):
206  for tag in tags:
207  self.insert_new_tag(tag)
208 
209  def insert_new_tag_inside_another(self, parent, tag, depth=2):
210  all_elements = []
211  all_elements.append(self.get_tab_element(depth))
212  all_elements.append(tag)
213 
214  if len(parent.childNodes) == 0:
215  parent.childNodes = all_elements + [self.get_tab_element()]
216  else:
217  parent.childNodes = parent.childNodes[:-1] + all_elements + parent.childNodes[-1:]
218  self.changed = True
219 
220  def insert_new_packages(self, tag, values):
221  for pkg in sorted(values):
222  print('\tInserting %s: %s' % (tag, pkg))
223  node = self.tree.createElement(tag)
224  node.appendChild(self.tree.createTextNode(pkg))
225  self.insert_new_tag(node)
226 
227  def add_packages(self, build_depends, run_depends, test_depends=None, prefer_depend_tag=True):
228  if self.format == 1:
229  run_depends.update(build_depends)
230  existing_build = self.get_packages('build')
231  existing_run = self.get_packages('run')
232  build_depends = build_depends - existing_build
233  run_depends = run_depends - existing_run
234  if self.format == 1:
235  self.insert_new_packages('build_depend', build_depends)
236  self.insert_new_packages('run_depend', run_depends)
237  elif prefer_depend_tag:
238  depend_tags = build_depends.union(run_depends)
239 
240  # Remove tags that overlap with new depends
241  self.remove_dependencies('build_depend', existing_build.intersection(depend_tags))
242  self.remove_dependencies('exec_depend', existing_run.intersection(depend_tags))
243 
244  # Insert depends
245  self.insert_new_packages('depend', depend_tags)
246  else:
247  both = build_depends.intersection(run_depends)
248  self.insert_new_packages('depend', both)
249  self.insert_new_packages('build_depend', build_depends - both)
250  self.insert_new_packages('exec_depend', build_depends - both - existing_run)
251  self.insert_new_packages('exec_depend', run_depends - both)
252 
253  if test_depends is not None and len(test_depends) > 0:
254  existing_test = self.get_packages('test')
255  test_depends = set(test_depends) - existing_build - build_depends - existing_test
256  self.insert_new_packages('test_depend', test_depends)
257 
258  def remove_element(self, element):
259  """ Remove the given element AND the text element before it if it is just an indentation """
260  parent = element.parentNode
261  index = parent.childNodes.index(element)
262  if index > 0:
263  previous = parent.childNodes[index - 1]
264  if previous.nodeType == previous.TEXT_NODE and INDENT_PATTERN.match(previous.nodeValue):
265  parent.removeChild(previous)
266  parent.removeChild(element)
267  self.changed = True
268 
269  def remove_dependencies(self, name, pkgs, quiet=False):
270  for el in self.root.getElementsByTagName(name):
271  pkg = el.childNodes[0].nodeValue
272  if pkg in pkgs:
273  if not quiet:
274  print('\tRemoving %s %s' % (name, pkg))
275  self.remove_element(el)
276 
277  def get_elements_by_tags(self, tags):
278  elements = []
279  for tag in tags:
280  elements += self.root.getElementsByTagName(tag)
281  return elements
282 
283  def get_people(self):
284  people = []
285  for el in self.get_elements_by_tags(PEOPLE_TAGS):
286  name = el.childNodes[0].nodeValue
287  email = el.getAttribute('email')
288  people.append((name, email))
289  return people
290 
291  def update_people(self, target_name, target_email=None, search_name=None, search_email=None):
292  for el in self.get_elements_by_tags(PEOPLE_TAGS):
293  name = el.childNodes[0].nodeValue
294  email = el.getAttribute('email') if el.hasAttribute('email') else ''
295  if (search_name is None or name == search_name) and (search_email is None or email == search_email):
296  el.childNodes[0].nodeValue = target_name
297  if target_email:
298  el.setAttribute('email', target_email)
299  print('\tReplacing %s %s/%s with %s/%s' % (el.nodeName, name, email, target_name, target_email))
300  self.changed = True
301 
303  els = self.root.getElementsByTagName('license')
304  if len(els) == 0:
305  return None
306  return els[0]
307 
308  def get_license(self):
309  el = self.get_license_element()
310  return el.childNodes[0].nodeValue
311 
312  def set_license(self, license):
313  el = self.get_license_element()
314  if license != el.childNodes[0].nodeValue:
315  el.childNodes[0].nodeValue = license
316  self.changed = True
317 
318  def is_metapackage(self):
319  for node in self.root.getElementsByTagName('export'):
320  for child in node.childNodes:
321  if child.nodeType == child.ELEMENT_NODE:
322  if child.nodeName == 'metapackage':
323  return True
324  return False
325 
326  def get_plugin_xmls(self):
327  """ Returns a mapping from the package name to a list of the relative path(s) for the plugin xml(s) """
328  xmls = collections.defaultdict(list)
329  export = self.root.getElementsByTagName('export')
330  if len(export) == 0:
331  return xmls
332  for ex in export:
333  for n in ex.childNodes:
334  if n.nodeType == self.root.ELEMENT_NODE:
335  plugin = n.getAttribute('plugin').replace('${prefix}/', '')
336  xmls[n.nodeName].append(plugin)
337  return xmls
338 
339  def add_plugin_export(self, pkg_name, xml_path):
340  """ Adds the plugin configuration if not found. Adds export tag as needed.
341  Returns the export tag it was added to."""
342  export_tags = self.root.getElementsByTagName('export')
343  if len(export_tags) == 0:
344  export_tag = self.tree.createElement('export')
345  self.insert_new_tag(export_tag)
346  export_tags = [export_tag]
347 
348  attr = '${prefix}/' + xml_path
349  for ex_tag in export_tags:
350  for tag in ex_tag.childNodes:
351  if tag.nodeName != pkg_name:
352  continue
353  plugin = tag.attributes.get('plugin')
354  if plugin and plugin.value == attr:
355  return
356 
357  ex_el = export_tags[0]
358  pe = self.tree.createElement(pkg_name)
359  pe.setAttribute('plugin', attr)
360  self.insert_new_tag_inside_another(ex_el, pe)
361  return ex_el
362 
363  def write(self, new_fn=None):
364  if new_fn is None:
365  new_fn = self.fn
366 
367  if new_fn == self.fn and not self.changed:
368  return
369 
370  s = self.tree.toxml(self.tree.encoding)
371  index = get_package_tag_index(s)
372  s = self.header + s[index:]
373 
374  with open(new_fn, 'w') as f:
375  f.write(s.encode('UTF-8'))
376  f.write('\n')
def update_people(self, target_name, target_email=None, search_name=None, search_email=None)
Definition: package_xml.py:291
def get_package_tag_index(s, key='< package')
Definition: package_xml.py:29
def remove_dependencies(self, name, pkgs, quiet=False)
Definition: package_xml.py:269
def add_packages(self, build_depends, run_depends, test_depends=None, prefer_depend_tag=True)
Definition: package_xml.py:227
def insert_new_packages(self, tag, values)
Definition: package_xml.py:220
def get_insertion_index(self, tag, tag_value=None)
Definition: package_xml.py:147
def add_plugin_export(self, pkg_name, xml_path)
Definition: package_xml.py:339
def get_ordering_index(name, whiny=True)
Definition: package_xml.py:17
def get_packages(self, mode='build')
Definition: package_xml.py:96
def insert_new_tag_inside_another(self, parent, tag, depth=2)
Definition: package_xml.py:209


ros_introspection
Author(s):
autogenerated on Wed Jun 19 2019 19:56:52