arelle-release 2.37.12__py3-none-any.whl → 2.37.14__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.

Files changed (40) hide show
  1. arelle/CntlrCmdLine.py +1 -66
  2. arelle/ModelDtsObject.py +1 -1
  3. arelle/ModelInstanceObject.py +9 -0
  4. arelle/ModelTestcaseObject.py +8 -1
  5. arelle/Validate.py +58 -23
  6. arelle/ValidateDuplicateFacts.py +4 -10
  7. arelle/_version.py +2 -2
  8. arelle/formula/XPathParser.py +9 -3
  9. arelle/packages/report/ReportPackage.py +7 -3
  10. arelle/packages/report/ReportPackageValidator.py +4 -3
  11. arelle/plugin/inlineXbrlDocumentSet.py +1 -1
  12. arelle/plugin/validate/DBA/PluginValidationDataExtension.py +9 -1
  13. arelle/plugin/validate/DBA/ValidationPluginExtension.py +4 -2
  14. arelle/plugin/validate/DBA/rules/fr.py +30 -32
  15. arelle/plugin/validate/DBA/rules/tm.py +22 -12
  16. arelle/plugin/validate/ESEF/ESEF_2021/ValidateXbrlFinally.py +3 -3
  17. arelle/plugin/validate/ESEF/ESEF_Current/ValidateXbrlFinally.py +4 -5
  18. arelle/plugin/validate/NL/PluginValidationDataExtension.py +163 -32
  19. arelle/plugin/validate/NL/__init__.py +0 -11
  20. arelle/plugin/validate/NL/rules/nl_kvk.py +322 -12
  21. arelle/plugin/validate/ROS/rules/ros.py +1 -1
  22. arelle/typing.py +8 -1
  23. arelle/utils/EntryPointDetection.py +73 -0
  24. {arelle_release-2.37.12.dist-info → arelle_release-2.37.14.dist-info}/METADATA +1 -1
  25. {arelle_release-2.37.12.dist-info → arelle_release-2.37.14.dist-info}/RECORD +40 -38
  26. {arelle_release-2.37.12.dist-info → arelle_release-2.37.14.dist-info}/WHEEL +1 -1
  27. tests/integration_tests/ui_tests/ArelleGUITest/ArelleGUITest/ArelleGUITest.csproj +1 -1
  28. tests/integration_tests/validation/conformance_suite_configurations/nl_inline_2024.py +14 -21
  29. tests/integration_tests/validation/conformance_suite_configurations/xbrl_report_packages_1_0.py +3 -1
  30. tests/resources/conformance_suites/dba/fr/fr7-invalid.xhtml +1 -1
  31. tests/resources/conformance_suites/dba/fr/fr83-invalid.xbrl +1 -1
  32. tests/resources/conformance_suites/dba/fr/fr89-testcase.xml +10 -0
  33. tests/resources/conformance_suites/dba/fr/fr89-valid.xhtml +6816 -0
  34. tests/resources/conformance_suites/dba/fr/fr91-invalid.xhtml +1 -2
  35. tests/resources/conformance_suites/dba/tm/tm29-invalid.xhtml +0 -1
  36. tests/resources/conformance_suites/dba/tm/tm31-invalid.xhtml +10 -1
  37. tests/resources/conformance_suites/dba/tr/tr06-invalid.xhtml +1 -1
  38. {arelle_release-2.37.12.dist-info → arelle_release-2.37.14.dist-info}/entry_points.txt +0 -0
  39. {arelle_release-2.37.12.dist-info → arelle_release-2.37.14.dist-info}/licenses/LICENSE.md +0 -0
  40. {arelle_release-2.37.12.dist-info → arelle_release-2.37.14.dist-info}/top_level.txt +0 -0
@@ -228,12 +228,12 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
228
228
  _baseName, _baseExt = os.path.splitext(doc.basename)
229
229
  if _baseExt not in (".xhtml",".html"):
230
230
  if val.consolidated:
231
- XHTMLExtensionGuidance = "2.6.1"
231
+ errorCode = "ESEF.2.6.1.incorrectFileExtension"
232
232
  reportType = _("Inline XBRL document included within a ESEF report package")
233
233
  else:
234
- XHTMLExtensionGuidance = "4.1.1"
234
+ errorCode = "ESEF.4.1.1.incorrectFileExtension"
235
235
  reportType = _("Stand-alone XHTML document")
236
- modelXbrl.error(f"ESEF.{XHTMLExtensionGuidance}.incorrectFileExtension",
236
+ modelXbrl.error(errorCode,
237
237
  _("%(reportType)s MUST have a .html or .xhtml extension: %(fileName)s"),
238
238
  modelObject=doc, fileName=doc.basename, reportType=reportType)
239
239
  docinfo = doc.xmlRootElement.getroottree().docinfo
@@ -664,8 +664,7 @@ def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None:
664
664
  if esefDisclosureSystemYear >= 2024:
665
665
  if not f.id:
666
666
  factsMissingId.append(f)
667
- escaped = f.get("escape") in ("true", "1")
668
- if f.concept is not None and escaped != f.concept.isTextBlock:
667
+ if isinstance(f, ModelInlineFact) and f.concept is not None and f.isEscaped != f.concept.isTextBlock:
669
668
  modelXbrl.error("ESEF.2.2.7.improperApplicationOfEscapeAttribute",
670
669
  _("Facts with datatype 'dtr-types:textBlockItemType' MUST use the 'escape' attribute set to 'true'. Facts with any other datatype MUST use the 'escape' attribute set to 'false' - fact %(conceptName)s"),
671
670
  modelObject=f, conceptName=f.concept.qname)
@@ -6,18 +6,23 @@ from __future__ import annotations
6
6
  from collections import defaultdict
7
7
  from dataclasses import dataclass
8
8
  from functools import lru_cache
9
- from typing import Any, cast
9
+ from pathlib import Path
10
+ from typing import Any, TYPE_CHECKING, cast
10
11
 
11
12
  import regex as re
12
13
  from lxml.etree import _Comment, _ElementTree, _Entity, _ProcessingInstruction
13
14
 
14
15
  from arelle.FunctionIxt import ixtNamespaces
15
- from arelle.ModelInstanceObject import ModelContext, ModelFact, ModelInlineFootnote, ModelUnit
16
+ from arelle.ModelInstanceObject import ModelContext, ModelFact, ModelInlineFootnote, ModelUnit, ModelInlineFact
17
+ from arelle.ModelObject import ModelObject
16
18
  from arelle.ModelValue import QName
17
19
  from arelle.ModelXbrl import ModelXbrl
20
+ from arelle.typing import assert_type
18
21
  from arelle.utils.PluginData import PluginData
19
22
  from arelle.utils.validate.ValidationUtil import etreeIterWithDepth
23
+ from arelle.XbrlConst import ixbrl11
20
24
  from arelle.XmlValidate import lexicalPatterns
25
+ from arelle.XmlValidateConst import VALID
21
26
 
22
27
  XBRLI_IDENTIFIER_PATTERN = re.compile(r"^(?!00)\d{8}$")
23
28
  XBRLI_IDENTIFIER_SCHEMA = 'http://www.kvk.nl/kvk-id'
@@ -27,6 +32,25 @@ DISALLOWED_IXT_NAMESPACES = frozenset((
27
32
  ixtNamespaces["ixt v2"],
28
33
  ixtNamespaces["ixt v3"],
29
34
  ))
35
+ UNTRANSFORMABLE_TYPES = frozenset((
36
+ "anyURI",
37
+ "base64Binary",
38
+ "duration",
39
+ "hexBinary",
40
+ "NOTATION",
41
+ "QName",
42
+ "time",
43
+ "token",
44
+ "language",
45
+ ))
46
+ STYLE_IX_HIDDEN_PATTERN = re.compile(r"(.*[^\w]|^)ix-hidden\s*:\s*([\w.-]+).*")
47
+
48
+ ALLOWABLE_LANGUAGES = frozenset((
49
+ 'nl',
50
+ 'en',
51
+ 'de',
52
+ 'fr'
53
+ ))
30
54
 
31
55
  @dataclass(frozen=True)
32
56
  class ContextData:
@@ -36,10 +60,18 @@ class ContextData:
36
60
  contextsWithSegments: list[ModelContext | None]
37
61
 
38
62
  @dataclass(frozen=True)
39
- class FootnoteData:
63
+ class HiddenElementsData:
64
+ eligibleForTransformHiddenFacts: set[ModelInlineFact]
65
+ hiddenFactsOutsideHiddenSection: set[ModelInlineFact]
66
+ requiredToDisplayFacts: set[ModelInlineFact]
67
+
68
+ @dataclass(frozen=True)
69
+ class InlineHTMLData:
40
70
  noMatchLangFootnotes: set[ModelInlineFootnote]
41
71
  orphanedFootnotes: set[ModelInlineFootnote]
72
+ tupleElements: set[tuple[Any]]
42
73
  factLangFootnotes: dict[ModelInlineFootnote, set[str]]
74
+ fractionElements: set[Any]
43
75
 
44
76
  @dataclass
45
77
  class PluginValidationDataExtension(PluginData):
@@ -101,29 +133,83 @@ class PluginValidationDataExtension(PluginData):
101
133
  )
102
134
 
103
135
  @lru_cache(1)
104
- def checkFootnotes(self, modelXbrl: ModelXbrl) -> FootnoteData:
136
+ def checkHiddenElements(self, modelXbrl: ModelXbrl) -> HiddenElementsData:
137
+ eligibleForTransformHiddenFacts = set()
138
+ hiddenEltIds = {}
139
+ hiddenFactsOutsideHiddenSection = set()
140
+ presentedHiddenEltIds = defaultdict(list)
141
+ requiredToDisplayFacts = set()
142
+ for ixdsHtmlRootElt in modelXbrl.ixdsHtmlElements:
143
+ ixNStag = getattr(ixdsHtmlRootElt.modelDocument, "ixNStag", ixbrl11)
144
+ for ixHiddenElt in ixdsHtmlRootElt.iterdescendants(tag=ixNStag + "hidden"):
145
+ for tag in (ixNStag + "nonNumeric", ixNStag+"nonFraction"):
146
+ for ixElt in ixHiddenElt.iterdescendants(tag=tag):
147
+ if getattr(ixElt, "xValid", 0) >= VALID:
148
+ if ixElt.concept.baseXsdType not in UNTRANSFORMABLE_TYPES and not ixElt.isNil:
149
+ eligibleForTransformHiddenFacts.add(ixElt)
150
+ if ixElt.id:
151
+ hiddenEltIds[ixElt.id] = ixElt
152
+ for ixdsHtmlRootElt in modelXbrl.ixdsHtmlElements:
153
+ for ixElt in ixdsHtmlRootElt.getroottree().iterfind(".//{http://www.w3.org/1999/xhtml}*[@style]"):
154
+ styleValue = ixElt.get("style","")
155
+ hiddenFactRefMatch = STYLE_IX_HIDDEN_PATTERN.match(styleValue)
156
+ if hiddenFactRefMatch:
157
+ hiddenFactRef = hiddenFactRefMatch.group(2)
158
+ if hiddenFactRef not in hiddenEltIds:
159
+ hiddenFactsOutsideHiddenSection.add(ixElt)
160
+ else:
161
+ presentedHiddenEltIds[hiddenFactRef].append(ixElt)
162
+ for hiddenEltId, ixElt in hiddenEltIds.items():
163
+ if (hiddenEltId not in presentedHiddenEltIds and
164
+ getattr(ixElt, "xValid", 0) >= VALID and # may not be validated
165
+ (ixElt.concept.baseXsdType in UNTRANSFORMABLE_TYPES or ixElt.isNil)):
166
+ requiredToDisplayFacts.add(ixElt)
167
+ return HiddenElementsData(
168
+ eligibleForTransformHiddenFacts=eligibleForTransformHiddenFacts,
169
+ hiddenFactsOutsideHiddenSection=hiddenFactsOutsideHiddenSection,
170
+ requiredToDisplayFacts=requiredToDisplayFacts,
171
+ )
172
+
173
+ @lru_cache(1)
174
+ def checkInlineHTMLElements(self, modelXbrl: ModelXbrl) -> InlineHTMLData:
105
175
  factLangs = self.factLangs(modelXbrl)
106
176
  footnotesRelationshipSet = modelXbrl.relationshipSet("XBRL-footnotes")
107
- orphanedFootnotes = set()
108
- noMatchLangFootnotes = set()
109
177
  factLangFootnotes = defaultdict(set)
110
- for elts in modelXbrl.ixdsEltById.values(): # type: ignore[attr-defined]
111
- for elt in elts:
112
- if isinstance(elt, ModelInlineFootnote):
113
- if elt.textValue is not None:
114
- if not any(isinstance(rel.fromModelObject, ModelFact)
115
- for rel in footnotesRelationshipSet.toModelObject(elt)):
116
- orphanedFootnotes.add(elt)
117
- if elt.xmlLang not in factLangs:
118
- noMatchLangFootnotes.add(elt)
119
- for rel in footnotesRelationshipSet.toModelObject(elt):
120
- if rel.fromModelObject is not None:
121
- factLangFootnotes[rel.fromModelObject].add(elt.xmlLang)
178
+ fractionElements = set()
179
+ noMatchLangFootnotes = set()
180
+ tupleElements = set()
181
+ orphanedFootnotes = set()
182
+ for ixdsHtmlRootElt in modelXbrl.ixdsHtmlElements:
183
+ ixNStag = getattr(ixdsHtmlRootElt.modelDocument, "ixNStag", ixbrl11)
184
+ ixTupleTag = ixNStag + "tuple"
185
+ ixFractionTag = ixNStag + "fraction"
186
+ for elts in modelXbrl.ixdsEltById.values(): # type: ignore[attr-defined]
187
+ for elt in elts:
188
+ if isinstance(elt, ModelInlineFootnote):
189
+ if elt.textValue is not None:
190
+ if not any(isinstance(rel.fromModelObject, ModelFact)
191
+ for rel in footnotesRelationshipSet.toModelObject(elt)):
192
+ orphanedFootnotes.add(elt)
193
+ if elt.xmlLang not in factLangs:
194
+ noMatchLangFootnotes.add(elt)
195
+ if elt.xmlLang is not None:
196
+ for rel in footnotesRelationshipSet.toModelObject(elt):
197
+ if rel.fromModelObject is not None:
198
+ fromObj = cast(ModelObject, rel.fromModelObject)
199
+ lang = cast(str, elt.xmlLang)
200
+ factLangFootnotes[fromObj].add(lang)
201
+ if elt.tag == ixTupleTag:
202
+ tupleElements.add(elt)
203
+ if elt.tag == ixFractionTag:
204
+ fractionElements.add(elt)
122
205
  factLangFootnotes.default_factory = None
123
- return FootnoteData(
206
+ assert_type(factLangFootnotes, defaultdict[ModelObject, set[str]])
207
+ return InlineHTMLData(
208
+ factLangFootnotes=cast(dict[ModelInlineFootnote, set[str]], factLangFootnotes),
209
+ fractionElements=fractionElements,
124
210
  noMatchLangFootnotes=noMatchLangFootnotes,
125
211
  orphanedFootnotes=orphanedFootnotes,
126
- factLangFootnotes=dict(factLangFootnotes),
212
+ tupleElements=tupleElements,
127
213
  )
128
214
 
129
215
  @lru_cache(1)
@@ -158,33 +244,78 @@ class PluginValidationDataExtension(PluginData):
158
244
  def getContextsWithSegments(self, modelXbrl: ModelXbrl) -> list[ModelContext | None]:
159
245
  return self.checkContexts(modelXbrl).contextsWithSegments
160
246
 
247
+ def getEligibleForTransformHiddenFacts(self, modelXbrl: ModelXbrl) -> set[ModelInlineFact]:
248
+ return self.checkHiddenElements(modelXbrl).eligibleForTransformHiddenFacts
249
+
250
+ def getFactLangFootnotes(self, modelXbrl: ModelXbrl) -> dict[ModelInlineFootnote, set[str]]:
251
+ return self.checkInlineHTMLElements(modelXbrl).factLangFootnotes
252
+
253
+ def getFractionElements(self, modelXbrl: ModelXbrl) -> set[Any]:
254
+ return self.checkInlineHTMLElements(modelXbrl).fractionElements
255
+
256
+ def getHiddenFactsOutsideHiddenSection(self, modelXbrl: ModelXbrl) -> set[ModelInlineFact]:
257
+ return self.checkHiddenElements(modelXbrl).hiddenFactsOutsideHiddenSection
258
+
259
+ @lru_cache(1)
260
+ def getFilenameAllowedCharactersPattern(self) -> re.Pattern[str]:
261
+ return re.compile(
262
+ r"^[\w\.-]*$",
263
+ flags=re.ASCII
264
+ )
265
+
266
+ @lru_cache(1)
267
+ def getFilenameFormatPattern(self) -> re.Pattern[str]:
268
+ return re.compile(
269
+ r"^(?<base>[^-]*)"
270
+ r"-(?<year>\d{4})-(?<month>0[1-9]|1[012])-(?<day>0?[1-9]|[12][0-9]|3[01])"
271
+ r"-(?<lang>[^-]*)"
272
+ r"\.(?<extension>html|htm|xhtml)$",
273
+ flags=re.ASCII
274
+ )
275
+
276
+ @lru_cache(1)
277
+ def getFilenameParts(self, filename: str) -> dict[str, Any] | None:
278
+ match = self.getFilenameFormatPattern().match(filename)
279
+ if match:
280
+ return match.groupdict()
281
+ return None
282
+
283
+ @lru_cache(1)
284
+ def getIxdsDocBasenames(self, modelXbrl: ModelXbrl) -> set[str]:
285
+ return set(Path(url).name for url in modelXbrl.ixdsDocUrls)
286
+
161
287
  def getNoMatchLangFootnotes(self, modelXbrl: ModelXbrl) -> set[ModelInlineFootnote]:
162
- return self.checkFootnotes(modelXbrl).noMatchLangFootnotes
288
+ return self.checkInlineHTMLElements(modelXbrl).noMatchLangFootnotes
163
289
 
164
290
  def getOrphanedFootnotes(self, modelXbrl: ModelXbrl) -> set[ModelInlineFootnote]:
165
- return self.checkFootnotes(modelXbrl).orphanedFootnotes
291
+ return self.checkInlineHTMLElements(modelXbrl).orphanedFootnotes
166
292
 
167
- def getFactLangFootnotes(self, modelXbrl: ModelXbrl) -> dict[ModelInlineFootnote, set[str]]:
168
- return self.checkFootnotes(modelXbrl).factLangFootnotes
293
+ def getRequiredToDisplayFacts(self, modelXbrl: ModelXbrl) -> set[ModelInlineFact]:
294
+ return self.checkHiddenElements(modelXbrl).requiredToDisplayFacts
295
+
296
+ def getTupleElements(self, modelXbrl: ModelXbrl) -> set[tuple[Any]]:
297
+ return self.checkInlineHTMLElements(modelXbrl).tupleElements
169
298
 
170
299
  @lru_cache(1)
171
300
  def getReportXmlLang(self, modelXbrl: ModelXbrl) -> str | None:
172
- firstIxdsDoc = True
173
301
  reportXmlLang = None
174
302
  firstRootmostXmlLangDepth = 9999999
175
- for ixdsHtmlRootElt in modelXbrl.ixdsHtmlElements:
176
- for uncast_elt, depth in etreeIterWithDepth(ixdsHtmlRootElt):
177
- elt = cast(Any, uncast_elt)
303
+ if modelXbrl.ixdsHtmlElements:
304
+ ixdsHtmlRootElt = modelXbrl.ixdsHtmlElements[0]
305
+ for elt, depth in etreeIterWithDepth(ixdsHtmlRootElt):
178
306
  if isinstance(elt, (_Comment, _ElementTree, _Entity, _ProcessingInstruction)):
179
307
  continue
180
- if firstIxdsDoc and (not reportXmlLang or depth < firstRootmostXmlLangDepth):
181
- xmlLang = elt.get("{http://www.w3.org/XML/1998/namespace}lang")
182
- if xmlLang:
308
+ if not reportXmlLang or depth < firstRootmostXmlLangDepth:
309
+ if xmlLang := elt.get("{http://www.w3.org/XML/1998/namespace}lang"):
183
310
  reportXmlLang = xmlLang
184
311
  firstRootmostXmlLangDepth = depth
185
- firstIxdsDoc = False
186
312
  return reportXmlLang
187
313
 
314
+ @lru_cache(1)
315
+ def isFilenameValidCharacters(self, filename: str) -> bool:
316
+ match = self.getFilenameAllowedCharactersPattern().match(filename)
317
+ return match is not None
318
+
188
319
  @lru_cache(1)
189
320
  def unitsByDocument(self, modelXbrl: ModelXbrl) -> dict[str, list[ModelUnit]]:
190
321
  unitsByDocument = defaultdict(list)
@@ -48,16 +48,6 @@ def validateXbrlFinally(*args: Any, **kwargs: Any) -> None:
48
48
  return validationPlugin.validateXbrlFinally(*args, **kwargs)
49
49
 
50
50
 
51
- def modelTestcaseVariationReportPackageIxdsOptions(
52
- val: ValidateXbrl,
53
- rptPkgIxdsOptions: dict[str, bool],
54
- *args: Any,
55
- **kwargs: Any,
56
- ) -> None:
57
- rptPkgIxdsOptions["lookOutsideReportsDirectory"] = True
58
- rptPkgIxdsOptions["combineIntoSingleIxds"] = True
59
-
60
-
61
51
  __pluginInfo__ = {
62
52
  "name": PLUGIN_NAME,
63
53
  "version": "0.0.1",
@@ -70,5 +60,4 @@ __pluginInfo__ = {
70
60
  "DisclosureSystem.ConfigURL": disclosureSystemConfigURL,
71
61
  "ModelXbrl.LoadComplete": modelXbrlLoadComplete,
72
62
  "Validate.XBRL.Finally": validateXbrlFinally,
73
- "ModelTestcaseVariation.ReportPackageIxdsOptions": modelTestcaseVariationReportPackageIxdsOptions,
74
63
  }