numbers-parser 4.4.6__tar.gz → 4.4.8__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.4.6 → numbers_parser-4.4.8}/PKG-INFO +8 -5
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/README.md +5 -3
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/pyproject.toml +65 -26
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/__init__.py +7 -5
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/_cat_numbers.py +1 -2
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/_unpack_numbers.py +14 -16
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/cell.py +33 -29
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/cell_storage.py +36 -29
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/constants.py +2 -2
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/containers.py +12 -14
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/document.py +54 -45
- numbers_parser-4.4.8/src/numbers_parser/exceptions.py +40 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/file.py +18 -8
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/formula.py +7 -3
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/iwafile.py +23 -27
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/model.py +112 -131
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/numbers_cache.py +3 -2
- numbers_parser-4.4.6/src/numbers_parser/exceptions.py +0 -40
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/LICENSE.rst +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/bullets.py +1 -1
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/data/empty.numbers +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/experimental.py +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TNArchives_pb2.py +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TNArchives_sos_pb2.py +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TNCommandArchives_pb2.py +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TNCommandArchives_sos_pb2.py +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSAArchives_pb2.py +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSAArchives_sos_pb2.py +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSACommandArchives_sos_pb2.py +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSCEArchives_pb2.py +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSCH3DArchives_pb2.py +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSCHArchives_Common_pb2.py +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSCHArchives_GEN_pb2.py +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSCHArchives_pb2.py +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSCHArchives_sos_pb2.py +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSCHCommandArchives_pb2.py +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSCHPreUFFArchives_pb2.py +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSDArchives_pb2.py +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSDArchives_sos_pb2.py +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSDCommandArchives_pb2.py +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSKArchives_pb2.py +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSKArchives_sos_pb2.py +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSPArchiveMessages_pb2.py +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSPDatabaseMessages_pb2.py +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSPMessages_pb2.py +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSSArchives_pb2.py +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSSArchives_sos_pb2.py +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSTArchives_pb2.py +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSTArchives_sos_pb2.py +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSTCommandArchives_pb2.py +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSTStylePropertyArchiving_pb2.py +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSWPArchives_pb2.py +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSWPArchives_sos_pb2.py +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/TSWPCommandArchives_pb2.py +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/__init__.py +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/fontmap.py +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/generated/functionmap.py +0 -0
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/src/numbers_parser/mapping.py +24 -24
- {numbers_parser-4.4.6 → numbers_parser-4.4.8}/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.4.
|
|
3
|
+
Version: 4.4.8
|
|
4
4
|
Summary: Read and write Apple Numbers spreadsheets
|
|
5
5
|
Home-page: https://github.com/masaccio/numbers-parser
|
|
6
6
|
License: MIT
|
|
@@ -17,11 +17,12 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
17
17
|
Classifier: Programming Language :: Python :: 3
|
|
18
18
|
Classifier: Topic :: Office/Business :: Financial :: Spreadsheet
|
|
19
19
|
Requires-Dist: compact-json (>=1.1.3,<2.0.0)
|
|
20
|
-
Requires-Dist: pendulum (>=
|
|
20
|
+
Requires-Dist: pendulum (>=3.0,<4.0)
|
|
21
21
|
Requires-Dist: protobuf (>=4.21.1,<5.0.0)
|
|
22
22
|
Requires-Dist: python-snappy (>=0.6.1,<0.7.0)
|
|
23
23
|
Requires-Dist: regex (>=2022.9.13,<2023.0.0)
|
|
24
24
|
Requires-Dist: roman (>=3.3,<4.0)
|
|
25
|
+
Requires-Dist: setuptools (>=69.0.3,<70.0.0)
|
|
25
26
|
Requires-Dist: sigfig (>=1.3.2,<2.0.0)
|
|
26
27
|
Project-URL: Documentation, https://github.com/masaccio/numbers-parser/blob/main/README.md
|
|
27
28
|
Project-URL: Repository, https://github.com/masaccio/numbers-parser
|
|
@@ -34,9 +35,9 @@ Description-Content-Type: text/markdown
|
|
|
34
35
|
[](https://codecov.io/gh/masaccio/numbers-parser)
|
|
35
36
|
[](https://badge.fury.io/py/numbers-parser)
|
|
36
37
|
|
|
37
|
-
`numbers-parser` is a Python module for parsing [Apple Numbers](https://www.apple.com/numbers/)`.numbers` files. It supports Numbers files generated by Numbers version 10.3, and up with the latest tested version being 13.
|
|
38
|
+
`numbers-parser` is a Python module for parsing [Apple Numbers](https://www.apple.com/numbers/)`.numbers` files. It supports Numbers files generated by Numbers version 10.3, and up with the latest tested version being 13.2 (current as of September 2023).
|
|
38
39
|
|
|
39
|
-
It supports and is tested against Python versions from 3.8 onwards. It is not compatible with earlier versions of Python.
|
|
40
|
+
It supports and is tested against Python versions from 3.8 onwards. It is not compatible with earlier versions of Python. Python 3.12 is not supported because of dependencies that do not currently work with 3.12. This is expected to be resolved by November 2023.
|
|
40
41
|
|
|
41
42
|
## Installation
|
|
42
43
|
|
|
@@ -78,7 +79,7 @@ C:\Users\Jon>pip install C:\Users\Jon\Downloads\python_snappy-0.6.1-cp311-cp311-
|
|
|
78
79
|
|
|
79
80
|
To better partition cell styles, background image data which was supported in earlier versions through the methods `image_data` and `image_filename` is now part of the new `cell_style` property. Using the deprecated methods `image_data` and `image_filename` will issue a `DeprecationWarning` if used.The legacy methods will be removed in a future version of numbers-parser.
|
|
80
81
|
|
|
81
|
-
`NumberCell` cell values are now limited to 15 significant figures to match the implementation of floating point numbers in Apple Numbers. For example, the value `1234567890123456` is rounded to `1234567890123460` in the same
|
|
82
|
+
`NumberCell` cell values are now limited to 15 significant figures to match the implementation of floating point numbers in Apple Numbers. For example, the value `1234567890123456` is rounded to `1234567890123460` in the same way as in Numbers. Previously, using native `float` with no checking resulted in rounding errors in unpacking internal numbers. Attempting to write a number with too many significant digits results in a `RuntimeWarning`.
|
|
82
83
|
|
|
83
84
|
The previously deprecated methods `Document.sheets()` and `Sheet.tables()` are now only available using the properties of the same name (see examples in this README).
|
|
84
85
|
|
|
@@ -282,6 +283,8 @@ with open (cell.style.bg_image.filename, "wb") as f:
|
|
|
282
283
|
f.write(cell.style.bg_image.data)
|
|
283
284
|
```
|
|
284
285
|
|
|
286
|
+
Due to a limitation in Python's [ZipFile](https://docs.python.org/3/library/zipfile.html), Python versions older than 3.11 do not support image filenames with UTF-8 characters (see [issue 69](https://github.com/masaccio/numbers-parser/issues/69)). `cell.style.bg_image` returns `None` for such files and issues a `RuntimeWarning`.
|
|
287
|
+
|
|
285
288
|
### Borders
|
|
286
289
|
|
|
287
290
|
`numbers-parser` supports reading and writing cell borders, though the interface for each differs. Individual cells can have each of their four borders tested, but when drawing new borders, these are set for the table to allow for drawing borders across multiple cells. Setting the border of merged cells is not possible unless the edge of the cells is at the end of the merged region.
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
[](https://codecov.io/gh/masaccio/numbers-parser)
|
|
6
6
|
[](https://badge.fury.io/py/numbers-parser)
|
|
7
7
|
|
|
8
|
-
`numbers-parser` is a Python module for parsing [Apple Numbers](https://www.apple.com/numbers/)`.numbers` files. It supports Numbers files generated by Numbers version 10.3, and up with the latest tested version being 13.
|
|
8
|
+
`numbers-parser` is a Python module for parsing [Apple Numbers](https://www.apple.com/numbers/)`.numbers` files. It supports Numbers files generated by Numbers version 10.3, and up with the latest tested version being 13.2 (current as of September 2023).
|
|
9
9
|
|
|
10
|
-
It supports and is tested against Python versions from 3.8 onwards. It is not compatible with earlier versions of Python.
|
|
10
|
+
It supports and is tested against Python versions from 3.8 onwards. It is not compatible with earlier versions of Python. Python 3.12 is not supported because of dependencies that do not currently work with 3.12. This is expected to be resolved by November 2023.
|
|
11
11
|
|
|
12
12
|
## Installation
|
|
13
13
|
|
|
@@ -49,7 +49,7 @@ C:\Users\Jon>pip install C:\Users\Jon\Downloads\python_snappy-0.6.1-cp311-cp311-
|
|
|
49
49
|
|
|
50
50
|
To better partition cell styles, background image data which was supported in earlier versions through the methods `image_data` and `image_filename` is now part of the new `cell_style` property. Using the deprecated methods `image_data` and `image_filename` will issue a `DeprecationWarning` if used.The legacy methods will be removed in a future version of numbers-parser.
|
|
51
51
|
|
|
52
|
-
`NumberCell` cell values are now limited to 15 significant figures to match the implementation of floating point numbers in Apple Numbers. For example, the value `1234567890123456` is rounded to `1234567890123460` in the same
|
|
52
|
+
`NumberCell` cell values are now limited to 15 significant figures to match the implementation of floating point numbers in Apple Numbers. For example, the value `1234567890123456` is rounded to `1234567890123460` in the same way as in Numbers. Previously, using native `float` with no checking resulted in rounding errors in unpacking internal numbers. Attempting to write a number with too many significant digits results in a `RuntimeWarning`.
|
|
53
53
|
|
|
54
54
|
The previously deprecated methods `Document.sheets()` and `Sheet.tables()` are now only available using the properties of the same name (see examples in this README).
|
|
55
55
|
|
|
@@ -253,6 +253,8 @@ with open (cell.style.bg_image.filename, "wb") as f:
|
|
|
253
253
|
f.write(cell.style.bg_image.data)
|
|
254
254
|
```
|
|
255
255
|
|
|
256
|
+
Due to a limitation in Python's [ZipFile](https://docs.python.org/3/library/zipfile.html), Python versions older than 3.11 do not support image filenames with UTF-8 characters (see [issue 69](https://github.com/masaccio/numbers-parser/issues/69)). `cell.style.bg_image` returns `None` for such files and issues a `RuntimeWarning`.
|
|
257
|
+
|
|
256
258
|
### Borders
|
|
257
259
|
|
|
258
260
|
`numbers-parser` supports reading and writing cell borders, though the interface for each differs. Individual cells can have each of their four borders tested, but when drawing new borders, these are set for the table to allow for drawing borders across multiple cells. Setting the border of merged cells is not possible unless the edge of the cells is at the end of the merged region.
|
|
@@ -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.4.
|
|
15
|
+
version = "4.4.8"
|
|
16
16
|
|
|
17
17
|
[tool.poetry.scripts]
|
|
18
18
|
cat-numbers = "numbers_parser._cat_numbers:main"
|
|
@@ -20,29 +20,31 @@ unpack-numbers = "numbers_parser._unpack_numbers:main"
|
|
|
20
20
|
|
|
21
21
|
[tool.poetry.dependencies]
|
|
22
22
|
compact-json = "^1.1.3"
|
|
23
|
-
pendulum = "^
|
|
23
|
+
pendulum = "^3.0"
|
|
24
24
|
protobuf = "^4.21.1"
|
|
25
25
|
python = "^3.8"
|
|
26
26
|
python-snappy = "^0.6.1"
|
|
27
27
|
regex = "^2022.9.13"
|
|
28
28
|
roman = "^3.3"
|
|
29
29
|
sigfig = "^1.3.2"
|
|
30
|
+
setuptools = "^69.0.3"
|
|
30
31
|
|
|
31
32
|
[tool.poetry.group.dev.dependencies]
|
|
32
33
|
black = {version = "^22.10.0", allow-prereleases = true}
|
|
33
|
-
|
|
34
|
+
gprof2dot = "^2022.7.29"
|
|
35
|
+
line-profiler = "^4.0.3"
|
|
36
|
+
mock = "^5.1.0"
|
|
37
|
+
psutil = "^5.9.5"
|
|
34
38
|
pytest = "^7.2.0"
|
|
35
39
|
pytest-check = "^1.0.10"
|
|
36
40
|
pytest-console-scripts = "^1.3.1"
|
|
37
41
|
pytest-cov = "^4.0.0"
|
|
38
42
|
pytest-profiling = "^1.7.0"
|
|
39
|
-
python-magic = "^0.4.27"
|
|
40
|
-
termcolor = "^2.2.0"
|
|
41
|
-
gprof2dot = "^2022.7.29"
|
|
42
|
-
line-profiler = "^4.0.3"
|
|
43
|
-
mock = "^5.1.0"
|
|
44
43
|
pytest-xdist = "^3.3.1"
|
|
45
|
-
|
|
44
|
+
ruff = "^0.0.292"
|
|
45
|
+
termcolor = "^2.2.0"
|
|
46
|
+
tox = "^4.11.4"
|
|
47
|
+
python-magic = "^0.4.27"
|
|
46
48
|
|
|
47
49
|
[build-system]
|
|
48
50
|
build-backend = "poetry.core.masonry.api"
|
|
@@ -58,31 +60,68 @@ directory = "coverage_html_report"
|
|
|
58
60
|
show_contexts = true
|
|
59
61
|
|
|
60
62
|
[tool.pytest.ini_options]
|
|
61
|
-
addopts = "--cov=src/numbers_parser --cov-report=term-missing:skip-covered --cov-context=test"
|
|
63
|
+
addopts = "--cov=src/numbers_parser --cov-report=html --cov-report=term-missing:skip-covered --cov-context=test"
|
|
62
64
|
|
|
63
65
|
[tool.tox]
|
|
64
66
|
legacy_tox_ini = """
|
|
65
67
|
[tox]
|
|
66
68
|
isolated_build = true
|
|
67
|
-
envlist = py38, py39, py310, py311
|
|
69
|
+
envlist = py38, py39, py310, py311, py312
|
|
68
70
|
[testenv]
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
71
|
+
deps =
|
|
72
|
+
pytest
|
|
73
|
+
pytest-check
|
|
74
|
+
pytest-console-scripts
|
|
75
|
+
pytest-cov
|
|
76
|
+
pytest-xdist
|
|
77
|
+
python-magic
|
|
78
|
+
psutil
|
|
79
|
+
commands =
|
|
80
|
+
pytest tests/ --import-mode importlib -n logical --no-cov
|
|
72
81
|
"""
|
|
73
82
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
83
|
+
[tool.isort]
|
|
84
|
+
profile = "black"
|
|
85
|
+
|
|
86
|
+
[tool.ruff]
|
|
87
|
+
exclude = [
|
|
88
|
+
# Machine-generated files
|
|
89
|
+
".bootstrap/*",
|
|
90
|
+
"src/numbers_parser/generated/*",
|
|
91
|
+
# Third-party files not to lint
|
|
92
|
+
"src/debug/lldbutil.py", # Tox
|
|
93
|
+
".tox/*",
|
|
94
|
+
]
|
|
95
|
+
fix = true
|
|
96
|
+
ignore = [
|
|
97
|
+
"PLR2004", # Allow constant values
|
|
98
|
+
]
|
|
99
|
+
line-length = 100
|
|
100
|
+
select = [
|
|
101
|
+
# Pyflakes including bugbears
|
|
102
|
+
"F",
|
|
103
|
+
"B", # Pycodestyle
|
|
104
|
+
"E",
|
|
105
|
+
"W", # isort
|
|
106
|
+
"I", # PEP naming
|
|
107
|
+
"N", # pyupgrade
|
|
108
|
+
"UP", # Pylama
|
|
109
|
+
"PL",
|
|
110
|
+
]
|
|
111
|
+
src = ["src", "tests"]
|
|
112
|
+
target-version = "py38"
|
|
113
|
+
unfixable = [
|
|
114
|
+
"ERA", # do not autoremove commented out code
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
[tool.ruff.pylint]
|
|
118
|
+
max-statements = 100
|
|
119
|
+
max-branches = 20
|
|
79
120
|
|
|
80
|
-
[tool.
|
|
81
|
-
|
|
82
|
-
ignore = "E203,E231,W503"
|
|
121
|
+
[tool.ruff.flake8-tidy-imports]
|
|
122
|
+
ban-relative-imports = "all"
|
|
83
123
|
|
|
84
|
-
[tool.
|
|
85
|
-
|
|
124
|
+
[tool.ruff.per-file-ignores]
|
|
125
|
+
"src/bootstrap/**" = ["PLR2004"]
|
|
86
126
|
|
|
87
|
-
[
|
|
88
|
-
builtins = "_"
|
|
127
|
+
"tests/**" = ["PLR2004", "S101", "D103", "ANN201", "ANN001"]
|
|
@@ -23,9 +23,9 @@ import os
|
|
|
23
23
|
import plistlib
|
|
24
24
|
import warnings
|
|
25
25
|
|
|
26
|
-
from numbers_parser.
|
|
27
|
-
from numbers_parser.
|
|
28
|
-
from numbers_parser.exceptions import *
|
|
26
|
+
from numbers_parser.cell import *
|
|
27
|
+
from numbers_parser.document import Document
|
|
28
|
+
from numbers_parser.exceptions import *
|
|
29
29
|
|
|
30
30
|
__version__ = importlib.metadata.version("numbers-parser")
|
|
31
31
|
|
|
@@ -53,12 +53,14 @@ def _get_version():
|
|
|
53
53
|
def _check_installed_numbers_version():
|
|
54
54
|
try:
|
|
55
55
|
fp = open(os.path.join(_DEFAULT_NUMBERS_INSTALL_PATH, _VERSION_PLIST_PATH), "rb")
|
|
56
|
-
except
|
|
56
|
+
except OSError:
|
|
57
57
|
return None
|
|
58
58
|
version_dict = plistlib.load(fp)
|
|
59
59
|
installed_version = version_dict["CFBundleShortVersionString"]
|
|
60
60
|
if installed_version not in _SUPPORTED_NUMBERS_VERSIONS:
|
|
61
|
-
warnings.warn(
|
|
61
|
+
warnings.warn(
|
|
62
|
+
f"Numbers version {installed_version} not tested with this version", stacklevel=2
|
|
63
|
+
)
|
|
62
64
|
fp.close()
|
|
63
65
|
return installed_version
|
|
64
66
|
|
|
@@ -5,9 +5,8 @@ import sys
|
|
|
5
5
|
|
|
6
6
|
import sigfig
|
|
7
7
|
|
|
8
|
-
from numbers_parser import Document, ErrorCell, FileFormatError, NumberCell,
|
|
8
|
+
from numbers_parser import Document, ErrorCell, FileError, FileFormatError, NumberCell, _get_version
|
|
9
9
|
from numbers_parser import __name__ as numbers_parser_name
|
|
10
|
-
from numbers_parser import _get_version
|
|
11
10
|
from numbers_parser.constants import MAX_SIGNIFICANT_DIGITS
|
|
12
11
|
from numbers_parser.experimental import _enable_experimental_features
|
|
13
12
|
|
|
@@ -1,33 +1,31 @@
|
|
|
1
|
-
import
|
|
1
|
+
import contextlib
|
|
2
2
|
import json
|
|
3
3
|
import logging
|
|
4
|
-
import
|
|
4
|
+
import os
|
|
5
5
|
import sys
|
|
6
|
-
|
|
7
|
-
from array import array
|
|
8
6
|
from argparse import ArgumentParser
|
|
7
|
+
from array import array
|
|
9
8
|
from base64 import b64decode
|
|
10
9
|
from binascii import hexlify
|
|
10
|
+
|
|
11
|
+
import regex
|
|
11
12
|
from compact_json import Formatter
|
|
12
13
|
|
|
13
|
-
from numbers_parser.file import read_numbers_file
|
|
14
|
-
from numbers_parser import _get_version
|
|
15
14
|
from numbers_parser import __name__ as numbers_parser_name
|
|
15
|
+
from numbers_parser import _get_version
|
|
16
|
+
from numbers_parser.exceptions import FileError, FileFormatError, UnsupportedError
|
|
17
|
+
from numbers_parser.file import read_numbers_file
|
|
16
18
|
from numbers_parser.iwafile import IWAFile
|
|
17
|
-
from numbers_parser.exceptions import FileFormatError, UnsupportedError, FileError
|
|
18
19
|
from numbers_parser.numbers_uuid import NumbersUUID
|
|
19
20
|
|
|
20
|
-
|
|
21
21
|
logger = logging.getLogger(numbers_parser_name)
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
def ensure_directory_exists(prefix, path):
|
|
25
25
|
"""Ensure that a path's directory exists."""
|
|
26
26
|
parts = os.path.split(path)
|
|
27
|
-
|
|
27
|
+
with contextlib.suppress(OSError):
|
|
28
28
|
os.makedirs(os.path.join(*([prefix] + list(parts[:-1]))))
|
|
29
|
-
except OSError:
|
|
30
|
-
pass
|
|
31
29
|
|
|
32
30
|
|
|
33
31
|
def prettify_uuids(obj):
|
|
@@ -54,18 +52,18 @@ def prettify_uuids(obj):
|
|
|
54
52
|
def prettify_cell_storage(obj):
|
|
55
53
|
if isinstance(obj, dict):
|
|
56
54
|
for k, v in obj.items():
|
|
57
|
-
if isinstance(v, dict
|
|
55
|
+
if isinstance(v, (dict, list)):
|
|
58
56
|
prettify_cell_storage(v)
|
|
59
|
-
elif k
|
|
57
|
+
elif k in ["cell_storage_buffer", "cell_storage_buffer_pre_bnc"]:
|
|
60
58
|
obj[k] = str(hexlify(b64decode(obj[k]), sep=":"))
|
|
61
59
|
obj[k] = obj[k].replace("b'", "").replace("'", "")
|
|
62
|
-
elif k
|
|
60
|
+
elif k in ["cell_offsets", k == "cell_offsets_pre_bnc"]:
|
|
63
61
|
offsets = array("h", b64decode(obj[k])).tolist()
|
|
64
62
|
obj[k] = ",".join([str(x) for x in offsets])
|
|
65
63
|
obj[k] = regex.sub(r"(?:,-1)+$", ",[...]", obj[k])
|
|
66
64
|
else: # list
|
|
67
65
|
for v in obj:
|
|
68
|
-
if isinstance(v, dict
|
|
66
|
+
if isinstance(v, (dict, list)):
|
|
69
67
|
prettify_cell_storage(v)
|
|
70
68
|
|
|
71
69
|
|
|
@@ -137,7 +135,7 @@ def main():
|
|
|
137
135
|
try:
|
|
138
136
|
read_numbers_file(
|
|
139
137
|
document,
|
|
140
|
-
file_handler=lambda filename, blob: process_file(
|
|
138
|
+
file_handler=lambda filename, blob, output_dir=output_dir: process_file(
|
|
141
139
|
filename, blob, output_dir, args
|
|
142
140
|
),
|
|
143
141
|
)
|
|
@@ -29,7 +29,7 @@ from numbers_parser.generated import TSTArchives_pb2 as TSTArchives
|
|
|
29
29
|
from numbers_parser.generated.TSWPArchives_pb2 import (
|
|
30
30
|
ParagraphStylePropertiesArchive as ParagraphStyle,
|
|
31
31
|
)
|
|
32
|
-
from numbers_parser.numbers_cache import
|
|
32
|
+
from numbers_parser.numbers_cache import Cacheable, cache
|
|
33
33
|
|
|
34
34
|
__all__ = [
|
|
35
35
|
"Alignment",
|
|
@@ -122,8 +122,7 @@ class Alignment(_Alignment):
|
|
|
122
122
|
raise TypeError("invalid vertical alignment")
|
|
123
123
|
vertical = VERTICAL_MAP[vertical]
|
|
124
124
|
|
|
125
|
-
|
|
126
|
-
return self
|
|
125
|
+
return super(_Alignment, cls).__new__(cls, (horizontal, vertical))
|
|
127
126
|
|
|
128
127
|
|
|
129
128
|
DEFAULT_ALIGNMENT_CLASS = Alignment(*DEFAULT_ALIGNMENT)
|
|
@@ -186,13 +185,11 @@ class Style:
|
|
|
186
185
|
|
|
187
186
|
@classmethod
|
|
188
187
|
def from_storage(cls, cell_storage: object, model: object):
|
|
189
|
-
style = Style()
|
|
190
|
-
|
|
191
188
|
if cell_storage.image_data is not None:
|
|
192
189
|
bg_image = BackgroundImage(*cell_storage.image_data)
|
|
193
190
|
else:
|
|
194
191
|
bg_image = None
|
|
195
|
-
|
|
192
|
+
return Style(
|
|
196
193
|
alignment=model.cell_alignment(cell_storage),
|
|
197
194
|
bg_image=bg_image,
|
|
198
195
|
bg_color=model.cell_bg_color(cell_storage),
|
|
@@ -212,7 +209,6 @@ class Style:
|
|
|
212
209
|
_text_style_obj_id=model.text_style_object_id(cell_storage),
|
|
213
210
|
_cell_style_obj_id=model.cell_style_object_id(cell_storage),
|
|
214
211
|
)
|
|
215
|
-
return style
|
|
216
212
|
|
|
217
213
|
def __post_init__(self):
|
|
218
214
|
self.bg_color = rgb_color(self.bg_color)
|
|
@@ -229,7 +225,8 @@ class Style:
|
|
|
229
225
|
|
|
230
226
|
def __setattr__(self, name: str, value: Any) -> None:
|
|
231
227
|
"""Detect changes to cell styles and flag the style for
|
|
232
|
-
possible updates when saving the document
|
|
228
|
+
possible updates when saving the document.
|
|
229
|
+
"""
|
|
233
230
|
if name in ["bg_color", "font_color"]:
|
|
234
231
|
value = rgb_color(value)
|
|
235
232
|
if name == "alignment":
|
|
@@ -244,13 +241,13 @@ class Style:
|
|
|
244
241
|
|
|
245
242
|
|
|
246
243
|
def rgb_color(color) -> RGB:
|
|
247
|
-
"""Raise a TypeError if a color is not a valid RGB value"""
|
|
244
|
+
"""Raise a TypeError if a color is not a valid RGB value."""
|
|
248
245
|
if color is None:
|
|
249
246
|
return None
|
|
250
247
|
if isinstance(color, RGB):
|
|
251
248
|
return color
|
|
252
249
|
if isinstance(color, tuple):
|
|
253
|
-
if not (len(color) == 3 and all(
|
|
250
|
+
if not (len(color) == 3 and all(isinstance(x, int) for x in color)):
|
|
254
251
|
raise TypeError("RGB color must be an RGB or a tuple of 3 integers")
|
|
255
252
|
return RGB(*color)
|
|
256
253
|
elif isinstance(color, list):
|
|
@@ -259,13 +256,13 @@ def rgb_color(color) -> RGB:
|
|
|
259
256
|
|
|
260
257
|
|
|
261
258
|
def alignment(value) -> Alignment:
|
|
262
|
-
"""Raise a TypeError if a alignment is not a valid"""
|
|
259
|
+
"""Raise a TypeError if a alignment is not a valid."""
|
|
263
260
|
if value is None:
|
|
264
261
|
return Alignment()
|
|
265
262
|
if isinstance(value, Alignment):
|
|
266
263
|
return value
|
|
267
264
|
if isinstance(value, tuple):
|
|
268
|
-
if not (len(value) == 2 and all(
|
|
265
|
+
if not (len(value) == 2 and all(isinstance(x, (int, str)) for x in value)):
|
|
269
266
|
raise TypeError("Alignment must be an Alignment or a tuple of 2 integers/strings")
|
|
270
267
|
return Alignment(*value)
|
|
271
268
|
raise TypeError("Alignment must be an Alignment or a tuple of 2 integers/strings")
|
|
@@ -285,16 +282,20 @@ class Border:
|
|
|
285
282
|
def __init__(
|
|
286
283
|
self,
|
|
287
284
|
width: float = DEFAULT_BORDER_WIDTH,
|
|
288
|
-
color: RGB =
|
|
289
|
-
style: BorderType =
|
|
285
|
+
color: RGB = None,
|
|
286
|
+
style: BorderType = None,
|
|
290
287
|
_order: int = 0,
|
|
291
288
|
):
|
|
292
289
|
if not isinstance(width, float):
|
|
293
290
|
raise TypeError("width must be a float number of points")
|
|
294
291
|
self.width = width
|
|
295
292
|
|
|
293
|
+
if color is None:
|
|
294
|
+
color = RGB(*DEFAULT_BORDER_COLOR)
|
|
296
295
|
self.color = rgb_color(color)
|
|
297
296
|
|
|
297
|
+
if style is None:
|
|
298
|
+
style = BorderType(BORDER_STYLE_MAP[DEFAULT_BORDER_STYLE])
|
|
298
299
|
if isinstance(style, str):
|
|
299
300
|
style = style.lower()
|
|
300
301
|
if style not in BORDER_STYLE_MAP:
|
|
@@ -394,14 +395,14 @@ class CellBorder:
|
|
|
394
395
|
|
|
395
396
|
|
|
396
397
|
class MergeReference:
|
|
397
|
-
"""Cell reference for cells eliminated by a merge"""
|
|
398
|
+
"""Cell reference for cells eliminated by a merge."""
|
|
398
399
|
|
|
399
400
|
def __init__(self, row_start: int, col_start: int, row_end: int, col_end: int):
|
|
400
401
|
self.rect = (row_start, col_start, row_end, col_end)
|
|
401
402
|
|
|
402
403
|
|
|
403
404
|
class MergeAnchor:
|
|
404
|
-
"""Cell reference for the merged cell"""
|
|
405
|
+
"""Cell reference for the merged cell."""
|
|
405
406
|
|
|
406
407
|
def __init__(self, size: Tuple):
|
|
407
408
|
self.size = size
|
|
@@ -475,11 +476,12 @@ class Cell(Cacheable):
|
|
|
475
476
|
warn(
|
|
476
477
|
f"'{value}' rounded to {MAX_SIGNIFICANT_DIGITS} significant digits",
|
|
477
478
|
RuntimeWarning,
|
|
479
|
+
stacklevel=2,
|
|
478
480
|
)
|
|
479
481
|
return NumberCell(row_num, col_num, rounded_value)
|
|
480
|
-
elif isinstance(value,
|
|
482
|
+
elif isinstance(value, (DateTime, builtin_datetime)):
|
|
481
483
|
return DateCell(row_num, col_num, pendulum_instance(value))
|
|
482
|
-
elif isinstance(value,
|
|
484
|
+
elif isinstance(value, (Duration, builtin_timedelta)):
|
|
483
485
|
return DurationCell(row_num, col_num, value)
|
|
484
486
|
else:
|
|
485
487
|
raise ValueError("Can't determine cell type from type " + type(value).__name__)
|
|
@@ -527,6 +529,7 @@ class Cell(Cacheable):
|
|
|
527
529
|
"image_filename is deprecated and will be removed in the future. "
|
|
528
530
|
+ "Please use the style property",
|
|
529
531
|
DeprecationWarning,
|
|
532
|
+
stacklevel=2,
|
|
530
533
|
)
|
|
531
534
|
if self.style is not None and self.style.bg_image is not None:
|
|
532
535
|
return self.style.bg_image.filename
|
|
@@ -539,6 +542,7 @@ class Cell(Cacheable):
|
|
|
539
542
|
"image_data is deprecated and will be removed in the future. "
|
|
540
543
|
+ "Please use the style property",
|
|
541
544
|
DeprecationWarning,
|
|
545
|
+
stacklevel=2,
|
|
542
546
|
)
|
|
543
547
|
if self.style is not None and self.style.bg_image is not None:
|
|
544
548
|
return self.style.bg_image.data
|
|
@@ -555,8 +559,7 @@ class Cell(Cacheable):
|
|
|
555
559
|
def formula(self):
|
|
556
560
|
if self._formula_key is not None:
|
|
557
561
|
table_formulas = self._model.table_formulas(self._table_id)
|
|
558
|
-
|
|
559
|
-
return formula
|
|
562
|
+
return table_formulas.formula(self._formula_key, self.row, self.col)
|
|
560
563
|
else:
|
|
561
564
|
return None
|
|
562
565
|
|
|
@@ -580,7 +583,11 @@ class Cell(Cacheable):
|
|
|
580
583
|
|
|
581
584
|
@style.setter
|
|
582
585
|
def style(self, _):
|
|
583
|
-
warn(
|
|
586
|
+
warn(
|
|
587
|
+
"cell style cannot be set; use Table.set_cell_style() instead",
|
|
588
|
+
UnsupportedWarning,
|
|
589
|
+
stacklevel=2,
|
|
590
|
+
)
|
|
584
591
|
|
|
585
592
|
@property
|
|
586
593
|
def border(self):
|
|
@@ -592,6 +599,7 @@ class Cell(Cacheable):
|
|
|
592
599
|
warn(
|
|
593
600
|
"cell border values cannot be set; use Table.set_cell_border() instead",
|
|
594
601
|
UnsupportedWarning,
|
|
602
|
+
stacklevel=2,
|
|
595
603
|
)
|
|
596
604
|
|
|
597
605
|
|
|
@@ -718,8 +726,7 @@ range_parts = re.compile(r"(\$?)([A-Z]{1,3})(\$?)(\d+)")
|
|
|
718
726
|
|
|
719
727
|
|
|
720
728
|
def xl_cell_to_rowcol(cell_str: str) -> tuple:
|
|
721
|
-
"""
|
|
722
|
-
Convert a cell reference in A1 notation to a zero indexed row and column.
|
|
729
|
+
"""Convert a cell reference in A1 notation to a zero indexed row and column.
|
|
723
730
|
Args:
|
|
724
731
|
cell_str: A1 style string.
|
|
725
732
|
Returns:
|
|
@@ -750,8 +757,7 @@ def xl_cell_to_rowcol(cell_str: str) -> tuple:
|
|
|
750
757
|
|
|
751
758
|
|
|
752
759
|
def xl_range(first_row, first_col, last_row, last_col):
|
|
753
|
-
"""
|
|
754
|
-
Convert zero indexed row and col cell references to a A1:B1 range string.
|
|
760
|
+
"""Convert zero indexed row and col cell references to a A1:B1 range string.
|
|
755
761
|
Args:
|
|
756
762
|
first_row: The first cell row. Int.
|
|
757
763
|
first_col: The first cell column. Int.
|
|
@@ -770,8 +776,7 @@ def xl_range(first_row, first_col, last_row, last_col):
|
|
|
770
776
|
|
|
771
777
|
|
|
772
778
|
def xl_rowcol_to_cell(row, col, row_abs=False, col_abs=False):
|
|
773
|
-
"""
|
|
774
|
-
Convert a zero indexed row and column cell reference to a A1 style string.
|
|
779
|
+
"""Convert a zero indexed row and column cell reference to a A1 style string.
|
|
775
780
|
Args:
|
|
776
781
|
row: The cell row. Int.
|
|
777
782
|
col: The cell column. Int.
|
|
@@ -795,8 +800,7 @@ def xl_rowcol_to_cell(row, col, row_abs=False, col_abs=False):
|
|
|
795
800
|
|
|
796
801
|
|
|
797
802
|
def xl_col_to_name(col, col_abs=False):
|
|
798
|
-
"""
|
|
799
|
-
Convert a zero indexed column cell reference to a string.
|
|
803
|
+
"""Convert a zero indexed column cell reference to a string.
|
|
800
804
|
Args:
|
|
801
805
|
col: The cell column. Int.
|
|
802
806
|
col_abs: Optional flag to make the column absolute. Bool.
|