88
99from __future__ import annotations
1010
11+ import itertools
1112import typing
1213from dataclasses import dataclass
1314
1415if typing .TYPE_CHECKING :
1516 import numpy .typing as npt
1617
17- if typing .TYPE_CHECKING :
18- import lxml .etree
18+ import lxml .etree
1919
2020from colour_clf_io .errors import ParsingError
2121from colour_clf_io .parsing import (
2222 ParserConfig ,
2323 XMLParsable ,
24+ XMLWritable ,
2425 check_none ,
2526 child_element ,
2627 child_element_or_exception ,
2728 map_optional ,
2829 retrieve_attributes ,
2930 retrieve_attributes_as_float ,
31+ set_attr_if_not_none ,
32+ set_element_if_not_none ,
3033 three_floats ,
3134)
3235from colour_clf_io .values import Channel
5053
5154
5255@dataclass
53- class Array (XMLParsable ):
56+ class Array (XMLParsable , XMLWritable ):
5457 """
5558 Represent an *Array* element.
5659
@@ -124,6 +127,27 @@ def from_xml(
124127
125128 return Array (values = values , dim = dimensions )
126129
130+ def to_xml (self ) -> lxml .etree ._Element :
131+ """
132+ Serialise this object as an XML object.
133+
134+ Returns
135+ -------
136+ :class:`lxml.etree._Element`
137+ """
138+ xml = lxml .etree .Element ("Array" )
139+ xml .set ("dim" , " " .join (map (str , self .dim )))
140+ if len (self .dim ) <= 1 :
141+ xml .text = "\n " .join (map (str , self .values ))
142+ else :
143+ row_length = self .dim [- 1 ]
144+ text = "\n " .join (
145+ " " .join (map (str , row ))
146+ for row in itertools .batched (self .values , row_length )
147+ )
148+ xml .text = text
149+ return xml
150+
127151 def as_array (self ) -> npt .NDArray :
128152 """
129153 Convert the *CLF* element into a numpy array.
@@ -144,7 +168,7 @@ def as_array(self) -> npt.NDArray:
144168
145169
146170@dataclass
147- class CalibrationInfo (XMLParsable ):
171+ class CalibrationInfo (XMLParsable , XMLWritable ):
148172 """
149173 Represent a *CalibrationInfo* container element for a
150174 :class:`colour_clf_io.ProcessList` class instance.
@@ -227,9 +251,32 @@ def from_xml(
227251
228252 return CalibrationInfo (** attributes )
229253
254+ def to_xml (self ) -> lxml .etree ._Element :
255+ """
256+ Serialise this object as an XML object.
257+
258+ Returns
259+ -------
260+ :class:`lxml.etree._Element`
261+ """
262+ xml = lxml .etree .Element ("CalibrationInfo" )
263+ set_attr_if_not_none (
264+ xml , "DisplayDeviceSerialNum" , self .display_device_serial_num
265+ )
266+ set_attr_if_not_none (
267+ xml , "DisplayDeviceHostName" , self .display_device_host_name
268+ )
269+ set_attr_if_not_none (xml , "OperatorName" , self .operator_name )
270+ set_attr_if_not_none (xml , "CalibrationDateTime" , self .calibration_date_time )
271+ set_attr_if_not_none (xml , "MeasurementProbe" , self .measurement_probe )
272+ set_attr_if_not_none (
273+ xml , "CalibrationSoftwareName" , self .calibration_software_name
274+ )
275+ return xml
276+
230277
231278@dataclass
232- class SOPNode (XMLParsable ):
279+ class SOPNode (XMLParsable , XMLWritable ):
233280 """
234281 Represent a *SOPNode* element for a :class:`colour_clf_io.ASC_CDL`
235282 *Process Node*.
@@ -312,6 +359,20 @@ def from_xml(
312359
313360 return SOPNode (slope = slope , offset = offset , power = power )
314361
362+ def to_xml (self ) -> lxml .etree ._Element :
363+ """
364+ Serialise this object as an XML object.
365+
366+ Returns
367+ -------
368+ :class:`lxml.etree._Element`
369+ """
370+ xml = lxml .etree .Element ("SOPNode" )
371+ set_element_if_not_none (xml , "Slope" , " " .join (map (str , self .slope )))
372+ set_element_if_not_none (xml , "Offset" , " " .join (map (str , self .offset )))
373+ set_element_if_not_none (xml , "Power" , " " .join (map (str , self .power )))
374+ return xml
375+
315376 @classmethod
316377 def default (cls ) -> SOPNode :
317378 """
@@ -331,7 +392,7 @@ def default(cls) -> SOPNode:
331392
332393
333394@dataclass
334- class SatNode (XMLParsable ):
395+ class SatNode (XMLParsable , XMLWritable ):
335396 """
336397 Represent a *SatNode* element for a :class:`colour_clf_io.ASC_CDL`
337398 *Process Node*.
@@ -399,6 +460,18 @@ def from_xml(
399460
400461 return SatNode (saturation = saturation )
401462
463+ def to_xml (self ) -> lxml .etree ._Element :
464+ """
465+ Serialise this object as an XML object.
466+
467+ Returns
468+ -------
469+ :class:`lxml.etree._Element`
470+ """
471+ xml = lxml .etree .Element ("SatNode" )
472+ set_element_if_not_none (xml , "Saturation" , self .saturation )
473+ return xml
474+
402475 @classmethod
403476 def default (cls ) -> SatNode :
404477 """
@@ -414,7 +487,7 @@ def default(cls) -> SatNode:
414487
415488
416489@dataclass
417- class Info (XMLParsable ):
490+ class Info (XMLParsable , XMLWritable ):
418491 """
419492 Represent an *Info* element.
420493
@@ -520,9 +593,27 @@ def from_xml(xml: lxml.etree._Element | None, config: ParserConfig) -> Info | No
520593
521594 return Info (calibration_info = calibration_info , ** attributes )
522595
596+ def to_xml (self ) -> lxml .etree ._Element :
597+ """
598+ Serialise this object as an XML object.
599+
600+ Returns
601+ -------
602+ :class:`lxml.etree._Element`
603+ """
604+ xml = lxml .etree .Element ("Info" )
605+ set_attr_if_not_none (xml , "AppRelease" , self .app_release )
606+ set_attr_if_not_none (xml , "Copyright" , self .copyright )
607+ set_attr_if_not_none (xml , "Revision" , self .revision )
608+ set_attr_if_not_none (xml , "AcesTransformID" , self .aces_transform_id )
609+ set_attr_if_not_none (xml , "AcesUserName" , self .aces_user_name )
610+ if self .calibration_info is not None :
611+ xml .append (self .calibration_info .to_xml ())
612+ return xml
613+
523614
524615@dataclass
525- class LogParams (XMLParsable ):
616+ class LogParams (XMLParsable , XMLWritable ):
526617 """
527618 Represent a *LogParams* element for a :class:`colour_clf_io.Log`
528619 *Process Node*.
@@ -649,6 +740,26 @@ def from_xml(
649740
650741 return LogParams (channel = channel , ** attributes )
651742
743+ def to_xml (self ) -> lxml .etree ._Element :
744+ """
745+ Serialise this object as an XML object.
746+
747+ Returns
748+ -------
749+ :class:`lxml.etree._Element`
750+ """
751+ xml = lxml .etree .Element ("LogParams" )
752+ set_attr_if_not_none (xml , "base" , self .base )
753+ set_attr_if_not_none (xml , "logSideSlope" , self .log_side_slope )
754+ set_attr_if_not_none (xml , "logSideOffset" , self .log_side_offset )
755+ set_attr_if_not_none (xml , "linSideSlope" , self .lin_side_slope )
756+ set_attr_if_not_none (xml , "linSideOffset" , self .lin_side_offset )
757+ set_attr_if_not_none (xml , "linSideBreak" , self .lin_side_break )
758+ set_attr_if_not_none (xml , "linearSlope" , self .linear_slope )
759+ if self .channel is not None :
760+ xml .set ("channel" , self .channel .value )
761+ return xml
762+
652763 @classmethod
653764 def default (cls ) -> LogParams :
654765 """
@@ -673,7 +784,7 @@ def default(cls) -> LogParams:
673784
674785
675786@dataclass
676- class ExponentParams (XMLParsable ):
787+ class ExponentParams (XMLParsable , XMLWritable ):
677788 """
678789 Represent a *ExponentParams* element for a :class:`colour_clf_io.Exponent`
679790 *Process Node*.
@@ -772,6 +883,21 @@ def from_xml(
772883
773884 return ExponentParams (channel = channel , exponent = exponent , ** attributes )
774885
886+ def to_xml (self ) -> lxml .etree ._Element :
887+ """
888+ Serialise this object as an XML object.
889+
890+ Returns
891+ -------
892+ :class:`lxml.etree._Element`
893+ """
894+ xml = lxml .etree .Element ("ExponentParams" )
895+ set_attr_if_not_none (xml , "exponent" , self .exponent )
896+ set_attr_if_not_none (xml , "offset" , self .offset )
897+ if self .channel is not None :
898+ xml .set ("channel" , self .channel .value )
899+ return xml
900+
775901 @classmethod
776902 def default (cls ) -> ExponentParams :
777903 """
0 commit comments