modm_data.utils

 1# Copyright 2022, Niklas Hauser
 2# SPDX-License-Identifier: MPL-2.0
 3
 4from .math import Point, Line, VLine, HLine, Rectangle, Region
 5from .helper import list_lstrip, list_rstrip, list_strip, pkg_file_exists, pkg_apply_patch, apply_patch
 6from .anytree import ReversePreOrderIter
 7from .path import root_path, ext_path, cache_path, patch_path
 8from .xml import XmlReader
 9
10__all__ = [
11    "Point",
12    "Line",
13    "VLine",
14    "HLine",
15    "Rectangle",
16    "Region",
17    "list_lstrip",
18    "list_rstrip",
19    "list_strip",
20    "pkg_file_exists",
21    "pkg_apply_patch",
22    "apply_patch",
23    "ReversePreOrderIter",
24    "root_path",
25    "ext_path",
26    "cache_path",
27    "patch_path",
28    "XmlReader",
29]
class Point:
13class Point:
14    __slots__ = ("type", "x", "y")
15
16    def __init__(self, *xy, type: Enum = None):
17        if isinstance(xy[0], tuple):
18            self.x = xy[0][0]
19            self.y = xy[0][1]
20        else:
21            self.x = xy[0]
22            self.y = xy[1]
23        self.type = type
24
25    def isclose(self, other, rtol: float = 1e-09, atol: float = 0.0) -> bool:
26        return math.isclose(self.x, other.x, rel_tol=rtol, abs_tol=atol) and math.isclose(
27            self.y, other.y, rel_tol=rtol, abs_tol=atol
28        )
29
30    def distance_squared(self, other) -> float:
31        return math.pow(self.x - other.x, 2) + math.pow(self.y - other.y, 2)
32
33    def distance(self, other) -> float:
34        return math.sqrt(self.distance_squared(other))
35
36    def __neg__(self):
37        return Point(-self.x, -self.y)
38
39    def __hash__(self):
40        return hash(f"{self.x} {self.y}")
41
42    def __repr__(self) -> str:
43        x = f"{self.x:.1f}" if isinstance(self.x, float) else self.x
44        y = f"{self.y:.1f}" if isinstance(self.y, float) else self.y
45        out = [x, y] if self.type is None else [x, y, self.type.name]
46        return f"({','.join(out)})"
Point(*xy, type: enum.Enum = None)
16    def __init__(self, *xy, type: Enum = None):
17        if isinstance(xy[0], tuple):
18            self.x = xy[0][0]
19            self.y = xy[0][1]
20        else:
21            self.x = xy[0]
22            self.y = xy[1]
23        self.type = type
type
def isclose(self, other, rtol: float = 1e-09, atol: float = 0.0) -> bool:
25    def isclose(self, other, rtol: float = 1e-09, atol: float = 0.0) -> bool:
26        return math.isclose(self.x, other.x, rel_tol=rtol, abs_tol=atol) and math.isclose(
27            self.y, other.y, rel_tol=rtol, abs_tol=atol
28        )
def distance_squared(self, other) -> float:
30    def distance_squared(self, other) -> float:
31        return math.pow(self.x - other.x, 2) + math.pow(self.y - other.y, 2)
def distance(self, other) -> float:
33    def distance(self, other) -> float:
34        return math.sqrt(self.distance_squared(other))
x
y
class Line:
 49class Line:
 50    class Direction(Enum):
 51        ANGLE = 0
 52        VERTICAL = 1
 53        HORIZONTAL = 2
 54
 55    def __init__(self, *r, width: float = None, type: Enum = None):
 56        if isinstance(r[0], Rectangle):
 57            self.p0 = r[0].p0
 58            self.p1 = r[0].p1
 59        elif isinstance(r[0], Point):
 60            self.p0 = r[0]
 61            self.p1 = r[1]
 62        elif isinstance(r[0], tuple):
 63            self.p0 = Point(r[0][0], r[0][1])
 64            self.p1 = Point(r[1][0], r[1][1])
 65        else:
 66            self.p0 = Point(r[0], r[1])
 67            self.p1 = Point(r[2], r[3])
 68
 69        self.width = 0.1 if width is None else width
 70        self.type = type
 71
 72    @cached_property
 73    def bbox(self):
 74        return Rectangle(
 75            min(self.p0.x, self.p1.x), min(self.p0.y, self.p1.y), max(self.p0.x, self.p1.x), max(self.p0.y, self.p1.y)
 76        )
 77
 78    def isclose(self, other, rtol: float = 1e-09, atol: float = 0.0) -> bool:
 79        return self.p0.isclose(other.p0, rtol, atol) and self.p1.isclose(other.p1, rtol, atol)
 80
 81    def contains(self, point, atol: float = 0.0) -> bool:
 82        # if the point lies on the line (A-C---B), the distance A-C + C-B = A-B
 83        ac = self.p0.distance_squared(point)
 84        cb = self.p1.distance_squared(point)
 85        ab = self.p0.distance_squared(self.p1)
 86        return (ac + cb + math.pow(atol, 2)) <= ab
 87
 88    @property
 89    def direction(self):
 90        if math.isclose(self.p0.x, self.p1.x):
 91            return Line.Direction.VERTICAL
 92        if math.isclose(self.p0.y, self.p1.y):
 93            return Line.Direction.HORIZONTAL
 94        return Line.Direction.ANGLE
 95
 96    def specialize(self):
 97        if self.direction == Line.Direction.VERTICAL:
 98            return VLine(self.p0.x, self.p0.y, self.p1.y, self.width)
 99        if self.direction == Line.Direction.HORIZONTAL:
100            return HLine(self.p0.y, self.p0.x, self.p1.x, self.width)
101        return Line(self.p0, self.p1, width=self.width)
102
103    def __repr__(self) -> str:
104        data = [repr(self.p0), repr(self.p1)]
105        if self.width:
106            data += [f"{self.width:.1f}"]
107        if self.type is not None:
108            data += [self.type.name]
109        return f"<{','.join(data)}>"
Line(*r, width: float = None, type: enum.Enum = None)
55    def __init__(self, *r, width: float = None, type: Enum = None):
56        if isinstance(r[0], Rectangle):
57            self.p0 = r[0].p0
58            self.p1 = r[0].p1
59        elif isinstance(r[0], Point):
60            self.p0 = r[0]
61            self.p1 = r[1]
62        elif isinstance(r[0], tuple):
63            self.p0 = Point(r[0][0], r[0][1])
64            self.p1 = Point(r[1][0], r[1][1])
65        else:
66            self.p0 = Point(r[0], r[1])
67            self.p1 = Point(r[2], r[3])
68
69        self.width = 0.1 if width is None else width
70        self.type = type
width
type
bbox
72    @cached_property
73    def bbox(self):
74        return Rectangle(
75            min(self.p0.x, self.p1.x), min(self.p0.y, self.p1.y), max(self.p0.x, self.p1.x), max(self.p0.y, self.p1.y)
76        )
def isclose(self, other, rtol: float = 1e-09, atol: float = 0.0) -> bool:
78    def isclose(self, other, rtol: float = 1e-09, atol: float = 0.0) -> bool:
79        return self.p0.isclose(other.p0, rtol, atol) and self.p1.isclose(other.p1, rtol, atol)
def contains(self, point, atol: float = 0.0) -> bool:
81    def contains(self, point, atol: float = 0.0) -> bool:
82        # if the point lies on the line (A-C---B), the distance A-C + C-B = A-B
83        ac = self.p0.distance_squared(point)
84        cb = self.p1.distance_squared(point)
85        ab = self.p0.distance_squared(self.p1)
86        return (ac + cb + math.pow(atol, 2)) <= ab
direction
88    @property
89    def direction(self):
90        if math.isclose(self.p0.x, self.p1.x):
91            return Line.Direction.VERTICAL
92        if math.isclose(self.p0.y, self.p1.y):
93            return Line.Direction.HORIZONTAL
94        return Line.Direction.ANGLE
def specialize(self):
 96    def specialize(self):
 97        if self.direction == Line.Direction.VERTICAL:
 98            return VLine(self.p0.x, self.p0.y, self.p1.y, self.width)
 99        if self.direction == Line.Direction.HORIZONTAL:
100            return HLine(self.p0.y, self.p0.x, self.p1.x, self.width)
101        return Line(self.p0, self.p1, width=self.width)
class Line.Direction(enum.Enum):
50    class Direction(Enum):
51        ANGLE = 0
52        VERTICAL = 1
53        HORIZONTAL = 2
ANGLE = <Direction.ANGLE: 0>
VERTICAL = <Direction.VERTICAL: 1>
HORIZONTAL = <Direction.HORIZONTAL: 2>
Inherited Members
enum.Enum
name
value
class VLine(modm_data.utils.Line):
112class VLine(Line):
113    def __init__(self, x: float, y0: float, y1: float, width: float = None):
114        if y0 > y1:
115            y0, y1 = y1, y0
116        super().__init__(Point(x, y0), Point(x, y1), width=width)
117        self.length = y1 - y0
118
119    @property
120    def direction(self):
121        return Line.Direction.VERTICAL
122
123    def __repr__(self) -> str:
124        x = f"{self.p0.x:.1f}" if isinstance(self.p0.x, float) else self.p0.x
125        y0 = f"{self.p0.y:.1f}" if isinstance(self.p0.y, float) else self.p0.y
126        y1 = f"{self.p1.y:.1f}" if isinstance(self.p1.y, float) else self.p1.y
127        out = f"<X{x}:{y0},{y1}"
128        if self.width:
129            out += f"|{self.width:.1f}"
130        return out + ">"
VLine(x: float, y0: float, y1: float, width: float = None)
113    def __init__(self, x: float, y0: float, y1: float, width: float = None):
114        if y0 > y1:
115            y0, y1 = y1, y0
116        super().__init__(Point(x, y0), Point(x, y1), width=width)
117        self.length = y1 - y0
length
direction
119    @property
120    def direction(self):
121        return Line.Direction.VERTICAL
class HLine(modm_data.utils.Line):
133class HLine(Line):
134    def __init__(self, y: float, x0: float, x1: float, width: float = None):
135        if x0 > x1:
136            x0, x1 = x1, x0
137        super().__init__(Point(x0, y), Point(x1, y), width=width)
138        self.length = x1 - x0
139
140    @property
141    def direction(self):
142        return Line.Direction.HORIZONTAL
143
144    def __repr__(self) -> str:
145        y = f"{self.p0.y:.1f}" if isinstance(self.p0.y, float) else self.p0.y
146        x0 = f"{self.p0.x:.1f}" if isinstance(self.p0.x, float) else self.p0.x
147        x1 = f"{self.p1.x:.1f}" if isinstance(self.p1.x, float) else self.p1.x
148        out = f"<Y{y}:{x0},{x1}"
149        if self.width:
150            out += f"|{self.width:.1f}"
151        return out + ">"
HLine(y: float, x0: float, x1: float, width: float = None)
134    def __init__(self, y: float, x0: float, x1: float, width: float = None):
135        if x0 > x1:
136            x0, x1 = x1, x0
137        super().__init__(Point(x0, y), Point(x1, y), width=width)
138        self.length = x1 - x0
length
direction
140    @property
141    def direction(self):
142        return Line.Direction.HORIZONTAL
class Rectangle:
154class Rectangle:
155    __slots__ = ("_midpoint", "bottom", "height", "left", "p0", "p1", "right", "top", "width", "x", "y")
156
157    def __init__(self, *r):
158        # P0 is left, bottom
159        # P1 is right, top
160        if isinstance(r[0], pp.raw.FS_RECTF):
161            self.p0 = Point(r[0].left, r[0].bottom)
162            self.p1 = Point(r[0].right, r[0].top)
163        elif isinstance(r[0], Point):
164            self.p0 = r[0]
165            self.p1 = r[1]
166        elif isinstance(r[0], tuple):
167            self.p0 = Point(r[0][0], r[0][1])
168            self.p1 = Point(r[1][0], r[1][1])
169        else:
170            self.p0 = Point(r[0], r[1])
171            self.p1 = Point(r[2], r[3])
172
173        # Ensure the correct ordering of point values
174        if self.p0.x > self.p1.x:
175            self.p0.x, self.p1.x = self.p1.x, self.p0.x
176        if self.p0.y > self.p1.y:
177            self.p0.y, self.p1.y = self.p1.y, self.p0.y
178
179        # assert self.p0.x <= self.p1.x
180        # assert self.p0.y <= self.p1.y
181
182        self.x = self.p0.x
183        self.y = self.p0.y
184        self.left = self.p0.x
185        self.bottom = self.p0.y
186
187        self.right = self.p1.x
188        self.top = self.p1.y
189
190        self.width = self.p1.x - self.p0.x
191        self.height = self.p1.y - self.p0.y
192        self._midpoint = None
193
194    def contains(self, other) -> bool:
195        if isinstance(other, Point):
196            return self.bottom <= other.y <= self.top and self.left <= other.x <= self.right
197        # Comparing y-axis first may be faster for "content areas filtering"
198        # when doing subparsing of page content (like in tables)
199        return (
200            self.bottom <= other.bottom
201            and other.top <= self.top
202            and self.left <= other.left
203            and other.right <= self.right
204        )
205
206    def overlaps(self, other) -> bool:
207        return self.contains(other.p0) or self.contains(other.p1)
208
209    def isclose(self, other, rtol: float = 1e-09, atol: float = 0.0) -> bool:
210        return self.p0.isclose(other.p0, rtol, atol) and self.p1.isclose(other.p1, rtol, atol)
211
212    @property
213    def midpoint(self) -> Point:
214        if self._midpoint is None:
215            self._midpoint = Point((self.p1.x + self.p0.x) / 2, (self.p1.y + self.p0.y) / 2)
216        return self._midpoint
217
218    @property
219    def points(self) -> list[Point]:
220        return [self.p0, Point(self.right, self.bottom), self.p1, Point(self.left, self.top)]
221
222    def offset(self, offset):
223        return Rectangle(self.p0.x - offset, self.p0.y - offset, self.p1.x + offset, self.p1.y + offset)
224
225    def offset_x(self, offset):
226        return Rectangle(self.p0.x - offset, self.p0.y, self.p1.x + offset, self.p1.y)
227
228    def offset_y(self, offset):
229        return Rectangle(self.p0.x, self.p0.y - offset, self.p1.x, self.p1.y + offset)
230
231    def translated(self, point):
232        return Rectangle(self.p0.x + point.x, self.p0.y + point.y, self.p1.x + point.x, self.p1.y + point.y)
233
234    def rotated(self, rotation):
235        cos = math.cos(math.radians(rotation))
236        sin = math.sin(math.radians(rotation))
237        return Rectangle(
238            self.p0.x * cos - self.p0.y * sin,
239            self.p0.x * sin + self.p0.y * cos,
240            self.p1.x * cos - self.p1.y * sin,
241            self.p1.x * sin + self.p1.y * cos,
242        )
243
244    def joined(self, other):
245        return Rectangle(
246            min(self.p0.x, other.p0.x),
247            min(self.p0.y, other.p0.y),
248            max(self.p1.x, other.p1.x),
249            max(self.p1.y, other.p1.y),
250        )
251
252    def round(self, accuracy=0):
253        return Rectangle(
254            round(self.p0.x, accuracy),
255            round(self.p0.y, accuracy),
256            round(self.p1.x, accuracy),
257            round(self.p1.y, accuracy),
258        )
259
260    def __hash__(self):
261        return hash(self.p0) + hash(self.p1)
262
263    def __repr__(self) -> str:
264        return f"[{repr(self.p0)},{repr(self.p1)}]"
Rectangle(*r)
157    def __init__(self, *r):
158        # P0 is left, bottom
159        # P1 is right, top
160        if isinstance(r[0], pp.raw.FS_RECTF):
161            self.p0 = Point(r[0].left, r[0].bottom)
162            self.p1 = Point(r[0].right, r[0].top)
163        elif isinstance(r[0], Point):
164            self.p0 = r[0]
165            self.p1 = r[1]
166        elif isinstance(r[0], tuple):
167            self.p0 = Point(r[0][0], r[0][1])
168            self.p1 = Point(r[1][0], r[1][1])
169        else:
170            self.p0 = Point(r[0], r[1])
171            self.p1 = Point(r[2], r[3])
172
173        # Ensure the correct ordering of point values
174        if self.p0.x > self.p1.x:
175            self.p0.x, self.p1.x = self.p1.x, self.p0.x
176        if self.p0.y > self.p1.y:
177            self.p0.y, self.p1.y = self.p1.y, self.p0.y
178
179        # assert self.p0.x <= self.p1.x
180        # assert self.p0.y <= self.p1.y
181
182        self.x = self.p0.x
183        self.y = self.p0.y
184        self.left = self.p0.x
185        self.bottom = self.p0.y
186
187        self.right = self.p1.x
188        self.top = self.p1.y
189
190        self.width = self.p1.x - self.p0.x
191        self.height = self.p1.y - self.p0.y
192        self._midpoint = None
x
y
left
bottom
right
top
width
height
def contains(self, other) -> bool:
194    def contains(self, other) -> bool:
195        if isinstance(other, Point):
196            return self.bottom <= other.y <= self.top and self.left <= other.x <= self.right
197        # Comparing y-axis first may be faster for "content areas filtering"
198        # when doing subparsing of page content (like in tables)
199        return (
200            self.bottom <= other.bottom
201            and other.top <= self.top
202            and self.left <= other.left
203            and other.right <= self.right
204        )
def overlaps(self, other) -> bool:
206    def overlaps(self, other) -> bool:
207        return self.contains(other.p0) or self.contains(other.p1)
def isclose(self, other, rtol: float = 1e-09, atol: float = 0.0) -> bool:
209    def isclose(self, other, rtol: float = 1e-09, atol: float = 0.0) -> bool:
210        return self.p0.isclose(other.p0, rtol, atol) and self.p1.isclose(other.p1, rtol, atol)
midpoint: Point
212    @property
213    def midpoint(self) -> Point:
214        if self._midpoint is None:
215            self._midpoint = Point((self.p1.x + self.p0.x) / 2, (self.p1.y + self.p0.y) / 2)
216        return self._midpoint
points: list[Point]
218    @property
219    def points(self) -> list[Point]:
220        return [self.p0, Point(self.right, self.bottom), self.p1, Point(self.left, self.top)]
def offset(self, offset):
222    def offset(self, offset):
223        return Rectangle(self.p0.x - offset, self.p0.y - offset, self.p1.x + offset, self.p1.y + offset)
def offset_x(self, offset):
225    def offset_x(self, offset):
226        return Rectangle(self.p0.x - offset, self.p0.y, self.p1.x + offset, self.p1.y)
def offset_y(self, offset):
228    def offset_y(self, offset):
229        return Rectangle(self.p0.x, self.p0.y - offset, self.p1.x, self.p1.y + offset)
def translated(self, point):
231    def translated(self, point):
232        return Rectangle(self.p0.x + point.x, self.p0.y + point.y, self.p1.x + point.x, self.p1.y + point.y)
def rotated(self, rotation):
234    def rotated(self, rotation):
235        cos = math.cos(math.radians(rotation))
236        sin = math.sin(math.radians(rotation))
237        return Rectangle(
238            self.p0.x * cos - self.p0.y * sin,
239            self.p0.x * sin + self.p0.y * cos,
240            self.p1.x * cos - self.p1.y * sin,
241            self.p1.x * sin + self.p1.y * cos,
242        )
def joined(self, other):
244    def joined(self, other):
245        return Rectangle(
246            min(self.p0.x, other.p0.x),
247            min(self.p0.y, other.p0.y),
248            max(self.p1.x, other.p1.x),
249            max(self.p1.y, other.p1.y),
250        )
def round(self, accuracy=0):
252    def round(self, accuracy=0):
253        return Rectangle(
254            round(self.p0.x, accuracy),
255            round(self.p0.y, accuracy),
256            round(self.p1.x, accuracy),
257            round(self.p1.y, accuracy),
258        )
p0
p1
class Region:
267class Region:
268    def __init__(self, v0, v1, obj=None):
269        if v0 > v1:
270            v0, v1 = v1, v0
271        self.v0 = v0
272        self.v1 = v1
273        self.objs = [] if obj is None else [obj]
274        self.subregions = []
275
276    def overlaps(self, o0, o1, atol=0) -> bool:
277        if o0 > o1:
278            o0, o1 = o1, o0
279        # if reg top is lower then o0
280        if (self.v1 + atol) <= o0:
281            return False
282        # if reg bottom is higher than o1
283        if o1 <= (self.v0 - atol):
284            return False
285        return True
286
287    def contains(self, v, atol=0) -> bool:
288        return self.v0 - atol <= v <= self.v1 + atol
289
290    @property
291    def delta(self) -> float:
292        return self.v1 - self.v0
293
294    def __repr__(self):
295        r = f"<{int(self.v0)}->{int(self.v1)}"
296        if self.objs:
297            r += f"|{len(self.objs)}"
298        if self.subregions:
299            r += f"|{repr(self.subregions)}"
300        return r + ">"
Region(v0, v1, obj=None)
268    def __init__(self, v0, v1, obj=None):
269        if v0 > v1:
270            v0, v1 = v1, v0
271        self.v0 = v0
272        self.v1 = v1
273        self.objs = [] if obj is None else [obj]
274        self.subregions = []
v0
v1
objs
subregions
def overlaps(self, o0, o1, atol=0) -> bool:
276    def overlaps(self, o0, o1, atol=0) -> bool:
277        if o0 > o1:
278            o0, o1 = o1, o0
279        # if reg top is lower then o0
280        if (self.v1 + atol) <= o0:
281            return False
282        # if reg bottom is higher than o1
283        if o1 <= (self.v0 - atol):
284            return False
285        return True
def contains(self, v, atol=0) -> bool:
287    def contains(self, v, atol=0) -> bool:
288        return self.v0 - atol <= v <= self.v1 + atol
delta: float
290    @property
291    def delta(self) -> float:
292        return self.v1 - self.v0
def list_lstrip(input_values: list, strip_fn) -> list:
11def list_lstrip(input_values: list, strip_fn) -> list:
12    ii = 0
13    for value in input_values:
14        if strip_fn(value):
15            ii += 1
16        else:
17            break
18    return input_values[ii:]
def list_rstrip(input_values: list, strip_fn) -> list:
21def list_rstrip(input_values: list, strip_fn) -> list:
22    ii = 0
23    for value in reversed(input_values):
24        if strip_fn(value):
25            ii += 1
26        else:
27            break
28    return input_values[: len(input_values) - ii]
def list_strip(input_values: list, strip_fn) -> list:
31def list_strip(input_values: list, strip_fn) -> list:
32    return list_rstrip(list_lstrip(input_values, strip_fn), strip_fn)
def pkg_file_exists(pkg, file: pathlib.Path) -> bool:
35def pkg_file_exists(pkg, file: Path) -> bool:
36    return files(pkg).joinpath(file).is_file()
def pkg_apply_patch(pkg, patch: pathlib.Path, base_dir: pathlib.Path) -> bool:
39def pkg_apply_patch(pkg, patch: Path, base_dir: Path) -> bool:
40    with as_file(files(pkg).joinpath(patch)) as patch_file:
41        return apply_patch(patch_file, base_dir)
def apply_patch(patch_file: pathlib.Path, base_dir: pathlib.Path) -> bool:
44def apply_patch(patch_file: Path, base_dir: Path) -> bool:
45    cmds = [
46        "patch",
47        "--strip=1",
48        "--ignore-whitespace",
49        "--reject-file=-",
50        "--forward",
51        "--posix",
52        "--directory",
53        Path(base_dir).absolute(),
54        "--input",
55        Path(patch_file).absolute(),
56    ]
57
58    def run(*args) -> bool:
59        result = subprocess.run(cmds + list(args), capture_output=True, text=True)
60        # Hunks that the conversion already fixed are ignored, which is fine
61        if result.returncode and re.search(r"failed|can't find|malformed|garbage", result.stdout + result.stderr):
62            print(result.stdout + result.stderr)
63            return False
64        return True
65
66    return run("--dry-run") and run()
class ReversePreOrderIter(anytree.iterators.abstractiter.AbstractIter):
 8class ReversePreOrderIter(AbstractIter):
 9    @staticmethod
10    def _iter(children, filter_, stop, maxlevel):
11        for child_ in reversed(children):
12            if not AbstractIter._abort_at_level(2, maxlevel):
13                descendantmaxlevel = maxlevel - 1 if maxlevel else None
14                yield from ReversePreOrderIter._iter(child_.children, filter_, stop, descendantmaxlevel)
15                if stop(child_):
16                    continue
17                if filter_(child_):
18                    yield child_

Iterate over tree starting at node.

Base class for all iterators.

Keyword Args: filter_: function called with every node as argument, node is returned if True. stop: stop iteration at node if stop function returns True for node. maxlevel (int): maximum descending in the node hierarchy.

Inherited Members
anytree.iterators.abstractiter.AbstractIter
AbstractIter
node
filter_
stop
maxlevel
def root_path(path) -> pathlib.Path:
8def root_path(path) -> Path:
9    return Path(__file__).parents[3] / path
def ext_path(path) -> pathlib.Path:
12def ext_path(path) -> Path:
13    return root_path(Path("ext") / path)
def cache_path(path) -> pathlib.Path:
16def cache_path(path) -> Path:
17    return ext_path(Path("cache") / path)
def patch_path(path) -> pathlib.Path:
20def patch_path(path) -> Path:
21    return root_path(Path("patches") / path)
class XmlReader:
17class XmlReader:
18    def __init__(self, path):
19        self.filename = path
20        self.tree = self._openDeviceXML(self.filename)
21
22    def __repr__(self):
23        return f"XMLReader({os.path.basename(self.filename)})"
24
25    def _openDeviceXML(self, filename):
26        LOGGER.debug("Opening XML file '%s'", os.path.basename(filename))
27        xml_file = Path(filename).read_text("utf-8", errors="replace")
28        xml_file = re.sub(' xmlns="[^"]+"', "", xml_file, count=1).encode("utf-8")
29        xmltree = etree.fromstring(xml_file, parser=PARSER)
30        return xmltree
31
32    def queryTree(self, query):
33        """
34        This tries to apply the query to the device tree and returns either
35        - an array of element nodes,
36        - an array of strings or
37        - None, if the query failed.
38        """
39        response = None
40        try:
41            response = self.tree.xpath(query)
42        except Exception:
43            LOGGER.error(f"Query failed for '{query}'")
44
45        return response
46
47    def query(self, query, default=[]):
48        result = self.queryTree(query)
49        if result is not None:
50            sorted_results = []
51            for r in result:
52                if r not in sorted_results:
53                    sorted_results.append(r)
54            return sorted_results
55
56        return default
57
58    def compactQuery(self, query):
59        return self.query(query, None)
XmlReader(path)
18    def __init__(self, path):
19        self.filename = path
20        self.tree = self._openDeviceXML(self.filename)
filename
tree
def queryTree(self, query):
32    def queryTree(self, query):
33        """
34        This tries to apply the query to the device tree and returns either
35        - an array of element nodes,
36        - an array of strings or
37        - None, if the query failed.
38        """
39        response = None
40        try:
41            response = self.tree.xpath(query)
42        except Exception:
43            LOGGER.error(f"Query failed for '{query}'")
44
45        return response

This tries to apply the query to the device tree and returns either

  • an array of element nodes,
  • an array of strings or
  • None, if the query failed.
def query(self, query, default=[]):
47    def query(self, query, default=[]):
48        result = self.queryTree(query)
49        if result is not None:
50            sorted_results = []
51            for r in result:
52                if r not in sorted_results:
53                    sorted_results.append(r)
54            return sorted_results
55
56        return default
def compactQuery(self, query):
58    def compactQuery(self, query):
59        return self.query(query, None)