numbers-parser 4.13.2__tar.gz → 4.14.1__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 (61) hide show
  1. numbers_parser-4.13.2/README.md → numbers_parser-4.14.1/PKG-INFO +31 -15
  2. numbers_parser-4.13.2/PKG-INFO → numbers_parser-4.14.1/README.md +1 -48
  3. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/pyproject.toml +7 -9
  4. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/__init__.py +2 -1
  5. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/_cat_numbers.py +14 -5
  6. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/_csv2numbers.py +9 -1
  7. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/_unpack_numbers.py +2 -3
  8. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/bullets.py +7 -8
  9. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/cell.py +23 -26
  10. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/constants.py +16 -5
  11. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/document.py +3 -4
  12. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/formula.py +2 -3
  13. numbers_parser-4.14.1/src/numbers_parser/roman.py +18 -0
  14. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/LICENSE.rst +0 -0
  15. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/containers.py +0 -0
  16. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/currencies.py +0 -0
  17. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/data/empty.numbers +0 -0
  18. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/exceptions.py +0 -0
  19. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/experimental.py +0 -0
  20. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/generated/TNArchives_pb2.py +0 -0
  21. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/generated/TNArchives_sos_pb2.py +0 -0
  22. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/generated/TNCommandArchives_pb2.py +0 -0
  23. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/generated/TNCommandArchives_sos_pb2.py +0 -0
  24. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/generated/TSAArchives_pb2.py +0 -0
  25. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/generated/TSAArchives_sos_pb2.py +0 -0
  26. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/generated/TSACommandArchives_sos_pb2.py +0 -0
  27. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/generated/TSCEArchives_pb2.py +0 -0
  28. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/generated/TSCH3DArchives_pb2.py +0 -0
  29. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/generated/TSCHArchives_Common_pb2.py +0 -0
  30. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/generated/TSCHArchives_GEN_pb2.py +0 -0
  31. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/generated/TSCHArchives_pb2.py +0 -0
  32. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/generated/TSCHArchives_sos_pb2.py +0 -0
  33. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/generated/TSCHCommandArchives_pb2.py +0 -0
  34. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/generated/TSCHPreUFFArchives_pb2.py +0 -0
  35. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/generated/TSCKArchives_pb2.py +0 -0
  36. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/generated/TSCKArchives_sos_pb2.py +0 -0
  37. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/generated/TSDArchives_pb2.py +0 -0
  38. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/generated/TSDArchives_sos_pb2.py +0 -0
  39. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/generated/TSDCommandArchives_pb2.py +0 -0
  40. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/generated/TSKArchives_pb2.py +0 -0
  41. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/generated/TSPArchiveMessages_pb2.py +0 -0
  42. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/generated/TSPDatabaseMessages_pb2.py +0 -0
  43. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/generated/TSPMessages_pb2.py +0 -0
  44. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/generated/TSSArchives_pb2.py +0 -0
  45. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/generated/TSSArchives_sos_pb2.py +0 -0
  46. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/generated/TSTArchives_pb2.py +0 -0
  47. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/generated/TSTArchives_sos_pb2.py +0 -0
  48. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/generated/TSTCommandArchives_pb2.py +0 -0
  49. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/generated/TSTStylePropertyArchiving_pb2.py +0 -0
  50. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/generated/TSWPArchives_pb2.py +0 -0
  51. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/generated/TSWPArchives_sos_pb2.py +0 -0
  52. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/generated/TSWPCommandArchives_pb2.py +0 -0
  53. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/generated/__init__.py +0 -0
  54. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/generated/fontmap.py +0 -0
  55. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/generated/functionmap.py +8 -8
  56. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/generated/mapping.py +0 -0
  57. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/iwafile.py +0 -0
  58. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/iwork.py +0 -0
  59. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/model.py +0 -0
  60. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/numbers_cache.py +0 -0
  61. {numbers_parser-4.13.2 → numbers_parser-4.14.1}/src/numbers_parser/numbers_uuid.py +0 -0
@@ -1,3 +1,32 @@
1
+ Metadata-Version: 2.1
2
+ Name: numbers-parser
3
+ Version: 4.14.1
4
+ Summary: Read and write Apple Numbers spreadsheets
5
+ Home-page: https://github.com/masaccio/numbers-parser
6
+ License: MIT
7
+ Author: Jon Connell
8
+ Author-email: python@figsandfudge.com
9
+ Requires-Python: >=3.9,<4.0
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Office/Business :: Financial :: Spreadsheet
18
+ Requires-Dist: compact-json (>=1.1.3,<2.0.0)
19
+ Requires-Dist: enum-tools (>=0.11)
20
+ Requires-Dist: importlib-resources (>=6.1)
21
+ Requires-Dist: protobuf
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
28
+ Description-Content-Type: text/markdown
29
+
1
30
  # numbers-parser
2
31
 
3
32
  [![Test Status](https://github.com/masaccio/numbers-parser/actions/workflows/run-all-tests.yml/badge.svg)](https://github.com/masaccio/numbers-parser/actions/workflows/run-all-tests.yml)[![Security Checks](https://github.com/masaccio/numbers-parser/actions/workflows/codeql.yml/badge.svg)](https://github.com/masaccio/numbers-parser/actions/workflows/codeql.yml)[![Code Coverage](https://codecov.io/gh/masaccio/numbers-parser/branch/main/graph/badge.svg?token=EKIUFGT05E)](https://codecov.io/gh/masaccio/numbers-parser)[![PyPI Version](https://badge.fury.io/py/numbers-parser.svg)](https://badge.fury.io/py/numbers-parser)
@@ -97,21 +126,7 @@ the column values.
97
126
 
98
127
  Cells are objects with a common base class of `Cell`. All cell types
99
128
  have a property `value` which returns the contents of the cell as a
100
- python datatype. `numbers-parser` uses
101
- [pendulum](https://pendulum.eustace.io) instead of python’s builtin
102
- types. Available cell types are:
103
-
104
- | Cell type | value type | Additional properties |
105
- |--------------|---------------------|--------------------------------------------------------------------------------------------------------|
106
- | NumberCell | `float` | |
107
- | TextCell | `str` | |
108
- | RichTextCell | `str` | See [Rich text](https://masaccio.github.io/numbers-parser/api/cells.html#numbers_parser.RichTextCell) |
109
- | EmptyCell | `None` | |
110
- | BoolCell | `bool` | |
111
- | DateCell | `pendulum.datetime` | |
112
- | DurationCell | `pendulum.duration` | |
113
- | ErrorCell | `None` | |
114
- | MergedCell | `None` | See [Merged cells](https://masaccio.github.io/numbers-parser/api/cells.html#numbers_parser.MergedCell) |
129
+ python datatype. Available cell types are:
115
130
 
116
131
  Cell references can be either zero-offset row/column integers or an
117
132
  Excel/Numbers A1 notation. Where cell values are not `None` the
@@ -478,3 +493,4 @@ The following limitations are expected to always remain:
478
493
  ## License
479
494
 
480
495
  All code in this repository is licensed under the [MIT License](https://github.com/masaccio/numbers-parser/blob/master/LICENSE.rst).
496
+
@@ -1,35 +1,3 @@
1
- Metadata-Version: 2.1
2
- Name: numbers-parser
3
- Version: 4.13.2
4
- Summary: Read and write Apple Numbers spreadsheets
5
- Home-page: https://github.com/masaccio/numbers-parser
6
- License: MIT
7
- Author: Jon Connell
8
- Author-email: python@figsandfudge.com
9
- Requires-Python: >=3.9,<4.0
10
- Classifier: License :: OSI Approved :: MIT License
11
- Classifier: Operating System :: OS Independent
12
- Classifier: Programming Language :: Python :: 3
13
- Classifier: Programming Language :: Python :: 3.9
14
- Classifier: Programming Language :: Python :: 3.10
15
- Classifier: Programming Language :: Python :: 3.11
16
- Classifier: Programming Language :: Python :: 3.12
17
- Classifier: Topic :: Office/Business :: Financial :: Spreadsheet
18
- Requires-Dist: colorama (>=0.4.6,<0.5.0)
19
- Requires-Dist: compact-json (>=1.1.3,<2.0.0)
20
- Requires-Dist: enum-tools (>=0.11)
21
- Requires-Dist: importlib-resources (>=6.1)
22
- Requires-Dist: pendulum (>=3.0,<4.0)
23
- Requires-Dist: protobuf
24
- Requires-Dist: python-snappy (>=0.7,<0.8)
25
- Requires-Dist: regex (>2024.0)
26
- Requires-Dist: roman (>=4.0)
27
- Requires-Dist: setuptools (>=69.0.3)
28
- Requires-Dist: sigfig (>=1.3.2,<2.0.0)
29
- Project-URL: Documentation, https://github.com/masaccio/numbers-parser/blob/main/README.md
30
- Project-URL: Repository, https://github.com/masaccio/numbers-parser
31
- Description-Content-Type: text/markdown
32
-
33
1
  # numbers-parser
34
2
 
35
3
  [![Test Status](https://github.com/masaccio/numbers-parser/actions/workflows/run-all-tests.yml/badge.svg)](https://github.com/masaccio/numbers-parser/actions/workflows/run-all-tests.yml)[![Security Checks](https://github.com/masaccio/numbers-parser/actions/workflows/codeql.yml/badge.svg)](https://github.com/masaccio/numbers-parser/actions/workflows/codeql.yml)[![Code Coverage](https://codecov.io/gh/masaccio/numbers-parser/branch/main/graph/badge.svg?token=EKIUFGT05E)](https://codecov.io/gh/masaccio/numbers-parser)[![PyPI Version](https://badge.fury.io/py/numbers-parser.svg)](https://badge.fury.io/py/numbers-parser)
@@ -129,21 +97,7 @@ the column values.
129
97
 
130
98
  Cells are objects with a common base class of `Cell`. All cell types
131
99
  have a property `value` which returns the contents of the cell as a
132
- python datatype. `numbers-parser` uses
133
- [pendulum](https://pendulum.eustace.io) instead of python’s builtin
134
- types. Available cell types are:
135
-
136
- | Cell type | value type | Additional properties |
137
- |--------------|---------------------|--------------------------------------------------------------------------------------------------------|
138
- | NumberCell | `float` | |
139
- | TextCell | `str` | |
140
- | RichTextCell | `str` | See [Rich text](https://masaccio.github.io/numbers-parser/api/cells.html#numbers_parser.RichTextCell) |
141
- | EmptyCell | `None` | |
142
- | BoolCell | `bool` | |
143
- | DateCell | `pendulum.datetime` | |
144
- | DurationCell | `pendulum.duration` | |
145
- | ErrorCell | `None` | |
146
- | MergedCell | `None` | See [Merged cells](https://masaccio.github.io/numbers-parser/api/cells.html#numbers_parser.MergedCell) |
100
+ python datatype. Available cell types are:
147
101
 
148
102
  Cell references can be either zero-offset row/column integers or an
149
103
  Excel/Numbers A1 notation. Where cell values are not `None` the
@@ -510,4 +464,3 @@ The following limitations are expected to always remain:
510
464
  ## License
511
465
 
512
466
  All code in this repository is licensed under the [MIT License](https://github.com/masaccio/numbers-parser/blob/master/LICENSE.rst).
513
-
@@ -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.13.2"
15
+ version = "4.14.1"
16
16
 
17
17
  [tool.poetry.scripts]
18
18
  cat-numbers = "numbers_parser._cat_numbers:main"
@@ -21,17 +21,14 @@ csv2numbers = "numbers_parser._csv2numbers:main"
21
21
 
22
22
  [tool.poetry.dependencies]
23
23
  compact-json = "^1.1.3"
24
- pendulum = "^3.0"
25
24
  protobuf = "*"
26
25
  python = ">=3.9,<4.0"
27
26
  python-snappy = "^0.7"
28
- regex = ">=2022.9.13,>2023.0,>2024.0"
29
- roman = ">=3.3,>=4.0"
30
- sigfig = "^1.3.2"
31
- setuptools = ">=69.0.3"
27
+ sigfig = "^1.3.3"
28
+ setuptools = ">=70.0.0"
32
29
  importlib-resources = ">=6.1"
33
30
  enum-tools = ">=0.11"
34
- colorama = "^0.4.6"
31
+ python-dateutil = "^2.9.0.post0"
35
32
 
36
33
  [tool.poetry.group.dev.dependencies]
37
34
  black = "*"
@@ -43,12 +40,13 @@ pytest = ">=7.2.0"
43
40
  pytest-check = ">=1.0"
44
41
  pytest-console-scripts = "^1.3.1"
45
42
  pytest-cov = ">=4.0,>=5.0"
46
- pytest-profiling = "^1.7.0"
47
43
  pytest-xdist = "^3.3.1"
48
44
  ruff = "*"
49
45
  tox = "^4.11.4"
50
46
  python-magic = ">=0.4"
51
47
  tqdm = ">=4.66"
48
+ colorama = "^0.4.6"
49
+ roman = "^4.2"
52
50
 
53
51
  [tool.poetry.group.docs]
54
52
  optional = true
@@ -88,7 +86,7 @@ addopts = "--cov=src/numbers_parser --cov-report=html --cov-report=term-missing:
88
86
  legacy_tox_ini = """
89
87
  [tox]
90
88
  isolated_build = true
91
- envlist = py39, py310, py311, py312
89
+ envlist = py39, py310, py311, py312, py313
92
90
  [testenv]
93
91
  deps =
94
92
  pytest
@@ -37,7 +37,8 @@ def _check_installed_numbers_version():
37
37
  installed_version = re.sub(r"(\d+)\.(\d+)\.\d+", r"\1.\2", installed_version)
38
38
  if installed_version not in SUPPORTED_NUMBERS_VERSIONS:
39
39
  warnings.warn(
40
- f"Numbers version {installed_version} not tested with this version", stacklevel=2,
40
+ f"Numbers version {installed_version} not tested with this version",
41
+ stacklevel=2,
41
42
  )
42
43
  fp.close()
43
44
  return installed_version
@@ -3,7 +3,7 @@ import csv
3
3
  import logging
4
4
  import sys
5
5
 
6
- import sigfig
6
+ from sigfig import round as sigfig
7
7
 
8
8
  from numbers_parser import Document, ErrorCell, FileError, FileFormatError, NumberCell, _get_version
9
9
  from numbers_parser import __name__ as numbers_parser_name
@@ -49,15 +49,24 @@ def command_line_parser():
49
49
  help="Dump formatted cells (durations) as they appear in Numbers",
50
50
  )
51
51
  parser.add_argument(
52
- "-s", "--sheet", action="append", help="Names of sheet(s) to include in export",
52
+ "-s",
53
+ "--sheet",
54
+ action="append",
55
+ help="Names of sheet(s) to include in export",
53
56
  )
54
57
  parser.add_argument(
55
- "-t", "--table", action="append", help="Names of table(s) to include in export",
58
+ "-t",
59
+ "--table",
60
+ action="append",
61
+ help="Names of table(s) to include in export",
56
62
  )
57
63
  parser.add_argument("document", nargs="*", help="Document(s) to export")
58
64
  parser.add_argument("--debug", default=False, action="store_true", help="Enable debug logging")
59
65
  parser.add_argument(
60
- "--experimental", default=False, action="store_true", help=argparse.SUPPRESS,
66
+ "--experimental",
67
+ default=False,
68
+ action="store_true",
69
+ help=argparse.SUPPRESS,
61
70
  )
62
71
  return parser
63
72
 
@@ -81,7 +90,7 @@ def cell_as_string(args, cell):
81
90
  elif args.formatting and cell.formatted_value is not None:
82
91
  return cell.formatted_value
83
92
  elif isinstance(cell, NumberCell):
84
- return sigfig.round(cell.value, sigfigs=MAX_SIGNIFICANT_DIGITS, warn=False)
93
+ return sigfig(cell.value, sigfigs=MAX_SIGNIFICANT_DIGITS, warn=False)
85
94
  elif cell.value is None:
86
95
  return ""
87
96
  else:
@@ -33,6 +33,7 @@ class Converter:
33
33
  no_header: bool = False
34
34
  reverse: bool = False
35
35
  whitespace: bool = None
36
+ encoding: str = "utf-8"
36
37
 
37
38
  def __post_init__(self: Converter) -> None:
38
39
  """Parse CSV file with Pandas and return a dataframe."""
@@ -45,7 +46,7 @@ class Converter:
45
46
  dialect = csv.excel
46
47
  dialect.strict = True
47
48
  lineno = 1
48
- with open(self.input_filename) as csvfile:
49
+ with open(self.input_filename, encoding=self.encoding) as csvfile:
49
50
  csvreader = csv.reader(csvfile, dialect=dialect)
50
51
  if self.no_header:
51
52
  self.header = None
@@ -333,6 +334,12 @@ def command_line_parser() -> argparse.ArgumentParser:
333
334
  action="store_true",
334
335
  help="dates are represented day first in the CSV file (default: false)",
335
336
  )
337
+ parser.add_argument(
338
+ "--encoding",
339
+ required=False,
340
+ default="utf-8",
341
+ help="python-style text encoding of the CSV file (default: utf-8)",
342
+ )
336
343
  parser.add_argument(
337
344
  "--date",
338
345
  metavar="COLUMNS",
@@ -400,6 +407,7 @@ def main() -> None:
400
407
  date_columns=args.date,
401
408
  input_filename=input_filename,
402
409
  output_filename=output_filename,
410
+ encoding=args.encoding,
403
411
  )
404
412
 
405
413
  converter.transform_columns(args.transform)
@@ -11,7 +11,6 @@ from binascii import hexlify
11
11
  from dataclasses import dataclass
12
12
  from pathlib import Path
13
13
 
14
- import regex
15
14
  from compact_json import Formatter
16
15
 
17
16
  from numbers_parser import __name__ as numbers_parser_name
@@ -35,7 +34,7 @@ class NumbersUnpacker(IWorkHandler):
35
34
 
36
35
  def store_file(self, filename: str, blob: bytes) -> None:
37
36
  """Store a profobuf archive."""
38
- filename = regex.sub(r".*\.numbers/", "", str(filename))
37
+ filename = re.sub(r".*\.numbers/", "", str(filename))
39
38
  self.ensure_directory_exists(filename)
40
39
  target_path = os.path.join(self.output_dir, filename)
41
40
  if isinstance(blob, IWAFile):
@@ -102,7 +101,7 @@ class NumbersUnpacker(IWorkHandler):
102
101
  elif k in ["cell_offsets", k == "cell_offsets_pre_bnc"]:
103
102
  offsets = array("h", b64decode(obj[k])).tolist()
104
103
  obj[k] = ",".join([str(x) for x in offsets])
105
- obj[k] = regex.sub(r"(?:,-1)+$", ",[...]", obj[k])
104
+ obj[k] = re.sub(r"(?:,-1)+$", ",[...]", obj[k])
106
105
  else: # list
107
106
  for v in obj:
108
107
  if isinstance(v, (dict, list)):
@@ -1,6 +1,5 @@
1
- from roman import toRoman
2
-
3
1
  from numbers_parser.generated.TSWPArchives_pb2 import ListStyleArchive
2
+ from numbers_parser.roman import to_roman
4
3
 
5
4
  BULLET_PREFIXES = {
6
5
  ListStyleArchive.kNumericDecimal: "",
@@ -24,12 +23,12 @@ BULLET_CONVERSION = {
24
23
  ListStyleArchive.kNumericDecimal: lambda x: str(x + 1),
25
24
  ListStyleArchive.kNumericDoubleParen: lambda x: str(x + 1),
26
25
  ListStyleArchive.kNumericRightParen: lambda x: str(x + 1),
27
- ListStyleArchive.kRomanUpperDecimal: lambda x: toRoman(x + 1),
28
- ListStyleArchive.kRomanUpperDoubleParen: lambda x: toRoman(x + 1),
29
- ListStyleArchive.kRomanUpperRightParen: lambda x: toRoman(x + 1),
30
- ListStyleArchive.kRomanLowerDecimal: lambda x: toRoman(x + 1).lower(),
31
- ListStyleArchive.kRomanLowerDoubleParen: lambda x: toRoman(x + 1).lower(),
32
- ListStyleArchive.kRomanLowerRightParen: lambda x: toRoman(x + 1).lower(),
26
+ ListStyleArchive.kRomanUpperDecimal: lambda x: to_roman(x + 1),
27
+ ListStyleArchive.kRomanUpperDoubleParen: lambda x: to_roman(x + 1),
28
+ ListStyleArchive.kRomanUpperRightParen: lambda x: to_roman(x + 1),
29
+ ListStyleArchive.kRomanLowerDecimal: lambda x: to_roman(x + 1).lower(),
30
+ ListStyleArchive.kRomanLowerDoubleParen: lambda x: to_roman(x + 1).lower(),
31
+ ListStyleArchive.kRomanLowerRightParen: lambda x: to_roman(x + 1).lower(),
33
32
  ListStyleArchive.kAlphaUpperDecimal: lambda x: chr(x + 65),
34
33
  ListStyleArchive.kAlphaUpperDoubleParen: lambda x: chr(x + 65),
35
34
  ListStyleArchive.kAlphaUpperRightParen: lambda x: chr(x + 65),
@@ -3,8 +3,7 @@ import math
3
3
  import re
4
4
  from collections import namedtuple
5
5
  from dataclasses import asdict, dataclass, field, fields
6
- from datetime import datetime as builtin_datetime
7
- from datetime import timedelta as builtin_timedelta
6
+ from datetime import datetime, timedelta
8
7
  from enum import IntEnum
9
8
  from fractions import Fraction
10
9
  from hashlib import sha1
@@ -13,13 +12,9 @@ from struct import pack, unpack
13
12
  from typing import Any, List, Optional, Tuple, Union
14
13
  from warnings import warn
15
14
 
16
- import sigfig
17
- from pendulum import DateTime, Duration, datetime, duration
18
- from pendulum import instance as pendulum_instance
15
+ from sigfig import round as sigfig
19
16
 
20
17
  from numbers_parser import __name__ as numbers_parser_name
21
-
22
- # from numbers_parser.cell_storage import CellStorage, CellType
23
18
  from numbers_parser.constants import (
24
19
  CHECKBOX_FALSE_VALUE,
25
20
  CHECKBOX_TRUE_VALUE,
@@ -802,7 +797,7 @@ class Cell(CellStorageFlags, Cacheable):
802
797
  elif isinstance(value, int):
803
798
  cell = NumberCell(row, col, value)
804
799
  elif isinstance(value, float):
805
- rounded_value = sigfig.round(value, sigfigs=MAX_SIGNIFICANT_DIGITS, warn=False)
800
+ rounded_value = sigfig(value, sigfigs=MAX_SIGNIFICANT_DIGITS, warn=False)
806
801
  if rounded_value != value:
807
802
  warn(
808
803
  f"'{value}' rounded to {MAX_SIGNIFICANT_DIGITS} significant digits",
@@ -810,9 +805,9 @@ class Cell(CellStorageFlags, Cacheable):
810
805
  stacklevel=2,
811
806
  )
812
807
  cell = NumberCell(row, col, rounded_value)
813
- elif isinstance(value, (DateTime, builtin_datetime)):
814
- cell = DateCell(row, col, pendulum_instance(value))
815
- elif isinstance(value, (Duration, builtin_timedelta)):
808
+ elif isinstance(value, datetime):
809
+ cell = DateCell(row, col, value)
810
+ elif isinstance(value, timedelta):
816
811
  cell = DurationCell(row, col, value)
817
812
  else:
818
813
  raise ValueError("Can't determine cell type from type " + type(value).__name__)
@@ -882,7 +877,6 @@ class Cell(CellStorageFlags, Cacheable):
882
877
  offset += 4
883
878
  # Skip unused flags
884
879
  offset += 4 * bin(flags & 0x900).count("1")
885
- #
886
880
  if flags & 0x2000:
887
881
  storage_flags._num_format_id = unpack("<i", buffer[offset : offset + 4])[0]
888
882
  offset += 4
@@ -916,12 +910,12 @@ class Cell(CellStorageFlags, Cacheable):
916
910
  elif cell_type == TSTArchives.textCellType:
917
911
  cell = TextCell(row, col, model.table_string(table_id, storage_flags._string_id))
918
912
  elif cell_type == TSTArchives.dateCellType:
919
- cell = DateCell(row, col, EPOCH + duration(seconds=seconds))
913
+ cell = DateCell(row, col, EPOCH + timedelta(seconds=seconds))
920
914
  cell._datetime = cell._value
921
915
  elif cell_type == TSTArchives.boolCellType:
922
916
  cell = BoolCell(row, col, double > 0.0)
923
917
  elif cell_type == TSTArchives.durationCellType:
924
- cell = DurationCell(row, col, duration(seconds=double))
918
+ cell = DurationCell(row, col, timedelta(seconds=double))
925
919
  elif cell_type == TSTArchives.formulaErrorCellType:
926
920
  cell = ErrorCell(row, col)
927
921
  elif cell_type == TSTArchives.automaticCellType:
@@ -1024,7 +1018,11 @@ class Cell(CellStorageFlags, Cacheable):
1024
1018
  flags = 4
1025
1019
  length += 8
1026
1020
  cell_type = TSTArchives.dateCellType
1027
- date_delta = self._value.astimezone() - EPOCH
1021
+ # date_delta = self._value.astimezone() - EPOCH
1022
+ if self._value.tzinfo is None:
1023
+ date_delta = self._value - EPOCH
1024
+ else:
1025
+ date_delta = self._value - EPOCH.astimezone(self._value.tzinfo)
1028
1026
  value = pack("<d", float(date_delta.total_seconds()))
1029
1027
  elif isinstance(self, BoolCell):
1030
1028
  flags = 2
@@ -1483,7 +1481,7 @@ class DateCell(Cell):
1483
1481
  Do not instantiate directly. Cells are created by :py:class:`~numbers_parser.Document`.
1484
1482
  """ # fmt: skip
1485
1483
 
1486
- def __init__(self, row: int, col: int, value: DateTime) -> None:
1484
+ def __init__(self, row: int, col: int, value: datetime) -> None:
1487
1485
  super().__init__(row, col, value)
1488
1486
  self._type = CellType.DATE
1489
1487
 
@@ -1493,12 +1491,12 @@ class DateCell(Cell):
1493
1491
 
1494
1492
 
1495
1493
  class DurationCell(Cell):
1496
- def __init__(self, row: int, col: int, value: Duration) -> None:
1494
+ def __init__(self, row: int, col: int, value: timedelta) -> None:
1497
1495
  super().__init__(row, col, value)
1498
1496
  self._type = CellType.DURATION
1499
1497
 
1500
1498
  @property
1501
- def value(self) -> duration:
1499
+ def value(self) -> timedelta:
1502
1500
  return self._value
1503
1501
 
1504
1502
 
@@ -1734,7 +1732,7 @@ def _decode_number_format(format, value, name): # noqa: PLR0912
1734
1732
  int_width = num_integers
1735
1733
 
1736
1734
  # value_1 = str(value).split(".")[0]
1737
- # value_2 = sigfig.round(str(value).split(".")[1], sigfig=MAX_SIGNIFICANT_DIGITS, warn=False)
1735
+ # value_2 = sigfig(str(value).split(".")[1], sigfig=MAX_SIGNIFICANT_DIGITS, warn=False)
1738
1736
  # int_pad_space_as_zero = (
1739
1737
  # num_integers > 0
1740
1738
  # and num_decimals > 0
@@ -1812,16 +1810,16 @@ def _format_decimal(value: float, format, percent: bool = False) -> str:
1812
1810
  formatted_value = f"{int(value):{thousands}}"
1813
1811
  else:
1814
1812
  if format.decimal_places >= DECIMAL_PLACES_AUTO:
1815
- formatted_value = str(sigfig.round(value, MAX_SIGNIFICANT_DIGITS, warn=False))
1813
+ formatted_value = str(sigfig(value, MAX_SIGNIFICANT_DIGITS, warn=False))
1816
1814
  else:
1817
- formatted_value = sigfig.round(value, MAX_SIGNIFICANT_DIGITS, type=str, warn=False)
1818
- formatted_value = sigfig.round(
1815
+ formatted_value = sigfig(value, MAX_SIGNIFICANT_DIGITS, type=str, warn=False)
1816
+ formatted_value = sigfig(
1819
1817
  formatted_value,
1820
1818
  decimals=format.decimal_places,
1821
1819
  type=str,
1822
1820
  )
1823
1821
  if format.show_thousands_separator:
1824
- formatted_value = sigfig.round(formatted_value, spacer=",", spacing=3, type=str)
1822
+ formatted_value = sigfig(formatted_value, spacer=",", spacing=3, type=str)
1825
1823
  try:
1826
1824
  (integer, decimal) = formatted_value.split(".")
1827
1825
  formatted_value = integer + "." + decimal.replace(",", "")
@@ -1945,7 +1943,7 @@ def _format_fraction(value: float, format) -> str:
1945
1943
 
1946
1944
 
1947
1945
  def _format_scientific(value: float, format) -> str:
1948
- formatted_value = sigfig.round(value, sigfigs=MAX_SIGNIFICANT_DIGITS, warn=False)
1946
+ formatted_value = sigfig(value, sigfigs=MAX_SIGNIFICANT_DIGITS, warn=False)
1949
1947
  return f"{formatted_value:.{format.decimal_places}E}"
1950
1948
 
1951
1949
 
@@ -1992,8 +1990,7 @@ def _auto_units(cell_value, format):
1992
1990
  unit_smallest = DurationUnits.HOUR
1993
1991
  elif cell_value % SECONDS_IN_WEEK:
1994
1992
  unit_smallest = DurationUnits.DAY
1995
- if unit_smallest < unit_largest:
1996
- unit_smallest = unit_largest
1993
+ unit_smallest = max(unit_smallest, unit_largest)
1997
1994
 
1998
1995
  return unit_smallest, unit_largest
1999
1996
 
@@ -1,8 +1,9 @@
1
1
  from collections import OrderedDict
2
+ from datetime import datetime
2
3
  from enum import IntEnum
4
+ from math import ceil
3
5
 
4
6
  import enum_tools.documentation
5
- from pendulum import datetime
6
7
 
7
8
  try:
8
9
  from importlib.resources import files
@@ -99,6 +100,16 @@ def _days_occurred_in_month(value: datetime) -> str:
99
100
  return str(n_days)
100
101
 
101
102
 
103
+ def _day_of_year(value: datetime) -> int:
104
+ """Return the day number in a year for a datetime."""
105
+ return value.timetuple().tm_yday
106
+
107
+
108
+ def _week_of_month(value: datetime) -> int:
109
+ """Return the week number in a month for a datetime."""
110
+ return int(ceil((value.day + value.replace(day=1).weekday()) / 7.0))
111
+
112
+
102
113
  DATETIME_FIELD_MAP = OrderedDict(
103
114
  [
104
115
  ("a", lambda x: x.strftime("%p").lower()),
@@ -113,9 +124,9 @@ DATETIME_FIELD_MAP = OrderedDict(
113
124
  ("M", "%-m"),
114
125
  ("d", "%-d"),
115
126
  ("dd", "%d"),
116
- ("DDD", lambda x: str(x.day_of_year).zfill(3)),
117
- ("DD", lambda x: str(x.day_of_year).zfill(2)),
118
- ("D", lambda x: str(x.day_of_year).zfill(1)),
127
+ ("DDD", lambda x: str(_day_of_year(x)).zfill(3)),
128
+ ("DD", lambda x: str(_day_of_year(x)).zfill(2)),
129
+ ("D", lambda x: str(_day_of_year(x)).zfill(1)),
119
130
  ("HH", "%H"),
120
131
  ("H", "%-H"),
121
132
  ("hh", "%I"),
@@ -128,7 +139,7 @@ DATETIME_FIELD_MAP = OrderedDict(
128
139
  ("m", lambda x: str(x.minute)),
129
140
  ("ss", "%S"),
130
141
  ("s", lambda x: str(x.second)),
131
- ("W", lambda x: str(x.week_of_month - 1)),
142
+ ("W", lambda x: str(_week_of_month(x) - 1)),
132
143
  ("ww", "%W"),
133
144
  ("G", "AD"), # TODO: support BC
134
145
  ("F", lambda x: _days_occurred_in_month(x)),
@@ -1,9 +1,8 @@
1
+ from datetime import datetime, timedelta
1
2
  from pathlib import Path
2
3
  from typing import Dict, Iterator, List, Optional, Tuple, Union
3
4
  from warnings import warn
4
5
 
5
- from pendulum import DateTime, Duration
6
-
7
6
  from numbers_parser.cell import (
8
7
  BackgroundImage,
9
8
  Border,
@@ -951,7 +950,7 @@ class Table(Cacheable):
951
950
  self,
952
951
  num_rows: Optional[int] = 1,
953
952
  start_row: Optional[Union[int, None]] = None,
954
- default: Optional[Union[str, int, float, bool, DateTime, Duration]] = None,
953
+ default: Optional[Union[str, int, float, bool, datetime, timedelta]] = None,
955
954
  ) -> None:
956
955
  """Add or insert rows to the table.
957
956
 
@@ -1013,7 +1012,7 @@ class Table(Cacheable):
1013
1012
  self,
1014
1013
  num_cols: Optional[int] = 1,
1015
1014
  start_col: Optional[Union[int, None]] = None,
1016
- default: Optional[Union[str, int, float, bool, DateTime, Duration]] = None,
1015
+ default: Optional[Union[str, int, float, bool, datetime, timedelta]] = None,
1017
1016
  ) -> None:
1018
1017
  """Add or insert columns to the table.
1019
1018
 
@@ -1,7 +1,6 @@
1
1
  import re
2
2
  import warnings
3
-
4
- from pendulum import datetime, duration
3
+ from datetime import datetime, timedelta
5
4
 
6
5
  from numbers_parser.exceptions import UnsupportedWarning
7
6
  from numbers_parser.generated import TSCEArchives_pb2 as TSCEArchives
@@ -68,7 +67,7 @@ class Formula(list):
68
67
  def date(self, *args):
69
68
  # Date literals exported as DATE()
70
69
  node = args[2]
71
- dt = datetime(2001, 1, 1) + duration(seconds=node.AST_date_node_dateNum)
70
+ dt = datetime(2001, 1, 1) + timedelta(seconds=node.AST_date_node_dateNum)
72
71
  self.push(f"DATE({dt.year},{dt.month},{dt.day})")
73
72
 
74
73
  def div(self, *args):
@@ -0,0 +1,18 @@
1
+ def to_roman(value: int) -> str:
2
+ """Convert an integer to a Roman number in the available range of values."""
3
+ if value == 0:
4
+ return "N"
5
+
6
+ if value < 1 or value > 3999:
7
+ raise ValueError("Number out of range for Roman numerals")
8
+
9
+ int_values = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]
10
+ roman_symbols = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"]
11
+
12
+ roman_num = ""
13
+ for i in range(len(int_values)):
14
+ while value >= int_values[i]:
15
+ roman_num += roman_symbols[i]
16
+ value -= int_values[i]
17
+
18
+ return roman_num
@@ -15,11 +15,6 @@ FUNCTION_MAP = {
15
15
  14: "AVEDEV",
16
16
  15: "AVERAGE",
17
17
  16: "AVERAGEA",
18
- 330: "BITAND",
19
- 333: "BITLSHIFT",
20
- 334: "BITRSHIFT",
21
- 331: "BITOR",
22
- 332: "BITXOR",
23
18
  17: "CEILING",
24
19
  18: "CHAR",
25
20
  19: "CHOOSE",
@@ -28,7 +23,6 @@ FUNCTION_MAP = {
28
23
  22: "COLUMN",
29
24
  23: "COLUMNS",
30
25
  24: "COMBIN",
31
- 329: "CONCAT",
32
26
  25: "CONCATENATE",
33
27
  26: "CONFIDENCE",
34
28
  27: "CORREL",
@@ -77,7 +71,6 @@ FUNCTION_MAP = {
77
71
  70: "ISERROR",
78
72
  71: "ISEVEN",
79
73
  72: "ISODD",
80
- 335: "ISOWEEKNUM",
81
74
  73: "ISPMT",
82
75
  74: "LARGE",
83
76
  75: "LCM",
@@ -153,7 +146,6 @@ FUNCTION_MAP = {
153
146
  145: "SUMIF",
154
147
  146: "SUMPRODUCT",
155
148
  147: "SUMSQ",
156
- 336: "SWITCH",
157
149
  148: "SYD",
158
150
  149: "T",
159
151
  150: "TAN",
@@ -314,4 +306,12 @@ FUNCTION_MAP = {
314
306
  324: "REGEX.EXTRACT",
315
307
  325: "GETPIVOTDATA",
316
308
  328: "TEXTJOIN",
309
+ 329: "CONCAT",
310
+ 330: "BITAND",
311
+ 331: "BITOR",
312
+ 332: "BITXOR",
313
+ 333: "BITLSHIFT",
314
+ 334: "BITRSHIFT",
315
+ 335: "ISOWEEKNUM",
316
+ 336: "SWITCH",
317
317
  }