Source code for colour_clf_io.process_list

"""
*ProcessList*
============

Defines the top level *ProcessList* object that represents a *CLF* process.
"""

from __future__ import annotations

from dataclasses import dataclass

import lxml.etree
from _warnings import warn

from colour_clf_io.elements import Info
from colour_clf_io.errors import ParsingError
from colour_clf_io.parsing import (
    Namespaces,
    ParserConfig,
    UnknownNamespace,
    check_none,
    detect_namespace,
    element_as_text,
    elements_as_text_list,
    set_attr_if_not_none,
    set_element_if_not_none,
)
from colour_clf_io.process_nodes import (
    ProcessNode,
    assert_bit_depth_compatibility,
    parse_process_node,
)

__author__ = "Colour Developers"
__copyright__ = "Copyright 2024 Colour Developers"
__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
__maintainer__ = "Colour Developers"
__email__ = "colour-developers@colour-science.org"
__status__ = "Production"

__all__ = ["ProcessList"]


[docs] @dataclass class ProcessList: """ Represent a *ProcessList*, the root element for any *CLF* file. It is composed of one or more :class:`colour_clf_io.ProcessNodes` class instances. Attributes ---------- - :attr:`~colour_clf_io.ProcessList.id` - :attr:`~colour_clf_io.ProcessList.compatible_CLF_version` - :attr:`~colour_clf_io.ProcessList.name` - :attr:`~colour_clf_io.ProcessList.inverse_of` - :attr:`~colour_clf_io.ProcessList.description` - :attr:`~colour_clf_io.ProcessList.input_descriptor` - :attr:`~colour_clf_io.ProcessList.output_descriptor` - :attr:`~colour_clf_io.ProcessList.info` - :attr:`~colour_clf_io.ProcessList.process_nodes` Methods ------- - :meth:`~colour_clf_io.ProcessList.from_xml` References ---------- - https://docs.acescentral.com/specifications/clf/#processList """ id: str | None """A string to serve as a unique identifier of the *ProcessList*.""" compatible_CLF_version: str | None """ A string indicating the minimum compatible CLF specification version required to read this file. The compCLFversion corresponding to this version of the specification is be "3.0". """ name: str | None """ A concise string used as a text name of the *ProcessList* for display or selection from an application's user interface. """ inverse_of: str | None """ A string for linking to another *ProcessList* id (unique) which is the inverse of this one. """ description: list[str] """ A list for comments describing the function, usage, or any notes about the *ProcessList*. """ input_descriptor: str | None """ An arbitrary string used to describe the intended source code values of the *ProcessList*. """ output_descriptor: str | None """ An arbitrary string used to describe the intended output target of the *ProcessList* (e.g., target display). """ process_nodes: list[ProcessNode] """ A list of colour operators. The *ProcessList* must contain at least one *ProcessNode*. """ info: Info | None """ Optional element for including additional custom metadata not needed to interpret the transforms. """ """"""
[docs] @staticmethod def from_xml(xml: lxml.etree._Element | None) -> ProcessList | None: """ Parse and return a :class:`colour_clf_io.ProcessList` class instance from the given XML element. Returns ``None`` if the given XML element is ``None``. Expects the XML element to be a valid element, according to the *CLF* specification. Parameters ---------- xml XML element to parse. Returns ------- class:`colour_clf_io.ProcessList` or :py:data:`None` Parsed XML node. Raises ------ :class:`colour_clf_io.errors.ParsingError` If the node does not conform to the specification, a ``ParsingError`` exception will be raised. The error message will indicate the details of the issue that was encountered. """ if xml is None: return None detected_namespace = detect_namespace(xml) document_namespace: Namespaces | None = None match detected_namespace: case None: document_namespace = None case UnknownNamespace(value): exception = f"Found invalid xmlns attribute in *ProcessList*: {value}" raise ParsingError(exception) case Namespaces(): document_namespace = detected_namespace if document_namespace == Namespaces.SMTP: error = ( "SMPTE ST 2136-1 files are not fully supported. See " "https://github.com/colour-science/colour-clf-io/issues/6 " "for more information. " ) raise ParsingError(error) config = ParserConfig(namespace=document_namespace) id_ = xml.get("id") check_none(id_, "ProcessList must contain an `id` attribute") compatible_clf_version = xml.get("compCLFversion") check_none( compatible_clf_version, 'ProcessList must contain a "compCLFversion" attribute', ) name = xml.get("name") inverse_of = xml.get("inverseOf") info = Info.from_xml(xml, config) description = elements_as_text_list(xml, "Description", config) input_descriptor = element_as_text(xml, "InputDescriptor", config) output_descriptor = element_as_text(xml, "OutputDescriptor", config) ignore_nodes = ["Description", "InputDescriptor", "OutputDescriptor", "Info"] process_nodes = filter( lambda node: lxml.etree.QName(node).localname not in ignore_nodes, xml ) if not process_nodes: warn("Got empty process node.") process_nodes = [ parse_process_node(xml_node, config) for xml_node in process_nodes ] assert_bit_depth_compatibility(process_nodes) return ProcessList( id=id_, compatible_CLF_version=compatible_clf_version, process_nodes=process_nodes, name=name, inverse_of=inverse_of, input_descriptor=input_descriptor, output_descriptor=output_descriptor, info=info, description=description, )
[docs] def to_xml(self, name_space: Namespaces = Namespaces.AMPAS) -> lxml.etree._Element: """ Serialise this object as an XML object. Parameters ---------- name_space :class:`colour_clf_io.Namespaces` instance to be used for the namespace of the document. Returns ------- :class:`lxml.etree._Element` """ xml = lxml.etree.Element("ProcessList") xml.set("xmlns", name_space.value) for description_text in self.description: description_element = lxml.etree.SubElement(xml, "Description") description_element.text = description_text set_attr_if_not_none(xml, "id", self.id) set_attr_if_not_none(xml, "compCLFversion", self.compatible_CLF_version) set_attr_if_not_none(xml, "name", self.name) set_attr_if_not_none(xml, "inverseOf", self.inverse_of) set_element_if_not_none(xml, "InputDescriptor", self.input_descriptor) set_element_if_not_none(xml, "OutputDescriptor", self.output_descriptor) if self.info: xml.append(self.info.to_xml()) for process_node in self.process_nodes: xml.append(process_node.to_xml()) return xml