Source code for musicxml.xsd.xsdtree

import io
import io
import re
import xml.etree.ElementTree as ET
from contextlib import redirect_stdout
from typing import Optional

from musicxml.generate_classes.utils import musicxml_xsd_et_root, xml_xsd_et_root
from musicxml.util.core import cap_first, convert_to_xsd_class_name
from musicxml.util.helprervariables import xml_name_first_character_without_colon, name_character_without_colon, \
    name_character, \
    xml_name_first_character
from verysimpletree.tree import Tree

"""
XSD = XML Schema Definition
"""


[docs]class XSDTree(Tree): """ XSDTree gets a xml.etree.ElementTree.Element by initiation as its xml_element_tree_element property and prepares all needed information for generating a :obj:`~musicxml.xsd.xsdtree.XSDTreeElement` class (:obj:`~musicxml.xsd.xsdtree.XSDTreeElement` can be :obj:`~musicxml.xsd.xsdsimpletype.XSDSimpleType`, :obj:`~musicxml.xsd.xsdcomplextype.XSDComplexType`, :obj:`~musicxml.xsd.xsdindicator.XSDGroup`, XMLAttribute and XMLAttributeGroup) """ def __init__(self, xml_element_tree_element, *args, **kwargs): super().__init__(*args, **kwargs) self._namespace = None self._tag = None self._xsd_element_tree_element = None self._xml_tree_class_name = None self._xsd_indicator = None self._attributes = None self._text = None self._type = 'notset' self._name = 'notset' self.xml_element_tree_element = xml_element_tree_element self._populate_children() # ------------------ # private properties # ------------------ # private methods def _get_xsd_tree_class_name(self): tag = cap_first(self.tag) name = 'XSD' + f'{tag}' if self.name: name += ''.join([cap_first(partial) for partial in self.name.split('-')]) elif self.get_attributes().get('ref'): name += ''.join([cap_first(partial) for partial in self.get_attributes().get('ref').split('-')]) else: raise AttributeError return name def _populate_children(self): for child in [XSDTree(node) for node in self.xml_element_tree_element.findall('./')]: self.add_child(child) def _check_child_to_be_added(self, child): if not isinstance(child, XSDTree): raise TypeError # ------------------ # public properties @property def xsd_tree_base_class_names(self): if self.is_simple_type: # simple type if self.get_restriction(): base = self.get_restriction().get_attributes()['base'] return [convert_to_xsd_class_name(base)] elif self.get_union_member_types(): return [] else: raise AttributeError( f"Simple type {self} has no restriction with base attribute or union with memberTypes.") elif self.is_complex_type: # complex type if self.get_simple_content_extension(): base = self.get_simple_content_extension().get_attributes()['base'] return [convert_to_xsd_class_name(base, type_='simple_type')] else: return [] # raise AttributeError(f"Complex type {self} has no simple content extension with base attribute.") else: raise NotImplementedError @property def compact_repr(self): attrs = self.get_attributes() return f"{self.tag}{''.join([f'@{attribute}={attrs[attribute]}' for attribute in attrs])}" @property def is_simple_type(self): if self.tag == 'simpleType': return True return False @property def is_complex_type(self): if self.tag == 'complexType': return True return False @property def name(self): if self._name == 'notset': try: self._name = self.xml_element_tree_element.attrib['name'] except KeyError: self._name = None return self._name @property def namespace(self): if not self._namespace: self._namespace = re.match(r'({.*})(.*)', self.xml_element_tree_element.tag).group(1) return self._namespace @property def tag(self): if not self._tag: self._tag = re.match(r'({.*})(.*)', self.xml_element_tree_element.tag).group(2) return self._tag @property def text(self): if not self._text: self._text = self.xml_element_tree_element.text return self._text @property def type(self): if self._type == 'notset': try: self._type = self.xml_element_tree_element.attrib['type'] except KeyError: self._type = None return self._type @property def xml_element_tree_element(self): return self._xsd_element_tree_element @xml_element_tree_element.setter def xml_element_tree_element(self, value): if not isinstance(value, ET.Element): raise TypeError( f"XSDTree must be initiated with an xml_element_tree_element of type xml.etree.ElementTree.Element not " f"{type(value)}") self._xsd_element_tree_element = value @property def xsd_element_class_name(self): if self._xml_tree_class_name is None: self._xml_tree_class_name = self._get_xsd_tree_class_name() return self._xml_tree_class_name # ------------------ # public methods
[docs] def get_attributes(self): if self._attributes is None: self._attributes = self.xml_element_tree_element.attrib # if self.tag == 'element': # if 'minOccurs' in self._attributes: # self._attributes.pop('minOccurs') # if 'maxOccurs' in self._attributes: # self._attributes.pop('maxOccurs') return self._attributes
# def get_children(self): # return self._children
[docs] def get_complex_content(self): for node in self.get_children(): if node.tag == 'complexContent': return node
[docs] def get_complex_content_extension(self): if self.get_complex_content().get_children()[0].tag == 'extension': return self.get_complex_content().get_children()[0]
[docs] def get_doc(self): output = '' for node in self.traverse(): if node.tag == 'documentation': output = node.text.strip() output.replace('\t', ' ') break permitted = self.get_permitted() pattern = self.get_pattern() if permitted: output += '\n ' output += '\n ' permitted = [f"``'{perm}'``" if perm else "``''``" for perm in permitted] output += f"Permitted Values: {', '.join(perm for perm in permitted)}\n" if pattern: output += '\n ' output += '\n ' output += f" \nPattern: {pattern}\n" return output
[docs] def get_restriction(self): for node in self.get_children(): if node.tag == 'restriction': return node
[docs] def get_pattern(self, parent_xsd_tree=None): def get_xsd_pattern(restriction_): if restriction_ and restriction_.get_children() and restriction_.get_children()[0].tag == 'pattern': return rf"{restriction.get_children()[0].get_attributes()['value']}" else: if parent_xsd_tree: parent_restriction = parent_xsd_tree.get_restriction() if parent_restriction and parent_restriction.get_children() and parent_restriction.get_children()[ 0].tag == 'pattern': return rf"{parent_restriction.get_children()[0].get_attributes()['value']}" def translate_pattern(pattern_): if pattern_ == "[\\i-[:]][\\c-[:]]*": return rf"{xml_name_first_character_without_colon}{name_character_without_colon}*" pattern_ = pattern_.replace('\\c', name_character) pattern_ = pattern_.replace('\\i', xml_name_first_character) return pattern_ restriction = self.get_restriction() pattern = get_xsd_pattern(restriction) if pattern: return translate_pattern(pattern) else: return None
[docs] def get_permitted(self): restriction = self.get_restriction() if restriction: enumerations = [child for child in restriction.get_children() if child.tag == 'enumeration'] return [enumeration.get_attributes()['value'] for enumeration in enumerations]
[docs] def get_simple_content(self): for node in self.get_children(): if node.tag == 'simpleContent': return node
[docs] def get_simple_content_extension(self): for node in self.get_children(): if node.tag == 'simpleContent': if node.get_children()[0].tag == 'extension': return node.get_children()[0]
[docs] def get_union(self): for node in self.get_children(): if node.tag == 'union': return node
[docs] def get_union_member_types(self): if self.get_union(): return self.get_union().get_attributes()['memberTypes'].split(' ')
[docs] def get_xsd(self): with io.StringIO() as buf, redirect_stdout(buf): ET.dump(self.xml_element_tree_element) output = buf.getvalue() output = output.strip() output += '\n' return output
[docs] def get_xsd_indicator(self): return self._xsd_indicator
# ------------------ # magic methods def __deepcopy__(self, copy_parent=False): def copy_et_element(el): output = ET.Element(el.tag, el.attrib) output.text = el.text return output copied = self.__class__(xml_element_tree_element=copy_et_element(self.xml_element_tree_element)) copied._tag = self.tag if copy_parent and self.get_parent(): copied._parent = self.get_parent().__deepcopy__(copy_parent=True) for ch in self.get_children(): copied.add_child(ch.__deepcopy__()) return copied def __repr__(self): attrs = self.get_attributes() output = f"{self.__class__.__name__}(tag={self.tag}" if attrs: output += f", {' '.join([f'{attribute}={attrs[attribute]}' for attribute in attrs])})" else: output += ')' return output def __str__(self): return f"{self.__class__.__name__} {self.compact_repr}"
[docs]class XSDTreeElement: """ Abstract class of all generated XSD Classes """ XSD_TREE: Optional[XSDTree] = None _SEARCH_FOR_ELEMENT = "" _XSD_TREE = None
[docs] @classmethod def get_xsd_tree(cls): if cls._XSD_TREE: return cls._XSD_TREE found_xsd_element = musicxml_xsd_et_root.find(cls._SEARCH_FOR_ELEMENT) if found_xsd_element is None: return cls.XSD_TREE else: cls._XSD_TREE = XSDTree(found_xsd_element) return cls._XSD_TREE
[docs] @classmethod def get_xsd(cls): return cls.get_xsd_tree().get_xsd()
extra_elements = { 'score-partwise': {'search_for': ".//{*}element[@name='score-partwise']", }, 'part': {'search_for': ".//{*}element[@name='score-partwise']//{*}element[@name='part']", }, 'measure': {'search_for': ".//{*}element[@name='score-partwise']//{*}element[@name='measure']", }, 'directive': {'search_for': ".//{*}complexType[@name='attributes']//{*}element[@name='directive']", } } def _generate_xsd_tree(): """ Makes a dictionary out of musicxml_xsd_et_root children with appropriate keys """ output = {'simpleType': {}, 'complexType': {}, 'element': {}, 'group': {}, 'attribute': {}, 'attributeGroup': {}} for root in [xml_xsd_et_root, musicxml_xsd_et_root]: for node in root.iter(): tag_ = node.tag.split('}')[1] name = node.attrib.get('name') if name and tag_ in output: ET.indent(node, space=' ') output[tag_][name] = XSDTree(xml_element_tree_element=node) for el_name in extra_elements: tag_ = 'element' name = el_name node = musicxml_xsd_et_root.find(extra_elements[el_name]['search_for']) ET.indent(node, space=' ') output[tag_][name] = XSDTree(xml_element_tree_element=node) return output XSD_TREE_DICT = _generate_xsd_tree()