Source code for ocx_schema_parser.elements

"""The OCX Schema content classes."""

#  Copyright (c) 2022-2025. OCX Consortium https://3docx.org. See the LICENSE
from collections import defaultdict
from typing import Dict, List, Union

from loguru import logger

# Third party imports
from lxml.etree import Element, QName

from ocx_schema_parser.data_classes import OcxSchemaAttribute, OcxSchemaChild

# Module imports
from ocx_schema_parser.helpers import SchemaHelper
from ocx_schema_parser.xelement import LxmlElement


[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 Attributes: _element: The ``lxml.Element`` instance _attributes: The attributes of the global element including the attributes of all schema supertypes _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, namespaces: Dict): # Private self._element: Element = xsd_element self._attributes: List[OcxSchemaAttribute] = [] self._namespace: str = QName(unique_tag).namespace self._tag: str = unique_tag self._cardinality: tuple = LxmlElement.cardinality(xsd_element) self._children: List = [] self._parents: Dict = {} self._assertions: List = [] self._namespaces: Dict = namespaces
[docs] def add_attribute(self, attribute: OcxSchemaAttribute): """Add attributes to the global element. Arguments: attribute : The attribute instance to be added """ self._attributes.append(attribute)
[docs] def add_child(self, child: OcxSchemaChild): """Add a child of an OCX global element' Arguments: child: The added child instance 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: """Return all my parents 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 """ return [LxmlElement.strip_namespace_tag(tag) for tag in self._parents]
[docs] def get_assertion_tests(self) -> List: """Get all my assertions Returns: Assertion tests in a list """ return self._assertions
[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 namespace prefix of the global schema element """ nsprefix = list(iter(self._namespaces)) nstags = list(iter(self._namespaces.values())) prefix = "" try: index = nstags.index(self._namespace) prefix = nsprefix[index] if prefix is None: nstags.pop(index) nsprefix.pop(index) index = nstags.index(self._namespace) prefix = nsprefix.index(index) except ValueError as e: logger.error(f"{self._namespace} is not in the namespace list: {e}") if prefix == "": logger.debug(f"Empty namespace prefix in global elem,ent {self.get_name()}") return prefix
[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_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: main: 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.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: main: 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 sorted(self._children, key=lambda x: x.name): attributes = child.to_dict() for a in attributes: table[a].append(attributes[a]) return table