offagent 0.10.0__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.
- offagent/__init__.py +3 -0
- offagent/__main__.py +5 -0
- offagent/adapters/__init__.py +1 -0
- offagent/adapters/docx_adapter.py +1237 -0
- offagent/adapters/embedding_provider.py +132 -0
- offagent/adapters/pptx_adapter.py +940 -0
- offagent/adapters/xlsx_adapter.py +1266 -0
- offagent/app/__init__.py +1 -0
- offagent/app/progress.py +52 -0
- offagent/app/services.py +4267 -0
- offagent/config.py +287 -0
- offagent/domain/__init__.py +1 -0
- offagent/domain/locators.py +444 -0
- offagent/domain/models.py +477 -0
- offagent/domain/text_fragments.py +136 -0
- offagent/errors.py +29 -0
- offagent/indexing/__init__.py +1 -0
- offagent/indexing/store.py +795 -0
- offagent/interfaces/__init__.py +1 -0
- offagent/interfaces/cli.py +438 -0
- offagent/interfaces/cli_output.py +139 -0
- offagent/interfaces/cli_progress.py +120 -0
- offagent/interfaces/mcp.py +1145 -0
- offagent/interfaces/mcp_converters.py +80 -0
- offagent/interfaces/mcp_models.py +923 -0
- offagent/objects/__init__.py +3 -0
- offagent/objects/base.py +26 -0
- offagent/objects/docx_objects.py +951 -0
- offagent/objects/pptx_objects.py +895 -0
- offagent/objects/xlsx_objects.py +962 -0
- offagent/path_policy.py +42 -0
- offagent/storage/__init__.py +1 -0
- offagent/storage/versioning.py +31 -0
- offagent-0.10.0.dist-info/METADATA +546 -0
- offagent-0.10.0.dist-info/RECORD +39 -0
- offagent-0.10.0.dist-info/WHEEL +5 -0
- offagent-0.10.0.dist-info/entry_points.txt +2 -0
- offagent-0.10.0.dist-info/licenses/LICENSE +21 -0
- offagent-0.10.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,962 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from offagent.adapters import xlsx_adapter
|
|
8
|
+
from offagent.domain.locators import parse_locator, to_v2_locator
|
|
9
|
+
from offagent.domain.models import Capability, ChildSummary, ObjectPayload
|
|
10
|
+
from offagent.errors import InvalidArgumentsError, TargetNotFoundError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class _XlsxTarget:
|
|
15
|
+
canonical_locator: str
|
|
16
|
+
object_type: str
|
|
17
|
+
sheet_name: str | None = None
|
|
18
|
+
row_index: int | None = None
|
|
19
|
+
column_index: int | None = None
|
|
20
|
+
coordinate: str | None = None
|
|
21
|
+
cell_range: str | None = None
|
|
22
|
+
name: str | None = None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class XlsxObjectResolver:
|
|
26
|
+
def get_object(self, document_path: Path, locator: str) -> ObjectPayload:
|
|
27
|
+
canonical = to_v2_locator(locator, file_type="xlsx")
|
|
28
|
+
workbook = xlsx_adapter._open_workbook(document_path)
|
|
29
|
+
target = _parse_xlsx_target(canonical)
|
|
30
|
+
|
|
31
|
+
if target.object_type == "workbook":
|
|
32
|
+
return _build_workbook_payload(document_path, workbook)
|
|
33
|
+
if target.object_type == "worksheet":
|
|
34
|
+
return _build_worksheet_payload(document_path, workbook, target)
|
|
35
|
+
if target.object_type == "row":
|
|
36
|
+
return _build_row_payload(document_path, workbook, target)
|
|
37
|
+
if target.object_type == "column":
|
|
38
|
+
return _build_column_payload(document_path, workbook, target)
|
|
39
|
+
if target.object_type == "cell":
|
|
40
|
+
return _build_cell_payload(document_path, workbook, target)
|
|
41
|
+
if target.object_type == "range":
|
|
42
|
+
return _build_range_payload(document_path, workbook, target)
|
|
43
|
+
if target.object_type == "table":
|
|
44
|
+
return _build_table_payload(document_path, workbook, target)
|
|
45
|
+
if target.object_type == "merged_range":
|
|
46
|
+
return _build_merged_range_payload(document_path, workbook, target)
|
|
47
|
+
if target.object_type == "formula_cell":
|
|
48
|
+
return _build_formula_cell_payload(document_path, workbook, target)
|
|
49
|
+
if target.object_type == "named_range":
|
|
50
|
+
return _build_named_range_payload(document_path, workbook, target)
|
|
51
|
+
raise InvalidArgumentsError(f"Unsupported XLSX locator: {locator}")
|
|
52
|
+
|
|
53
|
+
def list_children(
|
|
54
|
+
self,
|
|
55
|
+
document_path: Path,
|
|
56
|
+
locator: str,
|
|
57
|
+
*,
|
|
58
|
+
child_type: str | None = None,
|
|
59
|
+
limit: int | None = None,
|
|
60
|
+
) -> list[ChildSummary]:
|
|
61
|
+
canonical = to_v2_locator(locator, file_type="xlsx")
|
|
62
|
+
workbook = xlsx_adapter._open_workbook(document_path)
|
|
63
|
+
target = _parse_xlsx_target(canonical)
|
|
64
|
+
|
|
65
|
+
if target.object_type == "workbook":
|
|
66
|
+
children = _workbook_children(workbook, child_type=child_type)
|
|
67
|
+
elif target.object_type == "worksheet":
|
|
68
|
+
children = _worksheet_children(workbook, target, child_type=child_type)
|
|
69
|
+
elif target.object_type == "row":
|
|
70
|
+
children = _row_children(workbook, target, child_type=child_type)
|
|
71
|
+
elif target.object_type == "column":
|
|
72
|
+
children = _column_children(workbook, target, child_type=child_type)
|
|
73
|
+
elif target.object_type == "range":
|
|
74
|
+
children = _range_children(workbook, target, child_type=child_type)
|
|
75
|
+
elif target.object_type == "table":
|
|
76
|
+
children = _table_children(workbook, target, child_type=child_type)
|
|
77
|
+
else:
|
|
78
|
+
children = ()
|
|
79
|
+
|
|
80
|
+
if limit is not None:
|
|
81
|
+
return list(children[:limit])
|
|
82
|
+
return list(children)
|
|
83
|
+
|
|
84
|
+
def resolve_capabilities(
|
|
85
|
+
self, document_path: Path, locator: str
|
|
86
|
+
) -> frozenset[Capability]:
|
|
87
|
+
workbook = xlsx_adapter._open_workbook(document_path)
|
|
88
|
+
canonical = to_v2_locator(locator, file_type="xlsx")
|
|
89
|
+
target = _parse_xlsx_target(canonical)
|
|
90
|
+
return _capabilities_for(workbook, target)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def write_range(
|
|
94
|
+
document_path: Path,
|
|
95
|
+
locator: str,
|
|
96
|
+
values: list[list[Any]],
|
|
97
|
+
*,
|
|
98
|
+
output_path: Path,
|
|
99
|
+
) -> tuple[str, str, dict[str, Any]]:
|
|
100
|
+
canonical = to_v2_locator(locator, file_type="xlsx")
|
|
101
|
+
workbook = xlsx_adapter._open_workbook(document_path)
|
|
102
|
+
target = _parse_xlsx_target(canonical)
|
|
103
|
+
if target.object_type != "range":
|
|
104
|
+
raise InvalidArgumentsError("xlsx_write_range requires an XLSX range locator.")
|
|
105
|
+
|
|
106
|
+
worksheet = _resolve_worksheet(workbook, target)
|
|
107
|
+
min_row, min_col, max_row, max_col = _range_bounds(target.cell_range, canonical)
|
|
108
|
+
expected_rows = max_row - min_row + 1
|
|
109
|
+
expected_cols = max_col - min_col + 1
|
|
110
|
+
if len(values) != expected_rows or any(len(row) != expected_cols for row in values):
|
|
111
|
+
raise InvalidArgumentsError(
|
|
112
|
+
"Value array dimensions do not match the target XLSX range."
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
for row_offset, row in enumerate(values):
|
|
116
|
+
for col_offset, value in enumerate(row):
|
|
117
|
+
worksheet.cell(
|
|
118
|
+
row=min_row + row_offset, column=min_col + col_offset
|
|
119
|
+
).value = _coerce_write_value(value)
|
|
120
|
+
|
|
121
|
+
workbook.save(output_path)
|
|
122
|
+
return (
|
|
123
|
+
canonical,
|
|
124
|
+
f"Wrote {expected_rows}x{expected_cols} values to {canonical}.",
|
|
125
|
+
{"row_count": expected_rows, "column_count": expected_cols},
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def insert_rows(
|
|
130
|
+
document_path: Path,
|
|
131
|
+
locator: str,
|
|
132
|
+
row_number: int,
|
|
133
|
+
count: int,
|
|
134
|
+
*,
|
|
135
|
+
output_path: Path,
|
|
136
|
+
) -> tuple[str, str, dict[str, Any]]:
|
|
137
|
+
if row_number < 1 or count < 1:
|
|
138
|
+
raise InvalidArgumentsError(
|
|
139
|
+
"xlsx_insert_rows requires positive row_number and count."
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
canonical = to_v2_locator(locator, file_type="xlsx")
|
|
143
|
+
workbook = xlsx_adapter._open_workbook(document_path)
|
|
144
|
+
target = _parse_xlsx_target(canonical)
|
|
145
|
+
if target.object_type != "worksheet":
|
|
146
|
+
raise InvalidArgumentsError(
|
|
147
|
+
"xlsx_insert_rows requires an XLSX worksheet locator."
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
worksheet = _resolve_worksheet(workbook, target)
|
|
151
|
+
worksheet.insert_rows(row_number, count)
|
|
152
|
+
workbook.save(output_path)
|
|
153
|
+
return (
|
|
154
|
+
f"xlsx:sheet:{worksheet.title}:row:{row_number}",
|
|
155
|
+
f"Inserted {count} rows at {canonical} row {row_number}.",
|
|
156
|
+
{"worksheet_locator": canonical, "row_number": row_number, "count": count},
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def insert_columns(
|
|
161
|
+
document_path: Path,
|
|
162
|
+
locator: str,
|
|
163
|
+
column_index: int,
|
|
164
|
+
count: int,
|
|
165
|
+
*,
|
|
166
|
+
output_path: Path,
|
|
167
|
+
) -> tuple[str, str, dict[str, Any]]:
|
|
168
|
+
if column_index < 1 or count < 1:
|
|
169
|
+
raise InvalidArgumentsError(
|
|
170
|
+
"xlsx_insert_columns requires positive column_index and count."
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
canonical = to_v2_locator(locator, file_type="xlsx")
|
|
174
|
+
workbook = xlsx_adapter._open_workbook(document_path)
|
|
175
|
+
target = _parse_xlsx_target(canonical)
|
|
176
|
+
if target.object_type != "worksheet":
|
|
177
|
+
raise InvalidArgumentsError(
|
|
178
|
+
"xlsx_insert_columns requires an XLSX worksheet locator."
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
worksheet = _resolve_worksheet(workbook, target)
|
|
182
|
+
worksheet.insert_cols(column_index, count)
|
|
183
|
+
workbook.save(output_path)
|
|
184
|
+
return (
|
|
185
|
+
f"xlsx:sheet:{worksheet.title}:col:{column_index}",
|
|
186
|
+
f"Inserted {count} columns at {canonical} column {column_index}.",
|
|
187
|
+
{"worksheet_locator": canonical, "column_index": column_index, "count": count},
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def set_formula(
|
|
192
|
+
document_path: Path,
|
|
193
|
+
locator: str,
|
|
194
|
+
formula: str,
|
|
195
|
+
*,
|
|
196
|
+
output_path: Path,
|
|
197
|
+
) -> tuple[str, str, dict[str, Any]]:
|
|
198
|
+
canonical = to_v2_locator(locator, file_type="xlsx")
|
|
199
|
+
workbook = xlsx_adapter._open_workbook(document_path)
|
|
200
|
+
target = _parse_xlsx_target(canonical)
|
|
201
|
+
if target.object_type not in {"cell", "formula_cell"}:
|
|
202
|
+
raise InvalidArgumentsError("xlsx_set_formula requires an XLSX cell locator.")
|
|
203
|
+
if not formula.startswith("=") or len(formula.strip()) <= 1:
|
|
204
|
+
raise InvalidArgumentsError(
|
|
205
|
+
"Invalid XLSX formula; formulas must start with '='."
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
worksheet = _resolve_worksheet(workbook, target)
|
|
209
|
+
cell = _resolve_cell(worksheet, target.coordinate, canonical)
|
|
210
|
+
cell.value = formula
|
|
211
|
+
workbook.save(output_path)
|
|
212
|
+
formula_locator = f"xlsx:sheet:{worksheet.title}:formula_cell:{cell.coordinate}"
|
|
213
|
+
return (
|
|
214
|
+
formula_locator,
|
|
215
|
+
f"Set formula on {formula_locator}.",
|
|
216
|
+
{"formula": formula},
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def merge_cells(
|
|
221
|
+
document_path: Path,
|
|
222
|
+
locator: str,
|
|
223
|
+
*,
|
|
224
|
+
output_path: Path,
|
|
225
|
+
) -> tuple[str, str, dict[str, Any]]:
|
|
226
|
+
canonical = to_v2_locator(locator, file_type="xlsx")
|
|
227
|
+
workbook = xlsx_adapter._open_workbook(document_path)
|
|
228
|
+
target = _parse_xlsx_target(canonical)
|
|
229
|
+
if target.object_type != "range":
|
|
230
|
+
raise InvalidArgumentsError("xlsx_merge_cells requires an XLSX range locator.")
|
|
231
|
+
|
|
232
|
+
worksheet = _resolve_worksheet(workbook, target)
|
|
233
|
+
target_bounds = _range_bounds(target.cell_range, canonical)
|
|
234
|
+
for merged in worksheet.merged_cells.ranges:
|
|
235
|
+
if _ranges_overlap(target_bounds, _range_bounds(str(merged), str(merged))):
|
|
236
|
+
raise InvalidArgumentsError(
|
|
237
|
+
f"Range {target.cell_range} overlaps existing merged range {merged}."
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
worksheet.merge_cells(target.cell_range)
|
|
241
|
+
workbook.save(output_path)
|
|
242
|
+
merged_locator = f"xlsx:sheet:{worksheet.title}:merged_range:{target.cell_range}"
|
|
243
|
+
return (
|
|
244
|
+
merged_locator,
|
|
245
|
+
f"Merged cells in {canonical}.",
|
|
246
|
+
{"range": target.cell_range},
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _build_workbook_payload(document_path: Path, workbook) -> ObjectPayload:
|
|
251
|
+
return ObjectPayload(
|
|
252
|
+
document=xlsx_adapter._document_ref(document_path),
|
|
253
|
+
locator="xlsx:workbook",
|
|
254
|
+
object_type="workbook",
|
|
255
|
+
preview=next((worksheet.title for worksheet in workbook.worksheets), ""),
|
|
256
|
+
properties={"sheet_count": len(workbook.worksheets)},
|
|
257
|
+
capabilities=_capability_tuple(
|
|
258
|
+
workbook, _XlsxTarget("xlsx:workbook", "workbook")
|
|
259
|
+
),
|
|
260
|
+
child_summary=_workbook_children(workbook),
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _build_worksheet_payload(
|
|
265
|
+
document_path: Path, workbook, target: _XlsxTarget
|
|
266
|
+
) -> ObjectPayload:
|
|
267
|
+
worksheet = _resolve_worksheet(workbook, target)
|
|
268
|
+
used_bounds = xlsx_adapter._used_bounds(worksheet)
|
|
269
|
+
return ObjectPayload(
|
|
270
|
+
document=xlsx_adapter._document_ref(document_path),
|
|
271
|
+
locator=target.canonical_locator,
|
|
272
|
+
object_type="worksheet",
|
|
273
|
+
preview=_worksheet_preview(worksheet),
|
|
274
|
+
properties={
|
|
275
|
+
"sheet_name": worksheet.title,
|
|
276
|
+
"used_range": xlsx_adapter._format_range(used_bounds),
|
|
277
|
+
"row_count": 0
|
|
278
|
+
if used_bounds is None
|
|
279
|
+
else used_bounds[2] - used_bounds[0] + 1,
|
|
280
|
+
"column_count": 0
|
|
281
|
+
if used_bounds is None
|
|
282
|
+
else used_bounds[3] - used_bounds[1] + 1,
|
|
283
|
+
"table_count": len(worksheet.tables),
|
|
284
|
+
"merged_range_count": len(worksheet.merged_cells.ranges),
|
|
285
|
+
},
|
|
286
|
+
capabilities=_capability_tuple(workbook, target),
|
|
287
|
+
parent_locator="xlsx:workbook",
|
|
288
|
+
child_summary=_worksheet_children(workbook, target),
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _build_row_payload(
|
|
293
|
+
document_path: Path, workbook, target: _XlsxTarget
|
|
294
|
+
) -> ObjectPayload:
|
|
295
|
+
worksheet = _resolve_worksheet(workbook, target)
|
|
296
|
+
assert target.row_index is not None
|
|
297
|
+
used_bounds = xlsx_adapter._used_bounds(worksheet)
|
|
298
|
+
max_col = 0 if used_bounds is None else used_bounds[3]
|
|
299
|
+
row_cells = [
|
|
300
|
+
worksheet.cell(row=target.row_index, column=column_index)
|
|
301
|
+
for column_index in range(1, max_col + 1)
|
|
302
|
+
]
|
|
303
|
+
return ObjectPayload(
|
|
304
|
+
document=xlsx_adapter._document_ref(document_path),
|
|
305
|
+
locator=target.canonical_locator,
|
|
306
|
+
object_type="row",
|
|
307
|
+
preview=" | ".join(
|
|
308
|
+
xlsx_adapter._display_text(cell)
|
|
309
|
+
for cell in row_cells
|
|
310
|
+
if xlsx_adapter._display_text(cell)
|
|
311
|
+
)[:120],
|
|
312
|
+
properties={
|
|
313
|
+
"sheet_name": worksheet.title,
|
|
314
|
+
"row_index": target.row_index,
|
|
315
|
+
"cell_count": len(row_cells),
|
|
316
|
+
"non_empty_cell_count": sum(
|
|
317
|
+
1 for cell in row_cells if xlsx_adapter._is_indexable_cell(cell)
|
|
318
|
+
),
|
|
319
|
+
},
|
|
320
|
+
capabilities=_capability_tuple(workbook, target),
|
|
321
|
+
parent_locator=f"xlsx:sheet:{worksheet.title}",
|
|
322
|
+
child_summary=_row_children(workbook, target),
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def _build_column_payload(
|
|
327
|
+
document_path: Path, workbook, target: _XlsxTarget
|
|
328
|
+
) -> ObjectPayload:
|
|
329
|
+
worksheet = _resolve_worksheet(workbook, target)
|
|
330
|
+
assert target.column_index is not None
|
|
331
|
+
used_bounds = xlsx_adapter._used_bounds(worksheet)
|
|
332
|
+
max_row = 0 if used_bounds is None else used_bounds[2]
|
|
333
|
+
column_cells = [
|
|
334
|
+
worksheet.cell(row=row_index, column=target.column_index)
|
|
335
|
+
for row_index in range(1, max_row + 1)
|
|
336
|
+
]
|
|
337
|
+
return ObjectPayload(
|
|
338
|
+
document=xlsx_adapter._document_ref(document_path),
|
|
339
|
+
locator=target.canonical_locator,
|
|
340
|
+
object_type="column",
|
|
341
|
+
preview=" | ".join(
|
|
342
|
+
xlsx_adapter._display_text(cell)
|
|
343
|
+
for cell in column_cells
|
|
344
|
+
if xlsx_adapter._display_text(cell)
|
|
345
|
+
)[:120],
|
|
346
|
+
properties={
|
|
347
|
+
"sheet_name": worksheet.title,
|
|
348
|
+
"column_index": target.column_index,
|
|
349
|
+
"column_letter": xlsx_adapter.get_column_letter(target.column_index),
|
|
350
|
+
"cell_count": len(column_cells),
|
|
351
|
+
"non_empty_cell_count": sum(
|
|
352
|
+
1 for cell in column_cells if xlsx_adapter._is_indexable_cell(cell)
|
|
353
|
+
),
|
|
354
|
+
},
|
|
355
|
+
capabilities=_capability_tuple(workbook, target),
|
|
356
|
+
parent_locator=f"xlsx:sheet:{worksheet.title}",
|
|
357
|
+
child_summary=_column_children(workbook, target),
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def _build_cell_payload(
|
|
362
|
+
document_path: Path, workbook, target: _XlsxTarget
|
|
363
|
+
) -> ObjectPayload:
|
|
364
|
+
worksheet = _resolve_worksheet(workbook, target)
|
|
365
|
+
cell = _resolve_cell(worksheet, target.coordinate, target.canonical_locator)
|
|
366
|
+
return ObjectPayload(
|
|
367
|
+
document=xlsx_adapter._document_ref(document_path),
|
|
368
|
+
locator=target.canonical_locator,
|
|
369
|
+
object_type="cell",
|
|
370
|
+
preview=xlsx_adapter._display_text(cell)[:120],
|
|
371
|
+
properties={
|
|
372
|
+
"sheet_name": worksheet.title,
|
|
373
|
+
"coordinate": cell.coordinate,
|
|
374
|
+
"value": cell.value,
|
|
375
|
+
"display_value": xlsx_adapter._display_text(cell),
|
|
376
|
+
"formula": xlsx_adapter._formula_text(cell),
|
|
377
|
+
"data_type": cell.data_type,
|
|
378
|
+
},
|
|
379
|
+
capabilities=_capability_tuple(workbook, target),
|
|
380
|
+
parent_locator=f"xlsx:sheet:{worksheet.title}",
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def _build_range_payload(
|
|
385
|
+
document_path: Path, workbook, target: _XlsxTarget
|
|
386
|
+
) -> ObjectPayload:
|
|
387
|
+
worksheet = _resolve_worksheet(workbook, target)
|
|
388
|
+
min_row, min_col, max_row, max_col = _range_bounds(
|
|
389
|
+
target.cell_range, target.canonical_locator
|
|
390
|
+
)
|
|
391
|
+
cells = [
|
|
392
|
+
worksheet.cell(row=row_index, column=column_index)
|
|
393
|
+
for row_index in range(min_row, max_row + 1)
|
|
394
|
+
for column_index in range(min_col, max_col + 1)
|
|
395
|
+
]
|
|
396
|
+
preview = next(
|
|
397
|
+
(
|
|
398
|
+
xlsx_adapter._display_text(cell)[:120]
|
|
399
|
+
for cell in cells
|
|
400
|
+
if xlsx_adapter._display_text(cell)
|
|
401
|
+
),
|
|
402
|
+
"",
|
|
403
|
+
)
|
|
404
|
+
return ObjectPayload(
|
|
405
|
+
document=xlsx_adapter._document_ref(document_path),
|
|
406
|
+
locator=target.canonical_locator,
|
|
407
|
+
object_type="range",
|
|
408
|
+
preview=preview,
|
|
409
|
+
properties={
|
|
410
|
+
"sheet_name": worksheet.title,
|
|
411
|
+
"range": target.cell_range,
|
|
412
|
+
"cell_count": len(cells),
|
|
413
|
+
},
|
|
414
|
+
capabilities=_capability_tuple(workbook, target),
|
|
415
|
+
parent_locator=f"xlsx:sheet:{worksheet.title}",
|
|
416
|
+
child_summary=_range_children(workbook, target),
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def _build_table_payload(
|
|
421
|
+
document_path: Path, workbook, target: _XlsxTarget
|
|
422
|
+
) -> ObjectPayload:
|
|
423
|
+
worksheet = _resolve_worksheet(workbook, target)
|
|
424
|
+
table = _resolve_table(worksheet, target.name, target.canonical_locator)
|
|
425
|
+
return ObjectPayload(
|
|
426
|
+
document=xlsx_adapter._document_ref(document_path),
|
|
427
|
+
locator=target.canonical_locator,
|
|
428
|
+
object_type="table",
|
|
429
|
+
preview=table.displayName,
|
|
430
|
+
properties={
|
|
431
|
+
"sheet_name": worksheet.title,
|
|
432
|
+
"table_name": table.displayName,
|
|
433
|
+
"range": table.ref,
|
|
434
|
+
},
|
|
435
|
+
capabilities=_capability_tuple(workbook, target),
|
|
436
|
+
parent_locator=f"xlsx:sheet:{worksheet.title}",
|
|
437
|
+
child_summary=_table_children(workbook, target),
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def _build_merged_range_payload(
|
|
442
|
+
document_path: Path, workbook, target: _XlsxTarget
|
|
443
|
+
) -> ObjectPayload:
|
|
444
|
+
worksheet = _resolve_worksheet(workbook, target)
|
|
445
|
+
merged = _resolve_merged_range(
|
|
446
|
+
worksheet, target.cell_range, target.canonical_locator
|
|
447
|
+
)
|
|
448
|
+
return ObjectPayload(
|
|
449
|
+
document=xlsx_adapter._document_ref(document_path),
|
|
450
|
+
locator=target.canonical_locator,
|
|
451
|
+
object_type="merged_range",
|
|
452
|
+
preview=str(merged),
|
|
453
|
+
properties={"sheet_name": worksheet.title, "range": str(merged)},
|
|
454
|
+
capabilities=_capability_tuple(workbook, target),
|
|
455
|
+
parent_locator=f"xlsx:sheet:{worksheet.title}",
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def _build_formula_cell_payload(
|
|
460
|
+
document_path: Path, workbook, target: _XlsxTarget
|
|
461
|
+
) -> ObjectPayload:
|
|
462
|
+
worksheet = _resolve_worksheet(workbook, target)
|
|
463
|
+
cell = _resolve_cell(worksheet, target.coordinate, target.canonical_locator)
|
|
464
|
+
if xlsx_adapter._formula_text(cell) is None:
|
|
465
|
+
raise TargetNotFoundError(f"Cell {cell.coordinate} does not contain a formula.")
|
|
466
|
+
return ObjectPayload(
|
|
467
|
+
document=xlsx_adapter._document_ref(document_path),
|
|
468
|
+
locator=target.canonical_locator,
|
|
469
|
+
object_type="formula_cell",
|
|
470
|
+
preview=xlsx_adapter._formula_text(cell)[:120],
|
|
471
|
+
properties={
|
|
472
|
+
"sheet_name": worksheet.title,
|
|
473
|
+
"coordinate": cell.coordinate,
|
|
474
|
+
"formula": xlsx_adapter._formula_text(cell),
|
|
475
|
+
"display_value": xlsx_adapter._display_text(cell),
|
|
476
|
+
},
|
|
477
|
+
capabilities=_capability_tuple(workbook, target),
|
|
478
|
+
parent_locator=f"xlsx:sheet:{worksheet.title}",
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
def _build_named_range_payload(
|
|
483
|
+
document_path: Path, workbook, target: _XlsxTarget
|
|
484
|
+
) -> ObjectPayload:
|
|
485
|
+
defined_name = _resolve_named_range(workbook, target.name, target.canonical_locator)
|
|
486
|
+
return ObjectPayload(
|
|
487
|
+
document=xlsx_adapter._document_ref(document_path),
|
|
488
|
+
locator=target.canonical_locator,
|
|
489
|
+
object_type="named_range",
|
|
490
|
+
preview=defined_name.name,
|
|
491
|
+
properties={"name": defined_name.name, "reference": defined_name.attr_text},
|
|
492
|
+
capabilities=_capability_tuple(workbook, target),
|
|
493
|
+
parent_locator="xlsx:workbook",
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
def _workbook_children(
|
|
498
|
+
workbook, *, child_type: str | None = None
|
|
499
|
+
) -> tuple[ChildSummary, ...]:
|
|
500
|
+
if child_type not in {None, "", "worksheet"}:
|
|
501
|
+
return ()
|
|
502
|
+
return tuple(
|
|
503
|
+
ChildSummary(
|
|
504
|
+
locator=f"xlsx:sheet:{worksheet.title}",
|
|
505
|
+
object_type="worksheet",
|
|
506
|
+
preview=_worksheet_preview(worksheet),
|
|
507
|
+
capabilities=_capability_tuple(
|
|
508
|
+
workbook,
|
|
509
|
+
_XlsxTarget(
|
|
510
|
+
f"xlsx:sheet:{worksheet.title}",
|
|
511
|
+
"worksheet",
|
|
512
|
+
sheet_name=worksheet.title,
|
|
513
|
+
),
|
|
514
|
+
),
|
|
515
|
+
)
|
|
516
|
+
for worksheet in workbook.worksheets
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
def _worksheet_children(
|
|
521
|
+
workbook, target: _XlsxTarget, *, child_type: str | None = None
|
|
522
|
+
) -> tuple[ChildSummary, ...]:
|
|
523
|
+
worksheet = _resolve_worksheet(workbook, target)
|
|
524
|
+
normalized_child_type = child_type or None
|
|
525
|
+
children: list[ChildSummary] = []
|
|
526
|
+
|
|
527
|
+
used_bounds = xlsx_adapter._used_bounds(worksheet)
|
|
528
|
+
if used_bounds is not None and normalized_child_type in {None, "row"}:
|
|
529
|
+
for row_index in range(used_bounds[0], used_bounds[2] + 1):
|
|
530
|
+
children.append(
|
|
531
|
+
ChildSummary(
|
|
532
|
+
locator=f"xlsx:sheet:{worksheet.title}:row:{row_index}",
|
|
533
|
+
object_type="row",
|
|
534
|
+
preview=_row_preview(worksheet, row_index),
|
|
535
|
+
capabilities=_capability_tuple(
|
|
536
|
+
workbook,
|
|
537
|
+
_XlsxTarget(
|
|
538
|
+
"", "row", sheet_name=worksheet.title, row_index=row_index
|
|
539
|
+
),
|
|
540
|
+
),
|
|
541
|
+
)
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
if used_bounds is not None and normalized_child_type in {None, "column"}:
|
|
545
|
+
for column_index in range(used_bounds[1], used_bounds[3] + 1):
|
|
546
|
+
children.append(
|
|
547
|
+
ChildSummary(
|
|
548
|
+
locator=f"xlsx:sheet:{worksheet.title}:col:{column_index}",
|
|
549
|
+
object_type="column",
|
|
550
|
+
preview=_column_preview(worksheet, column_index),
|
|
551
|
+
capabilities=_capability_tuple(
|
|
552
|
+
workbook,
|
|
553
|
+
_XlsxTarget(
|
|
554
|
+
"",
|
|
555
|
+
"column",
|
|
556
|
+
sheet_name=worksheet.title,
|
|
557
|
+
column_index=column_index,
|
|
558
|
+
),
|
|
559
|
+
),
|
|
560
|
+
)
|
|
561
|
+
)
|
|
562
|
+
|
|
563
|
+
if normalized_child_type in {None, "table"}:
|
|
564
|
+
for table_name in worksheet.tables:
|
|
565
|
+
children.append(
|
|
566
|
+
ChildSummary(
|
|
567
|
+
locator=f"xlsx:sheet:{worksheet.title}:table:{table_name}",
|
|
568
|
+
object_type="table",
|
|
569
|
+
preview=table_name,
|
|
570
|
+
capabilities=_capability_tuple(
|
|
571
|
+
workbook,
|
|
572
|
+
_XlsxTarget(
|
|
573
|
+
"", "table", sheet_name=worksheet.title, name=table_name
|
|
574
|
+
),
|
|
575
|
+
),
|
|
576
|
+
)
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
if normalized_child_type in {None, "merged_range"}:
|
|
580
|
+
for merged in worksheet.merged_cells.ranges:
|
|
581
|
+
children.append(
|
|
582
|
+
ChildSummary(
|
|
583
|
+
locator=f"xlsx:sheet:{worksheet.title}:merged_range:{merged}",
|
|
584
|
+
object_type="merged_range",
|
|
585
|
+
preview=str(merged),
|
|
586
|
+
capabilities=_capability_tuple(
|
|
587
|
+
workbook,
|
|
588
|
+
_XlsxTarget(
|
|
589
|
+
"",
|
|
590
|
+
"merged_range",
|
|
591
|
+
sheet_name=worksheet.title,
|
|
592
|
+
cell_range=str(merged),
|
|
593
|
+
),
|
|
594
|
+
),
|
|
595
|
+
)
|
|
596
|
+
)
|
|
597
|
+
|
|
598
|
+
if used_bounds is not None and normalized_child_type in {None, "formula_cell"}:
|
|
599
|
+
for row_index in range(used_bounds[0], used_bounds[2] + 1):
|
|
600
|
+
for column_index in range(used_bounds[1], used_bounds[3] + 1):
|
|
601
|
+
cell = worksheet.cell(row=row_index, column=column_index)
|
|
602
|
+
formula = xlsx_adapter._formula_text(cell)
|
|
603
|
+
if formula is None:
|
|
604
|
+
continue
|
|
605
|
+
children.append(
|
|
606
|
+
ChildSummary(
|
|
607
|
+
locator=f"xlsx:sheet:{worksheet.title}:formula_cell:{cell.coordinate}",
|
|
608
|
+
object_type="formula_cell",
|
|
609
|
+
preview=formula[:120],
|
|
610
|
+
capabilities=_capability_tuple(
|
|
611
|
+
workbook,
|
|
612
|
+
_XlsxTarget(
|
|
613
|
+
"",
|
|
614
|
+
"formula_cell",
|
|
615
|
+
sheet_name=worksheet.title,
|
|
616
|
+
coordinate=cell.coordinate,
|
|
617
|
+
),
|
|
618
|
+
),
|
|
619
|
+
)
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
return tuple(children)
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
def _row_children(
|
|
626
|
+
workbook, target: _XlsxTarget, *, child_type: str | None = None
|
|
627
|
+
) -> tuple[ChildSummary, ...]:
|
|
628
|
+
if child_type not in {None, "", "cell"}:
|
|
629
|
+
return ()
|
|
630
|
+
worksheet = _resolve_worksheet(workbook, target)
|
|
631
|
+
assert target.row_index is not None
|
|
632
|
+
used_bounds = xlsx_adapter._used_bounds(worksheet)
|
|
633
|
+
if used_bounds is None:
|
|
634
|
+
return ()
|
|
635
|
+
return tuple(
|
|
636
|
+
ChildSummary(
|
|
637
|
+
locator=f"xlsx:sheet:{worksheet.title}!{worksheet.cell(row=target.row_index, column=column_index).coordinate}",
|
|
638
|
+
object_type="cell",
|
|
639
|
+
preview=xlsx_adapter._display_text(
|
|
640
|
+
worksheet.cell(row=target.row_index, column=column_index)
|
|
641
|
+
)[:120],
|
|
642
|
+
capabilities=_capability_tuple(
|
|
643
|
+
workbook,
|
|
644
|
+
_XlsxTarget(
|
|
645
|
+
"",
|
|
646
|
+
"cell",
|
|
647
|
+
sheet_name=worksheet.title,
|
|
648
|
+
coordinate=worksheet.cell(
|
|
649
|
+
row=target.row_index, column=column_index
|
|
650
|
+
).coordinate,
|
|
651
|
+
),
|
|
652
|
+
),
|
|
653
|
+
)
|
|
654
|
+
for column_index in range(used_bounds[1], used_bounds[3] + 1)
|
|
655
|
+
)
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
def _column_children(
|
|
659
|
+
workbook, target: _XlsxTarget, *, child_type: str | None = None
|
|
660
|
+
) -> tuple[ChildSummary, ...]:
|
|
661
|
+
if child_type not in {None, "", "cell"}:
|
|
662
|
+
return ()
|
|
663
|
+
worksheet = _resolve_worksheet(workbook, target)
|
|
664
|
+
assert target.column_index is not None
|
|
665
|
+
used_bounds = xlsx_adapter._used_bounds(worksheet)
|
|
666
|
+
if used_bounds is None:
|
|
667
|
+
return ()
|
|
668
|
+
return tuple(
|
|
669
|
+
ChildSummary(
|
|
670
|
+
locator=f"xlsx:sheet:{worksheet.title}!{worksheet.cell(row=row_index, column=target.column_index).coordinate}",
|
|
671
|
+
object_type="cell",
|
|
672
|
+
preview=xlsx_adapter._display_text(
|
|
673
|
+
worksheet.cell(row=row_index, column=target.column_index)
|
|
674
|
+
)[:120],
|
|
675
|
+
capabilities=_capability_tuple(
|
|
676
|
+
workbook,
|
|
677
|
+
_XlsxTarget(
|
|
678
|
+
"",
|
|
679
|
+
"cell",
|
|
680
|
+
sheet_name=worksheet.title,
|
|
681
|
+
coordinate=worksheet.cell(
|
|
682
|
+
row=row_index, column=target.column_index
|
|
683
|
+
).coordinate,
|
|
684
|
+
),
|
|
685
|
+
),
|
|
686
|
+
)
|
|
687
|
+
for row_index in range(used_bounds[0], used_bounds[2] + 1)
|
|
688
|
+
)
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
def _range_children(
|
|
692
|
+
workbook, target: _XlsxTarget, *, child_type: str | None = None
|
|
693
|
+
) -> tuple[ChildSummary, ...]:
|
|
694
|
+
if child_type not in {None, "", "cell"}:
|
|
695
|
+
return ()
|
|
696
|
+
worksheet = _resolve_worksheet(workbook, target)
|
|
697
|
+
min_row, min_col, max_row, max_col = _range_bounds(
|
|
698
|
+
target.cell_range, target.canonical_locator
|
|
699
|
+
)
|
|
700
|
+
children: list[ChildSummary] = []
|
|
701
|
+
for row_index in range(min_row, max_row + 1):
|
|
702
|
+
for column_index in range(min_col, max_col + 1):
|
|
703
|
+
cell = worksheet.cell(row=row_index, column=column_index)
|
|
704
|
+
children.append(
|
|
705
|
+
ChildSummary(
|
|
706
|
+
locator=f"xlsx:sheet:{worksheet.title}!{cell.coordinate}",
|
|
707
|
+
object_type="cell",
|
|
708
|
+
preview=xlsx_adapter._display_text(cell)[:120],
|
|
709
|
+
capabilities=_capability_tuple(
|
|
710
|
+
workbook,
|
|
711
|
+
_XlsxTarget(
|
|
712
|
+
"",
|
|
713
|
+
"cell",
|
|
714
|
+
sheet_name=worksheet.title,
|
|
715
|
+
coordinate=cell.coordinate,
|
|
716
|
+
),
|
|
717
|
+
),
|
|
718
|
+
)
|
|
719
|
+
)
|
|
720
|
+
return tuple(children)
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
def _table_children(
|
|
724
|
+
workbook, target: _XlsxTarget, *, child_type: str | None = None
|
|
725
|
+
) -> tuple[ChildSummary, ...]:
|
|
726
|
+
if child_type not in {None, "", "row"}:
|
|
727
|
+
return ()
|
|
728
|
+
worksheet = _resolve_worksheet(workbook, target)
|
|
729
|
+
table = _resolve_table(worksheet, target.name, target.canonical_locator)
|
|
730
|
+
min_row, _, max_row, _ = _range_bounds(table.ref, target.canonical_locator)
|
|
731
|
+
return tuple(
|
|
732
|
+
ChildSummary(
|
|
733
|
+
locator=f"xlsx:sheet:{worksheet.title}:row:{row_index}",
|
|
734
|
+
object_type="row",
|
|
735
|
+
preview=_row_preview(worksheet, row_index),
|
|
736
|
+
capabilities=_capability_tuple(
|
|
737
|
+
workbook,
|
|
738
|
+
_XlsxTarget("", "row", sheet_name=worksheet.title, row_index=row_index),
|
|
739
|
+
),
|
|
740
|
+
)
|
|
741
|
+
for row_index in range(min_row, max_row + 1)
|
|
742
|
+
)
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
def _resolve_worksheet(workbook, target: _XlsxTarget):
|
|
746
|
+
assert target.sheet_name is not None
|
|
747
|
+
return xlsx_adapter._resolve_worksheet(workbook, target.sheet_name)
|
|
748
|
+
|
|
749
|
+
|
|
750
|
+
def _resolve_cell(worksheet, coordinate: str | None, locator: str):
|
|
751
|
+
if coordinate is None:
|
|
752
|
+
raise InvalidArgumentsError(f"Invalid XLSX locator: {locator}")
|
|
753
|
+
return worksheet[xlsx_adapter._normalize_coordinate(coordinate)]
|
|
754
|
+
|
|
755
|
+
|
|
756
|
+
def _resolve_table(worksheet, table_name: str | None, locator: str):
|
|
757
|
+
if table_name is None:
|
|
758
|
+
raise InvalidArgumentsError(f"Invalid XLSX table locator: {locator}")
|
|
759
|
+
try:
|
|
760
|
+
return worksheet.tables[table_name]
|
|
761
|
+
except KeyError as exc:
|
|
762
|
+
raise TargetNotFoundError(
|
|
763
|
+
f"Table {table_name!r} does not exist in worksheet {worksheet.title!r}."
|
|
764
|
+
) from exc
|
|
765
|
+
|
|
766
|
+
|
|
767
|
+
def _resolve_merged_range(worksheet, cell_range: str | None, locator: str):
|
|
768
|
+
if cell_range is None:
|
|
769
|
+
raise InvalidArgumentsError(f"Invalid merged range locator: {locator}")
|
|
770
|
+
for merged in worksheet.merged_cells.ranges:
|
|
771
|
+
if str(merged) == cell_range:
|
|
772
|
+
return merged
|
|
773
|
+
raise TargetNotFoundError(
|
|
774
|
+
f"Merged range {cell_range!r} does not exist in worksheet {worksheet.title!r}."
|
|
775
|
+
)
|
|
776
|
+
|
|
777
|
+
|
|
778
|
+
def _resolve_named_range(workbook, name: str | None, locator: str):
|
|
779
|
+
if name is None:
|
|
780
|
+
raise InvalidArgumentsError(f"Invalid named range locator: {locator}")
|
|
781
|
+
defined_name = workbook.defined_names.get(name)
|
|
782
|
+
if defined_name is None:
|
|
783
|
+
raise TargetNotFoundError(f"Named range {name!r} does not exist.")
|
|
784
|
+
return defined_name
|
|
785
|
+
|
|
786
|
+
|
|
787
|
+
def _worksheet_preview(worksheet) -> str:
|
|
788
|
+
first_cell = xlsx_adapter._first_indexable_cell(worksheet)
|
|
789
|
+
return "" if first_cell is None else xlsx_adapter._display_text(first_cell)[:120]
|
|
790
|
+
|
|
791
|
+
|
|
792
|
+
def _row_preview(worksheet, row_index: int) -> str:
|
|
793
|
+
used_bounds = xlsx_adapter._used_bounds(worksheet)
|
|
794
|
+
if used_bounds is None:
|
|
795
|
+
return ""
|
|
796
|
+
return " | ".join(
|
|
797
|
+
xlsx_adapter._display_text(worksheet.cell(row=row_index, column=column_index))
|
|
798
|
+
for column_index in range(used_bounds[1], used_bounds[3] + 1)
|
|
799
|
+
if xlsx_adapter._display_text(
|
|
800
|
+
worksheet.cell(row=row_index, column=column_index)
|
|
801
|
+
)
|
|
802
|
+
)[:120]
|
|
803
|
+
|
|
804
|
+
|
|
805
|
+
def _column_preview(worksheet, column_index: int) -> str:
|
|
806
|
+
used_bounds = xlsx_adapter._used_bounds(worksheet)
|
|
807
|
+
if used_bounds is None:
|
|
808
|
+
return ""
|
|
809
|
+
return " | ".join(
|
|
810
|
+
xlsx_adapter._display_text(worksheet.cell(row=row_index, column=column_index))
|
|
811
|
+
for row_index in range(used_bounds[0], used_bounds[2] + 1)
|
|
812
|
+
if xlsx_adapter._display_text(
|
|
813
|
+
worksheet.cell(row=row_index, column=column_index)
|
|
814
|
+
)
|
|
815
|
+
)[:120]
|
|
816
|
+
|
|
817
|
+
|
|
818
|
+
def _range_bounds(cell_range: str | None, locator: str) -> tuple[int, int, int, int]:
|
|
819
|
+
if cell_range is None or xlsx_adapter.range_boundaries is None:
|
|
820
|
+
raise InvalidArgumentsError(f"Invalid XLSX range locator: {locator}")
|
|
821
|
+
min_col, min_row, max_col, max_row = xlsx_adapter.range_boundaries(cell_range)
|
|
822
|
+
return (min_row, min_col, max_row, max_col)
|
|
823
|
+
|
|
824
|
+
|
|
825
|
+
def _ranges_overlap(
|
|
826
|
+
first: tuple[int, int, int, int],
|
|
827
|
+
second: tuple[int, int, int, int],
|
|
828
|
+
) -> bool:
|
|
829
|
+
return not (
|
|
830
|
+
first[2] < second[0]
|
|
831
|
+
or second[2] < first[0]
|
|
832
|
+
or first[3] < second[1]
|
|
833
|
+
or second[3] < first[1]
|
|
834
|
+
)
|
|
835
|
+
|
|
836
|
+
|
|
837
|
+
def _parse_xlsx_target(locator: str) -> _XlsxTarget:
|
|
838
|
+
parsed = parse_locator(locator)
|
|
839
|
+
components = parsed.components
|
|
840
|
+
if components == ("xlsx", "workbook"):
|
|
841
|
+
return _XlsxTarget(locator, "workbook")
|
|
842
|
+
if len(components) == 3 and components[:2] == ("xlsx", "named_range"):
|
|
843
|
+
return _XlsxTarget(locator, "named_range", name=components[2])
|
|
844
|
+
if len(components) == 3 and components[:2] == ("xlsx", "sheet"):
|
|
845
|
+
return _XlsxTarget(locator, "worksheet", sheet_name=components[2])
|
|
846
|
+
if (
|
|
847
|
+
len(components) == 5
|
|
848
|
+
and components[:2] == ("xlsx", "sheet")
|
|
849
|
+
and components[3] == "row"
|
|
850
|
+
):
|
|
851
|
+
return _XlsxTarget(
|
|
852
|
+
locator,
|
|
853
|
+
"row",
|
|
854
|
+
sheet_name=components[2],
|
|
855
|
+
row_index=_require_index(components[4], locator),
|
|
856
|
+
)
|
|
857
|
+
if (
|
|
858
|
+
len(components) == 5
|
|
859
|
+
and components[:2] == ("xlsx", "sheet")
|
|
860
|
+
and components[3] == "col"
|
|
861
|
+
):
|
|
862
|
+
return _XlsxTarget(
|
|
863
|
+
locator,
|
|
864
|
+
"column",
|
|
865
|
+
sheet_name=components[2],
|
|
866
|
+
column_index=_require_index(components[4], locator),
|
|
867
|
+
)
|
|
868
|
+
if len(components) == 4 and components[:2] == ("xlsx", "sheet"):
|
|
869
|
+
coordinate_or_range = components[3]
|
|
870
|
+
if ":" in coordinate_or_range:
|
|
871
|
+
return _XlsxTarget(
|
|
872
|
+
locator,
|
|
873
|
+
"range",
|
|
874
|
+
sheet_name=components[2],
|
|
875
|
+
cell_range=coordinate_or_range,
|
|
876
|
+
)
|
|
877
|
+
return _XlsxTarget(
|
|
878
|
+
locator, "cell", sheet_name=components[2], coordinate=coordinate_or_range
|
|
879
|
+
)
|
|
880
|
+
if (
|
|
881
|
+
len(components) == 5
|
|
882
|
+
and components[:2] == ("xlsx", "sheet")
|
|
883
|
+
and components[3] == "table"
|
|
884
|
+
):
|
|
885
|
+
return _XlsxTarget(
|
|
886
|
+
locator, "table", sheet_name=components[2], name=components[4]
|
|
887
|
+
)
|
|
888
|
+
if (
|
|
889
|
+
len(components) >= 5
|
|
890
|
+
and components[:2] == ("xlsx", "sheet")
|
|
891
|
+
and components[3] == "merged_range"
|
|
892
|
+
):
|
|
893
|
+
return _XlsxTarget(
|
|
894
|
+
locator,
|
|
895
|
+
"merged_range",
|
|
896
|
+
sheet_name=components[2],
|
|
897
|
+
cell_range=":".join(components[4:]),
|
|
898
|
+
)
|
|
899
|
+
if (
|
|
900
|
+
len(components) == 5
|
|
901
|
+
and components[:2] == ("xlsx", "sheet")
|
|
902
|
+
and components[3] == "formula_cell"
|
|
903
|
+
):
|
|
904
|
+
return _XlsxTarget(
|
|
905
|
+
locator, "formula_cell", sheet_name=components[2], coordinate=components[4]
|
|
906
|
+
)
|
|
907
|
+
raise InvalidArgumentsError(f"Unsupported XLSX locator: {locator}")
|
|
908
|
+
|
|
909
|
+
|
|
910
|
+
def _capabilities_for(workbook, target: _XlsxTarget) -> frozenset[Capability]:
|
|
911
|
+
if target.object_type == "workbook":
|
|
912
|
+
return frozenset({Capability.READ, Capability.ADD_CHILD})
|
|
913
|
+
if target.object_type == "worksheet":
|
|
914
|
+
capabilities = {
|
|
915
|
+
Capability.READ,
|
|
916
|
+
Capability.UPDATE,
|
|
917
|
+
Capability.ADD_CHILD,
|
|
918
|
+
Capability.MOVE,
|
|
919
|
+
Capability.COPY,
|
|
920
|
+
}
|
|
921
|
+
if len(workbook.worksheets) > 1:
|
|
922
|
+
capabilities.add(Capability.DELETE)
|
|
923
|
+
return frozenset(capabilities)
|
|
924
|
+
if target.object_type in {"row", "column", "table"}:
|
|
925
|
+
return frozenset(
|
|
926
|
+
{
|
|
927
|
+
Capability.READ,
|
|
928
|
+
Capability.UPDATE,
|
|
929
|
+
Capability.DELETE,
|
|
930
|
+
Capability.ADD_CHILD,
|
|
931
|
+
Capability.MOVE,
|
|
932
|
+
Capability.COPY,
|
|
933
|
+
}
|
|
934
|
+
)
|
|
935
|
+
if target.object_type in {"cell", "range", "formula_cell"}:
|
|
936
|
+
return frozenset({Capability.READ, Capability.UPDATE, Capability.STYLE})
|
|
937
|
+
if target.object_type == "merged_range":
|
|
938
|
+
return frozenset({Capability.READ, Capability.DELETE, Capability.STYLE})
|
|
939
|
+
if target.object_type == "named_range":
|
|
940
|
+
return frozenset({Capability.READ})
|
|
941
|
+
return frozenset({Capability.READ})
|
|
942
|
+
|
|
943
|
+
|
|
944
|
+
def _capability_tuple(workbook, target: _XlsxTarget) -> tuple[Capability, ...]:
|
|
945
|
+
return tuple(
|
|
946
|
+
sorted(
|
|
947
|
+
_capabilities_for(workbook, target), key=lambda capability: capability.value
|
|
948
|
+
)
|
|
949
|
+
)
|
|
950
|
+
|
|
951
|
+
|
|
952
|
+
def _require_index(raw: str, locator: str) -> int:
|
|
953
|
+
try:
|
|
954
|
+
return int(raw)
|
|
955
|
+
except ValueError as exc:
|
|
956
|
+
raise InvalidArgumentsError(f"Invalid XLSX locator: {locator}") from exc
|
|
957
|
+
|
|
958
|
+
|
|
959
|
+
def _coerce_write_value(value: Any) -> Any:
|
|
960
|
+
if isinstance(value, str):
|
|
961
|
+
return xlsx_adapter._coerce_value(value)
|
|
962
|
+
return value
|