numbers-parser 4.11.5__py3-none-any.whl → 4.12.1__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.
numbers_parser/cell.py CHANGED
@@ -993,7 +993,7 @@ class Cell(CellStorageFlags, Cacheable):
993
993
  )
994
994
  self._model.add_component_reference(
995
995
  self._style._text_style_obj_id,
996
- parent_id=self._model._table_styles.id(self._table_id),
996
+ component_id=self._model._table_styles.id(self._table_id),
997
997
  )
998
998
 
999
999
  if self._style._cell_style_obj_id is not None:
@@ -1003,7 +1003,7 @@ class Cell(CellStorageFlags, Cacheable):
1003
1003
  )
1004
1004
  self._model.add_component_reference(
1005
1005
  self._style._cell_style_obj_id,
1006
- parent_id=self._model._table_styles.id(self._table_id),
1006
+ component_id=self._model._table_styles.id(self._table_id),
1007
1007
  )
1008
1008
 
1009
1009
  length = 12
numbers_parser/model.py CHANGED
@@ -331,15 +331,159 @@ class _NumbersModel(Cacheable):
331
331
  else:
332
332
  return not table_info.super.caption_hidden
333
333
 
334
+ def find_style_id(self, style_substr: str):
335
+ stylesheet = self.objects[self.stylesheet_id()]
336
+ caption_styles = [
337
+ x for x in stylesheet.identifier_to_style_map if style_substr in x.identifier.lower()
338
+ ]
339
+ return caption_styles[0].style.identifier
340
+
341
+ def caption_paragraph_style_id(self):
342
+ style_map = {
343
+ id: self.objects[id]
344
+ for id in self.find_refs("ParagraphStyleArchive")
345
+ if "Caption" in self.objects[id].super.name
346
+ }
347
+ return list(style_map.keys())[0]
348
+
349
+ @cache(num_args=0)
350
+ def stylesheet_id(self):
351
+ return self.find_refs("StylesheetArchive")[0]
352
+
353
+ def set_reference(self, obj: object, id: int):
354
+ obj.MergeFrom(TSPMessages.Reference(identifier=id))
355
+
356
+ def create_path_source_archive(self, table_id):
357
+ box_size = 100.0
358
+ return TSDArchives.PathSourceArchive(
359
+ horizontalFlip=False,
360
+ verticalFlip=False,
361
+ bezier_path_source=TSDArchives.BezierPathSourceArchive(
362
+ naturalSize=TSPMessages.Size(width=self.table_width(table_id), height=0.0),
363
+ path=TSPMessages.Path(
364
+ elements=[
365
+ TSPMessages.Path.Element(
366
+ type=TSPMessages.Path.ElementType.moveTo,
367
+ points=[TSPMessages.Point(x=0.0, y=0.0)],
368
+ ),
369
+ TSPMessages.Path.Element(
370
+ type=TSPMessages.Path.ElementType.lineTo,
371
+ points=[TSPMessages.Point(x=box_size, y=0.0)],
372
+ ),
373
+ TSPMessages.Path.Element(
374
+ type=TSPMessages.Path.ElementType.lineTo,
375
+ points=[TSPMessages.Point(x=box_size, y=box_size)],
376
+ ),
377
+ TSPMessages.Path.Element(
378
+ type=TSPMessages.Path.ElementType.lineTo,
379
+ points=[TSPMessages.Point(x=0.0, y=box_size)],
380
+ ),
381
+ TSPMessages.Path.Element(
382
+ type=TSPMessages.Path.ElementType.closeSubpath,
383
+ ),
384
+ TSPMessages.Path.Element(
385
+ type=TSPMessages.Path.ElementType.moveTo,
386
+ points=[TSPMessages.Point(x=0.0, y=0.0)],
387
+ ),
388
+ ]
389
+ ),
390
+ ),
391
+ )
392
+
393
+ def create_caption_archive(self, table_id):
394
+ table_info_id = self.table_info_id(table_id)
395
+ table_info = self.objects[table_info_id]
396
+ caption_placement_id, _ = self.objects.create_object_from_dict(
397
+ "CalculationEngine",
398
+ {
399
+ "caption_anchor_location": 1, # UPPER_LEFT
400
+ "drawable_anchor_location": 7, # LOWER_LEFT
401
+ },
402
+ TSAArchives.CaptionPlacementArchive,
403
+ )
404
+ caption_info_id, caption_info = self.objects.create_object_from_dict(
405
+ "CalculationEngine",
406
+ {"childInfoKind": "Caption", "placement": {"identifier": caption_placement_id}},
407
+ TSAArchives.CaptionInfoArchive,
408
+ )
409
+ storage_id, storage = self.objects.create_object_from_dict(
410
+ "CalculationEngine",
411
+ {
412
+ "text": ["Caption"],
413
+ "in_document": True,
414
+ "style_sheet": {"identifier": self.stylesheet_id()},
415
+ "table_para_style": {
416
+ "entries": [
417
+ {
418
+ "character_index": 0,
419
+ "object": {"identifier": self.caption_paragraph_style_id()},
420
+ }
421
+ ]
422
+ },
423
+ "table_list_style": {
424
+ "entries": [
425
+ {
426
+ "character_index": 0,
427
+ "object": {"identifier": self.find_style_id("liststyle")},
428
+ }
429
+ ]
430
+ },
431
+ "table_para_starts": {"entries": [{"character_index": 0, "first": 0, "second": 0}]},
432
+ "table_para_bidi": {"entries": [{"character_index": 0, "first": 0, "second": 0}]},
433
+ "table_drop_cap_style": {"entries": [{"character_index": 0}]},
434
+ },
435
+ TSWPArchives.StorageArchive,
436
+ )
437
+ for object_id in [
438
+ storage_id,
439
+ self.find_style_id("caption"),
440
+ self.find_style_id("liststyle"),
441
+ self.caption_paragraph_style_id(),
442
+ ]:
443
+ self.add_component_reference(
444
+ object_id, location="CalculationEngine", component_id=self.stylesheet_id()
445
+ )
446
+ caption_info.super.MergeFrom(
447
+ TSWPArchives.ShapeInfoArchive(
448
+ is_text_box=True,
449
+ owned_storage=TSPMessages.Reference(identifier=storage_id),
450
+ deprecated_storage=TSPMessages.Reference(identifier=storage_id),
451
+ super=TSDArchives.ShapeArchive(
452
+ super=self.create_drawable(table_info_id, 0, 500, flags=1),
453
+ style={"identifier": self.find_style_id("caption")},
454
+ strokePatternOffsetDistance=0.0,
455
+ pathsource=self.create_path_source_archive(table_id),
456
+ ),
457
+ )
458
+ )
459
+
460
+ self.set_reference(table_info.super.caption, caption_info_id)
461
+ component = self.metadata_component(self.calc_engine_id())
462
+ component.object_uuid_map_entries.append(
463
+ TSPArchiveMessages.ObjectUUIDMapEntry(
464
+ identifier=caption_info_id, uuid=NumbersUUID().protobuf2
465
+ )
466
+ )
467
+
334
468
  def caption_text(self, table_id: int, caption: str = None) -> str:
335
469
  table_info = self.objects[self.table_info_id(table_id)]
336
470
  caption_info_id = table_info.super.caption.identifier
337
- caption_storage_id = self.objects[caption_info_id].super.owned_storage.identifier
338
- caption_text = self.objects[caption_storage_id].text
471
+ caption_archive = self.objects[caption_info_id]
472
+
473
+ if caption_archive.DESCRIPTOR.name == "StandinCaptionArchive":
474
+ if caption is None:
475
+ return "Caption"
476
+ else:
477
+ self.create_caption_archive(table_id)
478
+ caption_info_id = table_info.super.caption.identifier
479
+ caption_archive = self.objects[caption_info_id]
480
+
481
+ caption_storage_id = caption_archive.super.owned_storage.identifier
339
482
  if caption is not None:
340
- caption_text[0] = caption
483
+ clear_field_container(self.objects[caption_storage_id].text)
484
+ self.objects[caption_storage_id].text.append(caption)
341
485
  else:
342
- return caption_text[0]
486
+ return self.objects[caption_storage_id].text[0]
343
487
 
344
488
  @cache()
345
489
  def table_tiles(self, table_id):
@@ -882,7 +1026,7 @@ class _NumbersModel(Cacheable):
882
1026
  merge_map.cell_range.append(cell_range)
883
1027
 
884
1028
  base_data_store = self.objects[table_id].base_data_store
885
- base_data_store.merge_region_map.CopyFrom(TSPMessages.Reference(identifier=merge_map_id))
1029
+ self.set_reference(base_data_store.merge_region_map, merge_map_id)
886
1030
 
887
1031
  def recalculate_row_info(
888
1032
  self,
@@ -943,19 +1087,19 @@ class _NumbersModel(Cacheable):
943
1087
  save_token=1,
944
1088
  )
945
1089
  self.objects[PACKAGE_ID].components.append(component_info)
946
- self.add_component_reference(object_id, parent)
1090
+ self.add_component_reference(object_id, location=parent)
947
1091
 
948
1092
  def add_component_reference(
949
1093
  self,
950
1094
  object_id: int,
951
1095
  location: Optional[str] = None,
952
- parent_id: Optional[int] = None,
1096
+ component_id: Optional[int] = None,
953
1097
  is_weak: bool = False,
954
1098
  ):
955
1099
  """Add an external reference to an object in a metadata component."""
956
- component = self.metadata_component(location or parent_id)
957
- if parent_id is not None:
958
- params = {"object_identifier": object_id, "component_identifier": parent_id}
1100
+ component = self.metadata_component(location or component_id)
1101
+ if component_id is not None:
1102
+ params = {"object_identifier": object_id, "component_identifier": component_id}
959
1103
  else:
960
1104
  params = {"component_identifier": object_id}
961
1105
  if is_weak:
@@ -1175,7 +1319,15 @@ class _NumbersModel(Cacheable):
1175
1319
 
1176
1320
  return self.table_height(table_id) + y_offset
1177
1321
 
1178
- def create_drawable(self, sheet_id: int, x: float, y: float) -> object:
1322
+ def create_drawable(
1323
+ self,
1324
+ sheet_id: int,
1325
+ x: float,
1326
+ y: float,
1327
+ flags: int = 3,
1328
+ height: float = 231.0,
1329
+ width: float = 494.0,
1330
+ ) -> object:
1179
1331
  """Create a DrawableArchive for a new table in a sheet."""
1180
1332
  table_x = x if x is not None else 0.0
1181
1333
  table_y = y if y is not None else self.last_table_offset(sheet_id) + DEFAULT_TABLE_OFFSET
@@ -1183,9 +1335,9 @@ class _NumbersModel(Cacheable):
1183
1335
  parent=TSPMessages.Reference(identifier=sheet_id),
1184
1336
  geometry=TSDArchives.GeometryArchive(
1185
1337
  angle=0.0,
1186
- flags=3,
1338
+ flags=flags,
1187
1339
  position=TSPMessages.Point(x=table_x, y=table_y),
1188
- size=TSPMessages.Size(height=231.0, width=494.0),
1340
+ size=TSPMessages.Size(height=height, width=width),
1189
1341
  ),
1190
1342
  )
1191
1343
 
@@ -1239,35 +1391,12 @@ class _NumbersModel(Cacheable):
1239
1391
  "Tables/HeaderStorageBucket-{}",
1240
1392
  )
1241
1393
 
1242
- from_table_info = self.objects[self.table_info_id(from_table_id)]
1243
- from_caption_info = self.objects[from_table_info.super.caption.identifier]
1244
- caption_storage_id, caption_storage = self.objects.create_object_from_dict(
1245
- "CalculationEngine",
1246
- {"text": ["Caption"]},
1247
- TSWPArchives.StorageArchive,
1248
- )
1249
- caption_storage.MergeFrom(self.objects[from_caption_info.super.owned_storage.identifier])
1250
- caption_info_id, caption_info = self.objects.create_object_from_dict(
1251
- "CalculationEngine",
1252
- {},
1253
- TSAArchives.CaptionInfoArchive,
1254
- )
1255
- caption_info.super.CopyFrom(from_caption_info.super)
1256
- caption_info.super.super.CopyFrom(from_caption_info.super.super)
1257
- caption_info.super.super.super.CopyFrom(from_caption_info.super.super.super)
1258
- caption_info.super.deprecated_storage.MergeFrom(
1259
- TSPMessages.Reference(identifier=caption_storage_id)
1260
- )
1261
- caption_info.super.owned_storage.MergeFrom(
1262
- TSPMessages.Reference(identifier=caption_storage_id)
1263
- )
1264
-
1265
1394
  sidecar_id, _ = self.objects.create_object_from_dict(
1266
1395
  "CalculationEngine",
1267
1396
  {"max_order": 1, "column_count": 0, "row_count": 0},
1268
1397
  TSTArchives.StrokeSidecarArchive,
1269
1398
  )
1270
- table_model.stroke_sidecar.MergeFrom(TSPMessages.Reference(identifier=sidecar_id))
1399
+ self.set_reference(table_model.stroke_sidecar, sidecar_id)
1271
1400
 
1272
1401
  style_table_id, _ = self.objects.create_object_from_dict(
1273
1402
  "Index/Tables/DataList-{}",
@@ -1347,11 +1476,11 @@ class _NumbersModel(Cacheable):
1347
1476
  )
1348
1477
  table_info.tableModel.MergeFrom(TSPMessages.Reference(identifier=table_model_id))
1349
1478
  table_info.super.MergeFrom(self.create_drawable(sheet_id, x, y))
1350
- caption_info.super.super.super.parent.MergeFrom(
1351
- TSPMessages.Reference(identifier=table_info_id)
1479
+
1480
+ self.add_component_reference(
1481
+ table_info_id, location="Document", component_id=self.calc_engine_id()
1352
1482
  )
1353
- table_info.super.caption.MergeFrom(TSPMessages.Reference(identifier=caption_info_id))
1354
- self.add_component_reference(table_info_id, "Document", self.calc_engine_id())
1483
+ self.create_caption_archive(table_model_id)
1355
1484
 
1356
1485
  self.add_formula_owner(
1357
1486
  table_info_id,
@@ -1452,7 +1581,9 @@ class _NumbersModel(Cacheable):
1452
1581
  TNArchives.SheetArchive,
1453
1582
  )
1454
1583
 
1455
- self.add_component_reference(sheet_id, "CalculationEngine", DOCUMENT_ID, is_weak=True)
1584
+ self.add_component_reference(
1585
+ sheet_id, location="CalculationEngine", component_id=DOCUMENT_ID, is_weak=True
1586
+ )
1456
1587
 
1457
1588
  self.objects[DOCUMENT_ID].sheets.append(TSPMessages.Reference(identifier=sheet_id))
1458
1589
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: numbers-parser
3
- Version: 4.11.5
3
+ Version: 4.12.1
4
4
  Summary: Read and write Apple Numbers spreadsheets
5
5
  Home-page: https://github.com/masaccio/numbers-parser
6
6
  License: MIT
@@ -15,6 +15,7 @@ Classifier: Programming Language :: Python :: 3.10
15
15
  Classifier: Programming Language :: Python :: 3.11
16
16
  Classifier: Programming Language :: Python :: 3.12
17
17
  Classifier: Topic :: Office/Business :: Financial :: Spreadsheet
18
+ Requires-Dist: colorama (>=0.4.6,<0.5.0)
18
19
  Requires-Dist: compact-json (>=1.1.3,<2.0.0)
19
20
  Requires-Dist: enum-tools (>=0.11)
20
21
  Requires-Dist: importlib-resources (>=6.1)
@@ -383,9 +384,12 @@ Current known limitations of `numbers-parser` which may be implemented in the fu
383
384
  - Creating cells of type `BulletedTextCell` is not supported
384
385
  - New tables are inserted with a fixed offset below the last table in a
385
386
  worksheet which does not take into account title or caption size
387
+ - Captions can be created and edited as of numbers-parser version 4.12, but cannot
388
+ be styled. New captions adopt the first caption style available in the current
389
+ document
386
390
  - Formulas cannot be written to a document
387
391
  - Pivot tables are unsupported and saving a document with a pivot table issues
388
- a UnsupportedWarning.
392
+ a UnsupportedWarning (see [issue 73](https://github.com/masaccio/numbers-parser/issues/73) for details).
389
393
 
390
394
  The following limitations are expected to always remain:
391
395
 
@@ -396,7 +400,9 @@ The following limitations are expected to always remain:
396
400
  versions older than 3.11 do not support image filenames with UTF-8 characters
397
401
  [Cell.add_style.bg_image()](https://masaccio.github.io/numbers-parser/api/sheet.html#numbers_parser.Style) returns
398
402
  `None` for such files and issues a `RuntimeWarning`
399
- (see [issue 69](https://github.com/masaccio/numbers-parser/ssues/69) for details).
403
+ (see [issue 69](https://github.com/masaccio/numbers-parser/issues/69) for details).
404
+ - Password-encrypted documents cannot be opened. You must first re-save without
405
+ a password to read (see [issue 88](https://github.com/masaccio/numbers-parser/issues/88) for details).
400
406
 
401
407
  ## License
402
408
 
@@ -2,7 +2,7 @@ numbers_parser/__init__.py,sha256=1guReSiMinXSKkip4UoC-a1X9OAgdiYTTDiZ03FDmqE,14
2
2
  numbers_parser/_cat_numbers.py,sha256=3tLvBQEagGcNL26XxfqguNim0JDtTNgEbiGKpLjBxLI,4623
3
3
  numbers_parser/_unpack_numbers.py,sha256=7B6AJ2GB-MzFdrKal_GMMgUWzVUZ5h9PsPJecGk2ppY,7032
4
4
  numbers_parser/bullets.py,sha256=OnVVMPjhTDrC-ncw52Gb00UEXNmn2Rvd3xi7lfqW3hk,2616
5
- numbers_parser/cell.py,sha256=18xi25ZoIOEwuOEX8T1FyLQBt0lx9jURE0gDIKjyGnI,76340
5
+ numbers_parser/cell.py,sha256=mPSuRAXPx3SW6xUDhYj7T3Y5P9qmUaXL8rXXfZn8GpQ,76346
6
6
  numbers_parser/constants.py,sha256=L9_UhsWMqswsn1sCr-aAx_7mR2k8IHYdhpe8t_CP6lM,9924
7
7
  numbers_parser/containers.py,sha256=j0FhaXPUG5YSRK5_3WIxXQOeckHVu24KMlYetWIZ_Xg,4882
8
8
  numbers_parser/currencies.py,sha256=8k4a3WKmDoHeurkDICymHX13N7ManHSTaka_JNXCZYA,3767
@@ -50,11 +50,11 @@ numbers_parser/generated/functionmap.py,sha256=VdZo0ERMYONcrnJFwABcSCHb8pjA4wY2o
50
50
  numbers_parser/generated/mapping.py,sha256=xt1NaZtTse1OX3vBizENKkWl-0OgNb4SMJ0Uo-rRz0U,32342
51
51
  numbers_parser/iwafile.py,sha256=4_MMtHdWMAfIzwODyaM7DsWKh-8yJ2blTfbues8sbdI,11915
52
52
  numbers_parser/iwork.py,sha256=CXXM797MqcIokovrIBAx--LNG7tIVpKqeBwR4V2OrzQ,9141
53
- numbers_parser/model.py,sha256=OP11pcTZeDUKynoNRvVA5CCkprag_QBal1zULhTgb6E,101421
53
+ numbers_parser/model.py,sha256=Pmk-EDAru0zSmdzlA21yi2ASzz8W-aXWnRiMFOfnaDE,106550
54
54
  numbers_parser/numbers_cache.py,sha256=1ghEBghQAYFpPiEeOtb74i016mXc039v1pOubbqvaLs,1141
55
55
  numbers_parser/numbers_uuid.py,sha256=q0IbHFKuBXC7MnZN3g55dgCVKOLD-4SO4MdXeN6dt0g,2699
56
- numbers_parser-4.11.5.dist-info/LICENSE.rst,sha256=8vTa1-5KSdHrTpU9rlheO5005EWReEPMpjV7BjSaMc4,1050
57
- numbers_parser-4.11.5.dist-info/METADATA,sha256=9_q8XcLwLgdMu30GSGrLCEAu_CgjpRXWEgb3GgnOeuw,16212
58
- numbers_parser-4.11.5.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
59
- numbers_parser-4.11.5.dist-info/entry_points.txt,sha256=V91uB9vBPxf3eCY1h-0syv21imYCT0MJfMxf87DmwIk,115
60
- numbers_parser-4.11.5.dist-info/RECORD,,
56
+ numbers_parser-4.12.1.dist-info/LICENSE.rst,sha256=8vTa1-5KSdHrTpU9rlheO5005EWReEPMpjV7BjSaMc4,1050
57
+ numbers_parser-4.12.1.dist-info/METADATA,sha256=8QSGsiUju4R9rYJ0nz2PrCe31_M0ZRj7z7SiHx0keSM,16697
58
+ numbers_parser-4.12.1.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
59
+ numbers_parser-4.12.1.dist-info/entry_points.txt,sha256=V91uB9vBPxf3eCY1h-0syv21imYCT0MJfMxf87DmwIk,115
60
+ numbers_parser-4.12.1.dist-info/RECORD,,