numbers-parser 4.15.1__tar.gz → 4.16.2__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.
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/PKG-INFO +27 -25
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/README.md +8 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/pyproject.toml +66 -57
- numbers_parser-4.16.2/setup.cfg +4 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/_unpack_numbers.py +1 -7
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/constants.py +11 -2
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/document.py +28 -8
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/model.py +122 -2
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/numbers_cache.py +1 -1
- numbers_parser-4.16.2/src/numbers_parser.egg-info/PKG-INFO +494 -0
- numbers_parser-4.16.2/src/numbers_parser.egg-info/SOURCES.txt +97 -0
- numbers_parser-4.16.2/src/numbers_parser.egg-info/dependency_links.txt +1 -0
- numbers_parser-4.16.2/src/numbers_parser.egg-info/entry_points.txt +4 -0
- numbers_parser-4.16.2/src/numbers_parser.egg-info/requires.txt +8 -0
- numbers_parser-4.16.2/src/numbers_parser.egg-info/top_level.txt +1 -0
- numbers_parser-4.16.2/tests/test_all_formulas.py +88 -0
- numbers_parser-4.16.2/tests/test_api_change.py +28 -0
- numbers_parser-4.16.2/tests/test_borders.py +331 -0
- numbers_parser-4.16.2/tests/test_bullets.py +51 -0
- numbers_parser-4.16.2/tests/test_cat_numbers.py +283 -0
- numbers_parser-4.16.2/tests/test_categories.py +687 -0
- numbers_parser-4.16.2/tests/test_coverage.py +307 -0
- numbers_parser-4.16.2/tests/test_create_cells.py +82 -0
- numbers_parser-4.16.2/tests/test_csv2numbers.py +337 -0
- numbers_parser-4.16.2/tests/test_currency.py +25 -0
- numbers_parser-4.16.2/tests/test_folder.py +16 -0
- numbers_parser-4.16.2/tests/test_formatting.py +955 -0
- numbers_parser-4.16.2/tests/test_formulas.py +404 -0
- numbers_parser-4.16.2/tests/test_issues.py +608 -0
- numbers_parser-4.16.2/tests/test_large.py +39 -0
- numbers_parser-4.16.2/tests/test_memory_leaks.py +28 -0
- numbers_parser-4.16.2/tests/test_merges.py +79 -0
- numbers_parser-4.16.2/tests/test_package.py +58 -0
- numbers_parser-4.16.2/tests/test_properties.py +56 -0
- numbers_parser-4.16.2/tests/test_roman.py +24 -0
- numbers_parser-4.16.2/tests/test_save.py +348 -0
- numbers_parser-4.16.2/tests/test_slices.py +144 -0
- numbers_parser-4.16.2/tests/test_styles.py +444 -0
- numbers_parser-4.16.2/tests/test_table_size.py +115 -0
- numbers_parser-4.16.2/tests/test_tables.py +203 -0
- numbers_parser-4.16.2/tests/test_unpack_numbers.py +206 -0
- numbers_parser-4.16.2/tests/test_unsupported.py +8 -0
- numbers_parser-4.16.2/tests/test_uuids.py +37 -0
- numbers_parser-4.16.2/tests/test_version.py +52 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/LICENSE.rst +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/__init__.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/_cat_numbers.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/_csv2numbers.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/bullets.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/cell.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/containers.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/currencies.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/data/empty.numbers +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/exceptions.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/experimental.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/formula.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/generated/TNArchives_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/generated/TNArchives_sos_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/generated/TNCommandArchives_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/generated/TNCommandArchives_sos_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/generated/TSAArchives_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/generated/TSAArchives_sos_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/generated/TSACommandArchives_sos_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/generated/TSCEArchives_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/generated/TSCH3DArchives_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/generated/TSCHArchives_Common_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/generated/TSCHArchives_GEN_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/generated/TSCHArchives_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/generated/TSCHArchives_sos_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/generated/TSCHCommandArchives_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/generated/TSCHPreUFFArchives_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/generated/TSCKArchives_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/generated/TSCKArchives_sos_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/generated/TSDArchives_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/generated/TSDArchives_sos_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/generated/TSDCommandArchives_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/generated/TSKArchives_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/generated/TSPArchiveMessages_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/generated/TSPDatabaseMessages_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/generated/TSPMessages_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/generated/TSSArchives_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/generated/TSSArchives_sos_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/generated/TSTArchives_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/generated/TSTArchives_sos_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/generated/TSTCommandArchives_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/generated/TSTStylePropertyArchiving_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/generated/TSWPArchives_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/generated/TSWPArchives_sos_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/generated/TSWPCommandArchives_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/generated/__init__.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/generated/fontmap.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/generated/functionmap.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/generated/mapping.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/iwafile.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/iwork.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/numbers_uuid.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/roman.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/tokenizer.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.2}/src/numbers_parser/xrefs.py +0 -0
|
@@ -1,31 +1,26 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: numbers-parser
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.16.2
|
|
4
4
|
Summary: Read and write Apple Numbers spreadsheets
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
-
Classifier: Operating System :: OS Independent
|
|
11
|
-
Classifier: Programming Language :: Python :: 3
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
5
|
+
Author-email: Jon Connell <python@figsandfudge.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: repository, https://github.com/masaccio/numbers-parser
|
|
8
|
+
Project-URL: documentation, https://github.com/masaccio/numbers-parser/blob/main/README.md
|
|
17
9
|
Classifier: Topic :: Office/Business :: Financial :: Spreadsheet
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
Requires-
|
|
21
|
-
Requires-Dist: protobuf (>=4.0,<6.0)
|
|
22
|
-
Requires-Dist: python-dateutil (>=2.9.0.post0,<3.0.0)
|
|
23
|
-
Requires-Dist: python-snappy (>=0.7,<0.8)
|
|
24
|
-
Requires-Dist: setuptools (>=70.0.0)
|
|
25
|
-
Requires-Dist: sigfig (>=1.3.3,<2.0.0)
|
|
26
|
-
Project-URL: Documentation, https://github.com/masaccio/numbers-parser/blob/main/README.md
|
|
27
|
-
Project-URL: Repository, https://github.com/masaccio/numbers-parser
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: <4.0,>=3.9
|
|
28
13
|
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE.rst
|
|
15
|
+
Requires-Dist: compact-json<2.0.0,>=1.1.3
|
|
16
|
+
Requires-Dist: protobuf<6.0,>=4.0
|
|
17
|
+
Requires-Dist: python-snappy<1.0,>=0.7
|
|
18
|
+
Requires-Dist: sigfig<2.0.0,>=1.3.3
|
|
19
|
+
Requires-Dist: setuptools>=70.0.0
|
|
20
|
+
Requires-Dist: importlib-resources>=6.1
|
|
21
|
+
Requires-Dist: enum-tools>=0.11
|
|
22
|
+
Requires-Dist: python-dateutil<3.0.0.0,>=2.9.0.post0
|
|
23
|
+
Dynamic: license-file
|
|
29
24
|
|
|
30
25
|
# numbers-parser
|
|
31
26
|
|
|
@@ -485,8 +480,15 @@ The following limitations are expected to always remain:
|
|
|
485
480
|
- Password-encrypted documents cannot be opened. You must first re-save without
|
|
486
481
|
a password to read (see [issue 88](https://github.com/masaccio/numbers-parser/issues/88) for details).
|
|
487
482
|
A UnsupportedError exception is raised when such documents are opened.
|
|
483
|
+
- Due to changes in the format of Numbers documents, decoding of category groups
|
|
484
|
+
(introduced in `numbers-parser` version 4.16) is supported only for documents
|
|
485
|
+
created by Numbers 12.0 and later. No warnings are issued for earlier
|
|
486
|
+
Numbers documents.
|
|
487
|
+
- Only standard macOS fonts are not supported. If a document includes a non-standard
|
|
488
|
+
font, numbers-parser will issue a UnsupportedWarning and default styles to
|
|
489
|
+
Helvetica Neue. Reading font names from the system would add additional system-specific
|
|
490
|
+
dependencies to the package and so this is not planned to changed.
|
|
488
491
|
|
|
489
492
|
## License
|
|
490
493
|
|
|
491
494
|
All code in this repository is licensed under the [MIT License](https://github.com/masaccio/numbers-parser/blob/master/LICENSE.rst).
|
|
492
|
-
|
|
@@ -456,6 +456,14 @@ The following limitations are expected to always remain:
|
|
|
456
456
|
- Password-encrypted documents cannot be opened. You must first re-save without
|
|
457
457
|
a password to read (see [issue 88](https://github.com/masaccio/numbers-parser/issues/88) for details).
|
|
458
458
|
A UnsupportedError exception is raised when such documents are opened.
|
|
459
|
+
- Due to changes in the format of Numbers documents, decoding of category groups
|
|
460
|
+
(introduced in `numbers-parser` version 4.16) is supported only for documents
|
|
461
|
+
created by Numbers 12.0 and later. No warnings are issued for earlier
|
|
462
|
+
Numbers documents.
|
|
463
|
+
- Only standard macOS fonts are not supported. If a document includes a non-standard
|
|
464
|
+
font, numbers-parser will issue a UnsupportedWarning and default styles to
|
|
465
|
+
Helvetica Neue. Reading font names from the system would add additional system-specific
|
|
466
|
+
dependencies to the package and so this is not planned to changed.
|
|
459
467
|
|
|
460
468
|
## License
|
|
461
469
|
|
|
@@ -1,73 +1,81 @@
|
|
|
1
|
-
[
|
|
2
|
-
authors = [
|
|
1
|
+
[project]
|
|
2
|
+
authors = [
|
|
3
|
+
{name = "Jon Connell", email = "python@figsandfudge.com"},
|
|
4
|
+
]
|
|
5
|
+
license = "MIT"
|
|
6
|
+
requires-python = "<4.0,>=3.9"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"compact-json<2.0.0,>=1.1.3",
|
|
9
|
+
"protobuf<6.0,>=4.0",
|
|
10
|
+
"python-snappy<1.0,>=0.7",
|
|
11
|
+
"sigfig<2.0.0,>=1.3.3",
|
|
12
|
+
"setuptools>=70.0.0",
|
|
13
|
+
"importlib-resources>=6.1",
|
|
14
|
+
"enum-tools>=0.11",
|
|
15
|
+
"python-dateutil<3.0.0.0,>=2.9.0.post0",
|
|
16
|
+
]
|
|
3
17
|
classifiers = [
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
18
|
+
"Topic :: Office/Business :: Financial :: Spreadsheet",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Operating System :: OS Independent",
|
|
7
21
|
]
|
|
8
22
|
description = "Read and write Apple Numbers spreadsheets"
|
|
9
|
-
documentation = "https://github.com/masaccio/numbers-parser/blob/main/README.md"
|
|
10
|
-
license = "MIT"
|
|
11
23
|
name = "numbers-parser"
|
|
12
|
-
packages = [{include = "numbers_parser", from = "src"}]
|
|
13
24
|
readme = "README.md"
|
|
25
|
+
version = "4.16.2"
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
14
28
|
repository = "https://github.com/masaccio/numbers-parser"
|
|
15
|
-
|
|
29
|
+
documentation = "https://github.com/masaccio/numbers-parser/blob/main/README.md"
|
|
16
30
|
|
|
17
|
-
[
|
|
31
|
+
[project.scripts]
|
|
18
32
|
cat-numbers = "numbers_parser._cat_numbers:main"
|
|
19
33
|
unpack-numbers = "numbers_parser._unpack_numbers:main"
|
|
20
34
|
csv2numbers = "numbers_parser._csv2numbers:main"
|
|
21
35
|
|
|
22
|
-
[
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
[tool.poetry.group.docs.dependencies]
|
|
53
|
-
sphinx = ">= 7.3"
|
|
54
|
-
enum-tools = ">=0.11"
|
|
55
|
-
sphinx-toolbox = ">=3.5"
|
|
56
|
-
sphinx-nefertiti = ">=0.7"
|
|
57
|
-
sphinx-markdown-builder = ">=0.6"
|
|
58
|
-
sphinx-copybutton = ">=0.5"
|
|
59
|
-
|
|
60
|
-
[tool.poetry.group.bootstrap]
|
|
61
|
-
optional = true
|
|
62
|
-
|
|
63
|
-
[tool.poetry.group.bootstrap.dependencies]
|
|
64
|
-
pyobjc-core = ">=10.2"
|
|
65
|
-
pyobjc-framework-Cocoa = ">=10.2"
|
|
66
|
-
py2app = ">=0.28"
|
|
36
|
+
[dependency-groups]
|
|
37
|
+
dev = [
|
|
38
|
+
"gprof2dot<2023.0.0,>=2022.7.29",
|
|
39
|
+
"line-profiler<5.0.0,>=4.0.3",
|
|
40
|
+
"mock>=5.1.0",
|
|
41
|
+
"pytest>=7.2.0",
|
|
42
|
+
"pytest-check>=1.0",
|
|
43
|
+
"pytest-console-scripts<2.0.0,>=1.3.1",
|
|
44
|
+
"pytest-cov>=4.0,>=5.0",
|
|
45
|
+
"pytest-xdist<4.0.0,>=3.3.1",
|
|
46
|
+
"ruff",
|
|
47
|
+
"tox<5.0.0,>=4.11.4",
|
|
48
|
+
"python-magic>=0.4",
|
|
49
|
+
"tqdm>=4.66",
|
|
50
|
+
"colorama<1.0.0,>=0.4.6",
|
|
51
|
+
"pympler<2.0,>=1.1",
|
|
52
|
+
]
|
|
53
|
+
docs = [
|
|
54
|
+
"sphinx>=7.3",
|
|
55
|
+
"enum-tools>=0.11",
|
|
56
|
+
"sphinx-toolbox>=3.5",
|
|
57
|
+
"sphinx-nefertiti>=0.7",
|
|
58
|
+
"sphinx-markdown-builder>=0.6",
|
|
59
|
+
"sphinx-copybutton>=0.5",
|
|
60
|
+
]
|
|
61
|
+
bootstrap = [
|
|
62
|
+
"pyobjc-core>=10.2",
|
|
63
|
+
"pyobjc-framework-Cocoa>=10.2",
|
|
64
|
+
"py2app>=0.28",
|
|
65
|
+
]
|
|
67
66
|
|
|
68
67
|
[build-system]
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
requires = ["setuptools >= 61.0"]
|
|
69
|
+
build-backend = "setuptools.build_meta"
|
|
70
|
+
|
|
71
|
+
[tool.setuptools.packages.find]
|
|
72
|
+
where = ["src"]
|
|
73
|
+
include = ["numbers_parser*"]
|
|
74
|
+
exclude = ["*tests*"]
|
|
75
|
+
namespaces = false # disable scanning PEP 420 namespaces
|
|
76
|
+
|
|
77
|
+
[tool.setuptools.package-data]
|
|
78
|
+
"numbers_parser" = ["data/*"]
|
|
71
79
|
|
|
72
80
|
[tool.coverage.run]
|
|
73
81
|
branch = true
|
|
@@ -189,3 +197,4 @@ ban-relative-imports = "all"
|
|
|
189
197
|
"src/build/protodump.py" = ["PLR2004", "INP001", "PTH", "S110", "N806"]
|
|
190
198
|
"src/debug/**" = ["INP001"]
|
|
191
199
|
"tests/**" = ["PLR2004", "S101", "D103", "ANN201", "ANN001"]
|
|
200
|
+
|
|
@@ -49,13 +49,7 @@ class NumbersUnpacker(IWorkHandler):
|
|
|
49
49
|
if self.compact_json or self.pretty:
|
|
50
50
|
formatter = Formatter()
|
|
51
51
|
formatter.indent_spaces = 2
|
|
52
|
-
formatter.
|
|
53
|
-
formatter.max_compact_list_complexity = 100
|
|
54
|
-
formatter.max_inline_length = 160
|
|
55
|
-
formatter.max_compact_list_complexity = 2
|
|
56
|
-
formatter.simple_bracket_padding = True
|
|
57
|
-
formatter.nested_bracket_padding = False
|
|
58
|
-
formatter.always_expand_depth = 10
|
|
52
|
+
formatter.max_inline_length = 180
|
|
59
53
|
pretty_json = formatter.serialize(data)
|
|
60
54
|
out.write(pretty_json)
|
|
61
55
|
else:
|
|
@@ -5,15 +5,16 @@ from math import ceil
|
|
|
5
5
|
|
|
6
6
|
import enum_tools.documentation
|
|
7
7
|
|
|
8
|
+
# Path to package date varies by Python version
|
|
8
9
|
try:
|
|
9
10
|
from importlib.resources import files
|
|
10
|
-
# Can't cover exception using modern python
|
|
11
11
|
except ImportError: # pragma: nocover
|
|
12
12
|
from importlib_resources import files
|
|
13
13
|
|
|
14
14
|
__all__ = [
|
|
15
15
|
"CellPadding",
|
|
16
16
|
"CellType",
|
|
17
|
+
"CellValueType",
|
|
17
18
|
"ControlFormattingType",
|
|
18
19
|
"DurationStyle",
|
|
19
20
|
"DurationUnits",
|
|
@@ -112,7 +113,7 @@ def _day_of_year(value: datetime) -> int:
|
|
|
112
113
|
|
|
113
114
|
def _week_of_month(value: datetime) -> int:
|
|
114
115
|
"""Return the week number in a month for a datetime."""
|
|
115
|
-
return
|
|
116
|
+
return ceil((value.day + value.replace(day=1).weekday()) / 7.0)
|
|
116
117
|
|
|
117
118
|
|
|
118
119
|
DATETIME_FIELD_MAP = OrderedDict(
|
|
@@ -399,6 +400,14 @@ class CellInteractionType(IntEnum):
|
|
|
399
400
|
TOGGLE = 8
|
|
400
401
|
|
|
401
402
|
|
|
403
|
+
class CellValueType(IntEnum):
|
|
404
|
+
NIL_TYPE = 1
|
|
405
|
+
BOOLEAN_TYPE = 2
|
|
406
|
+
DATE_TYPE = 3
|
|
407
|
+
NUMBER_TYPE = 4
|
|
408
|
+
STRING_TYPE = 5
|
|
409
|
+
|
|
410
|
+
|
|
402
411
|
CONTROL_CELL_TYPE_MAP = {
|
|
403
412
|
FormattingType.POPUP: CellInteractionType.POPUP,
|
|
404
413
|
FormattingType.SLIDER: CellInteractionType.SLIDER,
|
|
@@ -40,14 +40,6 @@ if TYPE_CHECKING: # pragma: nocover
|
|
|
40
40
|
__all__ = ["Document", "Sheet", "Table"]
|
|
41
41
|
|
|
42
42
|
|
|
43
|
-
# class Sheet:
|
|
44
|
-
# pass
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
# class Table:
|
|
48
|
-
# pass
|
|
49
|
-
|
|
50
|
-
|
|
51
43
|
class Document:
|
|
52
44
|
"""
|
|
53
45
|
Create an instance of a new Numbers document.
|
|
@@ -1007,6 +999,34 @@ class Table(Cacheable):
|
|
|
1007
999
|
msg = "style must be a Style object or style name"
|
|
1008
1000
|
raise TypeError(msg)
|
|
1009
1001
|
|
|
1002
|
+
def categorized_data(self) -> dict | None:
|
|
1003
|
+
"""
|
|
1004
|
+
Return the table's data organised into categories, if enabled or ``None``
|
|
1005
|
+
if the table has not had categoried enabled.
|
|
1006
|
+
|
|
1007
|
+
The data is a dictionary with the category names as keys and a list
|
|
1008
|
+
dictionaries for each row in that category of the table. The table heading
|
|
1009
|
+
row is used as the keys for the row dictionary.
|
|
1010
|
+
|
|
1011
|
+
Example
|
|
1012
|
+
-------
|
|
1013
|
+
.. code:: python
|
|
1014
|
+
|
|
1015
|
+
"Transport": [
|
|
1016
|
+
{"Description": "Airplane", "Category": "Transport" },
|
|
1017
|
+
{"Description": "Bicycle", "Category": "Transport" },
|
|
1018
|
+
{"Description": "Bus", "Category": "Transport"},
|
|
1019
|
+
],
|
|
1020
|
+
"Fruit": [
|
|
1021
|
+
{"Description": "Apple", "Category": "Fruit" },
|
|
1022
|
+
{"Description": "Banana", "Category": "Fruit" },
|
|
1023
|
+
],
|
|
1024
|
+
|
|
1025
|
+
For tables with multiple categories, the top-level dictionary is nested.
|
|
1026
|
+
|
|
1027
|
+
"""
|
|
1028
|
+
return self._model.table_category_data(self._table_id)
|
|
1029
|
+
|
|
1010
1030
|
def add_row(
|
|
1011
1031
|
self,
|
|
1012
1032
|
num_rows: int | None = 1,
|
|
@@ -8,6 +8,8 @@ from itertools import chain
|
|
|
8
8
|
from math import floor
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
from struct import pack
|
|
11
|
+
from typing import TYPE_CHECKING
|
|
12
|
+
from warnings import warn
|
|
11
13
|
|
|
12
14
|
from numbers_parser.bullets import (
|
|
13
15
|
BULLET_CONVERSION,
|
|
@@ -37,6 +39,7 @@ from numbers_parser.constants import (
|
|
|
37
39
|
CUSTOM_TEXT_PLACEHOLDER,
|
|
38
40
|
DEFAULT_COLUMN_WIDTH,
|
|
39
41
|
DEFAULT_DOCUMENT,
|
|
42
|
+
DEFAULT_FONT,
|
|
40
43
|
DEFAULT_PRE_BNC_BYTES,
|
|
41
44
|
DEFAULT_ROW_HEIGHT,
|
|
42
45
|
DEFAULT_TABLE_OFFSET,
|
|
@@ -48,11 +51,12 @@ from numbers_parser.constants import (
|
|
|
48
51
|
MAX_TILE_SIZE,
|
|
49
52
|
PACKAGE_ID,
|
|
50
53
|
CellInteractionType,
|
|
54
|
+
CellValueType,
|
|
51
55
|
FormatType,
|
|
52
56
|
OwnerKind,
|
|
53
57
|
)
|
|
54
58
|
from numbers_parser.containers import ObjectStore
|
|
55
|
-
from numbers_parser.exceptions import UnsupportedError
|
|
59
|
+
from numbers_parser.exceptions import UnsupportedError, UnsupportedWarning
|
|
56
60
|
from numbers_parser.formula import TableFormulas
|
|
57
61
|
from numbers_parser.generated import TNArchives_pb2 as TNArchives
|
|
58
62
|
from numbers_parser.generated import TSAArchives_pb2 as TSAArchives
|
|
@@ -76,6 +80,9 @@ from numbers_parser.numbers_cache import Cacheable, cache
|
|
|
76
80
|
from numbers_parser.numbers_uuid import NumbersUUID, uuid_to_hex
|
|
77
81
|
from numbers_parser.xrefs import CellRange, ScopedNameRefCache
|
|
78
82
|
|
|
83
|
+
if TYPE_CHECKING:
|
|
84
|
+
from datetime import datetime
|
|
85
|
+
|
|
79
86
|
|
|
80
87
|
def create_font_name_map(font_map: dict) -> dict:
|
|
81
88
|
new_font_map = {}
|
|
@@ -238,6 +245,7 @@ class _NumbersModel(Cacheable):
|
|
|
238
245
|
"left": defaultdict(),
|
|
239
246
|
}
|
|
240
247
|
self.name_ref_cache = ScopedNameRefCache(self)
|
|
248
|
+
self.missing_fonts = {}
|
|
241
249
|
self.calculate_table_uuid_map()
|
|
242
250
|
|
|
243
251
|
def save(self, filepath: Path, package: bool) -> None:
|
|
@@ -289,7 +297,7 @@ class _NumbersModel(Cacheable):
|
|
|
289
297
|
# which is an ordered list that matches the storage buffers, but
|
|
290
298
|
# identifies which row a storage buffer belongs to (empty rows have
|
|
291
299
|
# no storage buffers).
|
|
292
|
-
row_bucket_map =
|
|
300
|
+
row_bucket_map = dict.fromkeys(range(self.objects[table_id].number_of_rows))
|
|
293
301
|
bds = self.objects[table_id].base_data_store
|
|
294
302
|
bucket_ids = [x.identifier for x in bds.rowHeaders.buckets]
|
|
295
303
|
idx = 0
|
|
@@ -2268,6 +2276,16 @@ class _NumbersModel(Cacheable):
|
|
|
2268
2276
|
def cell_font_name(self, obj: Cell | object) -> str:
|
|
2269
2277
|
style = self.cell_text_style(obj) if isinstance(obj, Cell) else obj
|
|
2270
2278
|
font_name = self.char_property(style, "font_name")
|
|
2279
|
+
if font_name not in FONT_NAME_TO_FAMILY:
|
|
2280
|
+
if font_name not in self.missing_fonts:
|
|
2281
|
+
warn(
|
|
2282
|
+
f"Custom font '{font_name}' unsupported; falling back to {DEFAULT_FONT}",
|
|
2283
|
+
UnsupportedWarning,
|
|
2284
|
+
stacklevel=2,
|
|
2285
|
+
)
|
|
2286
|
+
self.missing_fonts[font_name] = True
|
|
2287
|
+
return DEFAULT_FONT
|
|
2288
|
+
|
|
2271
2289
|
return FONT_NAME_TO_FAMILY[font_name]
|
|
2272
2290
|
|
|
2273
2291
|
def cell_first_indent(self, obj: Cell | object) -> float:
|
|
@@ -2550,6 +2568,108 @@ class _NumbersModel(Cacheable):
|
|
|
2550
2568
|
# datas never appears to be an empty list (default themes include images)
|
|
2551
2569
|
return max(image_ids) + 1
|
|
2552
2570
|
|
|
2571
|
+
def table_category_data(self, table_id: int) -> dict | None:
|
|
2572
|
+
category_owner_id = self.objects[table_id].category_owner.identifier
|
|
2573
|
+
category_archive_id = self.objects[category_owner_id].group_by[0].identifier
|
|
2574
|
+
category_archive = self.objects[category_archive_id]
|
|
2575
|
+
if not category_archive.is_enabled:
|
|
2576
|
+
return None
|
|
2577
|
+
|
|
2578
|
+
table_info = self.objects[self.table_info_id(table_id)]
|
|
2579
|
+
category_order = self.objects[table_info.category_order.identifier]
|
|
2580
|
+
row_uid_map = self.objects[category_order.uid_map.identifier]
|
|
2581
|
+
sorted_row_uuids = [
|
|
2582
|
+
NumbersUUID(row_uid_map.sorted_row_uids[i]).hex for i in row_uid_map.row_uid_for_index
|
|
2583
|
+
]
|
|
2584
|
+
|
|
2585
|
+
data = self._table_data[table_id]
|
|
2586
|
+
header = [cell.value for cell in data[0]]
|
|
2587
|
+
|
|
2588
|
+
def index_set_to_offsets(index_set: TSCEArchives.IndexSetArchive) -> list[int]:
|
|
2589
|
+
"""Convert an IndexSetArchive to a list of offsets."""
|
|
2590
|
+
offsets = []
|
|
2591
|
+
for entry in index_set.entries:
|
|
2592
|
+
if entry.HasField("range_end"):
|
|
2593
|
+
offsets += list(range(entry.range_begin, entry.range_end + 1))
|
|
2594
|
+
else:
|
|
2595
|
+
offsets += list(range(entry.range_begin, entry.range_begin + 1))
|
|
2596
|
+
return offsets
|
|
2597
|
+
|
|
2598
|
+
def cell_value_to_key(
|
|
2599
|
+
cell_value: TSCEArchives.CellValueArchive,
|
|
2600
|
+
) -> str | int | bool | datetime:
|
|
2601
|
+
"""Convert a CellValueArchive to a key."""
|
|
2602
|
+
cell_value_type = cell_value.cell_value_type
|
|
2603
|
+
if cell_value_type == CellValueType.STRING_TYPE:
|
|
2604
|
+
return cell_value.string_value.value
|
|
2605
|
+
if cell_value_type == CellValueType.NUMBER_TYPE:
|
|
2606
|
+
return cell_value.number_value.value
|
|
2607
|
+
if cell_value_type == CellValueType.BOOLEAN_TYPE:
|
|
2608
|
+
return cell_value.boolean_value.value
|
|
2609
|
+
# Must be DATE_TYPE
|
|
2610
|
+
return cell_value.date_value.value
|
|
2611
|
+
|
|
2612
|
+
group_node_to_key = {
|
|
2613
|
+
NumbersUUID(self.objects[_id].group_uid).hex: cell_value_to_key(
|
|
2614
|
+
self.objects[_id].group_cell_value,
|
|
2615
|
+
)
|
|
2616
|
+
for _id in self.find_refs("GroupNodeArchive")
|
|
2617
|
+
}
|
|
2618
|
+
group_uuids = [NumbersUUID(x.group_uid).hex for x in category_archive.group_node_root.child]
|
|
2619
|
+
group_uuids = [uuid for uuid in sorted_row_uuids if uuid in group_uuids]
|
|
2620
|
+
|
|
2621
|
+
def group_hierarchy(parent: str, children: list):
|
|
2622
|
+
nodes = {}
|
|
2623
|
+
for child in children:
|
|
2624
|
+
group_uuid = NumbersUUID(child.group_uid).hex
|
|
2625
|
+
if len(child.child) > 0:
|
|
2626
|
+
nodes[group_uuid] = group_hierarchy(group_uuid, child.child)
|
|
2627
|
+
else:
|
|
2628
|
+
nodes[group_uuid] = None
|
|
2629
|
+
return nodes
|
|
2630
|
+
|
|
2631
|
+
def assign_rows_to_categories(parent: str, children: list, categories: dict):
|
|
2632
|
+
for child in children:
|
|
2633
|
+
group_uuid = NumbersUUID(child.group_uid).hex
|
|
2634
|
+
if len(child.child) == 0:
|
|
2635
|
+
key = cell_value_to_key(child.group_cell_value)
|
|
2636
|
+
|
|
2637
|
+
row_offsets = index_set_to_offsets(child.row_lookup_uids)
|
|
2638
|
+
categories[group_uuid] = {
|
|
2639
|
+
"key": key,
|
|
2640
|
+
"parent": parent,
|
|
2641
|
+
"rows": [
|
|
2642
|
+
{header[col]: cell.value for col, cell in enumerate(data[row])}
|
|
2643
|
+
for row in row_offsets
|
|
2644
|
+
],
|
|
2645
|
+
}
|
|
2646
|
+
else:
|
|
2647
|
+
categories[group_uuid] = {
|
|
2648
|
+
"key": group_node_to_key[group_uuid],
|
|
2649
|
+
"parent": parent,
|
|
2650
|
+
"rows": None,
|
|
2651
|
+
}
|
|
2652
|
+
assign_rows_to_categories(group_uuid, child.child, categories)
|
|
2653
|
+
|
|
2654
|
+
category_tree = group_hierarchy(
|
|
2655
|
+
NumbersUUID(category_archive.group_node_root.group_uid).hex,
|
|
2656
|
+
category_archive.group_node_root.child,
|
|
2657
|
+
)
|
|
2658
|
+
|
|
2659
|
+
categories = {}
|
|
2660
|
+
assign_rows_to_categories(None, category_archive.group_node_root.child, categories)
|
|
2661
|
+
|
|
2662
|
+
def merge_trees(a: dict, b: dict):
|
|
2663
|
+
new_tree = {}
|
|
2664
|
+
for k, v in a.items():
|
|
2665
|
+
if v is not None:
|
|
2666
|
+
new_tree[b[k]["key"]] = merge_trees(v, b)
|
|
2667
|
+
else:
|
|
2668
|
+
new_tree[b[k]["key"]] = b[k]["rows"]
|
|
2669
|
+
return new_tree
|
|
2670
|
+
|
|
2671
|
+
return merge_trees(category_tree, categories)
|
|
2672
|
+
|
|
2553
2673
|
|
|
2554
2674
|
def rgb(obj) -> RGB:
|
|
2555
2675
|
"""Convert a TSPArchives.Color into an RGB tuple."""
|