numbers-parser 4.11.2__tar.gz → 4.11.4__tar.gz

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 (59) hide show
  1. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/PKG-INFO +1 -1
  2. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/pyproject.toml +1 -1
  3. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/_unpack_numbers.py +5 -3
  4. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/document.py +26 -6
  5. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/model.py +113 -11
  6. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/LICENSE.rst +0 -0
  7. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/README.md +0 -0
  8. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/__init__.py +0 -0
  9. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/_cat_numbers.py +0 -0
  10. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/bullets.py +0 -0
  11. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/cell.py +0 -0
  12. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/constants.py +0 -0
  13. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/containers.py +0 -0
  14. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/currencies.py +0 -0
  15. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/data/empty.numbers +0 -0
  16. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/exceptions.py +0 -0
  17. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/experimental.py +0 -0
  18. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/formula.py +0 -0
  19. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/generated/TNArchives_pb2.py +0 -0
  20. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/generated/TNArchives_sos_pb2.py +0 -0
  21. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/generated/TNCommandArchives_pb2.py +0 -0
  22. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/generated/TNCommandArchives_sos_pb2.py +0 -0
  23. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/generated/TSAArchives_pb2.py +0 -0
  24. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/generated/TSAArchives_sos_pb2.py +0 -0
  25. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/generated/TSACommandArchives_sos_pb2.py +0 -0
  26. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/generated/TSCEArchives_pb2.py +0 -0
  27. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/generated/TSCH3DArchives_pb2.py +0 -0
  28. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/generated/TSCHArchives_Common_pb2.py +0 -0
  29. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/generated/TSCHArchives_GEN_pb2.py +0 -0
  30. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/generated/TSCHArchives_pb2.py +0 -0
  31. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/generated/TSCHArchives_sos_pb2.py +0 -0
  32. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/generated/TSCHCommandArchives_pb2.py +0 -0
  33. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/generated/TSCHPreUFFArchives_pb2.py +0 -0
  34. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/generated/TSCKArchives_pb2.py +0 -0
  35. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/generated/TSCKArchives_sos_pb2.py +0 -0
  36. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/generated/TSDArchives_pb2.py +0 -0
  37. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/generated/TSDArchives_sos_pb2.py +0 -0
  38. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/generated/TSDCommandArchives_pb2.py +0 -0
  39. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/generated/TSKArchives_pb2.py +0 -0
  40. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/generated/TSPArchiveMessages_pb2.py +0 -0
  41. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/generated/TSPDatabaseMessages_pb2.py +0 -0
  42. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/generated/TSPMessages_pb2.py +0 -0
  43. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/generated/TSSArchives_pb2.py +0 -0
  44. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/generated/TSSArchives_sos_pb2.py +0 -0
  45. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/generated/TSTArchives_pb2.py +0 -0
  46. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/generated/TSTArchives_sos_pb2.py +0 -0
  47. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/generated/TSTCommandArchives_pb2.py +0 -0
  48. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/generated/TSTStylePropertyArchiving_pb2.py +0 -0
  49. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/generated/TSWPArchives_pb2.py +0 -0
  50. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/generated/TSWPArchives_sos_pb2.py +0 -0
  51. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/generated/TSWPCommandArchives_pb2.py +0 -0
  52. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/generated/__init__.py +0 -0
  53. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/generated/fontmap.py +0 -0
  54. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/generated/functionmap.py +0 -0
  55. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/generated/mapping.py +0 -0
  56. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/iwafile.py +0 -0
  57. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/iwork.py +0 -0
  58. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/numbers_cache.py +0 -0
  59. {numbers_parser-4.11.2 → numbers_parser-4.11.4}/src/numbers_parser/numbers_uuid.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: numbers-parser
3
- Version: 4.11.2
3
+ Version: 4.11.4
4
4
  Summary: Read and write Apple Numbers spreadsheets
5
5
  Home-page: https://github.com/masaccio/numbers-parser
6
6
  License: MIT
@@ -12,7 +12,7 @@ name = "numbers-parser"
12
12
  packages = [{include = "numbers_parser", from = "src"}]
13
13
  readme = "README.md"
14
14
  repository = "https://github.com/masaccio/numbers-parser"
15
- version = "4.11.2"
15
+ version = "4.11.4"
16
16
 
17
17
  [tool.poetry.scripts]
18
18
  cat-numbers = "numbers_parser._cat_numbers:main"
@@ -125,7 +125,9 @@ def main():
125
125
  parser.add_argument("--hex-uuids", action="store_true", help="print UUIDs as hex")
126
126
  parser.add_argument("--pretty-storage", action="store_true", help="pretty print cell storage")
127
127
  parser.add_argument(
128
- "--compact-json", action="store_true", help="Format JSON compactly as possible",
128
+ "--compact-json",
129
+ action="store_true",
130
+ help="Format JSON compactly as possible",
129
131
  )
130
132
  parser.add_argument("--pretty", action="store_true", help="Enable all prettifying options")
131
133
  parser.add_argument("--output", "-o", help="directory name to unpack into")
@@ -150,7 +152,7 @@ def main():
150
152
  else:
151
153
  logger.setLevel("ERROR")
152
154
  for document in args.document:
153
- args.output or document.replace(".numbers", "")
155
+ output_dir = args.output or document.replace(".numbers", "")
154
156
  try:
155
157
  iwork = IWork(
156
158
  handler=NumbersUnpacker(
@@ -158,7 +160,7 @@ def main():
158
160
  pretty=args.pretty,
159
161
  pretty_storage=args.pretty_storage,
160
162
  compact_json=args.compact_json,
161
- output_dir=args.output,
163
+ output_dir=output_dir,
162
164
  ),
163
165
  )
164
166
  iwork.open(Path(document))
@@ -989,13 +989,21 @@ class Table(Cacheable):
989
989
  self.num_rows += num_rows
990
990
  self._model.number_of_rows(self._table_id, self.num_rows)
991
991
 
992
- row = [
993
- Cell._empty_cell(self._table_id, self.num_rows - 1, col, self._model)
994
- for col in range(self.num_cols)
995
- ]
996
- rows = [row.copy() for _ in range(num_rows)]
992
+ rows = []
993
+ for row in range(start_row, start_row + num_rows):
994
+ rows.append(
995
+ [
996
+ Cell._empty_cell(self._table_id, row, col, self._model)
997
+ for col in range(self.num_cols)
998
+ ]
999
+ )
997
1000
  self._data[start_row:start_row] = rows
998
1001
 
1002
+ for row in range(start_row, self.num_rows):
1003
+ for col in range(self.num_cols):
1004
+ self._data[row][col].row = row
1005
+ self._data[row][col].col = col
1006
+
999
1007
  if default is not None:
1000
1008
  for row in range(start_row, start_row + num_rows):
1001
1009
  for col in range(self.num_cols):
@@ -1045,10 +1053,14 @@ class Table(Cacheable):
1045
1053
 
1046
1054
  for row in range(self.num_rows):
1047
1055
  cols = [
1048
- Cell._empty_cell(self._table_id, row, col, self._model) for col in range(num_cols)
1056
+ Cell._empty_cell(self._table_id, row, start_col + col, self._model)
1057
+ for col in range(num_cols)
1049
1058
  ]
1050
1059
  self._data[row][start_col:start_col] = cols
1051
1060
 
1061
+ for col in range(len(self._data[row])):
1062
+ self._data[row][col].col = col
1063
+
1052
1064
  if default is not None:
1053
1065
  for col in range(start_col, start_col + num_cols):
1054
1066
  self.write(row, col, default)
@@ -1091,6 +1103,12 @@ class Table(Cacheable):
1091
1103
  self.num_rows -= num_rows
1092
1104
  self._model.number_of_rows(self._table_id, self.num_rows)
1093
1105
 
1106
+ if start_row is not None:
1107
+ for row in range(start_row, self.num_rows):
1108
+ for col in range(self.num_cols):
1109
+ self._data[row][col].row = row
1110
+ self._data[row][col].col = col
1111
+
1094
1112
  def delete_column(
1095
1113
  self,
1096
1114
  num_cols: Optional[int] = 1,
@@ -1120,6 +1138,8 @@ class Table(Cacheable):
1120
1138
  del self._data[row][start_col : start_col + num_cols]
1121
1139
  else:
1122
1140
  del self._data[row][-num_cols:]
1141
+ for col in range(len(self._data[row])):
1142
+ self._data[row][col].col = col
1123
1143
 
1124
1144
  self.num_cols -= num_cols
1125
1145
  self._model.number_of_columns(self._table_id, self.num_cols)
@@ -2,6 +2,7 @@ import re
2
2
  from array import array
3
3
  from collections import defaultdict
4
4
  from hashlib import sha1
5
+ from math import floor
5
6
  from pathlib import Path
6
7
  from struct import pack
7
8
  from typing import Dict, List, Optional, Tuple, Union
@@ -853,10 +854,7 @@ class _NumbersModel(Cacheable):
853
854
 
854
855
  for col, cells in enumerate(col_data):
855
856
  num_rows = len(cells) - sum([isinstance(x, MergedCell) for x in cells])
856
- if table_id in self._col_widths and col in self._col_widths[table_id]:
857
- width = self._col_widths[table_id][col]
858
- else:
859
- width = current_column_widths[col]
857
+ width = current_column_widths[col]
860
858
  header = TSTArchives.HeaderStorageBucket.Header(
861
859
  index=col,
862
860
  numberOfCells=num_rows,
@@ -1040,7 +1038,7 @@ class _NumbersModel(Cacheable):
1040
1038
  height = 0.0
1041
1039
  for row in range(self.number_of_rows(table_id)):
1042
1040
  height += self.row_height(table_id, row)
1043
- return round(height)
1041
+ return floor(height)
1044
1042
 
1045
1043
  def row_height(self, table_id: int, row: int, height: Optional[int] = None) -> int:
1046
1044
  if height is not None:
@@ -1058,9 +1056,34 @@ class _NumbersModel(Cacheable):
1058
1056
  buckets = self.objects[bucket_id].headers
1059
1057
  bucket_map = {x.index: x for x in buckets}
1060
1058
  if row in bucket_map and bucket_map[row].size != 0.0:
1061
- return round(bucket_map[row].size)
1059
+ height = round(bucket_map[row].size)
1062
1060
  else:
1063
- return round(table_model.default_row_height)
1061
+ height = round(table_model.default_row_height)
1062
+
1063
+ data = self._table_data[table_id]
1064
+ max_top_border = max(
1065
+ [0.0]
1066
+ + [
1067
+ data[row][col].border.top.width
1068
+ for col in range(len(data[row]))
1069
+ if data[row][col].border.top is not None
1070
+ ]
1071
+ )
1072
+ max_bottom_border = max(
1073
+ [0.0]
1074
+ + [
1075
+ data[row][col].border.bottom.width
1076
+ for col in range(len(data[row]))
1077
+ if data[row][col].border.bottom is not None
1078
+ ]
1079
+ )
1080
+ height += max_top_border / 2
1081
+ height += max_bottom_border / 2
1082
+
1083
+ if table_id not in self._row_heights:
1084
+ self._row_heights[table_id] = {}
1085
+ self._row_heights[table_id][row] = floor(height)
1086
+ return self._row_heights[table_id][row]
1064
1087
 
1065
1088
  def table_width(self, table_id: int) -> int:
1066
1089
  """Return the width of a table in points."""
@@ -1085,9 +1108,34 @@ class _NumbersModel(Cacheable):
1085
1108
  buckets = self.objects[bucket_id].headers
1086
1109
  bucket_map = {x.index: x for x in buckets}
1087
1110
  if col in bucket_map and bucket_map[col].size != 0.0:
1088
- return round(bucket_map[col].size)
1111
+ width = round(bucket_map[col].size)
1089
1112
  else:
1090
- return round(table_model.default_column_width)
1113
+ width = round(table_model.default_column_width)
1114
+
1115
+ data = self._table_data[table_id]
1116
+ max_left_border = max(
1117
+ [0.0]
1118
+ + [
1119
+ data[row][col].border.left.width
1120
+ for row in range(len(data))
1121
+ if data[row][col].border.left is not None
1122
+ ]
1123
+ )
1124
+ max_right_border = max(
1125
+ [0.0]
1126
+ + [
1127
+ data[row][col].border.right.width
1128
+ for row in range(len(data))
1129
+ if data[row][col].border.right is not None
1130
+ ]
1131
+ )
1132
+ width += max_left_border / 2
1133
+ width += max_right_border / 2
1134
+
1135
+ if table_id not in self._col_widths:
1136
+ self._col_widths[table_id] = {}
1137
+ self._col_widths[table_id][col] = floor(width)
1138
+ return self._col_widths[table_id][col]
1091
1139
 
1092
1140
  def num_header_rows(self, table_id: int, num_headers: Optional[int] = None) -> int:
1093
1141
  """Return/set the number of header rows."""
@@ -1190,6 +1238,44 @@ class _NumbersModel(Cacheable):
1190
1238
  "Tables/HeaderStorageBucket-{}",
1191
1239
  )
1192
1240
 
1241
+ # caption_storage_id, caption_storage = self.objects.create_object_from_dict(
1242
+ # "CalculationEngine",
1243
+ # {"text": ["TEST Caption"]},
1244
+ # TSWPArchives.StorageArchive,
1245
+ # )
1246
+ # caption_info_id, caption_info = self.objects.create_object_from_dict(
1247
+ # "CalculationEngine",
1248
+ # {},
1249
+ # TSAArchives.CaptionInfoArchive,
1250
+ # )
1251
+ # # caption_info.super.MergeFrom(TSWPArchives.ShapeInfoArchive())
1252
+ # # caption_info.super.super.MergeFrom(TSDArchives.ShapeArchive())
1253
+ # # caption_info.super.super.super.MergeFrom(TSDArchives.DrawableArchive())
1254
+ # from_table_info = self.objects[self.table_info_id(from_table_id)]
1255
+ # from_caption_info = self.objects[from_table_info.super.caption.identifier]
1256
+ # caption_info.super.CopyFrom(from_caption_info.super)
1257
+ # caption_info.super.super.CopyFrom(from_caption_info.super.super)
1258
+ # caption_info.super.super.super.CopyFrom(from_caption_info.super.super.super)
1259
+ # caption_info.super.deprecated_storage.MergeFrom(
1260
+ # TSPMessages.Reference(identifier=caption_storage_id)
1261
+ # )
1262
+ # caption_info.super.owned_storage.MergeFrom(
1263
+ # TSPMessages.Reference(identifier=caption_storage_id)
1264
+ # )
1265
+
1266
+ # TST.TableModelArchive.stroke_sidecar -> TST.StrokeSidecarArchive
1267
+ # TST.StrokeSidecarArchive
1268
+ # top_row_stroke_layers -> [TST.StrokeLayerArchive]
1269
+ # left_row_stroke_layers -> [TST.StrokeLayerArchive]
1270
+ # right_row_stroke_layers -> [TST.StrokeLayerArchive]
1271
+ # bottom_row_stroke_layers -> [TST.StrokeLayerArchive]
1272
+ sidecar_id, _ = self.objects.create_object_from_dict(
1273
+ "CalculationEngine",
1274
+ {"max_order": 1, "column_count": 0, "row_count": 0},
1275
+ TSTArchives.StrokeSidecarArchive,
1276
+ )
1277
+ table_model.stroke_sidecar.MergeFrom(TSPMessages.Reference(identifier=sidecar_id))
1278
+
1193
1279
  style_table_id, _ = self.objects.create_object_from_dict(
1194
1280
  "Index/Tables/DataList-{}",
1195
1281
  {"listType": TSTArchives.TableDataList.ListType.STYLE, "nextListID": 1},
@@ -1255,11 +1341,11 @@ class _NumbersModel(Cacheable):
1255
1341
  TSPMessages.Reference(identifier=row_headers_id),
1256
1342
  )
1257
1343
 
1258
- data = [
1344
+ self._table_data[table_model_id] = [
1259
1345
  [Cell._empty_cell(table_model_id, row, col, self) for col in range(num_cols)]
1260
1346
  for row in range(num_rows)
1261
1347
  ]
1262
- self.recalculate_table_data(table_model_id, data)
1348
+ self.recalculate_table_data(table_model_id, self._table_data[table_model_id])
1263
1349
 
1264
1350
  table_info_id, table_info = self.objects.create_object_from_dict(
1265
1351
  "CalculationEngine",
@@ -1268,6 +1354,10 @@ class _NumbersModel(Cacheable):
1268
1354
  )
1269
1355
  table_info.tableModel.MergeFrom(TSPMessages.Reference(identifier=table_model_id))
1270
1356
  table_info.super.MergeFrom(self.create_drawable(sheet_id, x, y))
1357
+ # caption_info.super.super.super.parent.MergeFrom(
1358
+ # TSPMessages.Reference(identifier=table_info_id)
1359
+ # )
1360
+ # table_info.super.caption.MergeFrom(TSPMessages.Reference(identifier=caption_info_id))
1271
1361
  self.add_component_reference(table_info_id, "Document", self.calc_engine_id())
1272
1362
 
1273
1363
  self.add_formula_owner(
@@ -1982,21 +2072,33 @@ class _NumbersModel(Cacheable):
1982
2072
  cell._border.top = border_value
1983
2073
  if (cell := self.cell_for_stroke(table_id, "bottom", row - 1, col)) is not None:
1984
2074
  cell._border.bottom = border_value
2075
+ if table_id in self._row_heights:
2076
+ self._row_heights[table_id].pop(row, None)
2077
+ self._row_heights[table_id].pop(row - 1, None)
1985
2078
  elif side == "right":
1986
2079
  if (cell := self.cell_for_stroke(table_id, "right", row, col)) is not None:
1987
2080
  cell._border.right = border_value
1988
2081
  if (cell := self.cell_for_stroke(table_id, "left", row, col + 1)) is not None:
1989
2082
  cell._border.left = border_value
2083
+ if table_id in self._col_widths:
2084
+ self._col_widths[table_id].pop(col, None)
2085
+ self._col_widths[table_id].pop(col + 1, None)
1990
2086
  elif side == "bottom":
1991
2087
  if (cell := self.cell_for_stroke(table_id, "bottom", row, col)) is not None:
1992
2088
  cell._border.bottom = border_value
1993
2089
  if (cell := self.cell_for_stroke(table_id, "top", row + 1, col)) is not None:
1994
2090
  cell._border.top = border_value
2091
+ if table_id in self._row_heights:
2092
+ self._row_heights[table_id].pop(row, None)
2093
+ self._row_heights[table_id].pop(row + 1, None)
1995
2094
  else: # left border
1996
2095
  if (cell := self.cell_for_stroke(table_id, "left", row, col)) is not None:
1997
2096
  cell._border.left = border_value
1998
2097
  if (cell := self.cell_for_stroke(table_id, "right", row, col - 1)) is not None:
1999
2098
  cell._border.right = border_value
2099
+ if table_id in self._col_widths:
2100
+ self._col_widths[table_id].pop(col, None)
2101
+ self._col_widths[table_id].pop(col - 1, None)
2000
2102
 
2001
2103
  def extract_strokes_in_layers(self, table_id: int, layer_ids: List, side: str):
2002
2104
  for layer_id in layer_ids: