Source code for schema_parser.elements

"""The OCX Schema content classes."""
# Copyright (c) 2022-2023.  OCX Consortium https://3docx.org. See the LICENSE

from collections import defaultdict
from logging import Logger
from typing import Dict
from typing import List
from typing import Union

from lxml.etree import Element
from lxml.etree import QName

from .helpers import SchemaHelper
from .xelement import LxmlElement


[docs]class OcxAttribute: """Global schema attribute class capturing the xsd schema definition of a global xs:attribute. Args: xs_attribute: The lxml.etree.Element class Attributes: _name : The attribute name _type : The attribute type _use : Whether the attribute is optional or required _fixed: Whether the attribute has a fixed value if any _default: The deaflalt value of the attribute if any _annotation: The attribute description _is_global: True if the element is global, False otherwise """ def __init__(self, xs_attribute: Element): # Private self._name = LxmlElement.get_name(xs_attribute) self._type = SchemaHelper.get_type(xs_attribute) self._use = LxmlElement.get_use(xs_attribute) self._fixed = xs_attribute.get("fixed") self._default = xs_attribute.get("default") self._annotation = LxmlElement.get_element_text(xs_attribute) self._is_global = False
[docs] def get_use(self) -> str: """The xs:attribute use (optional or required) Returns: Returns either 'required' or 'optional' """ return self._use
[docs] def get_fixed(self) -> str: """The fixed value of the xs:attribute Returns: Returns the fixed value of the attribute or an empty string if None """ if self._fixed is None: return "" else: return self._fixed
[docs] def get_default(self) -> str: """The default value of the xs:attribute Returns: Returns the default value of the attribute or an empty string if None """ if self._default is None: return "" else: return self._default
[docs] def get_name(self) -> str: """The name of the xs:attribute Returns: The name of the attribute """ return self._name
[docs] def get_type(self) -> str: """The type of the xs:attribute Returns: The attribute type """ return self._type
[docs] def get_description(self) -> str: """The annotation string of the xs:attribute Returns: The attribute description text """ return self._annotation
[docs] def put_description(self, text: str): """Set the xs:attribute documentation string Returns: None """ self._annotation = text
[docs] def put_use(self, use: str): """Set the xs:attribute use string Returns: None """ self._use = use
[docs] def put_type(self, type: str): """Set the xs:attribute type string Returns: None """ self._type = type
[docs] def put_name(self, name: str): """Set the xs:attribute name Returns: None """ self._name = name
[docs] def attributes_to_dict(self) -> Dict: """A dictionary of the OcxAttribute values Returns: table: A dictionary of all ``OcxAttribute`` attribute values with columns: .. list-table:: Heading titles :widths: 25 25 25 25 25 50 * - Attribute - Type - Use - Default - Fixed - Description """ return { "Attribute": self.get_name(), "Type": self.get_type(), "Use": self.get_use(), "Default": self.get_default(), "Fixed": self.get_fixed(), "Description": self.get_description(), }
[docs]class OcxChildElement: """Class capturing the OCX xsd schema definition of a child or sub element ``xs:element``. Args: xs_element: The lxml.etree.Element class Attributes: _tag: The unique tag of th schema element _element: The ``xs:element`` instance _name : The attribute name _type : The attribute type _use : Whether the child is optional or required _cardinality : The cardinality of the element _annotation: The element description _is_choice: The child element is a ``xs:choice`` """ def __init__(self, xs_element: Element): # Private self._element = xs_element self._tag = "" self._name = LxmlElement.get_name(xs_element) self._type = SchemaHelper.get_type(xs_element) self._cardinality = LxmlElement.cardinality(xs_element) self._annotation = LxmlElement.get_element_text(xs_element) self._is_choice = LxmlElement.is_choice(xs_element)
[docs] def get_use(self) -> str: """Mandatory or optional sub element Returns: The element use, either ``req.`` or ``opt.`` """ lower, upper = self._cardinality if lower == 0: return "opt." else: return "req."
[docs] def get_cardinality(self) -> str: """Get the cardinality of the ``OcxChildElement`` Returns: The cardinality as sting represented by ``[lower, upper]`` """ lower, upper = self._cardinality if upper == "unbounded": upper = "\u221E" # UTF-8 Infinity symbol return f"[{lower}, {upper}]"
[docs] def get_name(self) -> str: """The name of the xs:attribute Returns: The name of the attribute """ return self._name
[docs] def get_type(self) -> str: """The type of the xs:attribute Returns: The attribute type """ return self._type
[docs] def get_description(self) -> str: """The annotation text of the element Returns: The description of the element """ return self._annotation
[docs] def put_description(self, text: str): """Set the xs:attribute documentation string Returns: None """ self._annotation = text
[docs] def put_use(self, use: str): """Set the xs:attribute use string Returns: None """ self._use = use
[docs] def put_reference(self, tag: str): """Set the tag reference to the global schema element Returns: None """ self._tag = tag
[docs] def is_mandatory(self) -> bool: """Whether the element mandatory or not Returns: True if the element is mandatory, False otherwise """ lower, upper = self._cardinality return lower != 0
[docs] def is_choice(self) -> bool: """Whether the element is a choice or not Returns: True if the element is a choice, False otherwise """ return self._is_choice
[docs] def is_global(self) -> bool: """Whether the element is a global schema element Returns: True if the element is global, False otherwise """ return self._tag != ""
[docs] def put_type(self, type: str): """Set the xs:attribute type string Returns: None """ self._type = type
[docs] def put_name(self, name: str): """Set the xs:attribute name Returns: None """ self._name = name
[docs] def attributes_to_dict(self) -> Dict: """A dictionary of the ''OcxChildElement'' values Returns: table: dictionary of all OcxAttribute attribute values with columns: .. list-table:: Heading titles :widths: 25 25 25 25 50 * - Child - Cardinality - Choice - Global - Description """ return { "Child": self.get_name(), "Type": self.get_type(), "Use": self.get_use(), "Cardinality": self.get_cardinality(), "Choice": self.is_choice(), "Global": self.is_global(), "Description": self.get_description(), }
[docs]class OcxGlobalElement: """Global schema element class capturing the xsd schema definition of a global ``xs:element``. Args: xsd_element: The lxml.etree.Element class logger: The main python logger Attributes: log: The Python logger instance _element: The ``lxml.Element`` instance _attributes: The attributes of the global element including the attributes of all schema supertypes _reference: The ``OcxGlobalElement`` hase reference to a global schema element. 'None' if no reference _tag: The unique global tag of the ``OcXGlobalElement`` _parents: Hash table of references to all parent schema types with tag as key _children: List of references to all children schema types with tag as key. Includes also children of all super-types. -assertions: List of any assertions associated with the ``xs:element`` """ def __init__(self, xsd_element: Element, unique_tag: str, logger: Logger): self.log = logger # Private self._element = xsd_element self._attributes = [] self._namespace = QName(unique_tag).namespace self._tag = unique_tag self._cardinality = LxmlElement.cardinality(xsd_element) self._children = [] self._parents = {} self._assertions = []
[docs] def add_attribute(self, attribute: OcxAttribute): """Add attributes to the global element Arguments: attribute : The ``OcxAttribute`` instance to be added """ self._attributes.append(attribute)
[docs] def add_child(self, child: OcxChildElement): """Add a child of an OCX global element' Arguments: child: The added child instance of a ``OcxChildElement`` class Returns: Nothing """ self._children.append(child)
[docs] def add_assertion(self, test: str): """Add an assertion test associated to me' Arguments: test: The definition of the assertion represented as a string Returns: Nothing """ self._assertions.append(test)
[docs] def has_assertion(self) -> bool: """Whether the element has assertions or not' Returns: Tru if the global element as assertions, False otherwise """ return len(self._assertions) > 0
[docs] def put_parent(self, tag: str, parent: Element): """Add a parent element Arguments: tag: The unique tag of the parent element parent: The parent xsd schema element Returns: None """ self._parents[tag] = parent
[docs] def get_parents(self) -> dict: """Get all my attributes Returns: Return all parents as a dict of key-value pairs ``(tag, Element)`` """ return self._parents
[docs] def get_parent_names(self) -> List: """Get all my parent names Returns: Return all parents names in a list """ parents = [] for tag in self._parents: parents.append(LxmlElement.strip_namespace_tag(tag)) return parents
[docs] def get_assertion_tests(self) -> List: """Get all my assertions Returns: Assertion tests in a list """ return self._assertions
[docs] def put_child(self, tag: str, child: OcxChildElement): """Add a child element of type ``OCxChildElement`` Arguments: tag: The unique tag of the parent element child: The child xsd schema element Returns: None """ self._children[tag] = child
[docs] def get_children(self) -> List: """Get all my children xsd types Returns: Return all children as a dict of key-value pairs ``(tag, OCXChildElement)`` """ return self._children
[docs] def get_namespace(self) -> str: """The element _namespace Returns: The _namespace of the global schema element as a str """ return self._namespace
[docs] def get_attributes(self) -> List: """The global element attributes including also parent attributes Returns: A dict of all attributes including also parent attributes """ return self._attributes
[docs] def get_name(self) -> str: """The global element name Returns: The name of the global schema element as a str """ return LxmlElement.get_name(self._element)
[docs] def get_annotation(self) -> str: """The global element annotation or description Returns: The annotation string of the element """ annotation = LxmlElement.find_child_with_name(self._element, "annotation") if annotation is not None: return LxmlElement.get_element_text(annotation)
[docs] def get_type(self) -> str: """The global element type Returns: The type of the global schema element as a str """ return SchemaHelper.get_type(self._element)
[docs] def get_prefix(self) -> str: """The global element _namespace prefix Returns: The type of the global schema element as a str """ if self.is_reference(): return LxmlElement.namespace_prefix(self.get_reference()) else: type = self.get_type() return LxmlElement.namespace_prefix(type)
[docs] def get_schema_element(self) -> Element: """Get the schema xsd element of the ``OcxSchemeElement`` object Returns: My xsd schema element """ return self._element
[docs] def put_cardinality(self, element: Element): """Override the cardinality of the OcxGlobalElement Args: element: the etree.Element node """ self._cardinality = LxmlElement.cardinality(element)
[docs] def get_reference(self) -> str: """Get the reference to a global element Returns: tag: The unique tag to the global referenced element """ return self._reference
[docs] def put_reference(self, tag): """Assign the reference to a global element Returns: tag: The unique tag to the global referenced element """ self._reference = tag
[docs] def get_cardinality(self) -> str: """Get the cardinality of the OcxGlobalElement Returns: The cardinality as sting represented by [lower, upper] """ lower, upper = self._cardinality if upper == "unbounded": upper = "\u221E" # UTF-8 Infinity symbol return f"[{lower}, {upper}]"
[docs] def is_reference(self) -> bool: """Whether the element has a reference or not Returns: is_reference : True if the element has a reference, False otherwise """ return LxmlElement.is_reference(self._element)
[docs] def is_mandatory(self) -> bool: """Whether the element mandatory or not Returns: Returns True if the element is mandatory, False otherwise """ return LxmlElement.is_mandatory(self._element)
[docs] def is_choice(self) -> bool: """Whether the element is a choice or not Returns: True if the element is a choice, False otherwise """ return LxmlElement.is_choice(self._element)
[docs] def is_substitution_group(self) -> bool: """Whether the element is part of a substitutionGroup Returns: True if the element is a substitutionGroup, False otherwise """ return LxmlElement.is_substitution_group(self._element)
[docs] def is_abstract(self) -> bool: """Whether the element is abstract Returns: True if the element is abstract, False otherwise """ return LxmlElement.is_abstract(self._element)
[docs] def get_substitution_group(self) -> Union[str, None]: """Return the name of the substitutionGroup Returns: The name of the ``substitutionGroup``, None otherwise """ return LxmlElement.get_substitution_group(self._element)
[docs] def get_tag(self) -> str: """The global schema element unique tag Returns: The element tag on the form ``{prefix}name`` """ return self._tag
[docs] def get_use(self) -> str: """The element's use, required or optional Returns: The element use: ``req.`` if mandatory, else ``opt`` """ use = "opt" if self.is_mandatory(): use = "req" return use
[docs] def get_properties(self) -> Dict: """A dictionary of all ``OcxGlobalElement`` property values Returns: table: A dictionary of property values with heading keys: .. list-table:: Heading keys :widths: 25 25 25 25 25 50 * - Name - Type - Use - Cardinality - Fixed - Description """ table = defaultdict(list) table["Name"].append(self.get_name()) table["Type"].append(self.get_type()) table["Use"].append(self.get_use()) table["Cardinality"].append(self.get_cardinality()) table["Description"].append(self.get_annotation()) return table
[docs] def attributes_to_dict(self) -> Dict: """A dictionary of all ``OcxGlobalElement`` attribute values Returns: A dictionary of attribute values with heading keys .. list-table:: Heading keys :widths: 25 25 25 25 25 50 * - Attribute - Type - Use - Default - Fixed - Description """ table = defaultdict(list) for attr in self._attributes: attributes = attr.attributes_to_dict() for a in attributes: table[a].append(attributes[a]) return table
[docs] def children_to_dict(self) -> Dict: """A dictionary of all ``OcxGlobalElement`` children values Returns: table: A dictionary of attribute values with heading keys: .. list-table:: Heading keys :widths: 25 25 25 25 50 * - Child - Type - Use - Cardinality - Description """ table = defaultdict(list) for child in self._children: attributes = child.attributes_to_dict() for a in attributes: table[a].append(attributes[a]) return table