modelwright 0.1.0a1__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.
@@ -0,0 +1,662 @@
1
+ """Workbook extraction records.
2
+
3
+ These records describe extracted workbook facts; they do not read workbook
4
+ files themselves.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from collections.abc import Callable
10
+ from datetime import date, datetime, time
11
+ from dataclasses import dataclass, field
12
+ from pathlib import Path
13
+ from typing import Any, Literal
14
+
15
+ from openpyxl import load_workbook
16
+ from openpyxl.formula.tokenizer import Tokenizer
17
+ from openpyxl.utils.cell import get_column_letter, range_boundaries
18
+
19
+
20
+ JsonValue = str | int | float | bool | None | list[Any] | dict[str, Any]
21
+ CellKind = Literal["value", "formula", "blank", "error"]
22
+ DiagnosticSeverity = Literal["info", "warning", "error"]
23
+ NamedRangeStatus = Literal["resolved", "partially_resolved", "unresolved"]
24
+
25
+ VOLATILE_FUNCTIONS = frozenset({"NOW", "TODAY", "RAND", "RANDBETWEEN", "OFFSET", "INDIRECT"})
26
+
27
+
28
+ @dataclass(frozen=True)
29
+ class ExtractionDiagnostic:
30
+ """Extraction or interpretation concern tied to workbook provenance."""
31
+
32
+ code: str
33
+ message: str
34
+ severity: DiagnosticSeverity = "warning"
35
+ location: str | None = None
36
+ raw_value: JsonValue = None
37
+
38
+ @classmethod
39
+ def from_dict(cls, data: dict[str, Any]) -> "ExtractionDiagnostic":
40
+ return cls(
41
+ code=data["code"],
42
+ message=data["message"],
43
+ severity=data.get("severity", "warning"),
44
+ location=data.get("location"),
45
+ raw_value=data.get("raw_value"),
46
+ )
47
+
48
+ def to_dict(self) -> dict[str, JsonValue]:
49
+ return {
50
+ "code": self.code,
51
+ "message": self.message,
52
+ "severity": self.severity,
53
+ "location": self.location,
54
+ "raw_value": self.raw_value,
55
+ }
56
+
57
+
58
+ @dataclass(frozen=True)
59
+ class FormulaRecord:
60
+ """Formula text and parsed extraction facts for one formula cell."""
61
+
62
+ raw_formula: str
63
+ tokens: tuple[str, ...] = field(default_factory=tuple)
64
+ raw_references: tuple[str, ...] = field(default_factory=tuple)
65
+ normalized_references: tuple[str, ...] = field(default_factory=tuple)
66
+ functions: tuple[str, ...] = field(default_factory=tuple)
67
+ diagnostics: tuple[ExtractionDiagnostic, ...] = field(default_factory=tuple)
68
+
69
+ @classmethod
70
+ def from_dict(cls, data: dict[str, Any]) -> "FormulaRecord":
71
+ return cls(
72
+ raw_formula=data["raw_formula"],
73
+ tokens=tuple(data.get("tokens", [])),
74
+ raw_references=tuple(data.get("raw_references", [])),
75
+ normalized_references=tuple(data.get("normalized_references", [])),
76
+ functions=tuple(data.get("functions", [])),
77
+ diagnostics=tuple(ExtractionDiagnostic.from_dict(item) for item in data.get("diagnostics", [])),
78
+ )
79
+
80
+ def to_dict(self) -> dict[str, JsonValue]:
81
+ return {
82
+ "raw_formula": self.raw_formula,
83
+ "tokens": list(self.tokens),
84
+ "raw_references": list(self.raw_references),
85
+ "normalized_references": list(self.normalized_references),
86
+ "functions": list(self.functions),
87
+ "diagnostics": [diagnostic.to_dict() for diagnostic in self.diagnostics],
88
+ }
89
+
90
+
91
+ @dataclass(frozen=True)
92
+ class CellRecord:
93
+ """Extracted cell facts for one canonical workbook cell reference."""
94
+
95
+ cell_ref: str
96
+ kind: CellKind
97
+ raw_value: JsonValue
98
+ data_type: str | None = None
99
+ cached_value: JsonValue = None
100
+ formula: FormulaRecord | None = None
101
+
102
+ @classmethod
103
+ def from_dict(cls, data: dict[str, Any]) -> "CellRecord":
104
+ formula_data = data.get("formula")
105
+ return cls(
106
+ cell_ref=data["cell_ref"],
107
+ kind=data["kind"],
108
+ raw_value=data.get("raw_value"),
109
+ data_type=data.get("data_type"),
110
+ cached_value=data.get("cached_value"),
111
+ formula=FormulaRecord.from_dict(formula_data) if formula_data is not None else None,
112
+ )
113
+
114
+ def to_dict(self) -> dict[str, JsonValue]:
115
+ return {
116
+ "cell_ref": self.cell_ref,
117
+ "kind": self.kind,
118
+ "raw_value": self.raw_value,
119
+ "data_type": self.data_type,
120
+ "cached_value": self.cached_value,
121
+ "formula": self.formula.to_dict() if self.formula is not None else None,
122
+ }
123
+
124
+
125
+ @dataclass(frozen=True)
126
+ class NamedRangeRecord:
127
+ """Workbook or worksheet scoped defined name."""
128
+
129
+ name: str
130
+ scope: str
131
+ raw_definition: str
132
+ destinations: tuple[str, ...] = field(default_factory=tuple)
133
+ status: NamedRangeStatus = "unresolved"
134
+ diagnostics: tuple[ExtractionDiagnostic, ...] = field(default_factory=tuple)
135
+
136
+ @classmethod
137
+ def from_dict(cls, data: dict[str, Any]) -> "NamedRangeRecord":
138
+ return cls(
139
+ name=data["name"],
140
+ scope=data["scope"],
141
+ raw_definition=data["raw_definition"],
142
+ destinations=tuple(data.get("destinations", [])),
143
+ status=data.get("status", "unresolved"),
144
+ diagnostics=tuple(ExtractionDiagnostic.from_dict(item) for item in data.get("diagnostics", [])),
145
+ )
146
+
147
+ def to_dict(self) -> dict[str, JsonValue]:
148
+ return {
149
+ "name": self.name,
150
+ "scope": self.scope,
151
+ "raw_definition": self.raw_definition,
152
+ "destinations": list(self.destinations),
153
+ "status": self.status,
154
+ "diagnostics": [diagnostic.to_dict() for diagnostic in self.diagnostics],
155
+ }
156
+
157
+
158
+ @dataclass(frozen=True)
159
+ class TableRecord:
160
+ """Worksheet table metadata needed to resolve structured references."""
161
+
162
+ name: str
163
+ sheet: str
164
+ ref: str
165
+ columns: tuple[str, ...] = field(default_factory=tuple)
166
+
167
+ @classmethod
168
+ def from_dict(cls, data: dict[str, Any]) -> "TableRecord":
169
+ return cls(
170
+ name=data["name"],
171
+ sheet=data["sheet"],
172
+ ref=data["ref"],
173
+ columns=tuple(data.get("columns", [])),
174
+ )
175
+
176
+ def to_dict(self) -> dict[str, JsonValue]:
177
+ return {
178
+ "name": self.name,
179
+ "sheet": self.sheet,
180
+ "ref": self.ref,
181
+ "columns": list(self.columns),
182
+ }
183
+
184
+
185
+ @dataclass(frozen=True)
186
+ class SheetRecord:
187
+ """Worksheet identity and ordering facts."""
188
+
189
+ sheet_id: str
190
+ title: str
191
+ state: str
192
+ index: int
193
+
194
+ @classmethod
195
+ def from_dict(cls, data: dict[str, Any]) -> "SheetRecord":
196
+ return cls(
197
+ sheet_id=data["sheet_id"],
198
+ title=data["title"],
199
+ state=data["state"],
200
+ index=data["index"],
201
+ )
202
+
203
+ def to_dict(self) -> dict[str, JsonValue]:
204
+ return {
205
+ "sheet_id": self.sheet_id,
206
+ "title": self.title,
207
+ "state": self.state,
208
+ "index": self.index,
209
+ }
210
+
211
+
212
+ @dataclass(frozen=True)
213
+ class WorkbookRecord:
214
+ """Extracted facts for one source workbook."""
215
+
216
+ workbook_id: str
217
+ source_path: str
218
+ sheets: tuple[SheetRecord, ...] = field(default_factory=tuple)
219
+ cells: tuple[CellRecord, ...] = field(default_factory=tuple)
220
+ named_ranges: tuple[NamedRangeRecord, ...] = field(default_factory=tuple)
221
+ tables: tuple[TableRecord, ...] = field(default_factory=tuple)
222
+ diagnostics: tuple[ExtractionDiagnostic, ...] = field(default_factory=tuple)
223
+
224
+ @classmethod
225
+ def from_dict(cls, data: dict[str, Any]) -> "WorkbookRecord":
226
+ return cls(
227
+ workbook_id=data["workbook_id"],
228
+ source_path=data["source_path"],
229
+ sheets=tuple(SheetRecord.from_dict(item) for item in data.get("sheets", [])),
230
+ cells=tuple(CellRecord.from_dict(item) for item in data.get("cells", [])),
231
+ named_ranges=tuple(NamedRangeRecord.from_dict(item) for item in data.get("named_ranges", [])),
232
+ tables=tuple(TableRecord.from_dict(item) for item in data.get("tables", [])),
233
+ diagnostics=tuple(ExtractionDiagnostic.from_dict(item) for item in data.get("diagnostics", [])),
234
+ )
235
+
236
+ def to_dict(self) -> dict[str, JsonValue]:
237
+ return {
238
+ "workbook_id": self.workbook_id,
239
+ "source_path": self.source_path,
240
+ "sheets": [sheet.to_dict() for sheet in self.sheets],
241
+ "cells": [cell.to_dict() for cell in self.cells],
242
+ "named_ranges": [named_range.to_dict() for named_range in self.named_ranges],
243
+ "tables": [table.to_dict() for table in self.tables],
244
+ "diagnostics": [diagnostic.to_dict() for diagnostic in self.diagnostics],
245
+ }
246
+
247
+
248
+ def extract_workbook(path: str | Path, progress: Callable[[str], None] | None = None) -> WorkbookRecord:
249
+ """Extract workbook facts with openpyxl into Modelwright records."""
250
+
251
+ workbook_path = Path(path)
252
+ _progress(progress, "load_workbook formulas start")
253
+ workbook = load_workbook(workbook_path, data_only=False)
254
+ _progress(progress, "load_workbook formulas done")
255
+ _progress(progress, "load_workbook cached_values start")
256
+ cached_workbook = load_workbook(workbook_path, data_only=True)
257
+ _progress(progress, "load_workbook cached_values done")
258
+
259
+ _progress(progress, "workbook diagnostics start")
260
+ diagnostics = _workbook_diagnostics(workbook)
261
+ _progress(progress, "workbook diagnostics done")
262
+ sheets = tuple(
263
+ SheetRecord(
264
+ sheet_id=worksheet.title,
265
+ title=worksheet.title,
266
+ state=worksheet.sheet_state,
267
+ index=index,
268
+ )
269
+ for index, worksheet in enumerate(workbook.worksheets)
270
+ )
271
+ _progress(progress, f"sheets extracted count={len(sheets)}")
272
+ _progress(progress, "tables start")
273
+ tables = tuple(table for worksheet in workbook.worksheets for table in _extract_tables(worksheet))
274
+ _progress(progress, f"tables done count={len(tables)}")
275
+ _progress(progress, "named ranges start")
276
+ named_ranges = tuple(
277
+ _extract_named_range(name, defined_name, tables=tables) for name, defined_name in workbook.defined_names.items()
278
+ )
279
+ _progress(progress, f"named ranges done count={len(named_ranges)}")
280
+
281
+ cell_records: list[CellRecord] = []
282
+ for index, worksheet in enumerate(workbook.worksheets, start=1):
283
+ populated_cells = _populated_cells(worksheet)
284
+ _progress(
285
+ progress,
286
+ f"sheet cells start index={index}/{len(workbook.worksheets)} populated={len(populated_cells)}",
287
+ )
288
+ sheet_cells = _extract_sheet_cells(
289
+ worksheet,
290
+ cached_workbook[worksheet.title],
291
+ populated_cells=populated_cells,
292
+ )
293
+ cell_records.extend(sheet_cells)
294
+ _progress(
295
+ progress,
296
+ f"sheet cells done index={index}/{len(workbook.worksheets)} extracted={len(sheet_cells)} total={len(cell_records)}",
297
+ )
298
+ cells = tuple(cell_records)
299
+ _progress(progress, f"workbook extraction done cells={len(cells)}")
300
+
301
+ return WorkbookRecord(
302
+ workbook_id=workbook_path.name,
303
+ source_path=str(workbook_path),
304
+ sheets=sheets,
305
+ cells=cells,
306
+ named_ranges=named_ranges,
307
+ tables=tables,
308
+ diagnostics=diagnostics,
309
+ )
310
+
311
+
312
+ def _workbook_diagnostics(workbook: Any) -> tuple[ExtractionDiagnostic, ...]:
313
+ diagnostics: list[ExtractionDiagnostic] = []
314
+ if getattr(workbook, "vba_archive", None) is not None:
315
+ diagnostics.append(
316
+ ExtractionDiagnostic(
317
+ code="unsupported_macros",
318
+ message="workbook contains macros, which are not extracted",
319
+ severity="warning",
320
+ location="workbook",
321
+ )
322
+ )
323
+ if getattr(workbook, "_external_links", None):
324
+ diagnostics.append(
325
+ ExtractionDiagnostic(
326
+ code="unsupported_external_link",
327
+ message="workbook contains external links, which are not extracted",
328
+ severity="warning",
329
+ location="workbook",
330
+ )
331
+ )
332
+ return tuple(diagnostics)
333
+
334
+
335
+ def _extract_named_range(name: str, defined_name: Any, *, tables: tuple[TableRecord, ...] = ()) -> NamedRangeRecord:
336
+ diagnostics: tuple[ExtractionDiagnostic, ...] = ()
337
+ try:
338
+ destinations = tuple(_cell_ref(sheet_name, coordinate) for sheet_name, coordinate in defined_name.destinations)
339
+ except Exception:
340
+ destinations = ()
341
+ if not destinations:
342
+ structured_destination = _structured_defined_name_destination(str(defined_name.attr_text), tables)
343
+ if structured_destination is not None:
344
+ destinations = (structured_destination,)
345
+ status: NamedRangeStatus = "resolved" if destinations else "unresolved"
346
+ if not destinations:
347
+ code = "named_range_source_error" if str(defined_name.attr_text).upper() == "#REF!" else "unresolved_named_range"
348
+ message = (
349
+ "named range definition contains a source workbook error"
350
+ if code == "named_range_source_error"
351
+ else "named range destinations could not be resolved"
352
+ )
353
+ diagnostics = (
354
+ ExtractionDiagnostic(
355
+ code=code,
356
+ message=message,
357
+ severity="warning",
358
+ location=name,
359
+ raw_value=defined_name.attr_text,
360
+ ),
361
+ )
362
+
363
+ scope = "workbook"
364
+ local_sheet_id = getattr(defined_name, "localSheetId", None)
365
+ if local_sheet_id is not None:
366
+ scope = f"sheet:{local_sheet_id}"
367
+
368
+ return NamedRangeRecord(
369
+ name=name,
370
+ scope=scope,
371
+ raw_definition=defined_name.attr_text,
372
+ destinations=destinations,
373
+ status=status,
374
+ diagnostics=diagnostics,
375
+ )
376
+
377
+
378
+ def _extract_tables(worksheet: Any) -> tuple[TableRecord, ...]:
379
+ records: list[TableRecord] = []
380
+ for table in worksheet.tables.values():
381
+ try:
382
+ min_col, header_row, max_col, _max_row = range_boundaries(table.ref)
383
+ except ValueError:
384
+ continue
385
+
386
+ columns = tuple(
387
+ str(worksheet[f"{get_column_letter(column)}{header_row}"].value)
388
+ for column in range(min_col, max_col + 1)
389
+ )
390
+ records.append(
391
+ TableRecord(
392
+ name=table.displayName,
393
+ sheet=worksheet.title,
394
+ ref=table.ref.replace("$", ""),
395
+ columns=columns,
396
+ )
397
+ )
398
+ return tuple(records)
399
+
400
+
401
+ def _structured_defined_name_destination(definition: str, tables: tuple[TableRecord, ...]) -> str | None:
402
+ parsed = _parse_structured_defined_name(definition)
403
+ if parsed is None:
404
+ return None
405
+
406
+ table = next((candidate for candidate in tables if candidate.name == parsed.table_name), None)
407
+ if table is None:
408
+ return None
409
+
410
+ try:
411
+ min_col, min_row, max_col, max_row = range_boundaries(table.ref)
412
+ except ValueError:
413
+ return None
414
+
415
+ try:
416
+ column_offset = table.columns.index(parsed.column)
417
+ except ValueError:
418
+ return None
419
+
420
+ column_name = get_column_letter(min_col + column_offset)
421
+ start_row = min_row if parsed.include_headers else min_row + 1
422
+ end_row = max_row
423
+ if start_row > end_row:
424
+ return None
425
+ return f"{table.sheet}!{column_name}{start_row}:{column_name}{end_row}"
426
+
427
+
428
+ @dataclass(frozen=True)
429
+ class _StructuredDefinedName:
430
+ table_name: str
431
+ column: str
432
+ include_headers: bool = False
433
+
434
+
435
+ def _parse_structured_defined_name(definition: str) -> _StructuredDefinedName | None:
436
+ if not _is_structured_reference(definition):
437
+ return None
438
+
439
+ table_name = definition.split("[", 1)[0]
440
+ if not table_name:
441
+ return None
442
+
443
+ bracketed_parts = _bracketed_parts(definition)
444
+ column = next(
445
+ (
446
+ _clean_structured_selector(part)
447
+ for part in reversed(bracketed_parts)
448
+ if not part.startswith("#")
449
+ ),
450
+ None,
451
+ )
452
+ if column is None:
453
+ return None
454
+ return _StructuredDefinedName(
455
+ table_name=table_name,
456
+ column=column,
457
+ include_headers=any(part in {"#All", "#Headers"} for part in bracketed_parts),
458
+ )
459
+
460
+
461
+ def _extract_sheet_cells(
462
+ worksheet: Any,
463
+ cached_worksheet: Any,
464
+ *,
465
+ populated_cells: tuple[Any, ...] | None = None,
466
+ ) -> tuple[CellRecord, ...]:
467
+ records: list[CellRecord] = []
468
+ for cell in populated_cells if populated_cells is not None else _populated_cells(worksheet):
469
+ if cell.value is None:
470
+ continue
471
+
472
+ cell_ref = _cell_ref(worksheet.title, cell.coordinate)
473
+ cached_value = cached_worksheet[cell.coordinate].value
474
+ if cell.data_type == "f":
475
+ formula = _extract_formula(cell_ref, str(cell.value), cached_value)
476
+ records.append(
477
+ CellRecord(
478
+ cell_ref=cell_ref,
479
+ kind="formula",
480
+ raw_value=_json_value(cell.value),
481
+ data_type=cell.data_type,
482
+ cached_value=_json_value(cached_value),
483
+ formula=formula,
484
+ )
485
+ )
486
+ continue
487
+
488
+ records.append(
489
+ CellRecord(
490
+ cell_ref=cell_ref,
491
+ kind="value",
492
+ raw_value=_json_value(cell.value),
493
+ data_type=cell.data_type,
494
+ cached_value=_json_value(cached_value),
495
+ formula=None,
496
+ )
497
+ )
498
+ return tuple(records)
499
+
500
+
501
+ def _populated_cells(worksheet: Any) -> tuple[Any, ...]:
502
+ cells = getattr(worksheet, "_cells", None)
503
+ if isinstance(cells, dict):
504
+ return tuple(cell for _, cell in sorted(cells.items()))
505
+
506
+ return tuple(cell for row in worksheet.iter_rows() for cell in row)
507
+
508
+
509
+ def _progress(progress: Callable[[str], None] | None, message: str) -> None:
510
+ if progress is not None:
511
+ progress(message)
512
+
513
+ def _extract_formula(cell_ref: str, raw_formula: str, cached_value: JsonValue) -> FormulaRecord:
514
+ try:
515
+ tokenizer = Tokenizer(raw_formula)
516
+ except Exception as error:
517
+ return FormulaRecord(
518
+ raw_formula=raw_formula,
519
+ diagnostics=(
520
+ ExtractionDiagnostic(
521
+ code="formula_tokenization_failed",
522
+ message=f"formula could not be tokenized: {error}",
523
+ severity="warning",
524
+ location=cell_ref,
525
+ raw_value=raw_formula,
526
+ ),
527
+ ),
528
+ )
529
+
530
+ tokens = tuple(token.value for token in tokenizer.items)
531
+ raw_references = tuple(
532
+ token.value for token in tokenizer.items if token.type == "OPERAND" and token.subtype == "RANGE"
533
+ )
534
+ functions = tuple(
535
+ token.value[:-1].upper() for token in tokenizer.items if token.type == "FUNC" and token.subtype == "OPEN"
536
+ )
537
+ diagnostics = _formula_diagnostics(
538
+ cell_ref=cell_ref,
539
+ raw_formula=raw_formula,
540
+ cached_value=cached_value,
541
+ functions=functions,
542
+ raw_references=raw_references,
543
+ )
544
+
545
+ return FormulaRecord(
546
+ raw_formula=raw_formula,
547
+ tokens=tokens,
548
+ raw_references=raw_references,
549
+ normalized_references=(),
550
+ functions=functions,
551
+ diagnostics=diagnostics,
552
+ )
553
+
554
+
555
+ def _formula_diagnostics(
556
+ *,
557
+ cell_ref: str,
558
+ raw_formula: str,
559
+ cached_value: JsonValue,
560
+ functions: tuple[str, ...],
561
+ raw_references: tuple[str, ...],
562
+ ) -> tuple[ExtractionDiagnostic, ...]:
563
+ diagnostics: list[ExtractionDiagnostic] = []
564
+ if cached_value is None:
565
+ diagnostics.append(
566
+ ExtractionDiagnostic(
567
+ code="missing_cached_formula_value",
568
+ message="formula cell has no cached value",
569
+ severity="warning",
570
+ location=cell_ref,
571
+ raw_value=raw_formula,
572
+ )
573
+ )
574
+
575
+ for function in functions:
576
+ if function in VOLATILE_FUNCTIONS:
577
+ diagnostics.append(
578
+ ExtractionDiagnostic(
579
+ code="unsupported_volatile_function",
580
+ message=f"formula uses volatile function {function}",
581
+ severity="warning",
582
+ location=cell_ref,
583
+ raw_value=raw_formula,
584
+ )
585
+ )
586
+
587
+ for reference in raw_references:
588
+ if _is_external_reference(reference):
589
+ diagnostics.append(
590
+ ExtractionDiagnostic(
591
+ code="unsupported_external_link",
592
+ message="formula references an external workbook",
593
+ severity="warning",
594
+ location=cell_ref,
595
+ raw_value=reference,
596
+ )
597
+ )
598
+ continue
599
+
600
+ if _is_structured_reference(reference):
601
+ diagnostics.append(
602
+ ExtractionDiagnostic(
603
+ code="unsupported_structured_reference",
604
+ message="formula uses an Excel structured reference",
605
+ severity="warning",
606
+ location=cell_ref,
607
+ raw_value=reference,
608
+ )
609
+ )
610
+
611
+ return tuple(diagnostics)
612
+
613
+
614
+ def _cell_ref(sheet_name: str, coordinate: str) -> str:
615
+ return f"{sheet_name}!{coordinate.replace('$', '')}"
616
+
617
+
618
+ def _json_value(value: Any) -> JsonValue:
619
+ if isinstance(value, str | int | float | bool) or value is None:
620
+ return value
621
+ if isinstance(value, datetime | date | time):
622
+ return value.isoformat()
623
+ return str(value)
624
+
625
+
626
+ def _is_external_reference(reference: str) -> bool:
627
+ return "[" in reference and "]" in reference and ("." in reference.split("]", 1)[0] or "!" in reference)
628
+
629
+
630
+ def _is_structured_reference(reference: str) -> bool:
631
+ return "[" in reference and "]" in reference and not _is_external_reference(reference)
632
+
633
+
634
+ def _bracketed_parts(reference: str) -> tuple[str, ...]:
635
+ parts: list[str] = []
636
+ current: list[str] = []
637
+ depth = 0
638
+ for character in reference:
639
+ if character == "[":
640
+ if depth > 0:
641
+ current.append(character)
642
+ depth += 1
643
+ continue
644
+ if character == "]":
645
+ depth -= 1
646
+ if depth == 0:
647
+ part = "".join(current)
648
+ current = []
649
+ if part.startswith("[") and part.endswith("]"):
650
+ parts.extend(_bracketed_parts(part))
651
+ elif part:
652
+ parts.append(part)
653
+ continue
654
+ current.append(character)
655
+ continue
656
+ if depth > 0:
657
+ current.append(character)
658
+ return tuple(parts)
659
+
660
+
661
+ def _clean_structured_selector(selector: str) -> str:
662
+ return selector.removeprefix("@").replace("''", "'")