Skip to content

Commit

Permalink
Merge pull request #892 from austinmatherne-wk/XT-3107
Browse files Browse the repository at this point in the history
Merge ESEF and ESEF 2022 Plugins
  • Loading branch information
derekgengenbacher-wf committed Oct 6, 2023
2 parents a6aa9b0 + 376a75a commit b07cf0c
Show file tree
Hide file tree
Showing 26 changed files with 2,901 additions and 3,816 deletions.
1 change: 0 additions & 1 deletion arelle/ValidateXbrl.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ class ValidateXbrl:
ixdsRoleRefURIs: dict[Any, Any]
ixdsArcroleRefURIs: dict[Any, Any]
unconsolidated: bool
validateESEFplugin: bool
priorFormulaOptionsRunIDs: str | None
primaryItems: set[Any]
remoteResourceLocElements: set[ModelObject]
Expand Down
137 changes: 101 additions & 36 deletions arelle/plugin/validate/ESEF/Const.py
Original file line number Diff line number Diff line change
@@ -1,68 +1,129 @@
'''
Filer Guidelines: esma32-60-254_esef_reporting_manual.pdf
"""
See COPYRIGHT.md for copyright information.
'''
"""
from __future__ import annotations

import regex as re
from typing import Any, Callable

from arelle import XbrlConst
from arelle.FunctionIxt import ixtNamespaces
from arelle.ModelValue import QName, qname
from arelle.XbrlConst import all, notAll, hypercubeDimension, dimensionDomain, domainMember, dimensionDefault, widerNarrower
from arelle.XmlValidate import lexicalPatterns

styleIxHiddenPattern = re.compile(r"(.*[^\w]|^)-esef-ix-hidden\s*:\s*([\w.-]+).*")
styleCssHiddenPattern = re.compile(r"(.*[^\w]|^)display\s*:\s*none([^\w].*|$)")
datetimePattern = lexicalPatterns["XBRLI_DATEUNION"]
docTypeXhtmlPattern = re.compile(r"^<!(?:DOCTYPE\s+)\s*html(?:PUBLIC\s+)?(?:.*-//W3C//DTD\s+(X?HTML)\s)?.*>$", re.IGNORECASE)

FOOTNOTE_LINK_CHILDREN = frozenset((
XbrlConst.qnLinkLoc,
XbrlConst.qnLinkFootnoteArc,
XbrlConst.qnLinkFootnote,
XbrlConst.qnIXbrl11Footnote,
))

PERCENT_TYPE = qname("{http://www.xbrl.org/dtr/type/numeric}num:percentItemType")

IXT_NAMESPACES = frozenset((
ixtNamespaces["ixt v4"], # only tr4 or newer REC is currently recommended
ixtNamespaces["ixt v5"],
))

browserMaxBase64ImageLength = 5242880 # 5MB

browserMaxBase64ImageLength = 5242880 # 5MB
supportedImgTypes = {
True: ("gif", "jpg", "jpeg", "png"), # file extensions
False: ("gif", "jpeg", "png") # mime types: jpg is not a valid mime type
}

esefTaxonomyNamespaceURIs = {
esefTaxonomyNamespaceURIs2021 = frozenset((
"http://xbrl.ifrs.org/taxonomy/20",
))

esefTaxonomyNamespaceURIs = frozenset((
"http://xbrl.ifrs.org/taxonomy/20",
}
"https://xbrl.ifrs.org/taxonomy/20",
))

disallowedURIsPattern = re.compile(
"http://xbrl.ifrs.org/taxonomy/[0-9-]{10}/full_ifrs/full_ifrs-cor_[0-9-]{10}[.]xsd|"
"http://www.esma.europa.eu/taxonomy/[0-9-]{10}/esef_all.xsd"
)
)

esefCorNsPattern = re.compile(
r"https?://www\.esma\.europa\.eu/taxonomy/[0-9-]{10}/esef_cor"
)

DefaultDimensionLinkroles = ("http://www.esma.europa.eu/xbrl/role/cor/ifrs-dim_role-990000",)
LineItemsNotQualifiedLinkrole = "http://www.esma.europa.eu/xbrl/role/cor/esef_role-999999"
DefaultDimensionLinkroles2021 = (
"http://www.esma.europa.eu/xbrl/role/cor/ifrs-dim_role-990000",
)

qnDomainItemTypes = {qname("{http://www.xbrl.org/dtr/type/non-numeric}nonnum:domainItemType"),
qname("{http://www.xbrl.org/dtr/type/2020-01-21}nonnum:domainItemType")}
DefaultDimensionLinkroles = (
"https://www.esma.europa.eu/xbrl/role/cor/ifrs-dim_role-990000", # preferred, new spec
"http://www.esma.europa.eu/xbrl/role/cor/ifrs-dim_role-990000",
)

LineItemsNotQualifiedLinkroles2021 = (
"http://www.esma.europa.eu/xbrl/role/cor/esef_role-999999",
)

LineItemsNotQualifiedLinkroles = (
"https://www.esma.europa.eu/xbrl/role/cor/esef_role-999999", # preferred, new spec
"http://www.esma.europa.eu/xbrl/role/cor/esef_role-999999",
)

qnDomainItemTypes = frozenset((
qname("{http://www.xbrl.org/dtr/type/non-numeric}nonnum:domainItemType"),
qname("{http://www.xbrl.org/dtr/type/2020-01-21}nonnum:domainItemType"),
))

linkbaseRefTypes = {
"http://www.xbrl.org/2003/role/calculationLinkbaseRef": "cal",
"http://www.xbrl.org/2003/role/definitionLinkbaseRef": "def",
"http://www.xbrl.org/2003/role/labelLinkbaseRef": "lab",
"http://www.xbrl.org/2003/role/presentationLinkbaseRef": "pre",
"http://www.xbrl.org/2003/role/referenceLinkbaseRef": "ref"
}
"http://www.xbrl.org/2003/role/referenceLinkbaseRef": "ref",
}

filenamePatterns = {
"cal": "{base}-{date}_cal.xml",
"def": "{base}-{date}_def.xml",
"lab": "{base}-{date}_lab-{lang}.xml",
"pre": "{base}-{date}_pre.xml",
"ref": "{base}-{date}_ref.xml"
}
"ref": "{base}-{date}_ref.xml",
}

filenameRegexes = {
"cal": r"(.{1,})-[0-9]{4}-[0-9]{2}-[0-9]{2}_cal[.]xml$",
"def": r"(.{1,})-[0-9]{4}-[0-9]{2}-[0-9]{2}_def[.]xml$",
"lab": r"(.{1,})-[0-9]{4}-[0-9]{2}-[0-9]{2}_lab-[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*[.]xml$",
"pre": r"(.{1,})-[0-9]{4}-[0-9]{2}-[0-9]{2}_pre[.]xml$",
"ref": r"(.{1,})-[0-9]{4}-[0-9]{2}-[0-9]{2}_ref[.]xml$"
}
"ref": r"(.{1,})-[0-9]{4}-[0-9]{2}-[0-9]{2}_ref[.]xml$",
}

mandatory: set[QName] = set() # mandatory element qnames
mandatory: set[QName] = set() # mandatory element qnames

# hidden references
untransformableTypes = {"anyURI", "base64Binary", "hexBinary", "NOTATION", "QName", "time",
"token", "language"}
untransformableTypes = frozenset((
"anyURI",
"base64Binary",
"hexBinary",
"NOTATION",
"QName",
"time",
"token",
"language",
))

esefDefinitionArcroles = {
all, notAll, hypercubeDimension, dimensionDomain, domainMember, dimensionDefault,
widerNarrower
}
esefDefinitionArcroles = frozenset((
XbrlConst.all,
XbrlConst.notAll,
XbrlConst.hypercubeDimension,
XbrlConst.dimensionDomain,
XbrlConst.domainMember,
XbrlConst.dimensionDefault,
XbrlConst.widerNarrower,
))

esefPrimaryStatementPlaceholderNames = (
# to be augmented with future IFRS releases as they come known, as well as further PFS placeholders
Expand All @@ -72,16 +133,20 @@
"StatementOfCashFlowsAbstract",
"StatementOfChangesInEquityAbstract",
"StatementOfChangesInNetAssetsAvailableForBenefitsAbstract",
"StatementOfProfitOrLossAndOtherComprehensiveIncomeAbstract"
)
"StatementOfProfitOrLossAndOtherComprehensiveIncomeAbstract",
)

esefStatementsOfMonetaryDeclarationNames = {
esefStatementsOfMonetaryDeclarationNames = frozenset((
# from Annex II para 1
"StatementOfFinancialPositionAbstract",
"StatementOfProfitOrLossAndOtherComprehensiveIncomeAbstract"
"StatementOfChangesInEquityAbstract",
"StatementOfCashFlowsAbstract",
}
))

esefNotesStatementConcepts = frozenset((
"NotesAccountingPoliciesAndMandatoryTags",
))

esefMandatoryElementNames2020 = (
"NameOfReportingEntityOrOtherMeansOfIdentification",
Expand All @@ -93,8 +158,8 @@
"PrincipalPlaceOfBusiness",
"DescriptionOfNatureOfEntitysOperationsAndPrincipalActivities",
"NameOfParentEntity",
"NameOfUltimateParentOfGroup"
)
"NameOfUltimateParentOfGroup",
)

esefMandatoryElementNames2022 = (
"AddressOfRegisteredOfficeOfEntity",
Expand Down Expand Up @@ -342,7 +407,7 @@
"StatementOfIFRSCompliance",
)

htmlEventHandlerAttributes = set((
htmlEventHandlerAttributes = frozenset((
"onabort",
"onafterprint",
"onbeforeprint",
Expand Down Expand Up @@ -416,7 +481,7 @@
"onwheel",
))

svgEventAttributes = set((
svgEventAttributes = frozenset((
"onabort",
"onactivate",
"onafterprint",
Expand Down Expand Up @@ -495,5 +560,5 @@
"onunload",
"onvolumechange",
"onwaiting",
"onzoom"
"onzoom",
))
54 changes: 17 additions & 37 deletions arelle/plugin/validate/ESEF/Dimensions.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
'''
Filer Guidelines: ESMA_ESEF Manula 2019.pdf
"""
See COPYRIGHT.md for copyright information.
'''
"""
from __future__ import annotations

from collections import defaultdict
from typing import Any, List, cast

import regex as re

from arelle import XbrlConst
from arelle.ModelDtsObject import ModelConcept, ModelLink
from arelle.ModelObject import ModelObject
from arelle.PrototypeDtsObject import PrototypeObject
from arelle import XbrlConst
from .Const import LineItemsNotQualifiedLinkrole, DefaultDimensionLinkroles
from .Util import isExtension, isInEsefTaxonomy
import regex as re
from arelle.ValidateXbrl import ValidateXbrl
from arelle.typing import TypeGetText
from .Util import isExtension, isInEsefTaxonomy

_: TypeGetText # Handle gettext


def checkFilingDimensions(val: ValidateXbrl) -> None:
def checkFilingDimensions(
val: ValidateXbrl,
defaultDimensionLinkroles: tuple[str, ...],
lineItemsNotQualifiedLinkroles: tuple[str, ...],
) -> None:

val.primaryItems = set() # concepts which are line items (should not also be dimension members
val.domainMembers = set() # concepts which are dimension domain members
Expand All @@ -32,7 +36,6 @@ def addDomMbrs(sourceDomMbr: ModelConcept, ELR: str, membersSet: set[ModelConcep
if isinstance(sourceDomMbr, ModelConcept) and sourceDomMbr not in membersSet:
membersSet.add(sourceDomMbr)
for domMbrRel in val.modelXbrl.relationshipSet(XbrlConst.domainMember, ELR).fromModelObject(sourceDomMbr):
#if domMbrRel.isUsable:
addDomMbrs(domMbrRel.toModelObject, domMbrRel.consecutiveLinkrole, membersSet)

for hasHypercubeArcrole in (XbrlConst.all, XbrlConst.notAll):
Expand Down Expand Up @@ -73,26 +76,14 @@ def addDomMbrs(sourceDomMbr: ModelConcept, ELR: str, membersSet: set[ModelConcep
if isinstance(dom, ModelConcept):
addDomMbrs(dom, dimDomRel.consecutiveLinkrole, hcMembers)
val.domainMembers.update(hcMembers)
if hasHcRel.linkrole == LineItemsNotQualifiedLinkrole or hcMembers:
if hasHcRel.linkrole in lineItemsNotQualifiedLinkroles or hcMembers:
for hcPrimaryItem in hcPrimaryItems:
if not hcPrimaryItem.isAbstract:
elrPrimaryItems[hasHcRel.linkrole].add(hcPrimaryItem)
elrPrimaryItems["*"].add(hcPrimaryItem) # members of any ELR
hcPrimaryItems.clear()
hcMembers.clear()

# find primary items with other dimensions in
#for ELR, priItems in elrPrimaryItems.items():
# if ELR != LineItemsNotQualifiedLinkrole:
# # consider any pri item in not reported non-dimensionally
# i = set(hcPrimaryItem
# for hcPrimaryItem in (priItems & elrPrimaryItems.get(LineItemsNotQualifiedLinkrole, set()))
# if not any(not f.context.qnameDims for f in val.modelXbrl.factsByQname.get(hcPrimaryItem.qname,())))
# if i:
# val.modelXbrl.warning("ESEF.3.4.2.extensionTaxonomyLineItemIncorrectlyLinkedToNonDimensionallyQualifiedHypercube",
# _("Dimensional line item SHOULD NOT also be linked to \"not dimensionally qualified\" hypercube from %(linkrole)s, primary item %(qnames)s"),
# modelObject=i, linkrole=ELR, qnames=", ".join(sorted(str(c.qname) for c in i)))

# reported pri items not in LineItemsNotQualifiedLinkrole
nsExcl = val.authParam.get("lineItemsNotDimQualExclusionNsPattern")
if nsExcl:
Expand All @@ -102,24 +93,13 @@ def addDomMbrs(sourceDomMbr: ModelConcept, ELR: str, membersSet: set[ModelConcep
if any(not f.context.qnameDims for f in facts if f.context is not None)
for concept in (val.modelXbrl.qnameConcepts.get(qn),)
if concept is not None and
concept not in elrPrimaryItems.get(LineItemsNotQualifiedLinkrole, set()) and
not any(concept in elrPrimaryItems.get(lr, set()) for lr in lineItemsNotQualifiedLinkroles) and
concept not in elrPrimaryItems.get("*", set()) and
(not nsExcl or not nsExclPat.match(cast(str, qn.namespaceURI))))
if i:
val.modelXbrl.error("ESEF.3.4.2.extensionTaxonomyLineItemNotLinkedToAnyHypercube",
_("Line items that do not require any dimensional information to tag data MUST be linked to the dedicated \"Line items not dimensionally qualified\" hypercube in %(linkrole)s declared in esef_cor.xsd, primary item %(qnames)s"),
modelObject=i, linkrole=LineItemsNotQualifiedLinkrole, qnames=", ".join(sorted(str(c.qname) for c in i)))
# pri items in LineItemsNotQualifiedLinkrole which are not used in report non-dimensionally
# check no longer in Filer Manual as of 2021
#i = set(hcPrimaryItem
# for hcPrimaryItem in elrPrimaryItems.get(LineItemsNotQualifiedLinkrole, set())
# if not any(not f.context.qnameDims
# for f in val.modelXbrl.factsByQname.get(hcPrimaryItem.qname,())
# if f.context is not None))
#if i:
# val.modelXbrl.warning("ESEF.3.4.2.extensionTaxonomyLineItemIncorrectlyLinkedToNonDimensionallyQualifiedHypercube",
# _("Dimensional line item not reported non-dimensionally has no need to be linked to \"not dimensionally qualified\" hypercube %(linkrole)s, primary item %(qnames)s"),
# modelObject=i, linkrole=LineItemsNotQualifiedLinkrole, qnames=", ".join(sorted(str(c.qname) for c in i)))
modelObject=i, linkrole=lineItemsNotQualifiedLinkroles[0], qnames=", ".join(sorted(str(c.qname) for c in i)))

# check ELRs with WiderNarrower relationships
elrsContainingDimensionalRelationships = set(
Expand Down Expand Up @@ -166,7 +146,7 @@ def addDomMbrs(sourceDomMbr: ModelConcept, ELR: str, membersSet: set[ModelConcep
val.modelXbrl.error("ESEF.3.4.3.extensionTaxonomyOverridesDefaultMembers",
_("The extension taxonomy MUST not modify (prohibit and/or override) default members assigned to dimensions by the ESEF taxonomy."),
modelObject=linkChild)
if modelLink.role not in DefaultDimensionLinkroles:
if modelLink.role not in defaultDimensionLinkroles:
val.modelXbrl.error("ESEF.3.4.3.dimensionDefaultLinkrole",
_("Each dimension in an issuer specific extension taxonomy MUST be assigned to a default member in the ELR with role URI http://www.esma.europa.eu/xbrl/role/cor/ifrs-dim_role-990000, but linkrole used is %(linkrole)s."),
modelObject=linkChild, linkrole=modelLink.role)

0 comments on commit b07cf0c

Please sign in to comment.