Warning

This document is for an in-development version of Galaxy. You can alternatively view this page in the latest release if it exists or view the top of the latest release's documentation.

Source code for galaxy.util.xml_macros

import os
from copy import deepcopy
from xml.etree import ElementInclude, ElementTree


REQUIRED_PARAMETER = object()


[docs]def load_with_references(path): """Load XML documentation from file system and preprocesses XML macros. Return the XML representation of the expanded tree and paths to referenced files that were imported (macros). """ tree = raw_xml_tree(path) root = tree.getroot() macro_paths = _import_macros(root, path) # Collect tokens tokens = _macros_of_type(root, 'token', lambda el: el.text or '') tokens = expand_nested_tokens(tokens) # Expand xml macros macro_dict = _macros_of_type(root, 'xml', lambda el: XmlMacroDef(el)) _expand_macros([root], macro_dict, tokens) return tree, macro_paths
[docs]def load(path): tree, _ = load_with_references(path) return tree
[docs]def template_macro_params(root): """ Look for template macros and populate param_dict (for cheetah) with these. """ param_dict = {} macro_dict = _macros_of_type(root, 'template', lambda el: el.text) for key, value in macro_dict.items(): param_dict[key] = value return param_dict
[docs]def raw_xml_tree(path): """ Load raw (no macro expansion) tree representation of XML represented at the specified path. """ tree = _parse_xml(path) return tree
[docs]def imported_macro_paths(root): macros_el = _macros_el(root) return _imported_macro_paths_from_el(macros_el)
def _import_macros(root, path): xml_base_dir = os.path.dirname(path) macros_el = _macros_el(root) if macros_el is not None: macro_els, macro_paths = _load_macros(macros_el, xml_base_dir) _xml_set_children(macros_el, macro_els) return macro_paths def _macros_el(root): return root.find('macros') def _macros_of_type(root, type, el_func): macros_el = root.find('macros') macro_dict = {} if macros_el is not None: macro_els = macros_el.findall('macro') filtered_els = [(macro_el.get("name"), el_func(macro_el)) for macro_el in macro_els if macro_el.get('type') == type] macro_dict = dict(filtered_els) return macro_dict def expand_nested_tokens(tokens): for token_name in tokens.keys(): for current_token_name, current_token_value in tokens.items(): if token_name in current_token_value: if token_name == current_token_name: raise Exception("Token '%s' cannot contain itself" % token_name) tokens[current_token_name] = current_token_value.replace(token_name, tokens[token_name]) return tokens def _expand_tokens(elements, tokens): if not tokens or elements is None: return for element in elements: _expand_tokens_for_el(element, tokens) def _expand_tokens_for_el(element, tokens): value = element.text if value: new_value = _expand_tokens_str(element.text, tokens) if not (new_value is value): element.text = new_value for key, value in element.attrib.items(): new_value = _expand_tokens_str(value, tokens) if not (new_value is value): element.attrib[key] = new_value _expand_tokens(list(element), tokens) def _expand_tokens_str(s, tokens): for key, value in tokens.items(): if key in s: s = s.replace(key, value) return s def _expand_macros(elements, macros, tokens): if not macros and not tokens: return for element in elements: while True: expand_el = element.find('.//expand') if expand_el is None: break _expand_macro(element, expand_el, macros, tokens) _expand_tokens_for_el(element, tokens) def _expand_macro(element, expand_el, macros, tokens): macro_name = expand_el.get('macro') macro_def = macros[macro_name] expanded_elements = deepcopy(macro_def.elements) _expand_yield_statements(expanded_elements, expand_el) # Recursively expand contained macros. _expand_macros(expanded_elements, macros, tokens) macro_tokens = macro_def.macro_tokens(expand_el) if macro_tokens: _expand_tokens(expanded_elements, macro_tokens) # HACK for elementtree, newer implementations (etree/lxml) won't # require this parent_map data structure but elementtree does not # track parents or recognize .find('..'). # TODO fix this now that we're not using elementtree parent_map = dict((c, p) for p in element.iter() for c in p) _xml_replace(expand_el, expanded_elements, parent_map) def _expand_yield_statements(macro_def, expand_el): yield_els = [yield_el for macro_def_el in macro_def for yield_el in macro_def_el.findall('.//yield')] expand_el_children = list(expand_el) macro_def_parent_map = \ dict((c, p) for macro_def_el in macro_def for p in macro_def_el.iter() for c in p) for yield_el in yield_els: _xml_replace(yield_el, expand_el_children, macro_def_parent_map) # Replace yields at the top level of a macro, seems hacky approach replace_yield = True while replace_yield: for i, macro_def_el in enumerate(macro_def): if macro_def_el.tag == "yield": for target in expand_el_children: i += 1 macro_def.insert(i, deepcopy(target)) macro_def.remove(macro_def_el) continue replace_yield = False def _load_macros(macros_el, xml_base_dir): macros = [] # Import macros from external files. imported_macros, macro_paths = _load_imported_macros(macros_el, xml_base_dir) macros.extend(imported_macros) # Load all directly defined macros. macros.extend(_load_embedded_macros(macros_el, xml_base_dir)) return macros, macro_paths def _load_embedded_macros(macros_el, xml_base_dir): macros = [] macro_els = [] # attribute typed macro if macros_el is not None: macro_els = macros_el.findall("macro") for macro in macro_els: if 'type' not in macro.attrib: macro.attrib['type'] = 'xml' macros.append(macro) # type shortcuts (<xml> is a shortcut for <macro type="xml", # likewise for <template>. typed_tag = ['template', 'xml', 'token'] for tag in typed_tag: macro_els = [] if macros_el is not None: macro_els = macros_el.findall(tag) for macro_el in macro_els: macro_el.attrib['type'] = tag macro_el.tag = 'macro' macros.append(macro_el) return macros def _load_imported_macros(macros_el, xml_base_dir): macros = [] macro_paths = [] for tool_relative_import_path in _imported_macro_paths_from_el(macros_el): import_path = \ os.path.join(xml_base_dir, tool_relative_import_path) macro_paths.append(import_path) file_macros, current_macro_paths = _load_macro_file(import_path, xml_base_dir) macros.extend(file_macros) macro_paths.extend(current_macro_paths) return macros, macro_paths def _imported_macro_paths_from_el(macros_el): imported_macro_paths = [] macro_import_els = [] if macros_el is not None: macro_import_els = macros_el.findall("import") for macro_import_el in macro_import_els: raw_import_path = macro_import_el.text imported_macro_paths.append(raw_import_path) return imported_macro_paths def _load_macro_file(path, xml_base_dir): tree = _parse_xml(path) root = tree.getroot() return _load_macros(root, xml_base_dir) def _xml_set_children(element, new_children): for old_child in element: element.remove(old_child) for i, new_child in enumerate(new_children): element.insert(i, new_child) def _xml_replace(query, targets, parent_map): # parent_el = query.find('..') ## Something like this would be better with newer xml library parent_el = parent_map[query] matching_index = -1 # for index, el in enumerate(parent_el.iter('.')): ## Something like this for newer implementation for index, el in enumerate(list(parent_el)): if el == query: matching_index = index break assert matching_index >= 0 current_index = matching_index for target in targets: current_index += 1 parent_el.insert(current_index, deepcopy(target)) parent_el.remove(query) class XmlMacroDef(object): def __init__(self, el): self.elements = list(el) parameters = {} tokens = [] token_quote = "@" for key, value in el.attrib.items(): if key == "token_quote": token_quote = value if key == "tokens": for token in value.split(","): tokens.append((token, REQUIRED_PARAMETER)) elif key.startswith("token_"): token = key[len("token_"):] tokens.append((token, value)) for name, default in tokens: parameters[name] = (token_quote, default) self.parameters = parameters def macro_tokens(self, expand_el): tokens = {} for key, (wrap_char, default_val) in self.parameters.items(): token_value = expand_el.attrib.get(key, default_val) if token_value is REQUIRED_PARAMETER: message = "Failed to expand macro - missing required parameter [%s]." raise ValueError(message % key) token_name = "%s%s%s" % (wrap_char, key.upper(), wrap_char) tokens[token_name] = token_value return tokens def _parse_xml(fname): tree = ElementTree.parse(fname) root = tree.getroot() ElementInclude.include(root) return tree __all__ = ( "imported_macro_paths", "load", "load_with_references", "raw_xml_tree", "template_macro_params", )