aspose-cells-foss 25.12.1__py3-none-any.whl → 26.2.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. aspose_cells/__init__.py +88 -0
  2. aspose_cells/auto_filter.py +527 -0
  3. aspose_cells/cell.py +483 -0
  4. aspose_cells/cell_value_handler.py +319 -0
  5. aspose_cells/cells.py +779 -0
  6. aspose_cells/cfb_handler.py +445 -0
  7. aspose_cells/cfb_writer.py +659 -0
  8. aspose_cells/cfb_writer_minimal.py +337 -0
  9. aspose_cells/comment_xml.py +475 -0
  10. aspose_cells/conditional_format.py +1185 -0
  11. aspose_cells/csv_handler.py +690 -0
  12. aspose_cells/data_validation.py +911 -0
  13. aspose_cells/document_properties.py +356 -0
  14. aspose_cells/encryption_crypto.py +247 -0
  15. aspose_cells/encryption_params.py +138 -0
  16. aspose_cells/hyperlink.py +372 -0
  17. aspose_cells/json_handler.py +185 -0
  18. aspose_cells/markdown_handler.py +583 -0
  19. aspose_cells/shared_strings.py +101 -0
  20. aspose_cells/style.py +841 -0
  21. aspose_cells/workbook.py +499 -0
  22. aspose_cells/workbook_hash_password.py +68 -0
  23. aspose_cells/workbook_properties.py +712 -0
  24. aspose_cells/worksheet.py +570 -0
  25. aspose_cells/worksheet_properties.py +1239 -0
  26. aspose_cells/xlsx_encryptor.py +403 -0
  27. aspose_cells/xml_autofilter_loader.py +195 -0
  28. aspose_cells/xml_autofilter_saver.py +173 -0
  29. aspose_cells/xml_conditional_format_loader.py +215 -0
  30. aspose_cells/xml_conditional_format_saver.py +351 -0
  31. aspose_cells/xml_datavalidation_loader.py +239 -0
  32. aspose_cells/xml_datavalidation_saver.py +245 -0
  33. aspose_cells/xml_hyperlink_handler.py +323 -0
  34. aspose_cells/xml_loader.py +986 -0
  35. aspose_cells/xml_properties_loader.py +512 -0
  36. aspose_cells/xml_properties_saver.py +607 -0
  37. aspose_cells/xml_saver.py +1306 -0
  38. aspose_cells_foss-26.2.2.dist-info/METADATA +190 -0
  39. aspose_cells_foss-26.2.2.dist-info/RECORD +41 -0
  40. {aspose_cells_foss-25.12.1.dist-info → aspose_cells_foss-26.2.2.dist-info}/WHEEL +1 -1
  41. aspose_cells_foss-26.2.2.dist-info/top_level.txt +1 -0
  42. aspose/__init__.py +0 -14
  43. aspose/cells/__init__.py +0 -31
  44. aspose/cells/cell.py +0 -350
  45. aspose/cells/constants.py +0 -44
  46. aspose/cells/converters/__init__.py +0 -13
  47. aspose/cells/converters/csv_converter.py +0 -55
  48. aspose/cells/converters/json_converter.py +0 -46
  49. aspose/cells/converters/markdown_converter.py +0 -453
  50. aspose/cells/drawing/__init__.py +0 -17
  51. aspose/cells/drawing/anchor.py +0 -172
  52. aspose/cells/drawing/collection.py +0 -233
  53. aspose/cells/drawing/image.py +0 -338
  54. aspose/cells/formats.py +0 -80
  55. aspose/cells/formula/__init__.py +0 -10
  56. aspose/cells/formula/evaluator.py +0 -360
  57. aspose/cells/formula/functions.py +0 -433
  58. aspose/cells/formula/tokenizer.py +0 -340
  59. aspose/cells/io/__init__.py +0 -27
  60. aspose/cells/io/csv/__init__.py +0 -8
  61. aspose/cells/io/csv/reader.py +0 -88
  62. aspose/cells/io/csv/writer.py +0 -98
  63. aspose/cells/io/factory.py +0 -138
  64. aspose/cells/io/interfaces.py +0 -48
  65. aspose/cells/io/json/__init__.py +0 -8
  66. aspose/cells/io/json/reader.py +0 -126
  67. aspose/cells/io/json/writer.py +0 -119
  68. aspose/cells/io/md/__init__.py +0 -8
  69. aspose/cells/io/md/reader.py +0 -161
  70. aspose/cells/io/md/writer.py +0 -334
  71. aspose/cells/io/models.py +0 -64
  72. aspose/cells/io/xlsx/__init__.py +0 -9
  73. aspose/cells/io/xlsx/constants.py +0 -312
  74. aspose/cells/io/xlsx/image_writer.py +0 -311
  75. aspose/cells/io/xlsx/reader.py +0 -284
  76. aspose/cells/io/xlsx/writer.py +0 -931
  77. aspose/cells/plugins/__init__.py +0 -6
  78. aspose/cells/plugins/docling_backend/__init__.py +0 -7
  79. aspose/cells/plugins/docling_backend/backend.py +0 -535
  80. aspose/cells/plugins/markitdown_plugin/__init__.py +0 -15
  81. aspose/cells/plugins/markitdown_plugin/plugin.py +0 -128
  82. aspose/cells/range.py +0 -210
  83. aspose/cells/style.py +0 -287
  84. aspose/cells/utils/__init__.py +0 -54
  85. aspose/cells/utils/coordinates.py +0 -68
  86. aspose/cells/utils/exceptions.py +0 -43
  87. aspose/cells/utils/validation.py +0 -102
  88. aspose/cells/workbook.py +0 -352
  89. aspose/cells/worksheet.py +0 -670
  90. aspose_cells_foss-25.12.1.dist-info/METADATA +0 -189
  91. aspose_cells_foss-25.12.1.dist-info/RECORD +0 -53
  92. aspose_cells_foss-25.12.1.dist-info/entry_points.txt +0 -2
  93. aspose_cells_foss-25.12.1.dist-info/top_level.txt +0 -1
@@ -0,0 +1,351 @@
1
+ """
2
+ Aspose.Cells for Python - XML Conditional Formatting Saver Module
3
+
4
+ This module provides the ConditionalFormatXMLWriter class which handles saving
5
+ conditional formatting data to XML format according to ECMA-376 specification.
6
+
7
+ ECMA-376 Section 18.3.1.18 defines the conditionalFormatting element structure.
8
+ """
9
+
10
+
11
+ class ConditionalFormatXMLWriter:
12
+ """
13
+ Handles writing conditional formatting data to XML format for .xlsx files.
14
+
15
+ The ConditionalFormatXMLWriter class is responsible for creating the XML
16
+ representation of conditional formatting rules including cell value rules,
17
+ text rules, date rules, formula rules, color scales, data bars, icon sets,
18
+ and more.
19
+
20
+ Examples:
21
+ >>> writer = ConditionalFormatXMLWriter(escape_xml_func)
22
+ >>> xml = writer.format_conditional_formatting_xml(worksheet.conditional_formats)
23
+ """
24
+
25
+ def __init__(self, escape_xml_func):
26
+ """
27
+ Initializes a new instance of the ConditionalFormatXMLWriter class.
28
+
29
+ Args:
30
+ escape_xml_func: A function to escape XML special characters.
31
+ """
32
+ self._escape_xml = escape_xml_func
33
+
34
+ def format_conditional_formatting_xml(self, conditional_formats):
35
+ """
36
+ Formats conditional formatting as XML according to ECMA-376 specification.
37
+
38
+ ECMA-376 Section 18.3.1.18 defines the conditionalFormatting element structure:
39
+ - conditionalFormatting: Main element with sqref attribute (cell ranges)
40
+ - cfRule: Individual conditional formatting rule
41
+
42
+ Args:
43
+ conditional_formats (ConditionalFormatCollection): The collection of conditional formats.
44
+
45
+ Returns:
46
+ str: XML representation of the conditional formatting.
47
+ """
48
+ xml = ''
49
+
50
+ # Group conditional formats by range (sqref)
51
+ formats_by_range = {}
52
+ for cf in conditional_formats:
53
+ if cf.range is None:
54
+ continue
55
+ if cf.range not in formats_by_range:
56
+ formats_by_range[cf.range] = []
57
+ formats_by_range[cf.range].append(cf)
58
+
59
+ # Write conditionalFormatting elements
60
+ for sqref, formats in formats_by_range.items():
61
+ xml += f' <conditionalFormatting sqref="{sqref}">\n'
62
+
63
+ for cf in formats:
64
+ xml += self._format_cf_rule_xml(cf)
65
+
66
+ xml += ' </conditionalFormatting>\n'
67
+
68
+ return xml
69
+
70
+ def _format_cf_rule_xml(self, cf):
71
+ """
72
+ Formats a single conditional formatting rule as XML.
73
+
74
+ Args:
75
+ cf (ConditionalFormat): The conditional format object.
76
+
77
+ Returns:
78
+ str: XML representation of the cfRule element.
79
+ """
80
+ # Map user-friendly type names to ECMA-376 XML type names
81
+ type_map = {
82
+ 'cellValue': 'cellIs',
83
+ 'text': 'containsText',
84
+ 'date': 'timePeriod',
85
+ 'formula': 'expression',
86
+ 'duplicateValues': 'duplicateValues',
87
+ 'uniqueValues': 'uniqueValues',
88
+ 'top10': 'top10',
89
+ 'bottom10': 'top10', # ECMA-376: both use 'top10' type, distinguished by bottom attribute
90
+ 'aboveAverage': 'aboveAverage',
91
+ 'belowAverage': 'aboveAverage', # ECMA-376: both use 'aboveAverage' type, distinguished by aboveAverage attribute
92
+ 'colorScale': 'colorScale',
93
+ 'dataBar': 'dataBar',
94
+ 'iconSet': 'iconSet',
95
+ 'containsText': 'containsText',
96
+ 'notContainsText': 'notContainsText',
97
+ 'beginsWith': 'beginsWith',
98
+ 'endsWith': 'endsWith',
99
+ 'containsBlanks': 'containsBlanks',
100
+ 'notContainsBlanks': 'notContainsBlanks',
101
+ 'containsErrors': 'containsErrors',
102
+ 'notContainsErrors': 'notContainsErrors',
103
+ 'timePeriod': 'timePeriod',
104
+ 'expression': 'expression',
105
+ 'cellIs': 'cellIs'
106
+ }
107
+
108
+ rule_type = type_map.get(cf._type, cf._type)
109
+ if rule_type is None:
110
+ return ''
111
+
112
+ # Build cfRule attributes
113
+ attrs = [f'type="{rule_type}"']
114
+
115
+ # Add dxfId if formatting is applied (will be set during dxf registration)
116
+ if hasattr(cf, '_dxf_id') and cf._dxf_id is not None:
117
+ attrs.append(f'dxfId="{cf._dxf_id}"')
118
+
119
+ attrs.append(f'priority="{cf.priority}"')
120
+
121
+ if cf.stop_if_true:
122
+ attrs.append('stopIfTrue="1"')
123
+
124
+ # Add operator for cellIs type
125
+ if rule_type == 'cellIs' and cf.operator:
126
+ attrs.append(f'operator="{cf.operator}"')
127
+
128
+ # Add text attribute for text-based rules
129
+ if rule_type in ('containsText', 'notContainsText', 'beginsWith', 'endsWith'):
130
+ if cf._formula1:
131
+ escaped_text = self._escape_xml(str(cf._formula1))
132
+ attrs.append(f'text="{escaped_text}"')
133
+
134
+ # Add timePeriod attribute
135
+ if rule_type == 'timePeriod' and cf._operator:
136
+ attrs.append(f'timePeriod="{cf._operator}"')
137
+
138
+ # Add top10 attributes
139
+ if rule_type == 'top10':
140
+ # Handle both 'top10' and 'bottom10' user types
141
+ # Both map to ECMA-376 'top10' type, distinguished by bottom attribute
142
+ if cf.top is not None:
143
+ attrs.append('bottom="0"' if cf.top else 'bottom="1"')
144
+ elif cf._type == 'bottom10':
145
+ # For bottom10 type, explicitly set bottom="1"
146
+ attrs.append('bottom="1"')
147
+ if cf.percent is not None:
148
+ attrs.append('percent="1"' if cf.percent else 'percent="0"')
149
+ if cf.rank is not None:
150
+ attrs.append(f'rank="{cf.rank}"')
151
+
152
+ # Add aboveAverage attributes
153
+ if rule_type == 'aboveAverage':
154
+ # Handle both 'aboveAverage' and 'belowAverage' user types
155
+ # Both map to ECMA-376 'aboveAverage' type, distinguished by aboveAverage attribute
156
+ if cf.above is not None:
157
+ attrs.append('aboveAverage="1"' if cf.above else 'aboveAverage="0"')
158
+ elif cf._type == 'belowAverage':
159
+ # For belowAverage type, explicitly set aboveAverage="0"
160
+ attrs.append('aboveAverage="0"')
161
+ if cf.std_dev is not None:
162
+ attrs.append(f'stdDev="{cf.std_dev}"')
163
+
164
+ xml = f' <cfRule {" ".join(attrs)}'
165
+
166
+ # Check if we need child elements
167
+ has_children = False
168
+ children_xml = ''
169
+
170
+ # Add formula elements based on rule type
171
+ if rule_type == 'expression':
172
+ # Expression (formula) rules use the _formula property
173
+ formula_value = cf._formula if cf._formula is not None else cf._formula1
174
+ if formula_value is not None:
175
+ formula_text = str(formula_value)
176
+ # Remove leading '=' if present
177
+ if formula_text.startswith('='):
178
+ formula_text = formula_text[1:]
179
+ escaped_formula = self._escape_xml(formula_text)
180
+ children_xml += f' <formula>{escaped_formula}</formula>\n'
181
+ has_children = True
182
+ elif rule_type == 'cellIs':
183
+ # Cell value rules use _formula1 and _formula2
184
+ if cf._formula1 is not None:
185
+ formula_text = str(cf._formula1)
186
+ if formula_text.startswith('='):
187
+ formula_text = formula_text[1:]
188
+ escaped_formula = self._escape_xml(formula_text)
189
+ children_xml += f' <formula>{escaped_formula}</formula>\n'
190
+ has_children = True
191
+ if cf._formula2 is not None:
192
+ formula_text = str(cf._formula2)
193
+ if formula_text.startswith('='):
194
+ formula_text = formula_text[1:]
195
+ escaped_formula = self._escape_xml(formula_text)
196
+ children_xml += f' <formula>{escaped_formula}</formula>\n'
197
+ has_children = True
198
+ elif rule_type in ('containsText', 'notContainsText', 'beginsWith', 'endsWith'):
199
+ # Text rules need a proper Excel formula
200
+ # Get the first cell of the range to build the formula
201
+ text_value = cf._formula1 if cf._formula1 is not None else ''
202
+ first_cell = self._get_first_cell_from_range(cf.range)
203
+ formula_text = self._build_text_rule_formula(rule_type, text_value, first_cell)
204
+ if formula_text:
205
+ escaped_formula = self._escape_xml(formula_text)
206
+ children_xml += f' <formula>{escaped_formula}</formula>\n'
207
+ has_children = True
208
+
209
+ # Add colorScale element
210
+ if rule_type == 'colorScale':
211
+ children_xml += self._format_color_scale_xml(cf)
212
+ has_children = True
213
+
214
+ # Add dataBar element
215
+ if rule_type == 'dataBar':
216
+ children_xml += self._format_data_bar_xml(cf)
217
+ has_children = True
218
+
219
+ # Add iconSet element
220
+ if rule_type == 'iconSet':
221
+ children_xml += self._format_icon_set_xml(cf)
222
+ has_children = True
223
+
224
+ if has_children:
225
+ xml += '>\n'
226
+ xml += children_xml
227
+ xml += ' </cfRule>\n'
228
+ else:
229
+ xml += '/>\n'
230
+
231
+ return xml
232
+
233
+ def _format_color_scale_xml(self, cf):
234
+ """Formats a colorScale element for conditional formatting."""
235
+ xml = ' <colorScale>\n'
236
+
237
+ # Determine if 2-color or 3-color scale
238
+ is_3_color = cf.mid_color is not None or cf.color_scale_type == '3-color'
239
+
240
+ # Add cfvo elements (conditional format value objects)
241
+ xml += ' <cfvo type="min"/>\n'
242
+ if is_3_color:
243
+ xml += ' <cfvo type="percentile" val="50"/>\n'
244
+ xml += ' <cfvo type="max"/>\n'
245
+
246
+ # Add color elements
247
+ min_color = cf.min_color or 'FFF8696B' # Default red
248
+ max_color = cf.max_color or 'FF63BE7B' # Default green
249
+ mid_color = cf.mid_color or 'FFFFEB84' # Default yellow
250
+
251
+ xml += f' <color rgb="{min_color}"/>\n'
252
+ if is_3_color:
253
+ xml += f' <color rgb="{mid_color}"/>\n'
254
+ xml += f' <color rgb="{max_color}"/>\n'
255
+
256
+ xml += ' </colorScale>\n'
257
+ return xml
258
+
259
+ def _format_data_bar_xml(self, cf):
260
+ """Formats a dataBar element for conditional formatting."""
261
+ xml = ' <dataBar>\n'
262
+
263
+ # Add cfvo elements
264
+ xml += ' <cfvo type="min"/>\n'
265
+ xml += ' <cfvo type="max"/>\n'
266
+
267
+ # Add color element
268
+ bar_color = cf.bar_color or 'FF638EC6' # Default blue
269
+ xml += f' <color rgb="{bar_color}"/>\n'
270
+
271
+ xml += ' </dataBar>\n'
272
+ return xml
273
+
274
+ def _format_icon_set_xml(self, cf):
275
+ """Formats an iconSet element for conditional formatting."""
276
+ icon_set_type = cf.icon_set_type or '3TrafficLights1'
277
+
278
+ attrs = [f'iconSet="{icon_set_type}"']
279
+ if cf.reverse_icons:
280
+ attrs.append('reverse="1"')
281
+ if cf.show_icon_only:
282
+ attrs.append('showValue="0"')
283
+
284
+ xml = f' <iconSet {" ".join(attrs)}>\n'
285
+
286
+ # Add cfvo elements based on icon set type
287
+ # Determine number of icons
288
+ num_icons = 3
289
+ if icon_set_type.startswith('4'):
290
+ num_icons = 4
291
+ elif icon_set_type.startswith('5'):
292
+ num_icons = 5
293
+
294
+ # Add cfvo elements with percent thresholds
295
+ for i in range(num_icons):
296
+ percent_val = int(100 * i / num_icons)
297
+ xml += f' <cfvo type="percent" val="{percent_val}"/>\n'
298
+
299
+ xml += ' </iconSet>\n'
300
+ return xml
301
+
302
+ def _get_first_cell_from_range(self, cell_range):
303
+ """
304
+ Gets the first cell reference from a range.
305
+
306
+ Args:
307
+ cell_range (str): Cell range (e.g., "A1:A10" or "A1")
308
+
309
+ Returns:
310
+ str: First cell reference (e.g., "A1")
311
+ """
312
+ if not cell_range:
313
+ return 'A1'
314
+ if ':' in cell_range:
315
+ return cell_range.split(':')[0]
316
+ return cell_range
317
+
318
+ def _build_text_rule_formula(self, rule_type, text_value, first_cell):
319
+ """
320
+ Builds the Excel formula for text-based conditional formatting rules.
321
+
322
+ According to ECMA-376, text rules require specific Excel formulas:
323
+ - containsText: NOT(ISERROR(SEARCH("text",A1)))
324
+ - notContainsText: ISERROR(SEARCH("text",A1))
325
+ - beginsWith: LEFT(A1,LEN("text"))="text"
326
+ - endsWith: RIGHT(A1,LEN("text"))="text"
327
+
328
+ Args:
329
+ rule_type (str): The rule type
330
+ text_value (str): The text to search for
331
+ first_cell (str): The first cell of the range
332
+
333
+ Returns:
334
+ str: The Excel formula
335
+ """
336
+ if not text_value:
337
+ return ''
338
+
339
+ # Escape double quotes in text value for Excel formula
340
+ escaped_text = str(text_value).replace('"', '""')
341
+
342
+ if rule_type == 'containsText':
343
+ return f'NOT(ISERROR(SEARCH("{escaped_text}",{first_cell})))'
344
+ elif rule_type == 'notContainsText':
345
+ return f'ISERROR(SEARCH("{escaped_text}",{first_cell}))'
346
+ elif rule_type == 'beginsWith':
347
+ return f'LEFT({first_cell},LEN("{escaped_text}"))="{escaped_text}"'
348
+ elif rule_type == 'endsWith':
349
+ return f'RIGHT({first_cell},LEN("{escaped_text}"))="{escaped_text}"'
350
+
351
+ return ''
@@ -0,0 +1,239 @@
1
+ """
2
+ Aspose.Cells for Python - Data Validation XML Loader
3
+
4
+ This module handles deserialization of DataValidation objects from ECMA-376 SpreadsheetML XML.
5
+
6
+ References:
7
+ - ECMA-376 Part 4, Section 3.3.1.30 (dataValidation)
8
+ - ECMA-376 Part 4, Section 3.3.1.31 (dataValidations)
9
+ """
10
+
11
+ import xml.etree.ElementTree as ET
12
+ from .data_validation import (
13
+ DataValidation, DataValidationCollection,
14
+ DataValidationType, DataValidationOperator,
15
+ DataValidationAlertStyle, DataValidationImeMode
16
+ )
17
+
18
+
19
+ # Mapping from XML attribute values to enum values
20
+ XML_TO_TYPE = {
21
+ 'none': DataValidationType.NONE,
22
+ 'whole': DataValidationType.WHOLE_NUMBER,
23
+ 'decimal': DataValidationType.DECIMAL,
24
+ 'list': DataValidationType.LIST,
25
+ 'date': DataValidationType.DATE,
26
+ 'time': DataValidationType.TIME,
27
+ 'textLength': DataValidationType.TEXT_LENGTH,
28
+ 'custom': DataValidationType.CUSTOM,
29
+ }
30
+
31
+ XML_TO_OPERATOR = {
32
+ 'between': DataValidationOperator.BETWEEN,
33
+ 'notBetween': DataValidationOperator.NOT_BETWEEN,
34
+ 'equal': DataValidationOperator.EQUAL,
35
+ 'notEqual': DataValidationOperator.NOT_EQUAL,
36
+ 'greaterThan': DataValidationOperator.GREATER_THAN,
37
+ 'lessThan': DataValidationOperator.LESS_THAN,
38
+ 'greaterThanOrEqual': DataValidationOperator.GREATER_THAN_OR_EQUAL,
39
+ 'lessThanOrEqual': DataValidationOperator.LESS_THAN_OR_EQUAL,
40
+ }
41
+
42
+ XML_TO_ALERT_STYLE = {
43
+ 'stop': DataValidationAlertStyle.STOP,
44
+ 'warning': DataValidationAlertStyle.WARNING,
45
+ 'information': DataValidationAlertStyle.INFORMATION,
46
+ }
47
+
48
+ XML_TO_IME_MODE = {
49
+ 'noControl': DataValidationImeMode.NO_CONTROL,
50
+ 'off': DataValidationImeMode.OFF,
51
+ 'on': DataValidationImeMode.ON,
52
+ 'disabled': DataValidationImeMode.DISABLED,
53
+ 'hiragana': DataValidationImeMode.HIRAGANA,
54
+ 'fullKatakana': DataValidationImeMode.FULL_KATAKANA,
55
+ 'halfKatakana': DataValidationImeMode.HALF_KATAKANA,
56
+ 'fullAlpha': DataValidationImeMode.FULL_ALPHA,
57
+ 'halfAlpha': DataValidationImeMode.HALF_ALPHA,
58
+ 'fullHangul': DataValidationImeMode.FULL_HANGUL,
59
+ 'halfHangul': DataValidationImeMode.HALF_HANGUL,
60
+ }
61
+
62
+
63
+ class DataValidationXmlLoader:
64
+ """
65
+ Loads DataValidation objects from ECMA-376 SpreadsheetML XML format.
66
+ """
67
+
68
+ def __init__(self, namespace='http://schemas.openxmlformats.org/spreadsheetml/2006/main'):
69
+ """
70
+ Initializes the DataValidationXmlLoader.
71
+
72
+ Args:
73
+ namespace (str): The SpreadsheetML namespace URI.
74
+ """
75
+ self.namespace = namespace
76
+ self.ns_prefix = '{' + namespace + '}'
77
+
78
+ def load_data_validations(self, parent_element):
79
+ """
80
+ Loads a DataValidationCollection from a parent XML element.
81
+
82
+ Args:
83
+ parent_element (Element): The parent XML element containing dataValidations.
84
+
85
+ Returns:
86
+ DataValidationCollection: The loaded validations.
87
+ """
88
+ validations = DataValidationCollection()
89
+
90
+ # Find dataValidations element
91
+ dv_collection = parent_element.find(f'{self.ns_prefix}dataValidations')
92
+
93
+ if dv_collection is None:
94
+ # Try without namespace
95
+ dv_collection = parent_element.find('dataValidations')
96
+
97
+ if dv_collection is None:
98
+ return validations
99
+
100
+ # Load collection-level attributes
101
+ disable_prompts = dv_collection.get('disablePrompts', '0')
102
+ validations.disable_prompts = disable_prompts == '1' or disable_prompts.lower() == 'true'
103
+
104
+ x_window = dv_collection.get('xWindow')
105
+ if x_window is not None:
106
+ validations.x_window = int(x_window)
107
+
108
+ y_window = dv_collection.get('yWindow')
109
+ if y_window is not None:
110
+ validations.y_window = int(y_window)
111
+
112
+ # Load each dataValidation element
113
+ for dv_elem in dv_collection.findall(f'{self.ns_prefix}dataValidation'):
114
+ validation = self._load_data_validation(dv_elem)
115
+ validations.add_validation(validation)
116
+
117
+ # Try without namespace if none found
118
+ if validations.count == 0:
119
+ for dv_elem in dv_collection.findall('dataValidation'):
120
+ validation = self._load_data_validation(dv_elem)
121
+ validations.add_validation(validation)
122
+
123
+ return validations
124
+
125
+ def _load_data_validation(self, dv_elem):
126
+ """
127
+ Loads a single DataValidation from an XML element.
128
+
129
+ Args:
130
+ dv_elem (Element): The dataValidation XML element.
131
+
132
+ Returns:
133
+ DataValidation: The loaded validation.
134
+ """
135
+ # Get sqref (required)
136
+ sqref = dv_elem.get('sqref', '')
137
+ validation = DataValidation(sqref)
138
+
139
+ # Load type
140
+ type_str = dv_elem.get('type', 'none')
141
+ validation.type = XML_TO_TYPE.get(type_str, DataValidationType.NONE)
142
+
143
+ # Load operator
144
+ operator_str = dv_elem.get('operator', 'between')
145
+ validation.operator = XML_TO_OPERATOR.get(operator_str, DataValidationOperator.BETWEEN)
146
+
147
+ # Load error style
148
+ error_style_str = dv_elem.get('errorStyle', 'stop')
149
+ validation.alert_style = XML_TO_ALERT_STYLE.get(error_style_str, DataValidationAlertStyle.STOP)
150
+
151
+ # Load IME mode
152
+ ime_mode_str = dv_elem.get('imeMode', 'noControl')
153
+ validation.ime_mode = XML_TO_IME_MODE.get(ime_mode_str, DataValidationImeMode.NO_CONTROL)
154
+
155
+ # Load boolean attributes
156
+ allow_blank = dv_elem.get('allowBlank', '0')
157
+ validation.allow_blank = allow_blank == '1' or allow_blank.lower() == 'true'
158
+
159
+ # Note: In ECMA-376, showDropDown="1" means HIDE the dropdown (counterintuitive)
160
+ show_dropdown = dv_elem.get('showDropDown', '0')
161
+ validation.show_dropdown = not (show_dropdown == '1' or show_dropdown.lower() == 'true')
162
+
163
+ show_input = dv_elem.get('showInputMessage', '0')
164
+ validation.show_input_message = show_input == '1' or show_input.lower() == 'true'
165
+
166
+ show_error = dv_elem.get('showErrorMessage', '0')
167
+ validation.show_error_message = show_error == '1' or show_error.lower() == 'true'
168
+
169
+ # Load string attributes
170
+ validation.error_title = dv_elem.get('errorTitle')
171
+ validation.error_message = dv_elem.get('error')
172
+ validation.input_title = dv_elem.get('promptTitle')
173
+ validation.input_message = dv_elem.get('prompt')
174
+
175
+ # Load formula elements
176
+ formula1_elem = dv_elem.find(f'{self.ns_prefix}formula1')
177
+ if formula1_elem is None:
178
+ formula1_elem = dv_elem.find('formula1')
179
+ if formula1_elem is not None and formula1_elem.text:
180
+ validation.formula1 = formula1_elem.text
181
+
182
+ formula2_elem = dv_elem.find(f'{self.ns_prefix}formula2')
183
+ if formula2_elem is None:
184
+ formula2_elem = dv_elem.find('formula2')
185
+ if formula2_elem is not None and formula2_elem.text:
186
+ validation.formula2 = formula2_elem.text
187
+
188
+ return validation
189
+
190
+ def load_from_xml_string(self, xml_string):
191
+ """
192
+ Loads validations from an XML string.
193
+
194
+ Args:
195
+ xml_string (str): The XML string containing dataValidations.
196
+
197
+ Returns:
198
+ DataValidationCollection: The loaded validations.
199
+ """
200
+ # Parse the XML
201
+ root = ET.fromstring(xml_string)
202
+
203
+ # Check if root is dataValidations
204
+ if root.tag.endswith('dataValidations'):
205
+ # Create a temporary parent
206
+ temp_parent = ET.Element('temp')
207
+ temp_parent.append(root)
208
+ return self.load_data_validations(temp_parent)
209
+
210
+ return self.load_data_validations(root)
211
+
212
+ def load_from_file(self, file_path):
213
+ """
214
+ Loads validations from an XML file.
215
+
216
+ Args:
217
+ file_path (str): Path to the XML file.
218
+
219
+ Returns:
220
+ DataValidationCollection: The loaded validations.
221
+ """
222
+ tree = ET.parse(file_path)
223
+ root = tree.getroot()
224
+ return self.load_data_validations(root)
225
+
226
+
227
+ def load_data_validations_from_worksheet_xml(worksheet_element, namespace):
228
+ """
229
+ Convenience function to load data validations from a worksheet XML element.
230
+
231
+ Args:
232
+ worksheet_element (Element): The worksheet XML element.
233
+ namespace (str): The SpreadsheetML namespace.
234
+
235
+ Returns:
236
+ DataValidationCollection: The loaded validations.
237
+ """
238
+ loader = DataValidationXmlLoader(namespace)
239
+ return loader.load_data_validations(worksheet_element)