Skip to content

Commit 541d429

Browse files
Merge b7322da into 55a9b32
2 parents 55a9b32 + b7322da commit 541d429

2 files changed

Lines changed: 191 additions & 2 deletions

File tree

source/textInfos/__init__.py

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,14 @@
1212
from abc import abstractmethod
1313
import weakref
1414
import re
15-
from typing import Any, Union, List, Optional, Dict
15+
from typing import (
16+
Any,
17+
Union,
18+
List,
19+
Optional,
20+
Dict,
21+
Tuple,
22+
)
1623

1724
import baseObject
1825
import config
@@ -307,6 +314,18 @@ def __init__(self,obj,position):
307314
#: The position with which this instance was constructed.
308315
self.basePosition=position
309316

317+
def _get_start(self) -> "TextInfoEndpoint":
318+
return TextInfoEndpoint(self,True)
319+
320+
def _set_start(self, otherEndpoint: "TextInfoEndpoint"):
321+
self.start.moveTo(otherEndpoint)
322+
323+
def _get_end(self) -> "TextInfoEndpoint":
324+
return TextInfoEndpoint(self, False)
325+
326+
def _set_end(self, otherEndpoint: "TextInfoEndpoint"):
327+
self.end.moveTo(otherEndpoint)
328+
310329
def _get_obj(self):
311330
"""The object containing the range of text being represented."""
312331
return self._obj()
@@ -624,3 +643,75 @@ def turnPage(self, previous=False):
624643
@raise RuntimeError: If there are no further pages.
625644
"""
626645
raise NotImplementedError
646+
647+
class TextInfoEndpoint:
648+
"""
649+
Represents one end of a TextInfo instance.
650+
This object can be compared with another end from the same or a different TextInfo instance,
651+
Using the standard math comparison operators:
652+
< <= == != >= >
653+
"""
654+
655+
_whichMap: Dict[Tuple[bool, bool], str] = {
656+
(True, True): "startToStart",
657+
(True, False): "startToEnd",
658+
(False, True): "endToStart",
659+
(False, False): "endToEnd",
660+
}
661+
662+
def _cmp(self, other: "TextInfoEndpoint"):
663+
"""
664+
A standard cmp function returning:
665+
-1 for less than, 0 for equal and 1 for greater than.
666+
"""
667+
if (
668+
not isinstance(other, TextInfoEndpoint)
669+
or type(self.textInfo) != type(other.textInfo)
670+
):
671+
raise ValueError(f"Cannot compare endpoint with different type: {other}")
672+
return self.textInfo.compareEndPoints(other.textInfo, self._whichMap[self.isStart, other.isStart])
673+
674+
def __init__(
675+
self,
676+
textInfo: TextInfo,
677+
isStart: bool
678+
):
679+
"""
680+
@param textInfo: the TextInfo instance you wish to represent an endpoint of.
681+
@param isStart: true to represent the start, false for the end.
682+
"""
683+
self.textInfo = textInfo
684+
self.isStart = isStart
685+
686+
def __lt__(self,other):
687+
return self._cmp(other) < 0
688+
689+
def __le__(self,other):
690+
return self._cmp(other) <= 0
691+
692+
def __eq__(self,other):
693+
return self._cmp(other) == 0
694+
695+
def __ne__(self,other):
696+
return self._cmp(other) != 0
697+
698+
def __ge__(self,other):
699+
return self._cmp(other) >= 0
700+
701+
def __gt__(self,other):
702+
return self._cmp(other) > 0
703+
704+
def moveTo(self, other: "TextInfoEndpoint") -> None:
705+
"""
706+
Moves the end of the TextInfo this endpoint represents to the position of the given endpoint.
707+
"""
708+
if (
709+
not isinstance(other, TextInfoEndpoint)
710+
or type(self.textInfo) != type(other.textInfo)
711+
):
712+
raise ValueError(f"Cannot move endpoint to different type: {other}")
713+
self.textInfo.setEndPoint(other.textInfo, self._whichMap[(self.isStart, other.isStart)])
714+
715+
def __repr__(self):
716+
endpointLabel = "start" if self.isStart else "end"
717+
return f"{endpointLabel} endpoint of {self.textInfo}"

tests/unit/test_textInfos.py

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ def test_surrogatePairsBackward(self):
6060
obj = BasicTextProvider(text=u"\U0001f926\U0001f60a\U0001f44d") # 🤦😊👍
6161
ti = obj.makeTextInfo(Offsets(5, 5))
6262
ti.expand(textInfos.UNIT_CHARACTER) # Range at 👍
63-
self.assertEqual(ti.offsets, (4, 6)) # Two offsets
6463
ti.move(textInfos.UNIT_CHARACTER, -1)
6564
ti.expand(textInfos.UNIT_CHARACTER) # Range at 😊
6665
self.assertEqual(ti.offsets, (2, 4)) # Two offsets
@@ -131,3 +130,102 @@ def test_mixedSurrogatePairsNonSurrogatesAndSingleSurrogatesBackward(self):
131130
ti.move(textInfos.UNIT_CHARACTER, -1)
132131
ti.expand(textInfos.UNIT_CHARACTER) # Range at a
133132
self.assertEqual(ti.offsets, (0, 1)) # One offset
133+
134+
class TestEndpoints(unittest.TestCase):
135+
136+
def test_lessThan(self):
137+
obj = BasicTextProvider(text="abcdef")
138+
wholeTi = obj.makeTextInfo(Offsets(0, 2))
139+
subTi1 = obj.makeTextInfo(Offsets(0, 3))
140+
subTi2 = obj.makeTextInfo(Offsets(2, 5))
141+
self.assertTrue(wholeTi.start < wholeTi.end)
142+
self.assertFalse(wholeTi.end < wholeTi.start)
143+
self.assertFalse(wholeTi.start < wholeTi.start)
144+
self.assertFalse(wholeTi.end < wholeTi.end)
145+
self.assertFalse(wholeTi.start < subTi1.start)
146+
self.assertTrue(subTi1.start < subTi2.start)
147+
self.assertTrue(subTi2.start < subTi1.end)
148+
self.assertFalse(subTi2.end < wholeTi.end)
149+
150+
def test_lessThanOrEqualTo(self):
151+
obj = BasicTextProvider(text="abcdef")
152+
wholeTi = obj.makeTextInfo(Offsets(0, 5))
153+
subTi1 = obj.makeTextInfo(Offsets(0, 3))
154+
subTi2 = obj.makeTextInfo(Offsets(2, 5))
155+
self.assertTrue(wholeTi.start <= wholeTi.end)
156+
self.assertFalse(wholeTi.end <= wholeTi.start)
157+
self.assertTrue(wholeTi.start <= wholeTi.start)
158+
self.assertTrue(wholeTi.end <= wholeTi.end)
159+
self.assertTrue(wholeTi.start <= subTi1.start)
160+
self.assertTrue(subTi1.start <= subTi2.start)
161+
self.assertTrue(subTi2.start <= subTi1.end)
162+
self.assertTrue(subTi2.end <= wholeTi.end)
163+
164+
def test_greaterThanOrEqualTo(self):
165+
obj = BasicTextProvider(text="abcdef")
166+
wholeTi = obj.makeTextInfo(Offsets(0, 5))
167+
subTi1 = obj.makeTextInfo(Offsets(0, 3))
168+
subTi2 = obj.makeTextInfo(Offsets(2, 5))
169+
self.assertFalse(wholeTi.start >= wholeTi.end)
170+
self.assertTrue(wholeTi.end >= wholeTi.start)
171+
self.assertTrue(wholeTi.start >= wholeTi.start)
172+
self.assertTrue(wholeTi.end >= wholeTi.end)
173+
self.assertTrue(wholeTi.start >= subTi1.start)
174+
self.assertFalse(subTi1.start >= subTi2.start)
175+
self.assertFalse(subTi2.start >= subTi1.end)
176+
self.assertTrue(subTi2.end >= wholeTi.end)
177+
178+
def test_greaterThan(self):
179+
obj = BasicTextProvider(text="abcdef")
180+
wholeTi = obj.makeTextInfo(Offsets(0, 5))
181+
subTi1 = obj.makeTextInfo(Offsets(0, 3))
182+
subTi2 = obj.makeTextInfo(Offsets(2, 5))
183+
self.assertFalse(wholeTi.start > wholeTi.end)
184+
self.assertTrue(wholeTi.end > wholeTi.start)
185+
self.assertFalse(wholeTi.start > wholeTi.start)
186+
self.assertFalse(wholeTi.end > wholeTi.end)
187+
self.assertFalse(wholeTi.start > subTi1.start)
188+
self.assertFalse(subTi1.start > subTi2.start)
189+
self.assertFalse(subTi2.start > subTi1.end)
190+
self.assertFalse(subTi2.end > wholeTi.end)
191+
192+
def test_equal(self):
193+
obj = BasicTextProvider(text="abcdef")
194+
wholeTi = obj.makeTextInfo(Offsets(0, 5))
195+
subTi1 = obj.makeTextInfo(Offsets(0, 3))
196+
subTi2 = obj.makeTextInfo(Offsets(2, 5))
197+
self.assertFalse(wholeTi.start == wholeTi.end)
198+
self.assertFalse(wholeTi.end == wholeTi.start)
199+
self.assertTrue(wholeTi.start == wholeTi.start)
200+
self.assertTrue(wholeTi.end == wholeTi.end)
201+
self.assertTrue(wholeTi.start == subTi1.start)
202+
self.assertFalse(subTi1.start == subTi2.start)
203+
self.assertFalse(subTi2.start == subTi1.end)
204+
self.assertTrue(subTi2.end == wholeTi.end)
205+
206+
def test_notEqual(self):
207+
obj = BasicTextProvider(text="abcdef")
208+
wholeTi = obj.makeTextInfo(Offsets(0, 5))
209+
subTi1 = obj.makeTextInfo(Offsets(0, 3))
210+
subTi2 = obj.makeTextInfo(Offsets(2, 5))
211+
self.assertTrue(wholeTi.start != wholeTi.end)
212+
self.assertTrue(wholeTi.end != wholeTi.start)
213+
self.assertFalse(wholeTi.start != wholeTi.start)
214+
self.assertFalse(wholeTi.end != wholeTi.end)
215+
self.assertFalse(wholeTi.start != subTi1.start)
216+
self.assertTrue(subTi1.start != subTi2.start)
217+
self.assertTrue(subTi2.start != subTi1.end)
218+
self.assertFalse(subTi2.end != wholeTi.end)
219+
220+
def test_setStart(self):
221+
obj = BasicTextProvider(text="abcdef")
222+
ti1 = obj.makeTextInfo(Offsets(0, 2))
223+
ti2 = obj.makeTextInfo(Offsets(3, 5))
224+
ti1.end = ti2.end
225+
self.assertEqual((ti1._startOffset, ti1._endOffset), (0, 5))
226+
ti1.start = ti2.start
227+
self.assertEqual((ti1._startOffset, ti1._endOffset), (3, 5))
228+
ti1.end = ti2.start
229+
self.assertEqual((ti1._startOffset, ti1._endOffset), (3, 3))
230+
ti1.start = ti2.end
231+
self.assertEqual((ti1._startOffset, ti1._endOffset), (5, 5))

0 commit comments

Comments
 (0)