numbers-parser 4.14.1__py3-none-any.whl → 4.14.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- numbers_parser/__init__.py +3 -3
- numbers_parser/_cat_numbers.py +10 -11
- numbers_parser/_csv2numbers.py +13 -14
- numbers_parser/_unpack_numbers.py +4 -4
- numbers_parser/cell.py +257 -229
- numbers_parser/constants.py +6 -3
- numbers_parser/containers.py +11 -10
- numbers_parser/document.py +196 -149
- numbers_parser/exceptions.py +1 -8
- numbers_parser/formula.py +28 -30
- numbers_parser/generated/TSKArchives_pb2.py +92 -92
- numbers_parser/generated/TSSArchives_pb2.py +36 -36
- numbers_parser/generated/TSWPCommandArchives_pb2.py +99 -99
- numbers_parser/generated/fontmap.py +16 -10
- numbers_parser/generated/mapping.py +0 -1
- numbers_parser/iwafile.py +16 -16
- numbers_parser/iwork.py +32 -17
- numbers_parser/model.py +222 -210
- numbers_parser/numbers_cache.py +6 -7
- numbers_parser/numbers_uuid.py +4 -1
- numbers_parser/roman.py +21 -7
- {numbers_parser-4.14.1.dist-info → numbers_parser-4.14.2.dist-info}/METADATA +14 -1
- {numbers_parser-4.14.1.dist-info → numbers_parser-4.14.2.dist-info}/RECORD +26 -26
- {numbers_parser-4.14.1.dist-info → numbers_parser-4.14.2.dist-info}/LICENSE.rst +0 -0
- {numbers_parser-4.14.1.dist-info → numbers_parser-4.14.2.dist-info}/WHEEL +0 -0
- {numbers_parser-4.14.1.dist-info → numbers_parser-4.14.2.dist-info}/entry_points.txt +0 -0
numbers_parser/__init__.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
"""Parse and extract data from Apple Numbers spreadsheets."""
|
|
2
2
|
|
|
3
3
|
import importlib.metadata
|
|
4
4
|
import os
|
|
@@ -20,11 +20,11 @@ _DEFAULT_NUMBERS_INSTALL_PATH = "/Applications/Numbers.app"
|
|
|
20
20
|
_VERSION_PLIST_PATH = "Contents/version.plist"
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
def _get_version():
|
|
23
|
+
def _get_version() -> str:
|
|
24
24
|
return __version__
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
def _check_installed_numbers_version():
|
|
27
|
+
def _check_installed_numbers_version() -> str:
|
|
28
28
|
try:
|
|
29
29
|
fp = open(os.path.join(_DEFAULT_NUMBERS_INSTALL_PATH, _VERSION_PLIST_PATH), "rb")
|
|
30
30
|
except OSError:
|
numbers_parser/_cat_numbers.py
CHANGED
|
@@ -71,12 +71,12 @@ def command_line_parser():
|
|
|
71
71
|
return parser
|
|
72
72
|
|
|
73
73
|
|
|
74
|
-
def print_sheet_names(filename):
|
|
74
|
+
def print_sheet_names(filename) -> None:
|
|
75
75
|
for sheet in Document(filename).sheets:
|
|
76
76
|
print(f"{filename}: {sheet.name}")
|
|
77
77
|
|
|
78
78
|
|
|
79
|
-
def print_table_names(filename):
|
|
79
|
+
def print_table_names(filename) -> None:
|
|
80
80
|
for sheet in Document(filename).sheets:
|
|
81
81
|
for table in sheet.tables:
|
|
82
82
|
print(f"{filename}: {sheet.name}: {table.name}")
|
|
@@ -85,19 +85,18 @@ def print_table_names(filename):
|
|
|
85
85
|
def cell_as_string(args, cell):
|
|
86
86
|
if isinstance(cell, ErrorCell) and not (args.formulas):
|
|
87
87
|
return "#REF!"
|
|
88
|
-
|
|
88
|
+
if args.formulas and cell.formula is not None:
|
|
89
89
|
return cell.formula
|
|
90
|
-
|
|
90
|
+
if args.formatting and cell.formatted_value is not None:
|
|
91
91
|
return cell.formatted_value
|
|
92
|
-
|
|
92
|
+
if isinstance(cell, NumberCell):
|
|
93
93
|
return sigfig(cell.value, sigfigs=MAX_SIGNIFICANT_DIGITS, warn=False)
|
|
94
|
-
|
|
94
|
+
if cell.value is None:
|
|
95
95
|
return ""
|
|
96
|
-
|
|
97
|
-
return str(cell.value)
|
|
96
|
+
return str(cell.value)
|
|
98
97
|
|
|
99
98
|
|
|
100
|
-
def print_table(args, filename):
|
|
99
|
+
def print_table(args, filename) -> None:
|
|
101
100
|
writer = csv.writer(sys.stdout, dialect="excel")
|
|
102
101
|
for sheet in Document(filename).sheets:
|
|
103
102
|
if args.sheet is not None and sheet.name not in args.sheet:
|
|
@@ -112,7 +111,7 @@ def print_table(args, filename):
|
|
|
112
111
|
writer.writerow(cells)
|
|
113
112
|
|
|
114
113
|
|
|
115
|
-
def main():
|
|
114
|
+
def main() -> None:
|
|
116
115
|
parser = command_line_parser()
|
|
117
116
|
args = parser.parse_args()
|
|
118
117
|
|
|
@@ -138,7 +137,7 @@ def main():
|
|
|
138
137
|
print_table_names(filename)
|
|
139
138
|
else:
|
|
140
139
|
print_table(args, filename)
|
|
141
|
-
except FileFormatError as e:
|
|
140
|
+
except FileFormatError as e: # noqa: PERF203
|
|
142
141
|
print(f"{filename}:", str(e), file=sys.stderr)
|
|
143
142
|
sys.exit(1)
|
|
144
143
|
except FileError as e:
|
numbers_parser/_csv2numbers.py
CHANGED
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import argparse
|
|
6
|
+
import contextlib
|
|
6
7
|
import csv
|
|
7
8
|
import re
|
|
8
9
|
from dataclasses import dataclass
|
|
9
10
|
from datetime import datetime, timezone
|
|
10
11
|
from pathlib import Path
|
|
11
12
|
from sys import exit, stderr
|
|
12
|
-
from typing import NamedTuple
|
|
13
|
+
from typing import NamedTuple
|
|
13
14
|
|
|
14
15
|
from dateutil.parser import parse
|
|
15
16
|
|
|
@@ -68,18 +69,18 @@ class Converter:
|
|
|
68
69
|
"""Parse a date string and return a datetime."""
|
|
69
70
|
return parse(x, dayfirst=self.day_first).replace(tzinfo=timezone.utc)
|
|
70
71
|
|
|
71
|
-
def _transform_data(self):
|
|
72
|
+
def _transform_data(self) -> None:
|
|
72
73
|
"""Apply type transformations to the data based in current configuration."""
|
|
73
74
|
# Convert data rows to dicts. csv.DictReader is not enough as we support CSV
|
|
74
75
|
# files with no header.
|
|
75
76
|
if self.no_header:
|
|
76
|
-
self.header =
|
|
77
|
-
self.data = [
|
|
77
|
+
self.header = list(range(len(self.data[0])))
|
|
78
|
+
self.data = [dict(dict(zip(self.header, row)).items()) for row in self.data]
|
|
78
79
|
|
|
79
80
|
if self.reverse:
|
|
80
81
|
self.data = list(reversed(self.data))
|
|
81
82
|
if self.date_columns is not None:
|
|
82
|
-
is_date_column = {x:
|
|
83
|
+
is_date_column = {x: x in self.date_columns for x in self.header}
|
|
83
84
|
for row in self.data:
|
|
84
85
|
for k, v in row.items():
|
|
85
86
|
if self.whitespace:
|
|
@@ -88,17 +89,15 @@ class Converter:
|
|
|
88
89
|
row[k] = self._parse_date(v)
|
|
89
90
|
else:
|
|
90
91
|
# Attempt to coerce value into float
|
|
91
|
-
|
|
92
|
+
with contextlib.suppress(ValueError):
|
|
92
93
|
row[k] = float(v.replace(",", ""))
|
|
93
|
-
except ValueError:
|
|
94
|
-
pass
|
|
95
94
|
|
|
96
95
|
def rename_columns(self: Converter, mapper: dict) -> None:
|
|
97
96
|
"""Rename columns using column map."""
|
|
98
97
|
if mapper is None:
|
|
99
98
|
return
|
|
100
99
|
self.no_header = False
|
|
101
|
-
self.header = [mapper
|
|
100
|
+
self.header = [mapper.get(x, x) for x in self.header]
|
|
102
101
|
|
|
103
102
|
def delete_columns(self: Converter, columns: list) -> None:
|
|
104
103
|
"""Delete columns from the data."""
|
|
@@ -131,10 +130,7 @@ class Converter:
|
|
|
131
130
|
doc = Document(num_rows=2, num_cols=2)
|
|
132
131
|
table = doc.sheets[0].tables[0]
|
|
133
132
|
|
|
134
|
-
if self.no_header
|
|
135
|
-
data = []
|
|
136
|
-
else:
|
|
137
|
-
data = [self.header]
|
|
133
|
+
data = [] if self.no_header else [self.header]
|
|
138
134
|
data += [row.values() for row in self.data]
|
|
139
135
|
|
|
140
136
|
for row_num, row in enumerate(data):
|
|
@@ -142,7 +138,10 @@ class Converter:
|
|
|
142
138
|
table.write(row_num, col_num, value)
|
|
143
139
|
if isinstance(value, datetime):
|
|
144
140
|
table.set_cell_formatting(
|
|
145
|
-
row_num,
|
|
141
|
+
row_num,
|
|
142
|
+
col_num,
|
|
143
|
+
"datetime",
|
|
144
|
+
date_time_format="d MMM yyyy",
|
|
146
145
|
)
|
|
147
146
|
|
|
148
147
|
doc.save(self.output_filename)
|
|
@@ -64,13 +64,13 @@ class NumbersUnpacker(IWorkHandler):
|
|
|
64
64
|
with open(target_path, "wb") as out:
|
|
65
65
|
out.write(blob)
|
|
66
66
|
|
|
67
|
-
def ensure_directory_exists(self, path: str):
|
|
67
|
+
def ensure_directory_exists(self, path: str) -> None:
|
|
68
68
|
"""Ensure that a path's directory exists."""
|
|
69
69
|
parts = os.path.split(path)
|
|
70
70
|
with contextlib.suppress(OSError):
|
|
71
71
|
os.makedirs(os.path.join(*([self.output_dir, *list(parts[:-1])])))
|
|
72
72
|
|
|
73
|
-
def prettify_uuids(self, obj: object):
|
|
73
|
+
def prettify_uuids(self, obj: object) -> None:
|
|
74
74
|
if isinstance(obj, dict):
|
|
75
75
|
for k, v in obj.items():
|
|
76
76
|
if isinstance(v, dict):
|
|
@@ -90,7 +90,7 @@ class NumbersUnpacker(IWorkHandler):
|
|
|
90
90
|
elif isinstance(v, list):
|
|
91
91
|
self.prettify_uuids(v)
|
|
92
92
|
|
|
93
|
-
def prettify_cell_storage(self, obj: object):
|
|
93
|
+
def prettify_cell_storage(self, obj: object) -> None:
|
|
94
94
|
if isinstance(obj, dict):
|
|
95
95
|
for k, v in obj.items():
|
|
96
96
|
if isinstance(v, (dict, list)):
|
|
@@ -117,7 +117,7 @@ class NumbersUnpacker(IWorkHandler):
|
|
|
117
117
|
return version in SUPPORTED_NUMBERS_VERSIONS
|
|
118
118
|
|
|
119
119
|
|
|
120
|
-
def main():
|
|
120
|
+
def main() -> None:
|
|
121
121
|
parser = ArgumentParser()
|
|
122
122
|
parser.add_argument("document", help="Apple Numbers file(s)", nargs="*")
|
|
123
123
|
parser.add_argument("-V", "--version", action="store_true")
|