Skip to content

Commit 4683874

Browse files
authored
ENH: Add += and -= operators to ArrayObject (#2510)
1 parent f432687 commit 4683874

File tree

3 files changed

+96
-1
lines changed

3 files changed

+96
-1
lines changed

pypdf/generic/_data_structures.py

+66
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
import logging
3333
import re
34+
import sys
3435
from io import BytesIO
3536
from typing import (
3637
Any,
@@ -70,6 +71,7 @@
7071
from ..errors import STREAM_TRUNCATED_PREMATURELY, PdfReadError, PdfStreamError
7172
from ._base import (
7273
BooleanObject,
74+
ByteStringObject,
7375
FloatObject,
7476
IndirectObject,
7577
NameObject,
@@ -81,6 +83,11 @@
8183
from ._fit import Fit
8284
from ._utils import read_hex_string_from_stream, read_string_from_stream
8385

86+
if sys.version_info >= (3, 11):
87+
from typing import Self
88+
else:
89+
from typing_extensions import Self
90+
8491
logger = logging.getLogger(__name__)
8592
NumberSigns = b"+-"
8693
IndirectPattern = re.compile(rb"[+-]?(\d+)\s+(\d+)\s+R[^a-zA-Z]")
@@ -121,6 +128,65 @@ def items(self) -> Iterable[Any]:
121128
"""Emulate DictionaryObject.items for a list (index, object)."""
122129
return enumerate(self)
123130

131+
def _to_lst(self, lst: Any) -> List[Any]:
132+
# Convert to list, internal
133+
if isinstance(lst, (list, tuple, set)):
134+
pass
135+
elif isinstance(lst, PdfObject):
136+
lst = [lst]
137+
elif isinstance(lst, str):
138+
if lst[0] == "/":
139+
lst = [NameObject(lst)]
140+
else:
141+
lst = [TextStringObject(lst)]
142+
elif isinstance(lst, bytes):
143+
lst = [ByteStringObject(lst)]
144+
else: # for numbers,...
145+
lst = [lst]
146+
return lst
147+
148+
def __add__(self, lst: Any) -> "ArrayObject":
149+
"""
150+
Allow extension by adding list or add one element only
151+
152+
Args:
153+
lst: any list, tuples are extended the list.
154+
other types(numbers,...) will be appended.
155+
if str is passed it will be converted into TextStringObject
156+
or NameObject (if starting with "/")
157+
if bytes is passed it will be converted into ByteStringObject
158+
159+
Returns:
160+
ArrayObject with all elements
161+
"""
162+
temp = ArrayObject(self)
163+
temp.extend(self._to_lst(lst))
164+
return temp
165+
166+
def __iadd__(self, lst: Any) -> Self:
167+
"""
168+
Allow extension by adding list or add one element only
169+
170+
Args:
171+
lst: any list, tuples are extended the list.
172+
other types(numbers,...) will be appended.
173+
if str is passed it will be converted into TextStringObject
174+
or NameObject (if starting with "/")
175+
if bytes is passed it will be converted into ByteStringObject
176+
"""
177+
self.extend(self._to_lst(lst))
178+
return self
179+
180+
def __isub__(self, lst: Any) -> Self:
181+
"""Allow to remove items"""
182+
for x in self._to_lst(lst):
183+
try:
184+
x = self.index(x)
185+
del self[x]
186+
except ValueError:
187+
pass
188+
return self
189+
124190
def write_to_stream(
125191
self, stream: StreamType, encryption_key: Union[None, str, bytes] = None
126192
) -> None:

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ classifiers = [
2929
]
3030

3131
dependencies = [
32-
"typing_extensions >= 3.7.4.3; python_version < '3.10'",
32+
"typing_extensions >= 4.0; python_version < '3.11'",
3333
"dataclasses; python_version < '3.7'",
3434
]
3535

tests/test_generic.py

+29
Original file line numberDiff line numberDiff line change
@@ -1278,3 +1278,32 @@ def test_indirect_object_page_dimensions():
12781278
reader = PdfReader(data, strict=False)
12791279
mediabox = reader.pages[0].mediabox
12801280
assert mediabox == RectangleObject((0, 0, 792, 612))
1281+
1282+
1283+
def test_array_operators():
1284+
a = ArrayObject(
1285+
[
1286+
NumberObject(1),
1287+
NumberObject(2),
1288+
NumberObject(3),
1289+
NumberObject(4),
1290+
]
1291+
)
1292+
b = a + 5
1293+
assert isinstance(b, ArrayObject)
1294+
assert b == [1, 2, 3, 4, 5]
1295+
assert a == [1, 2, 3, 4]
1296+
a -= 2
1297+
a += "abc"
1298+
a -= (3, 4)
1299+
a += ["d", "e"]
1300+
a += BooleanObject(True)
1301+
assert a == [1, "abc", "d", "e", True]
1302+
a += "/toto"
1303+
assert isinstance(a[-1], NameObject)
1304+
assert isinstance(a[1], TextStringObject)
1305+
a += b"1234"
1306+
assert a[-1] == ByteStringObject(b"1234")
1307+
la = len(a)
1308+
a -= 300
1309+
assert len(a) == la

0 commit comments

Comments
 (0)