comparison planemo/lib/python3.7/site-packages/galaxy/util/xml_macros.py @ 1:56ad4e20f292 draft

"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
author guerler
date Fri, 31 Jul 2020 00:32:28 -0400
parents
children
comparison
equal deleted inserted replaced
0:d30785e31577 1:56ad4e20f292
1 import os
2 from copy import deepcopy
3
4 from galaxy.util import parse_xml
5
6 REQUIRED_PARAMETER = object()
7
8
9 def load_with_references(path):
10 """Load XML documentation from file system and preprocesses XML macros.
11
12 Return the XML representation of the expanded tree and paths to
13 referenced files that were imported (macros).
14 """
15 tree = raw_xml_tree(path)
16 root = tree.getroot()
17
18 macro_paths = _import_macros(root, path)
19
20 # Collect tokens
21 tokens = _macros_of_type(root, 'token', lambda el: el.text or '')
22 tokens = expand_nested_tokens(tokens)
23
24 # Expand xml macros
25 macro_dict = _macros_of_type(root, 'xml', lambda el: XmlMacroDef(el))
26 _expand_macros([root], macro_dict, tokens)
27
28 return tree, macro_paths
29
30
31 def load(path):
32 tree, _ = load_with_references(path)
33 return tree
34
35
36 def template_macro_params(root):
37 """
38 Look for template macros and populate param_dict (for cheetah)
39 with these.
40 """
41 param_dict = {}
42 macro_dict = _macros_of_type(root, 'template', lambda el: el.text)
43 for key, value in macro_dict.items():
44 param_dict[key] = value
45 return param_dict
46
47
48 def raw_xml_tree(path):
49 """ Load raw (no macro expansion) tree representation of XML represented
50 at the specified path.
51 """
52 tree = parse_xml(path, strip_whitespace=False, remove_comments=True)
53 return tree
54
55
56 def imported_macro_paths(root):
57 macros_el = _macros_el(root)
58 return _imported_macro_paths_from_el(macros_el)
59
60
61 def _import_macros(root, path):
62 xml_base_dir = os.path.dirname(path)
63 macros_el = _macros_el(root)
64 if macros_el is not None:
65 macro_els, macro_paths = _load_macros(macros_el, xml_base_dir)
66 _xml_set_children(macros_el, macro_els)
67 return macro_paths
68
69
70 def _macros_el(root):
71 return root.find('macros')
72
73
74 def _macros_of_type(root, type, el_func):
75 macros_el = root.find('macros')
76 macro_dict = {}
77 if macros_el is not None:
78 macro_els = macros_el.findall('macro')
79 filtered_els = [(macro_el.get("name"), el_func(macro_el))
80 for macro_el in macro_els
81 if macro_el.get('type') == type]
82 macro_dict = dict(filtered_els)
83 return macro_dict
84
85
86 def expand_nested_tokens(tokens):
87 for token_name in tokens.keys():
88 for current_token_name, current_token_value in tokens.items():
89 if token_name in current_token_value:
90 if token_name == current_token_name:
91 raise Exception("Token '%s' cannot contain itself" % token_name)
92 tokens[current_token_name] = current_token_value.replace(token_name, tokens[token_name])
93 return tokens
94
95
96 def _expand_tokens(elements, tokens):
97 if not tokens or elements is None:
98 return
99
100 for element in elements:
101 _expand_tokens_for_el(element, tokens)
102
103
104 def _expand_tokens_for_el(element, tokens):
105 value = element.text
106 if value:
107 new_value = _expand_tokens_str(element.text, tokens)
108 if not (new_value is value):
109 element.text = new_value
110 for key, value in element.attrib.items():
111 new_value = _expand_tokens_str(value, tokens)
112 if not (new_value is value):
113 element.attrib[key] = new_value
114 _expand_tokens(list(element), tokens)
115
116
117 def _expand_tokens_str(s, tokens):
118 for key, value in tokens.items():
119 if key in s:
120 s = s.replace(key, value)
121 return s
122
123
124 def _expand_macros(elements, macros, tokens):
125 if not macros and not tokens:
126 return
127
128 for element in elements:
129 while True:
130 expand_el = element.find('.//expand')
131 if expand_el is None:
132 break
133 _expand_macro(element, expand_el, macros, tokens)
134
135 _expand_tokens_for_el(element, tokens)
136
137
138 def _expand_macro(element, expand_el, macros, tokens):
139 macro_name = expand_el.get('macro')
140 macro_def = macros[macro_name]
141 expanded_elements = deepcopy(macro_def.elements)
142
143 _expand_yield_statements(expanded_elements, expand_el)
144
145 # Recursively expand contained macros.
146 _expand_macros(expanded_elements, macros, tokens)
147 macro_tokens = macro_def.macro_tokens(expand_el)
148 if macro_tokens:
149 _expand_tokens(expanded_elements, macro_tokens)
150
151 # HACK for elementtree, newer implementations (etree/lxml) won't
152 # require this parent_map data structure but elementtree does not
153 # track parents or recognize .find('..').
154 # TODO fix this now that we're not using elementtree
155 parent_map = dict((c, p) for p in element.iter() for c in p)
156 _xml_replace(expand_el, expanded_elements, parent_map)
157
158
159 def _expand_yield_statements(macro_def, expand_el):
160 yield_els = [yield_el for macro_def_el in macro_def for yield_el in macro_def_el.findall('.//yield')]
161
162 expand_el_children = list(expand_el)
163 macro_def_parent_map = \
164 dict((c, p) for macro_def_el in macro_def for p in macro_def_el.iter() for c in p)
165
166 for yield_el in yield_els:
167 _xml_replace(yield_el, expand_el_children, macro_def_parent_map)
168
169 # Replace yields at the top level of a macro, seems hacky approach
170 replace_yield = True
171 while replace_yield:
172 for i, macro_def_el in enumerate(macro_def):
173 if macro_def_el.tag == "yield":
174 for target in expand_el_children:
175 i += 1
176 macro_def.insert(i, target)
177 macro_def.remove(macro_def_el)
178 continue
179
180 replace_yield = False
181
182
183 def _load_macros(macros_el, xml_base_dir):
184 macros = []
185 # Import macros from external files.
186 imported_macros, macro_paths = _load_imported_macros(macros_el, xml_base_dir)
187 macros.extend(imported_macros)
188 # Load all directly defined macros.
189 macros.extend(_load_embedded_macros(macros_el, xml_base_dir))
190 return macros, macro_paths
191
192
193 def _load_embedded_macros(macros_el, xml_base_dir):
194 macros = []
195
196 macro_els = []
197 # attribute typed macro
198 if macros_el is not None:
199 macro_els = macros_el.findall("macro")
200 for macro in macro_els:
201 if 'type' not in macro.attrib:
202 macro.attrib['type'] = 'xml'
203 macros.append(macro)
204
205 # type shortcuts (<xml> is a shortcut for <macro type="xml",
206 # likewise for <template>.
207 typed_tag = ['template', 'xml', 'token']
208 for tag in typed_tag:
209 macro_els = []
210 if macros_el is not None:
211 macro_els = macros_el.findall(tag)
212 for macro_el in macro_els:
213 macro_el.attrib['type'] = tag
214 macro_el.tag = 'macro'
215 macros.append(macro_el)
216
217 return macros
218
219
220 def _load_imported_macros(macros_el, xml_base_dir):
221 macros = []
222 macro_paths = []
223
224 for tool_relative_import_path in _imported_macro_paths_from_el(macros_el):
225 import_path = \
226 os.path.join(xml_base_dir, tool_relative_import_path)
227 macro_paths.append(import_path)
228 file_macros, current_macro_paths = _load_macro_file(import_path, xml_base_dir)
229 macros.extend(file_macros)
230 macro_paths.extend(current_macro_paths)
231
232 return macros, macro_paths
233
234
235 def _imported_macro_paths_from_el(macros_el):
236 imported_macro_paths = []
237 macro_import_els = []
238 if macros_el is not None:
239 macro_import_els = macros_el.findall("import")
240 for macro_import_el in macro_import_els:
241 raw_import_path = macro_import_el.text
242 imported_macro_paths.append(raw_import_path)
243 return imported_macro_paths
244
245
246 def _load_macro_file(path, xml_base_dir):
247 tree = parse_xml(path, strip_whitespace=False)
248 root = tree.getroot()
249 return _load_macros(root, xml_base_dir)
250
251
252 def _xml_set_children(element, new_children):
253 for old_child in element:
254 element.remove(old_child)
255 for i, new_child in enumerate(new_children):
256 element.insert(i, new_child)
257
258
259 def _xml_replace(query, targets, parent_map):
260 # parent_el = query.find('..') ## Something like this would be better with newer xml library
261 parent_el = parent_map[query]
262 matching_index = -1
263 # for index, el in enumerate(parent_el.iter('.')): ## Something like this for newer implementation
264 for index, el in enumerate(list(parent_el)):
265 if el == query:
266 matching_index = index
267 break
268 assert matching_index >= 0
269 current_index = matching_index
270 for target in targets:
271 current_index += 1
272 parent_el.insert(current_index, deepcopy(target))
273 parent_el.remove(query)
274
275
276 class XmlMacroDef(object):
277
278 def __init__(self, el):
279 self.elements = list(el)
280 parameters = {}
281 tokens = []
282 token_quote = "@"
283 for key, value in el.attrib.items():
284 if key == "token_quote":
285 token_quote = value
286 if key == "tokens":
287 for token in value.split(","):
288 tokens.append((token, REQUIRED_PARAMETER))
289 elif key.startswith("token_"):
290 token = key[len("token_"):]
291 tokens.append((token, value))
292 for name, default in tokens:
293 parameters[name] = (token_quote, default)
294 self.parameters = parameters
295
296 def macro_tokens(self, expand_el):
297 tokens = {}
298 for key, (wrap_char, default_val) in self.parameters.items():
299 token_value = expand_el.attrib.get(key, default_val)
300 if token_value is REQUIRED_PARAMETER:
301 message = "Failed to expand macro - missing required parameter [%s]."
302 raise ValueError(message % key)
303 token_name = "%s%s%s" % (wrap_char, key.upper(), wrap_char)
304 tokens[token_name] = token_value
305 return tokens
306
307
308 __all__ = (
309 "imported_macro_paths",
310 "load",
311 "load_with_references",
312 "raw_xml_tree",
313 "template_macro_params",
314 )