Source code for musicxml.xmlelement.xmlchildcontainer

import xml.etree.ElementTree as ET

from musicxml.util.core import convert_to_xml_class_name, cap_first
from musicxml.xmlelement.exceptions import XMLChildContainerFactoryError, XMLChildContainerWrongElementError, \
    XMLChildContainerChoiceHasAnotherChosenChild, XMLChildContainerMaxOccursError
from musicxml.xsd.xsdelement import XSDElement
from musicxml.xsd.xsdindicator import *
from musicxml.xsd.xsdtree import XSDTree
from tree.tree import Tree


def _convert_xsd_child_to_xsd_container(xsd_child):
    min_occurrences = xsd_child.get_attributes().get('minOccurs')
    max_occurrences = xsd_child.get_attributes().get('maxOccurs')

    copied_xsd_child = xsd_child.__deepcopy__()
    if min_occurrences is not None:
        copied_xsd_child.get_attributes().pop('minOccurs')
    if max_occurrences is not None:
        copied_xsd_child.get_attributes().pop('maxOccurs')
    if xsd_child.tag == 'element':
        return XMLChildContainer(content=XSDElement(copied_xsd_child), min_occurrences=min_occurrences,
                                 max_occurrences=max_occurrences)
    elif xsd_child.tag == 'sequence':
        return XMLChildContainer(content=XSDSequence(copied_xsd_child), min_occurrences=min_occurrences,
                                 max_occurrences=max_occurrences)
    elif xsd_child.tag == 'choice':
        return XMLChildContainer(content=XSDChoice(copied_xsd_child), min_occurrences=min_occurrences,
                                 max_occurrences=max_occurrences)
    elif xsd_child.tag == 'group':
        xsd_group_name = 'XSDGroup' + ''.join(
            [cap_first(partial) for partial in xsd_child.get_attributes()['ref'].split('-')])
        return XMLChildContainer(content=eval(xsd_group_name)(), min_occurrences=min_occurrences,
                                 max_occurrences=max_occurrences)
    else:
        raise NotImplementedError(xsd_child.tag)


def _check_if_container_requires_elements(xsd_container):
    if isinstance(xsd_container.content, XSDSequence):
        return _check_if_sequence_requires_elements(xsd_container)
    elif isinstance(xsd_container.content, XSDGroup):
        return _check_if_group_requires_elements(xsd_container)
    elif isinstance(xsd_container.content, XSDChoice):
        return _check_if_choice_requires_elements(xsd_container)
    else:
        raise NotImplementedError(xsd_container)


def _check_if_choice_requires_elements(xsd_container_choice):
    element_chosen = False
    for child in xsd_container_choice.get_children():
        if isinstance(child.content, XSDGroup):
            if child.get_children()[0].force_validate:
                _check_if_container_requires_elements(child.get_children()[0])
        elif child.force_validate:
            _check_if_container_requires_elements(child)
        else:
            if child.min_occurrences == 0:
                pass
            elif int(child.min_occurrences) == 1:
                if isinstance(child.content, XSDElement):
                    if len(child.content.xml_elements) == 0:
                        pass
                    elif len(child.content.xml_elements) == 1:
                        element_chosen = True
                    else:
                        raise NotImplementedError(child)
                else:
                    _check_if_container_requires_elements(child)
            else:
                raise NotImplementedError(f'child {child} with min_occurrence greater than 1')

    if element_chosen:
        xsd_container_choice.requirements_fulfilled = True


def _check_if_group_requires_elements(xsd_group_container):
    if xsd_group_container.min_occurrences == 0 and not xsd_group_container.get_children()[0].force_validate:
        return
    return _check_if_sequence_requires_elements(xsd_group_container.get_children()[0])


def _check_if_sequence_requires_elements(xsd_sequence_container):
    if xsd_sequence_container.force_validate:
        for child in xsd_sequence_container.get_children():
            if isinstance(child.content, XSDElement):
                if len(child.content.xml_elements) < child.min_occurrences:
                    child.requirements_fulfilled = False
                else:
                    pass
            else:
                _check_if_container_requires_elements(child)

    def validate_child(ch):
        if isinstance(ch.content, XSDElement):
            if child.choices_in_reversed_path:
                pass
            elif len(ch.content.xml_elements) < ch.min_occurrences:
                ch.requirements_fulfilled = False
            else:
                ch.requirements_fulfilled = True
        else:
            _check_if_container_requires_elements(ch)

    if xsd_sequence_container.min_occurrences > 0:
        for child in xsd_sequence_container.get_children():
            if child.force_validate is True:
                _check_if_container_requires_elements(child)
            elif child.min_occurrences == 0:
                pass
            elif child.min_occurrences == 1:
                validate_child(child)
            else:
                raise NotImplementedError(f'child {child} with min_occurrence greater than 1')


class DuplicationXSDSequence(XSDSequence):
    sequence_xsd = """
            <xs:sequence xmlns:xs="http://www.w3.org/2001/XMLSchema">
            </xs:sequence>
    """

    def __init__(self):
        xsd_tree_ = XSDTree(ET.fromstring(self.sequence_xsd))
        super().__init__(xsd_tree_)


[docs]class XMLChildContainer(Tree): def __init__(self, content, min_occurrences=None, max_occurrences=None, populate_children=True, *args, **kwargs): super().__init__(*args, **kwargs) self._content = None self._chosen_child = None self._required_element_names = None self._requirements_fulfilled = None self.min_occurrences = 1 if min_occurrences is None else int(min_occurrences) self.max_occurrences = 1 if max_occurrences is None else 'unbounded' if max_occurrences == 'unbounded' else int( max_occurrences) self.content = content self._force_validate = None self._parent_xml_element = None if populate_children: self._populate_children() # private methods def _add_duplication_parent(self): if not self.get_parent(): parent_container = XMLChildContainer(content=DuplicationXSDSequence()) parent_container.add_child(self) elif not (isinstance(self.get_parent().content, DuplicationXSDSequence)): parent_container = XMLChildContainer(content=DuplicationXSDSequence()) index = self.get_parent().get_children().index(self) parent = self.get_parent() parent.remove(self) parent.get_children().insert(index, parent_container) parent_container._parent = parent parent_container.add_child(self) if isinstance(parent.content, XSDChoice) and parent.chosen_child == self: parent.chosen_child = parent_container else: pass @staticmethod def _check_content_type(val): types = [XSDSequence, XSDChoice, XSDElement, XSDGroup] for type_ in types: if isinstance(val, type_): return raise TypeError(val) def _check_child_to_be_added(self, child): if not isinstance(child, XMLChildContainer): raise TypeError def _check_choices_intelligently(self, xml_element=None): """ Check if existing xml elements can be attached to other choice paths in order to fulfill all requirements. Only possible leaves forwards will be checked. If xml_element is given it will be treated as a new element which is going to be attached to the container. """ if self.get_parent_xml_element(): print(f'_check_choices_intelligently: intelligent choice for {self.get_parent_xml_element()}') def get_same_name_next_leaves(leaf_): output = [] index = -1 record = False for l in self.iterate_leaves(): if l.content.name == leaf_.content.name: index += 1 if not record: record = True else: output.append((l, index)) return output def get_sorted_xml_elements(sorted_options_): unsorted_elements = [el for el in self.get_attached_elements() if el.name != sorted_options_[-1][0]] output = [] for option in sorted_options_: selected_elements = [el for el in unsorted_elements if el.name == option[1]] for el in selected_elements: unsorted_elements.remove(el) output.append(el) output.extend(unsorted_elements) return output current_leaves_with_xml_elements = [leaf for leaf in self.iterate_leaves() if leaf.content.xml_elements] optional_next_leaves = [get_same_name_next_leaves(leaf) for leaf in current_leaves_with_xml_elements if get_same_name_next_leaves(leaf)] if optional_next_leaves: indices = {list_of_leaves[0][0].content.name: [l[1] for l in list_of_leaves] for list_of_leaves in optional_next_leaves} sorted_options = sorted([(k, v) for k, v in indices.items()], key=lambda v: -len(v)) sorted_xml_elements = get_sorted_xml_elements(sorted_options) efficient_xml_element_name, forward_indices = sorted_options.pop() efficient_xml_elements = [el for el in self.get_attached_elements() if el.name == efficient_xml_element_name] for element in efficient_xml_elements: for forward_index in forward_indices: copied_container = self._create_empty_copy() copied_container.add_element(element, forward_index) try: for el in sorted_xml_elements: copied_container.add_element(el, intelligent_choice=False) if xml_element: copied_container.add_element(xml_element, intelligent_choice=False) return copied_container if not copied_container.check_required_elements(): return copied_container except XMLChildContainerChoiceHasAnotherChosenChild: pass return None def _create_empty_copy(self): """ Creates a copy without attached elements or duplicated nodes :return: XMLChildContainer """ if isinstance(self.content, XSDChoice) or isinstance(self.content, XSDSequence): copied_content = self.content.__class__(self.content.xsd_tree) else: copied_content = eval(self.content.__class__.__name__)() return XMLChildContainer(copied_content, self.min_occurrences, self.max_occurrences) def _duplicate_parent_in_path(self): for node in list(self.reversed_path_to_root())[:-1]: if node.get_parent().max_occurrences == 'unbounded': return node.get_parent().duplicate() return None def _update_requirements_in_path(self): if not isinstance(self.content, XSDElement): raise ValueError if self.max_is_reached: self.requirements_fulfilled = True if self.content.xml_elements: for node in self.reversed_path_to_root(): if node.get_parent(): if isinstance(node.get_parent().content, XSDChoice): if node.get_parent().chosen_child: if node.get_parent().chosen_child != node: raise XMLChildContainerChoiceHasAnotherChosenChild else: break else: node.get_parent().chosen_child = node if node.get_parent().requirements_fulfilled is False: node.get_parent().requirements_fulfilled = True break node.get_parent().requirements_fulfilled = True elif isinstance(node.get_parent().content, XSDSequence): if node.get_parent().force_validate: break else: node.get_parent().set_force_validate(node, True) def _populate_children(self): for xsd_child in [child for child in self.content.xsd_tree.get_children() if child.tag != 'annotation' and child.tag != 'complexType']: container = _convert_xsd_child_to_xsd_container(xsd_child) self.add_child(container) def _set_requirements_fulfilled(self): for node in self.traverse(): if isinstance(node.content, XSDChoice) and node.requirements_fulfilled is None and False not in [ choice.requirements_fulfilled for choice in node.choices_in_reversed_path] and node.min_occurrences != 0: for leaf in node.iterate_leaves(): if leaf.content.xml_elements: node._requirements_fulfilled = True if node._requirements_fulfilled is None: for child in node.get_children(): if child.min_occurrences != 0: node._requirements_fulfilled = False break if node._requirements_fulfilled is None: node._requirements_fulfilled = True else: node._requirements_fulfilled = True # public properties @property def compact_repr(self): """ :return: A compact representation of :obj:`~content`. """ if isinstance(self.content, XSDSequence): type_ = 'Sequence' return f"{type_}@minOccurs={self.min_occurrences}@maxOccurs={self.max_occurrences}" if isinstance(self.content, XSDChoice): type_ = 'Choice' output = f"{type_}@minOccurs={self.min_occurrences}@maxOccurs={self.max_occurrences}" if self.requirements_fulfilled is False: output += '\n' output += self.get_indentation() + ' ' output += '!Required!' return output if isinstance(self.content, XSDGroup): type_ = 'Group' return f"{type_}@name={self.content.name}@minOccurs={self.min_occurrences}@maxOccurs={self.max_occurrences}" if isinstance(self.content, XSDElement): type_ = 'Element' output = f"{type_}@name={self.content.name}@minOccurs={self.min_occurrences}@maxOccurs={self.max_occurrences}" for xml_element in self.content.xml_elements: output += '\n' output += self.get_indentation() + ' ' output += xml_element.__class__.__name__ if self.requirements_fulfilled is False: output += '\n' output += self.get_indentation() + ' ' output += '!Required!' return output @property def content(self): """ :return: Content of a :obj:`~musicxml.xmlelement.xmlchildcontainer.XMLChildContainer` is its core property. It can be of types: :obj:`~musicxml.xsd.xsdindicator.XSDSequence`, :obj:`~musicxml.xsd.xsdindicator.XSDChoice`, :obj:`~musicxml.xsd.xsdindicator.XSDGroup` or :obj:`~musicxml.xsd.xsdelement.XSDElement` which are used to translate the behaviour of according xsd tags: ``sequence``, ``choice``, ``group`` and ``element``. """ return self._content @content.setter def content(self, val): self._check_content_type(val) self._content = val self._content.parent_container = self @property def choices_in_reversed_path(self): return [node for node in list(self.reversed_path_to_root())[1:] if isinstance(node.content, XSDChoice)] @property def chosen_child(self): return self._chosen_child @chosen_child.setter def chosen_child(self, val): if not isinstance(self.content, XSDChoice): raise TypeError self._chosen_child = val @property def force_validate(self): return self._force_validate @property def max_is_reached(self): if not isinstance(self.content, XSDElement): raise TypeError if self.max_occurrences == 'unbounded': return False else: if len(self.content.xml_elements) == self.max_occurrences: return True elif len(self.content.xml_elements) > self.max_occurrences: raise ValueError else: return False @property def requirements_fulfilled(self): return self._requirements_fulfilled @requirements_fulfilled.setter def requirements_fulfilled(self, val: bool): if not isinstance(val, bool): raise TypeError self._requirements_fulfilled = val # public methods
[docs] def add_element(self, xml_element, forward=None, intelligent_choice=True): # resets frozen tree iteration lists like traverse() iterate_leaves() etc. self._reset_iterators() if self._requirements_fulfilled is None: self.check_required_elements() def select_valid_leaves(leaves): output = [] choice_with_chosen_child = None for index, leaf in enumerate(leaves): for n in leaf.reversed_path_to_root(): if n.get_parent() and isinstance(n.get_parent().content, XSDChoice) and n.get_parent().chosen_child: choice_with_chosen_child = n.get_parent() if n == choice_with_chosen_child.chosen_child: output.append(leaf) break if not choice_with_chosen_child: return leaves elif not output: if choice_with_chosen_child.max_occurrences == 1: if choice_with_chosen_child.up and isinstance(choice_with_chosen_child.up.content, XSDSequence) and \ choice_with_chosen_child.up.max_occurrences == 'unbounded': return [] else: return None else: return [] else: return output if 'XMLElement' not in [cls.__name__ for cls in xml_element.__class__.__mro__]: raise TypeError(xml_element.__class__) same_name_leaves = [leaf for leaf in self.iterate_leaves() if leaf.content.name == xml_element.name] if not same_name_leaves: raise XMLChildContainerWrongElementError() selected_same_name_leaves = select_valid_leaves(same_name_leaves) if selected_same_name_leaves is None: intelligently_selected = None if forward is None and intelligent_choice is True: copy_with_intelligence = self._check_choices_intelligently(xml_element) if copy_with_intelligence: for old_child, new_child in zip(self.get_children(), copy_with_intelligence.get_children()): self.replace_child(old_child, new_child) return \ [l for l in copy_with_intelligence.iterate_leaves() if xml_element in l.content.xml_elements][0] if not intelligently_selected: msg = f"{self} By adding {xml_element.__class__.__name__} to {self.get_parent_xml_element().__class__.__name__}" if \ self.get_parent_xml_element() else f"{self} By adding {xml_element.__class__.__name__}" raise XMLChildContainerChoiceHasAnotherChosenChild(msg) else: self.check_required_elements() return intelligently_selected if selected_same_name_leaves == []: duplicated_parent = same_name_leaves[-1]._duplicate_parent_in_path() if duplicated_parent: selected_same_name_leaves = [leaf for leaf in duplicated_parent.iterate_leaves() if leaf.content.name == xml_element.name and not leaf.max_is_reached] if self._parent_xml_element and self.up: self._parent_xml_element._child_container_tree = self.up else: raise XMLChildContainerChoiceHasAnotherChosenChild if forward is not None: selected = same_name_leaves[forward] if selected not in selected_same_name_leaves: raise XMLChildContainerChoiceHasAnotherChosenChild('Wrong forwarding') else: selected_same_name_leaves_max_not_reached = [leaf for leaf in selected_same_name_leaves if not leaf.max_is_reached] if not selected_same_name_leaves_max_not_reached: duplicated_parent = selected_same_name_leaves[-1]._duplicate_parent_in_path() if duplicated_parent: selected_same_name_leaves_max_not_reached = [leaf for leaf in duplicated_parent.iterate_leaves() if leaf.content.name == xml_element.name and not leaf.max_is_reached] if self._parent_xml_element and self.up: self._parent_xml_element._child_container_tree = self.up else: raise XMLChildContainerMaxOccursError() selected = selected_same_name_leaves_max_not_reached[0] selected.content.add_xml_element(xml_element) selected._update_requirements_in_path() return selected
[docs] def check_required_elements(self, intelligent_choice=False): if self._requirements_fulfilled is None: self._set_requirements_fulfilled() _check_if_container_requires_elements(self) requirements_exist = False for node in self.traverse(): if node.requirements_fulfilled is False: requirements_exist = True if requirements_exist and intelligent_choice: if isinstance(self.content, XSDChoice): return requirements_exist copy_with_intelligence = self._check_choices_intelligently() if copy_with_intelligence: for old_child, new_child in zip(self.get_children(), copy_with_intelligence.get_children()): self.replace_child(old_child, new_child) return False return requirements_exist
check_requirements = check_required_elements
[docs] def duplicate(self): if not isinstance(self.content, XSDSequence) and not isinstance(self.content, XSDChoice) and not isinstance( self.content, XSDGroup): raise TypeError(self.content) if self.max_occurrences != 'unbounded': raise ValueError self._add_duplication_parent() copied_self = self._create_empty_copy() copied_self._parent = self.get_parent() self.get_parent().add_child(copied_self) return copied_self
[docs] def get_attached_elements(self): output = [] for leaf in self.iterate_leaves(): output.extend(leaf.content.xml_elements) return output
[docs] def get_leaves(self, function=None): if isinstance(self.content, XSDElement): if function: return function(self) else: return self.content elif isinstance(self.content, XSDGroup): return self.get_children()[0].get_leaves(function=function) elif isinstance(self.content, XSDSequence) or isinstance(self.content, XSDChoice): output = [node.get_leaves(function=function) for node in self.get_children() if node.get_leaves(function=function)] try: if not output or set(output) == {None}: return None except TypeError: pass if len(output) == 1: return output[0] return output if isinstance(self.content, XSDSequence) else tuple(output) else: raise NotImplementedError
[docs] def get_parent_xml_element(self): """ :return: :obj:`~musicxml.xmlelement.xmlelement.XMLElement` which child container is attached to. """ return self._parent_xml_element
[docs] def get_required_element_names(self, intelligent_choice=False): def func(leaf): if leaf.requirements_fulfilled is False: return convert_to_xml_class_name(leaf.content.name) elif leaf.min_occurrences != 0 and False in [choice.requirements_fulfilled for choice in leaf.choices_in_reversed_path]: if isinstance(leaf.get_parent().content, XSDSequence) and leaf.get_parent().min_occurrences == 0 and not leaf.get_parent( ).force_validate: pass else: return convert_to_xml_class_name(leaf.content.name) self.check_required_elements(intelligent_choice) return self.get_leaves(func)
[docs] def set_force_validate(self, node, val): self._force_validate = val for child in [ch for ch in self.get_children() if ch != node]: for n in child.traverse(): if isinstance(n.content, XSDChoice): if n.min_occurrences != 0 and not n.chosen_child and [ch for ch in n.get_children() if ch.min_occurrences != 0]: n.requirements_fulfilled = False break if isinstance(n.content, XSDSequence) and n.min_occurrences == 0: break if isinstance(n.content, XSDSequence) and n.min_occurrences != 0 and not ( isinstance(n.get_parent().content, XSDGroup) and n.get_parent().min_occurrences == 0): n._force_validate = val
def __repr__(self): return f"XMLChildContainer:{self.compact_repr} {self.get_coordinates_in_tree()}" def __copy__(self): copied = self.__class__(content=self.content.__copy__(), min_occurrences=self.min_occurrences, max_occurrences=self.max_occurrences, populate_children=False) for child in self.get_children(): copied.add_child(child.__copy__()) return copied
class XMLChildContainerFactory: def __init__(self, complex_type): self._child_container = None self._create_child_container(complex_type) def _create_child_container(self, complex_type): if 'XSDComplexType' not in [cls.__name__ for cls in complex_type.__mro__]: raise TypeError if not complex_type.get_xsd_indicator(): raise XMLChildContainerFactoryError(f'complex_type {complex_type} has no xsd_indicator.') child_container = XMLChildContainer(*complex_type.get_xsd_indicator()) self._child_container = child_container def get_child_container(self): return self._child_container