arelle-release 2.37.18__py3-none-any.whl → 2.37.20__py3-none-any.whl
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.
Potentially problematic release.
This version of arelle-release might be problematic. Click here for more details.
- arelle/CntlrWinMain.py +4 -1
- arelle/FileSource.py +2 -2
- arelle/ModelInstanceObject.py +1 -1
- arelle/ModelValue.py +1 -0
- arelle/ModelXbrl.py +1 -1
- arelle/PythonUtil.py +132 -24
- arelle/ViewWinTree.py +15 -9
- arelle/_version.py +2 -2
- arelle/formula/XPathContext.py +22 -22
- arelle/formula/XPathParser.py +2 -2
- arelle/plugin/OimTaxonomy/ModelValueMore.py +15 -0
- arelle/plugin/OimTaxonomy/ValidateDTS.py +484 -0
- arelle/plugin/OimTaxonomy/ViewXbrlTxmyObj.py +239 -0
- arelle/plugin/OimTaxonomy/XbrlAbstract.py +16 -0
- arelle/plugin/OimTaxonomy/XbrlConcept.py +68 -0
- arelle/plugin/OimTaxonomy/XbrlConst.py +261 -0
- arelle/plugin/OimTaxonomy/XbrlCube.py +91 -0
- arelle/plugin/OimTaxonomy/XbrlDimension.py +38 -0
- arelle/plugin/OimTaxonomy/XbrlDts.py +152 -0
- arelle/plugin/OimTaxonomy/XbrlEntity.py +16 -0
- arelle/plugin/OimTaxonomy/XbrlGroup.py +22 -0
- arelle/plugin/OimTaxonomy/XbrlImportedTaxonomy.py +22 -0
- arelle/plugin/OimTaxonomy/XbrlLabel.py +31 -0
- arelle/plugin/OimTaxonomy/XbrlNetwork.py +100 -0
- arelle/plugin/OimTaxonomy/XbrlProperty.py +28 -0
- arelle/plugin/OimTaxonomy/XbrlReference.py +33 -0
- arelle/plugin/OimTaxonomy/XbrlReport.py +24 -0
- arelle/plugin/OimTaxonomy/XbrlTableTemplate.py +35 -0
- arelle/plugin/OimTaxonomy/XbrlTaxonomy.py +93 -0
- arelle/plugin/OimTaxonomy/XbrlTaxonomyObject.py +154 -0
- arelle/plugin/OimTaxonomy/XbrlTransform.py +17 -0
- arelle/plugin/OimTaxonomy/XbrlTypes.py +23 -0
- arelle/plugin/OimTaxonomy/XbrlUnit.py +17 -0
- arelle/plugin/OimTaxonomy/__init__.py +1037 -0
- arelle/plugin/OimTaxonomy/resources/iso4217.json +4479 -0
- arelle/plugin/OimTaxonomy/resources/oim-taxonomy-schema.json +935 -0
- arelle/plugin/OimTaxonomy/resources/ref.json +333 -0
- arelle/plugin/OimTaxonomy/resources/transform-types.json +2481 -0
- arelle/plugin/OimTaxonomy/resources/types.json +727 -0
- arelle/plugin/OimTaxonomy/resources/utr.json +3046 -0
- arelle/plugin/OimTaxonomy/resources/xbrlSpec.json +1082 -0
- arelle/plugin/saveOIMTaxonomy.py +311 -0
- arelle/plugin/validate/NL/PluginValidationDataExtension.py +35 -1
- arelle/plugin/validate/NL/__init__.py +7 -0
- arelle/plugin/validate/NL/rules/nl_kvk.py +144 -2
- {arelle_release-2.37.18.dist-info → arelle_release-2.37.20.dist-info}/METADATA +3 -1
- {arelle_release-2.37.18.dist-info → arelle_release-2.37.20.dist-info}/RECORD +59 -24
- tests/integration_tests/validation/README.md +2 -0
- tests/integration_tests/validation/conformance_suite_configurations/nl_inline_2024.py +19 -5
- tests/integration_tests/validation/run_conformance_suites.py +10 -1
- tests/integration_tests/validation/validation_util.py +10 -5
- tests/unit_tests/arelle/test_frozen_dict.py +176 -0
- tests/unit_tests/arelle/test_frozen_ordered_set.py +315 -0
- tests/unit_tests/arelle/test_import.py +1 -0
- tests/unit_tests/arelle/test_ordered_set.py +272 -0
- {arelle_release-2.37.18.dist-info → arelle_release-2.37.20.dist-info}/WHEEL +0 -0
- {arelle_release-2.37.18.dist-info → arelle_release-2.37.20.dist-info}/entry_points.txt +0 -0
- {arelle_release-2.37.18.dist-info → arelle_release-2.37.20.dist-info}/licenses/LICENSE.md +0 -0
- {arelle_release-2.37.18.dist-info → arelle_release-2.37.20.dist-info}/top_level.txt +0 -0
arelle/CntlrWinMain.py
CHANGED
|
@@ -949,7 +949,10 @@ class CntlrWinMain (Cntlr.Cntlr):
|
|
|
949
949
|
self.parent.title(_("arelle - {0}").format(
|
|
950
950
|
os.path.basename(modelXbrl.modelDocument.uri)))
|
|
951
951
|
self.setValidateTooltipText()
|
|
952
|
-
if modelXbrl
|
|
952
|
+
if any(extViews(self, modelXbrl)
|
|
953
|
+
for extViews in pluginClassMethods("CntlrWinMain.Xbrl.Views")):
|
|
954
|
+
currentAction = "ext views provided"
|
|
955
|
+
elif modelXbrl.modelDocument.type in ModelDocument.Type.TESTCASETYPES:
|
|
953
956
|
currentAction = "tree view of tests"
|
|
954
957
|
ViewWinTests.viewTests(modelXbrl, self.tabWinTopRt)
|
|
955
958
|
topView = modelXbrl.views[-1]
|
arelle/FileSource.py
CHANGED
|
@@ -13,7 +13,7 @@ import struct
|
|
|
13
13
|
import tarfile
|
|
14
14
|
import zipfile
|
|
15
15
|
import zlib
|
|
16
|
-
from typing import IO, TYPE_CHECKING, Any, TextIO, cast
|
|
16
|
+
from typing import IO, TYPE_CHECKING, Any, Optional, TextIO, cast
|
|
17
17
|
|
|
18
18
|
import regex as re
|
|
19
19
|
from lxml import etree
|
|
@@ -452,7 +452,7 @@ class FileSource:
|
|
|
452
452
|
def reportPackage(self) -> ReportPackage | None:
|
|
453
453
|
try:
|
|
454
454
|
self._reportPackage: ReportPackage | None
|
|
455
|
-
return self._reportPackage
|
|
455
|
+
return cast(Optional[ReportPackage], self._reportPackage)
|
|
456
456
|
except AttributeError:
|
|
457
457
|
self._reportPackage = ReportPackage.fromFileSource(self)
|
|
458
458
|
return self._reportPackage
|
arelle/ModelInstanceObject.py
CHANGED
|
@@ -1133,7 +1133,7 @@ class ModelContext(ModelObject):
|
|
|
1133
1133
|
dimValue = self.modelXbrl.qnameDimensionDefaults.get(dimQname)
|
|
1134
1134
|
return dimValue
|
|
1135
1135
|
|
|
1136
|
-
def dimMemberQname(self, dimQname, includeDefaults=False):
|
|
1136
|
+
def dimMemberQname(self, dimQname: ModelValue.QName, includeDefaults: bool = False) -> ModelValue.QName | None:
|
|
1137
1137
|
"""(QName) -- QName of explicit dimension if reported (or defaulted if includeDefaults is True), else None"""
|
|
1138
1138
|
dimValue = self.dimValue(dimQname)
|
|
1139
1139
|
if isinstance(dimValue, (ModelDimensionValue,DimValuePrototype)) and dimValue.isExplicit:
|
arelle/ModelValue.py
CHANGED
arelle/ModelXbrl.py
CHANGED
|
@@ -878,7 +878,7 @@ class ModelXbrl:
|
|
|
878
878
|
def dimensionsInUse(self) -> set[Any]:
|
|
879
879
|
self._dimensionsInUse: set[Any]
|
|
880
880
|
try:
|
|
881
|
-
return self._dimensionsInUse
|
|
881
|
+
return cast(set[Any], self._dimensionsInUse)
|
|
882
882
|
except AttributeError:
|
|
883
883
|
self._dimensionsInUse = set(dim.dimension
|
|
884
884
|
for cntx in self.contexts.values() # use contextsInUse? slower?
|
arelle/PythonUtil.py
CHANGED
|
@@ -6,18 +6,19 @@ do not convert 3 to 2
|
|
|
6
6
|
'''
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
+
import fractions
|
|
9
10
|
import os
|
|
10
11
|
import subprocess
|
|
11
12
|
import sys
|
|
12
13
|
from collections import OrderedDict
|
|
13
|
-
from collections.abc import MappingView, MutableSet
|
|
14
|
+
from collections.abc import Iterable, Iterator, Mapping, MappingView, MutableSet, Set
|
|
14
15
|
from decimal import Decimal
|
|
15
|
-
from
|
|
16
|
-
from typing import Any
|
|
16
|
+
from types import MappingProxyType
|
|
17
|
+
from typing import Any, Generic, TypeVar
|
|
17
18
|
|
|
18
19
|
from arelle.typing import OptionalString
|
|
19
20
|
|
|
20
|
-
STR_NUM_TYPES = (str, int, float, Decimal, Fraction)
|
|
21
|
+
STR_NUM_TYPES = (str, int, float, Decimal, fractions.Fraction)
|
|
21
22
|
|
|
22
23
|
# python 3 unquote, because py2 unquote doesn't do utf-8 correctly
|
|
23
24
|
def py3unquote(string, encoding='utf-8', errors='replace'):
|
|
@@ -163,71 +164,178 @@ class OrderedDefaultDict(OrderedDict):
|
|
|
163
164
|
self[key] = _missingValue
|
|
164
165
|
return _missingValue
|
|
165
166
|
|
|
166
|
-
class OrderedSet(MutableSet):
|
|
167
167
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
168
|
+
T = TypeVar('T')
|
|
169
|
+
|
|
170
|
+
class OrderedSet(MutableSet[T]):
|
|
171
|
+
"""
|
|
172
|
+
OrderedSet implementation copied from Python recipe:
|
|
173
|
+
https://code.activestate.com/recipes/576694/
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
def __init__(self, iterable: Iterable[T] | None = None) -> None:
|
|
177
|
+
self.end: list[Any] = []
|
|
178
|
+
end = self.end
|
|
179
|
+
end += [None, end, end] # sentinel node for doubly linked list
|
|
180
|
+
self.map: dict[T, list[Any]] = {} # key --> [key, prev, next]
|
|
172
181
|
if iterable is not None:
|
|
173
|
-
self
|
|
182
|
+
self.update(iterable)
|
|
183
|
+
|
|
184
|
+
def __getitem__(self, index: int) -> T:
|
|
185
|
+
if not isinstance(index, int):
|
|
186
|
+
raise TypeError("Index must be an integer")
|
|
187
|
+
if index < 0:
|
|
188
|
+
index += len(self)
|
|
189
|
+
if index < 0 or index >= len(self):
|
|
190
|
+
raise IndexError("Index out of range")
|
|
191
|
+
end = self.end
|
|
192
|
+
curr = end[2]
|
|
193
|
+
for _ in range(index):
|
|
194
|
+
curr = curr[2]
|
|
195
|
+
return curr[0]
|
|
174
196
|
|
|
175
|
-
def __len__(self):
|
|
197
|
+
def __len__(self) -> int:
|
|
176
198
|
return len(self.map)
|
|
177
199
|
|
|
178
|
-
def __contains__(self, key):
|
|
200
|
+
def __contains__(self, key: object) -> bool:
|
|
179
201
|
return key in self.map
|
|
180
202
|
|
|
181
|
-
def add(self, key):
|
|
203
|
+
def add(self, key: T) -> None:
|
|
182
204
|
if key not in self.map:
|
|
183
205
|
end = self.end
|
|
184
206
|
curr = end[1]
|
|
185
207
|
curr[2] = end[1] = self.map[key] = [key, curr, end]
|
|
186
208
|
|
|
187
|
-
def update(self, other):
|
|
209
|
+
def update(self, other: Iterable[T]) -> None:
|
|
188
210
|
for key in other:
|
|
189
211
|
self.add(key)
|
|
190
212
|
|
|
191
|
-
def discard(self, key):
|
|
213
|
+
def discard(self, key: T) -> None:
|
|
192
214
|
if key in self.map:
|
|
193
215
|
key, prev, next = self.map.pop(key)
|
|
194
216
|
prev[2] = next
|
|
195
217
|
next[1] = prev
|
|
196
218
|
|
|
197
|
-
def __iter__(self):
|
|
219
|
+
def __iter__(self) -> Iterator[T]:
|
|
198
220
|
end = self.end
|
|
199
221
|
curr = end[2]
|
|
200
222
|
while curr is not end:
|
|
201
223
|
yield curr[0]
|
|
202
224
|
curr = curr[2]
|
|
203
225
|
|
|
204
|
-
def __reversed__(self):
|
|
226
|
+
def __reversed__(self) -> Iterator[T]:
|
|
205
227
|
end = self.end
|
|
206
228
|
curr = end[1]
|
|
207
229
|
while curr is not end:
|
|
208
230
|
yield curr[0]
|
|
209
231
|
curr = curr[1]
|
|
210
232
|
|
|
211
|
-
def pop(self, last=True):
|
|
233
|
+
def pop(self, last: bool = True) -> T:
|
|
212
234
|
if not self:
|
|
213
235
|
raise KeyError('set is empty')
|
|
214
236
|
key = self.end[1][0] if last else self.end[2][0]
|
|
215
237
|
self.discard(key)
|
|
216
238
|
return key
|
|
217
239
|
|
|
218
|
-
def __repr__(self):
|
|
240
|
+
def __repr__(self) -> str:
|
|
219
241
|
if not self:
|
|
220
|
-
return '
|
|
221
|
-
return '
|
|
242
|
+
return f'{self.__class__.__name__}()'
|
|
243
|
+
return f'{self.__class__.__name__}({list(self)!r})'
|
|
222
244
|
|
|
223
|
-
def __eq__(self, other):
|
|
245
|
+
def __eq__(self, other: object) -> bool:
|
|
224
246
|
if isinstance(other, OrderedSet):
|
|
225
247
|
return len(self) == len(other) and list(self) == list(other)
|
|
226
|
-
|
|
248
|
+
if isinstance(other, Iterable):
|
|
249
|
+
return set(self) == set(other)
|
|
250
|
+
return NotImplemented
|
|
251
|
+
|
|
252
|
+
class FrozenOrderedSet(Set[T]):
|
|
253
|
+
"""
|
|
254
|
+
Like frozenset vs set, this is the immutable counterpart to OrderedSet.
|
|
255
|
+
Maintains insertion order and provides set-like operations without mutation.
|
|
256
|
+
"""
|
|
257
|
+
|
|
258
|
+
def __init__(self, iterable: Iterable[T] | None = None) -> None:
|
|
259
|
+
if iterable is None:
|
|
260
|
+
self._items: tuple[T, ...] = ()
|
|
261
|
+
self._set: frozenset[T] = frozenset()
|
|
262
|
+
else:
|
|
263
|
+
unique_items = dict.fromkeys(iterable)
|
|
264
|
+
self._items = tuple(unique_items.keys())
|
|
265
|
+
self._set = frozenset(unique_items.keys())
|
|
266
|
+
self._hash: int | None = None
|
|
267
|
+
|
|
268
|
+
def __getitem__(self, index: int) -> T:
|
|
269
|
+
return self._items[index]
|
|
270
|
+
|
|
271
|
+
def __len__(self) -> int:
|
|
272
|
+
return len(self._items)
|
|
273
|
+
|
|
274
|
+
def __contains__(self, key: object) -> bool:
|
|
275
|
+
return key in self._set
|
|
276
|
+
|
|
277
|
+
def __iter__(self) -> Iterator[T]:
|
|
278
|
+
return iter(self._items)
|
|
279
|
+
|
|
280
|
+
def __reversed__(self) -> Iterator[T]:
|
|
281
|
+
return reversed(self._items)
|
|
282
|
+
|
|
283
|
+
def __repr__(self) -> str:
|
|
284
|
+
if not self:
|
|
285
|
+
return f'{self.__class__.__name__}()'
|
|
286
|
+
return f'{self.__class__.__name__}({self._items!r})'
|
|
287
|
+
|
|
288
|
+
def __eq__(self, other: object) -> bool:
|
|
289
|
+
if isinstance(other, (FrozenOrderedSet, OrderedSet)):
|
|
290
|
+
return len(self) == len(other) and list(self) == list(other)
|
|
291
|
+
if isinstance(other, Iterable):
|
|
292
|
+
return set(self) == set(other)
|
|
293
|
+
return NotImplemented
|
|
294
|
+
|
|
295
|
+
def __hash__(self) -> int:
|
|
296
|
+
if self._hash is None:
|
|
297
|
+
self._hash = hash(self._items)
|
|
298
|
+
return self._hash
|
|
299
|
+
|
|
300
|
+
KT = TypeVar('KT')
|
|
301
|
+
VT = TypeVar('VT')
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
class FrozenDict(Generic[KT, VT], Mapping[KT, VT]):
|
|
305
|
+
def __init__(self, data: Mapping[KT, VT] | None = None) -> None:
|
|
306
|
+
self._dict: Mapping[KT, VT] = MappingProxyType(dict(data) if data is not None else dict())
|
|
307
|
+
self._hash: int | None = None
|
|
308
|
+
|
|
309
|
+
def __getitem__(self, key: KT) -> VT:
|
|
310
|
+
return self._dict[key]
|
|
311
|
+
|
|
312
|
+
def __iter__(self) -> Iterator[KT]:
|
|
313
|
+
return iter(self._dict)
|
|
314
|
+
|
|
315
|
+
def __len__(self) -> int:
|
|
316
|
+
return len(self._dict)
|
|
317
|
+
|
|
318
|
+
def __repr__(self) -> str:
|
|
319
|
+
if not self:
|
|
320
|
+
return f'{self.__class__.__name__}()'
|
|
321
|
+
return f"{self.__class__.__name__}({self._dict})"
|
|
322
|
+
|
|
323
|
+
def __eq__(self, other: Any) -> bool:
|
|
324
|
+
if isinstance(other, FrozenDict):
|
|
325
|
+
return self._dict == other._dict
|
|
326
|
+
if isinstance(other, Mapping):
|
|
327
|
+
return self._dict == other
|
|
328
|
+
return NotImplemented
|
|
329
|
+
|
|
330
|
+
def __hash__(self) -> int:
|
|
331
|
+
if self._hash is None:
|
|
332
|
+
self._hash = hash(tuple(sorted(self._dict.items())))
|
|
333
|
+
return self._hash
|
|
334
|
+
|
|
227
335
|
|
|
228
336
|
def Fraction(numerator,denominator=None):
|
|
229
337
|
if denominator is None:
|
|
230
|
-
if isinstance(numerator, (Fraction,str,Decimal)):
|
|
338
|
+
if isinstance(numerator, (fractions.Fraction,str,Decimal)):
|
|
231
339
|
return Fraction(numerator)
|
|
232
340
|
elif isinstance(numerator, Decimal) and isinstance(denominator, Decimal):
|
|
233
341
|
return Fraction(int(numerator), int(denominator))
|
arelle/ViewWinTree.py
CHANGED
|
@@ -220,14 +220,16 @@ class ViewTree:
|
|
|
220
220
|
modelObject=self.modelXbrl.modelDocument, title=self.tabTitle, error=str(ex))
|
|
221
221
|
self.menu = None
|
|
222
222
|
|
|
223
|
-
def menuAddLabelRoles(self, includeConceptName=False, menulabel=None):
|
|
223
|
+
def menuAddLabelRoles(self, includeConceptName=False, menulabel=None, usedLabelroles=None):
|
|
224
224
|
if self.menu:
|
|
225
225
|
try:
|
|
226
226
|
if menulabel is None: menulabel = _("Label role")
|
|
227
227
|
rolesMenu = Menu(self.viewFrame, tearoff=0)
|
|
228
228
|
self.menu.add_cascade(label=menulabel, menu=rolesMenu, underline=0)
|
|
229
|
-
|
|
230
|
-
|
|
229
|
+
if usedLabelroles is None: # provided for OIM taxonomy
|
|
230
|
+
from arelle.ModelRelationshipSet import labelroles
|
|
231
|
+
usedLabelroles = labelroles(self.modelXbrl, includeConceptName) # arelle infrastructure
|
|
232
|
+
for x in usedLabelroles:
|
|
231
233
|
rolesMenu.add_command(label=x[0][1:], underline=0, command=lambda a=x[1]: self.setLabelrole(a))
|
|
232
234
|
except Exception as ex: # tkinter menu problem maybe
|
|
233
235
|
self.modelXbrl.info("arelle:internalException",
|
|
@@ -263,7 +265,7 @@ class ViewTree:
|
|
|
263
265
|
modelObject=self.modelXbrl.modelDocument, title=self.tabTitle, error=str(ex))
|
|
264
266
|
self.menu = None
|
|
265
267
|
|
|
266
|
-
def menuAddViews(self, addClose=True, tabWin=None):
|
|
268
|
+
def menuAddViews(self, addClose=True, tabWin=None, additionalViews=None, additionalViewMethod=None):
|
|
267
269
|
if self.menu:
|
|
268
270
|
try:
|
|
269
271
|
if tabWin is None: tabWin = self.tabWin
|
|
@@ -273,10 +275,14 @@ class ViewTree:
|
|
|
273
275
|
if addClose:
|
|
274
276
|
viewMenu.add_command(label=_("Close"), underline=0, command=self.close)
|
|
275
277
|
viewMenu.add_cascade(label=_("Additional view"), menu=newViewsMenu, underline=0)
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
278
|
+
if not additionalViews: # 2.1 view
|
|
279
|
+
newViewsMenu.add_command(label=_("Arcrole group..."), underline=0, command=lambda: self.newArcroleGroupView(tabWin))
|
|
280
|
+
from arelle.ModelRelationshipSet import baseSetArcroles
|
|
281
|
+
for x in baseSetArcroles(self.modelXbrl) + [( " Role Types","!CustomRoleTypes!"), (" Arcrole Types", "!CustomArcroleTypes!")]:
|
|
282
|
+
newViewsMenu.add_command(label=x[0][1:], underline=0, command=lambda a=x[1]: self.newView(a, tabWin))
|
|
283
|
+
else:
|
|
284
|
+
for viewParams in additionalViews:
|
|
285
|
+
newViewsMenu.add_command(label=viewParams[2], underline=0, command=lambda a=additionalViewMethod, b=viewParams, c=additionalViews: a(self.modelXbrl, *b, additionalViews=c))
|
|
280
286
|
except Exception as ex: # tkinter menu problem maybe
|
|
281
287
|
self.modelXbrl.info("arelle:internalException",
|
|
282
288
|
_("Exception creating context add-views menu in %(title)s: %(error)s"),
|
|
@@ -324,7 +330,7 @@ class ViewTree:
|
|
|
324
330
|
self.sortImages = (PhotoImage(file=os.path.join(self.modelXbrl.modelManager.cntlr.imagesDir, "columnSortUp.gif")),
|
|
325
331
|
PhotoImage(file=os.path.join(self.modelXbrl.modelManager.cntlr.imagesDir, "columnSortDown.gif")),
|
|
326
332
|
PhotoImage())
|
|
327
|
-
for col in ("#0",) + self.treeView["columns"]:
|
|
333
|
+
for col in ("#0",) + (self.treeView["columns"] or () ):
|
|
328
334
|
self.treeView.heading(col, command=lambda c=col: self.sortColumn(c))
|
|
329
335
|
if not startUnsorted:
|
|
330
336
|
self.treeView.heading(initialSortCol, image=self.sortImages[not initialSortDirForward])
|
arelle/_version.py
CHANGED
arelle/formula/XPathContext.py
CHANGED
|
@@ -351,8 +351,8 @@ class XPathContext:
|
|
|
351
351
|
raise XPathException(p, 'err:XPST0017', _('Function named {0} does not have a custom or built-in implementation.').format(op))
|
|
352
352
|
elif op in VALUE_OPS:
|
|
353
353
|
# binary arithmetic operations and value comparisons
|
|
354
|
-
s1 = self.atomize(p, resultStack.pop()) if len(resultStack) > 0 else []
|
|
355
|
-
s2 = self.atomize(p, self.evaluate(p.args, contextItem=contextItem))
|
|
354
|
+
s1: Sequence[ContextItem] = self.atomize(p, resultStack.pop()) if len(resultStack) > 0 else []
|
|
355
|
+
s2: Sequence[ContextItem] = self.atomize(p, self.evaluate(p.args, contextItem=contextItem))
|
|
356
356
|
# value comparisons
|
|
357
357
|
if len(s1) > 1 or len(s2) > 1:
|
|
358
358
|
raise XPathException(p, 'err:XPTY0004', _("Value operation '{0}' sequence length error").format(op))
|
|
@@ -383,35 +383,35 @@ class XPathContext:
|
|
|
383
383
|
elif isinstance(op2, Decimal) and isinstance(op1, float):
|
|
384
384
|
op2 = float(op2)
|
|
385
385
|
if op == '+':
|
|
386
|
-
result = op1 + op2
|
|
386
|
+
result = op1 + op2 # type: ignore[assignment,operator]
|
|
387
387
|
elif op == '-':
|
|
388
|
-
result = op1 - op2
|
|
388
|
+
result = op1 - op2 # type: ignore[assignment,operator]
|
|
389
389
|
elif op == '*':
|
|
390
|
-
result = op1 * op2
|
|
390
|
+
result = op1 * op2 # type: ignore[assignment,operator]
|
|
391
391
|
elif op in ('div', 'idiv', "mod"):
|
|
392
392
|
try:
|
|
393
393
|
if op == 'div':
|
|
394
|
-
result = op1 / op2
|
|
394
|
+
result = op1 / op2 # type: ignore[assignment,operator]
|
|
395
395
|
elif op == 'idiv':
|
|
396
|
-
result = op1 // op2
|
|
396
|
+
result = op1 // op2 # type: ignore[assignment,operator]
|
|
397
397
|
elif op == 'mod':
|
|
398
|
-
result = op1 % op2
|
|
398
|
+
result = op1 % op2 # type: ignore[assignment,operator]
|
|
399
399
|
except ZeroDivisionError:
|
|
400
400
|
raise XPathException(p, 'err:FOAR0001', _('Attempt to divide by zero: {0} {1} {2}.').format(op1, op, op2))
|
|
401
401
|
elif op == 'ge':
|
|
402
|
-
result = op1 >= op2
|
|
402
|
+
result = op1 >= op2 # type: ignore[operator]
|
|
403
403
|
elif op == 'gt':
|
|
404
|
-
result = op1 > op2
|
|
404
|
+
result = op1 > op2 # type: ignore[operator]
|
|
405
405
|
elif op == 'le':
|
|
406
|
-
result = op1 <= op2
|
|
406
|
+
result = op1 <= op2 # type: ignore[operator]
|
|
407
407
|
elif op == 'lt':
|
|
408
|
-
result = op1 < op2
|
|
408
|
+
result = op1 < op2 # type: ignore[operator]
|
|
409
409
|
elif op == 'eq':
|
|
410
410
|
result = op1 == op2
|
|
411
411
|
elif op == 'ne':
|
|
412
412
|
result = op1 != op2
|
|
413
413
|
elif op == 'to':
|
|
414
|
-
result = range(int(op1), int(op2) + 1)
|
|
414
|
+
result = range(int(op1), int(op2) + 1) # type: ignore[arg-type]
|
|
415
415
|
elif op in GENERALCOMPARISON_OPS:
|
|
416
416
|
# general comparisons
|
|
417
417
|
s1 = self.atomize(p, resultStack.pop()) if len(resultStack) > 0 else []
|
|
@@ -440,23 +440,23 @@ class XPathContext:
|
|
|
440
440
|
elif op in NODECOMPARISON_OPS:
|
|
441
441
|
# node comparisons
|
|
442
442
|
s1 = resultStack.pop() if len(resultStack) > 0 else []
|
|
443
|
-
|
|
444
|
-
if len(s1) > 1 or len(
|
|
443
|
+
_s2 = self.evaluate(p.args, contextItem=contextItem)
|
|
444
|
+
if len(s1) > 1 or len(_s2) > 1 or not self.isNodeSequence(s1) or not self.isNodeSequence(_s2[0]):
|
|
445
445
|
raise XPathException(p, 'err:XPTY0004', _('Node comparison sequence error'))
|
|
446
|
-
if len(s1) == 0 or len(
|
|
446
|
+
if len(s1) == 0 or len(_s2[0]) == 0:
|
|
447
447
|
result = []
|
|
448
448
|
else:
|
|
449
449
|
n1 = s1[0]
|
|
450
|
-
n2 =
|
|
450
|
+
n2 = _s2[0][0]
|
|
451
451
|
result = False
|
|
452
452
|
for op1 in s1:
|
|
453
|
-
for
|
|
453
|
+
for _op2 in _s2:
|
|
454
454
|
if op == 'is':
|
|
455
455
|
result = n1 == n2
|
|
456
456
|
elif op == '>>':
|
|
457
|
-
result = op1 >
|
|
457
|
+
result = op1 > _op2 # type: ignore[operator]
|
|
458
458
|
elif op == '<<':
|
|
459
|
-
result = op1 <=
|
|
459
|
+
result = op1 <= _op2 # type: ignore[operator]
|
|
460
460
|
if result:
|
|
461
461
|
break
|
|
462
462
|
elif op in COMBINING_OPS:
|
|
@@ -503,7 +503,7 @@ class XPathContext:
|
|
|
503
503
|
if op == 'u+':
|
|
504
504
|
result = op1
|
|
505
505
|
elif op == 'u-':
|
|
506
|
-
result = -op1
|
|
506
|
+
result = -op1 # type: ignore[assignment,operator]
|
|
507
507
|
elif op == 'instance':
|
|
508
508
|
result = False
|
|
509
509
|
s1 = self.flattenSequence(resultStack.pop()) if len(resultStack) > 0 else []
|
|
@@ -671,7 +671,7 @@ class XPathContext:
|
|
|
671
671
|
if hasPrevValue:
|
|
672
672
|
prevValue = self.inScopeVars[rvQname]
|
|
673
673
|
for rv in r:
|
|
674
|
-
self.inScopeVars[rvQname] = rv
|
|
674
|
+
self.inScopeVars[rvQname] = rv
|
|
675
675
|
self.evaluateRangeVars(op, args[0], args[1:], contextItem, result)
|
|
676
676
|
if op != 'for' and len(result) > 0:
|
|
677
677
|
break # short circuit evaluation
|
arelle/formula/XPathParser.py
CHANGED
|
@@ -9,7 +9,7 @@ import time
|
|
|
9
9
|
import traceback
|
|
10
10
|
from collections.abc import Iterable, Sequence
|
|
11
11
|
from decimal import Decimal
|
|
12
|
-
from typing import Any, TYPE_CHECKING, Union
|
|
12
|
+
from typing import Any, TYPE_CHECKING, Union, cast
|
|
13
13
|
from xml.dom import minidom
|
|
14
14
|
|
|
15
15
|
from pyparsing import (
|
|
@@ -954,7 +954,7 @@ def staticExpressionFunctionContext() -> minidom.Element:
|
|
|
954
954
|
' xmlns:fn="http://www.w3.org/2005/xpath-functions"'
|
|
955
955
|
'/>'
|
|
956
956
|
).documentElement
|
|
957
|
-
return _staticExpressionFunctionContext
|
|
957
|
+
return cast(minidom.Element, _staticExpressionFunctionContext)
|
|
958
958
|
|
|
959
959
|
|
|
960
960
|
def parse(
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""
|
|
2
|
+
See COPYRIGHT.md for copyright information.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Optional, Set, Any
|
|
6
|
+
|
|
7
|
+
from arelle.ModelValue import QName
|
|
8
|
+
|
|
9
|
+
class QNameAt(QName):
|
|
10
|
+
def __init__(self, prefix: Optional[str], namespaceURI: Optional[str], localName: str, atSuffix:str = "end") -> None:
|
|
11
|
+
super(QNameAt, self).__init__(prefix, namespaceURI, localName)
|
|
12
|
+
self.atSuffix: str = atSuffix # The context suffix must be either @end or @start. If an @ value is not provided then the suffix defaults to @end.
|
|
13
|
+
|
|
14
|
+
class SQName(QName):
|
|
15
|
+
pass # no properties added to QName, just the localName part doesn't have to be an xml identifier
|