From 85588dfb87fc9c3d37e2b64c69a54dce751542e7 Mon Sep 17 00:00:00 2001 From: Christophe De Bernardi Date: Fri, 31 Dec 2021 12:43:37 +0100 Subject: [PATCH 001/111] [ESEF] Fix severity level for three checks --- arelle/plugin/validate/ESEF/DTS.py | 4 ++-- arelle/plugin/validate/ESEF/Dimensions.py | 4 ++-- arelle/plugin/validate/ESEF/__init__.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/arelle/plugin/validate/ESEF/DTS.py b/arelle/plugin/validate/ESEF/DTS.py index 0f9aba3595..bcbb14207c 100644 --- a/arelle/plugin/validate/ESEF/DTS.py +++ b/arelle/plugin/validate/ESEF/DTS.py @@ -294,8 +294,8 @@ def checkFilingDTS(val, modelDocument, visited, hrefXlinkRole=None): modelObject=modelDocument.xmlRootElement, linkbaseType=linkbaseRefType, extendedLinkElement=linkEltName) elif len(linkbasesFound) > 1: - val.modelXbrl.warning("ESEF.3.1.1.linkbasesNotSeparateFiles", - _("Each linkbase type SHOULD be provided in a separate linkbase file, found: %(linkbasesFound)s."), + val.modelXbrl.error("ESEF.3.1.1.linkbasesNotSeparateFiles", + _("Each linkbase type MUST be provided in a separate linkbase file, found: %(linkbasesFound)s."), modelObject=modelDocument.xmlRootElement, linkbasesFound=", ".join(sorted(linkbasesFound))) # check for any prohibiting dimensionArc's diff --git a/arelle/plugin/validate/ESEF/Dimensions.py b/arelle/plugin/validate/ESEF/Dimensions.py index cc2cd97fb2..d73a4743e7 100644 --- a/arelle/plugin/validate/ESEF/Dimensions.py +++ b/arelle/plugin/validate/ESEF/Dimensions.py @@ -98,8 +98,8 @@ def addDomMbrs(sourceDomMbr, ELR, membersSet): concept not in elrPrimaryItems.get(LineItemsNotQualifiedLinkrole, set()) and concept not in elrPrimaryItems.get("*", set())) if i: - val.modelXbrl.warning("ESEF.3.4.2.extensionTaxonomyLineItemNotLinkedToAnyHypercube", - _("Dimensional line item reported non-dimensionally SHOULD be linked to \"not dimensionally qualified\" hypercube %(linkrole)s, primary item %(qnames)s"), + 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 diff --git a/arelle/plugin/validate/ESEF/__init__.py b/arelle/plugin/validate/ESEF/__init__.py index 87164da861..917ea24a79 100644 --- a/arelle/plugin/validate/ESEF/__init__.py +++ b/arelle/plugin/validate/ESEF/__init__.py @@ -884,7 +884,7 @@ def checkMonetaryUnits(parent, relSet, visited): checkMonetaryUnits(rootConcept, relSet, set()) if pfsConceptsRootInELR and (len(pfsConceptsRootInELR) + len(nonPfsConceptsRootInELR) ) > 1: roots = pfsConceptsRootInELR | nonPfsConceptsRootInELR - modelXbrl.warning("ESEF.3.4.7.singleExtendedLinkRoleUsedForAllPFSs", + modelXbrl.error("ESEF.3.4.7.singleExtendedLinkRoleUsedForAllPFSs", _("Separate Extended Link Roles are required by %(elr)s for hierarchies: %(roots)s."), modelObject=roots, elr=modelXbrl.roleTypeDefinition(ELR), roots=", ".join(sorted((str(c.qname) for c in roots)))) for labelrole, concepts in missingConceptLabels.items(): From 9769f7972cc2277e77bf420480a9da1534d68e10 Mon Sep 17 00:00:00 2001 From: "Austin M. Matherne" Date: Mon, 8 Aug 2022 19:01:59 -0500 Subject: [PATCH 002/111] Set package dependencies --- setup.py | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index d2b0394dd0..0629b4bd69 100644 --- a/setup.py +++ b/setup.py @@ -282,12 +282,35 @@ def get_version(): 'arelle-gui=arelle.CntlrWinMain:main', ] }, - # install_requires specifies a list of package dependencies that are - # installed when 'python setup.py install' is run. On Linux/Mac systems - # this also allows installation directly from the github repository - # (using 'pip install -e git+git://github.com/rheimbuchArelle.git#egg=Arelle') - # and the install_requires packages are auto-installed as well. - install_requires=['lxml', 'isodate', 'openpyxl'], + extras_require={ + 'Crypto': [ + 'pycryptodome==3.*', + ], + 'DB': [ + 'pg8000==1.*', + 'PyMySQL==1.*', + 'pyodbc==4.*', + 'rdflib==5.*', + ], + 'EFM': [ + 'holidays==0.*', + ], + 'ObjectMaker': [ + 'graphviz==0.*', + ], + 'WebServer': [ + 'cheroot==8.*', + 'CherryPy==18.*', + 'tornado==6.*', + ], + }, + install_requires=[ + 'isodate==0.*', + 'lxml==4.*', + 'numpy==1.*', + 'openpyxl==2.*', + 'regex==2022.*', + ], options=options, executables=cx_freeze_executables, ) From 429188e40c4b9f8a038201d5a8a13c0f8af03733 Mon Sep 17 00:00:00 2001 From: "Austin M. Matherne" Date: Mon, 8 Aug 2022 19:02:50 -0500 Subject: [PATCH 003/111] Specify package scripts --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 0629b4bd69..0b2f7766d3 100644 --- a/setup.py +++ b/setup.py @@ -279,8 +279,10 @@ def get_version(): entry_points={ 'console_scripts': [ 'arelle=arelle.CntlrCmdLine:main', + ], + 'gui_scripts': [ 'arelle-gui=arelle.CntlrWinMain:main', - ] + ], }, extras_require={ 'Crypto': [ From 76e57671dbfb4d90a02d0b5fc4702a97b70dfa58 Mon Sep 17 00:00:00 2001 From: Derek Gengenbacher Date: Thu, 11 Aug 2022 12:24:45 -0600 Subject: [PATCH 004/111] Exclude extension concept namespaces from `supported-taxonomy` validations --- arelle/plugin/validate/EFM/Filing.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/arelle/plugin/validate/EFM/Filing.py b/arelle/plugin/validate/EFM/Filing.py index 3711e71623..3116e7ab30 100644 --- a/arelle/plugin/validate/EFM/Filing.py +++ b/arelle/plugin/validate/EFM/Filing.py @@ -85,6 +85,7 @@ def validateFiling(val, modelXbrl, isEFM=False, isGFM=False): styleIxHiddenPattern = re.compile(r"(.*[^\w]|^)-sec-ix-hidden\s*:\s*([\w.-]+).*") efmRoleDefinitionPattern = re.compile(r"([0-9]+) - (Statement|Disclosure|Schedule|Document) - (.+)") messageKeySectionPattern = re.compile(r"(.*[{]efmSection[}]|[a-z]{2}-[0-9]{4})(.*)") + secDomainPattern = re.compile(r"((fasb)|(xbrl\.sec))\.((org)|(gov))") val._isStandardUri = {} modelXbrl.modelManager.disclosureSystem.loadStandardTaxonomiesDict() @@ -1149,7 +1150,10 @@ def isADR(f): if name.endswith(":*") and validation == "(supported-taxonomy)": # taxonomy-prefix filter txPrefix = name[:-2] ns = deiDefaultPrefixedNamespaces.get(txPrefix) - if ns: + # Its possible that extension concepts could have prefixes that match `cef` of `vip` + # and trip this validation so we exclude all extension namespaces by making sure the + # qname namespace matches known SEC domains. + if ns and secDomainPattern.match(ns): unexpectedFacts = set() for qn, facts in modelXbrl.factsByQname.items(): if qn.namespaceURI == ns: From f423acdf19d59ad9a57802ba9c01afa2ca880ff8 Mon Sep 17 00:00:00 2001 From: Patrik Lindgren <21142447+ggravlingen@users.noreply.github.com> Date: Thu, 11 Aug 2022 18:32:27 +0000 Subject: [PATCH 005/111] Add type hints for arelle/plugin/validate/ESEF/Dimensions.py --- arelle/ModelRelationshipSet.py | 68 ++++++++++++----------- arelle/ModelXbrl.py | 13 +++-- arelle/ValidateXbrl.py | 4 +- arelle/plugin/validate/ESEF/Dimensions.py | 51 ++++++++++------- pyproject.toml | 1 + 5 files changed, 77 insertions(+), 60 deletions(-) diff --git a/arelle/ModelRelationshipSet.py b/arelle/ModelRelationshipSet.py index 173ee3a19d..4c4b64b2e5 100644 --- a/arelle/ModelRelationshipSet.py +++ b/arelle/ModelRelationshipSet.py @@ -4,15 +4,17 @@ @author: Mark V Systems Limited (c) Copyright 2010 Mark V Systems Limited, All rights reserved. ''' +from __future__ import annotations # initialize object from loaded linkbases from collections import defaultdict -from arelle import ModelDtsObject, XbrlConst, XmlUtil, ModelValue +from typing import Any +from arelle import ModelDtsObject, XbrlConst, ModelValue from arelle.ModelObject import ModelObject from arelle.ModelDtsObject import ModelResource from arelle.PrototypeDtsObject import LocPrototype, PrototypeObject from arelle.XbrlConst import consecutiveArcrole -import os, sys +import sys USING_EQUIVALENCE_KEY = sys.intern(_STR_8BIT("using_equivalence_key")) # indicates hash entry replaced with keyed entry NoneType = type(None) @@ -24,8 +26,8 @@ def ineffectiveArcs(baseSetModelLinks, arcrole, arcqname=None): hashEquivalentRels = defaultdict(list) for modelLink in baseSetModelLinks: for linkChild in modelLink: - if (isinstance(linkChild,(ModelObject,PrototypeObject)) and - linkChild.get("{http://www.w3.org/1999/xlink}type") == "arc" and + if (isinstance(linkChild,(ModelObject,PrototypeObject)) and + linkChild.get("{http://www.w3.org/1999/xlink}type") == "arc" and arcrole == linkChild.get("{http://www.w3.org/1999/xlink}arcrole") and (arcqname is None or arcqname == linkChild)): fromLabel = linkChild.get("{http://www.w3.org/1999/xlink}from") @@ -83,13 +85,13 @@ def ineffectiveArcs(baseSetModelLinks, arcrole, arcqname=None): def baseSetArcroles(modelXbrl): # returns sorted list of tuples of arcrole basename and uri return sorted(set((XbrlConst.baseSetArcroleLabel(b[0]),b[0]) for b in modelXbrl.baseSets.keys())) - + def labelroles(modelXbrl, includeConceptName=False): # returns sorted list of tuples of arcrole basename and uri - return sorted(set((XbrlConst.labelroleLabel(r),r) + return sorted(set((XbrlConst.labelroleLabel(r),r) for r in (modelXbrl.labelroles | ({XbrlConst.conceptNameLabelRole} if includeConceptName else set())) if r is not None)) - + def baseSetRelationship(arcElement): modelXbrl = arcElement.modelXbrl arcrole = arcElement.get("{http://www.w3.org/1999/xlink}arcrole") @@ -103,7 +105,7 @@ class ModelRelationshipSet: __slots__ = ("isChanged", "modelXbrl", "arcrole", "linkrole", "linkqname", "arcqname", "modelRelationshipsFrom", "modelRelationshipsTo", "modelConceptRoots", "modellinkRoleUris", "modelRelationships", "_testHintedLabelLinkrole") - + # arcrole can either be a single string or a tuple or frozenset of strings def __init__(self, modelXbrl, arcrole, linkrole=None, linkqname=None, arcqname=None, includeProhibits=False): self.isChanged = False @@ -113,8 +115,8 @@ def __init__(self, modelXbrl, arcrole, linkrole=None, linkqname=None, arcqname=N self.linkqname = linkqname self.arcqname = arcqname - relationshipSetKey = (arcrole, linkrole, linkqname, arcqname, includeProhibits) - + relationshipSetKey = (arcrole, linkrole, linkqname, arcqname, includeProhibits) + # base sets does not care about the #includeProhibits if isinstance(arcrole, (str, NoneType)) and isinstance(linkrole, (str, NoneType)): modelLinks = self.modelXbrl.baseSets.get((arcrole, linkrole, linkqname, arcqname), []) @@ -123,7 +125,7 @@ def __init__(self, modelXbrl, arcrole, linkrole=None, linkqname=None, arcqname=N for ar in (arcrole,) if isinstance(arcrole, (str, NoneType)) else arcrole: for lr in (linkrole,) if isinstance(linkrole, (str, NoneType)) else linkrole: modelLinks.extend(self.modelXbrl.baseSets.get((ar, lr, linkqname, arcqname), [])) - + # gather arcs relationships = {} isDimensionRel = self.arcrole == "XBRL-dimensions" # all dimensional relationship arcroles @@ -132,7 +134,7 @@ def __init__(self, modelXbrl, arcrole, linkrole=None, linkqname=None, arcqname=N isFootnoteRel = self.arcrole == "XBRL-footnotes" # all footnote relationship arcroles if not isinstance(arcrole,(tuple,frozenset)): arcrole = (arcrole,) - + for modelLink in modelLinks: arcs = [] linkEltQname = modelLink.qname @@ -141,7 +143,7 @@ def __init__(self, modelXbrl, arcrole, linkrole=None, linkqname=None, arcqname=N if linkChild.get("{http://www.w3.org/1999/xlink}type") == "arc" and linkChildArcrole: if isFootnoteRel: # arcrole is fact-footnote or other custom footnote relationship arcs.append(linkChild) - elif isDimensionRel: + elif isDimensionRel: if XbrlConst.isDimensionArcrole(linkChildArcrole): arcs.append(linkChild) elif isFormulaRel: @@ -150,11 +152,11 @@ def __init__(self, modelXbrl, arcrole, linkrole=None, linkqname=None, arcqname=N elif isTableRenderingRel: if XbrlConst.isTableRenderingArcrole(linkChildArcrole): arcs.append(linkChild) - elif (linkChildArcrole in arcrole and - (arcqname is None or arcqname == linkChild.qname) and + elif (linkChildArcrole in arcrole and + (arcqname is None or arcqname == linkChild.qname) and (linkqname is None or linkqname == linkEltQname)): arcs.append(linkChild) - + # build network for arcElement in arcs: fromLabel = arcElement.get("{http://www.w3.org/1999/xlink}from") @@ -185,14 +187,14 @@ def __init__(self, modelXbrl, arcrole, linkrole=None, linkqname=None, arcqname=N self.modellinkRoleUris = None orderRels = defaultdict(list) for modelRel in relationships.values(): - if (modelRel is not USING_EQUIVALENCE_KEY and + if (modelRel is not USING_EQUIVALENCE_KEY and (includeProhibits or not modelRel.isProhibited)): orderRels[modelRel.order].append(modelRel) self.modelRelationships = [modelRel for order in sorted(orderRels.keys()) for modelRel in orderRels[order]] modelXbrl.relationshipSets[relationshipSetKey] = self - + def clear(self): # this object is slotted, clear slotted variables self.modelXbrl = None @@ -204,16 +206,16 @@ def clear(self): if self.modelConceptRoots is not None: del self.modelConceptRoots[:] self.linkqname = self.arcqname = None - + def __bool__(self): # some modelRelationships exist return len(self.modelRelationships) > 0 - + @property def linkRoleUris(self): if self.modellinkRoleUris is None: self.modellinkRoleUris = set(modelRel.linkrole for modelRel in self.modelRelationships) return self.modellinkRoleUris - + def loadModelRelationshipsFrom(self): if self.modelRelationshipsFrom is None: self.modelRelationshipsFrom = defaultdict(list) @@ -221,7 +223,7 @@ def loadModelRelationshipsFrom(self): fromModelObject = modelRel.fromModelObject if fromModelObject is not None: # none if concepts failed to load self.modelRelationshipsFrom[fromModelObject].append(modelRel) - + def loadModelRelationshipsTo(self): if self.modelRelationshipsTo is None: self.modelRelationshipsTo = defaultdict(list) @@ -229,25 +231,25 @@ def loadModelRelationshipsTo(self): toModelObject = modelRel.toModelObject if toModelObject is not None: # none if concepts failed to load self.modelRelationshipsTo[toModelObject].append(modelRel) - - def fromModelObjects(self): + + def fromModelObjects(self) -> dict[Any, list]: self.loadModelRelationshipsFrom() return self.modelRelationshipsFrom - def fromModelObject(self, modelFrom): + def fromModelObject(self, modelFrom) -> list[Any]: if self.modelRelationshipsFrom is None: self.loadModelRelationshipsFrom() return self.modelRelationshipsFrom.get(modelFrom, []) - + def toModelObjects(self): self.loadModelRelationshipsTo() return self.modelRelationshipsTo - def toModelObject(self, modelTo): + def toModelObject(self, modelTo) -> list[Any]: if self.modelRelationshipsTo is None: self.loadModelRelationshipsTo() return self.modelRelationshipsTo.get(modelTo, []) - + def fromToModelObjects(self, modelFrom, modelTo, checkBothDirections=False): self.loadModelRelationshipsFrom() rels = [rel for rel in self.fromModelObject(modelFrom) if rel.toModelObject is modelTo] @@ -267,13 +269,13 @@ def rootConcepts(self): len(self.modelRelationshipsTo[modelRelFrom]) == 1 and relFrom[0].fromModelObject == relFrom[0].toModelObject)] return self.modelConceptRoots - + # if modelFrom and modelTo are provided determine that they have specified relationship # if only modelFrom, determine that there are relationships present of specified axis def isRelated(self, modelFrom, axis, modelTo=None, visited=None, isDRS=False): # either model concept or qname - if isinstance(modelFrom,ModelValue.QName): + if isinstance(modelFrom,ModelValue.QName): modelFrom = self.modelXbrl.qnameConcepts.get(modelFrom) # fails if None - if isinstance(modelTo,ModelValue.QName): + if isinstance(modelTo,ModelValue.QName): modelTo = self.modelXbrl.qnameConcepts.get(modelTo) if modelTo is None: # note that modelTo None (not a bad QName) means to check for any relationship return False # if a QName and not existent then fails @@ -304,7 +306,7 @@ def isRelated(self, modelFrom, axis, modelTo=None, visited=None, isDRS=False): # if toConcept not in visited: visited.add(toConcept) if isDRS: - if (self.modelXbrl.relationshipSet(consecutiveArcrole[modelRel.arcrole], + if (self.modelXbrl.relationshipSet(consecutiveArcrole[modelRel.arcrole], modelRel.consecutiveLinkrole, self.linkqname, self.arcqname) .isRelated(toConcept, axis, modelTo, visited, isDRS)): return True @@ -313,7 +315,7 @@ def isRelated(self, modelFrom, axis, modelTo=None, visited=None, isDRS=False): # return True visited.discard(toConcept) return False - + def label(self, modelFrom, role, lang, returnMultiple=False, returnText=True, linkroleHint=None): _lang = lang.lower() if lang else lang # lang processing is case insensitive shorterLangInLabel = longerLangInLabel = None diff --git a/arelle/ModelXbrl.py b/arelle/ModelXbrl.py index 30ce5b4f57..802824eb0f 100644 --- a/arelle/ModelXbrl.py +++ b/arelle/ModelXbrl.py @@ -4,13 +4,14 @@ @author: Mark V Systems Limited (c) Copyright 2010 Mark V Systems Limited, All rights reserved. ''' +from __future__ import annotations from collections import defaultdict import os, sys, re, traceback, uuid +from typing import TYPE_CHECKING import logging from decimal import Decimal from arelle import UrlUtil, XmlUtil, ModelValue, XbrlConst, XmlValidate from arelle.FileSource import FileNamedStringIO -from arelle.ModelDtsObject import ModelRelationship from arelle.ModelObject import ModelObject, ObjectPropertyViewWrapper from arelle.Locale import format_string from arelle.PluginManager import pluginClassMethods @@ -19,8 +20,10 @@ from arelle.UrlUtil import isHttpUrl from arelle.ValidateXbrlDimensions import isFactDimensionallyValid +if TYPE_CHECKING: + from arelle.ModelRelationshipSet import ModelRelationshipSet as ModelRelationshipSetClass + -ModelRelationshipSet = None # dynamic import ModelFact = None profileStatNumber = 0 @@ -379,7 +382,7 @@ def displayUri(self): else: return self.fileSource.url - def relationshipSet(self, arcrole, linkrole=None, linkqname=None, arcqname=None, includeProhibits=False) -> ModelRelationship: + def relationshipSet(self, arcrole, linkrole=None, linkqname=None, arcqname=None, includeProhibits=False) -> ModelRelationshipSetClass: """Returns a relationship set matching specified parameters (only arcrole is required). Resolve and determine relationship set. If a relationship set of the same parameters was previously resolved, it is returned from a cache. @@ -394,9 +397,7 @@ def relationshipSet(self, arcrole, linkrole=None, linkqname=None, arcqname=None, :type includeProhibits: bool :returns: [ModelRelationship] -- Ordered list of effective relationship objects per parameters """ - global ModelRelationshipSet - if ModelRelationshipSet is None: - from arelle import ModelRelationshipSet + from arelle import ModelRelationshipSet key = (arcrole, linkrole, linkqname, arcqname, includeProhibits) if key not in self.relationshipSets: ModelRelationshipSet.create(self, arcrole, linkrole, linkqname, arcqname, includeProhibits) diff --git a/arelle/ValidateXbrl.py b/arelle/ValidateXbrl.py index e36f1abd93..785a6625e3 100644 --- a/arelle/ValidateXbrl.py +++ b/arelle/ValidateXbrl.py @@ -53,14 +53,14 @@ class ValidateXbrl: authority: str authParam: dict[str, Any] - domainMembers: list + domainMembers: set extensionImportedUrls: set hasExtensionCal: bool hasExtensionDef: bool hasExtensionLbl: bool hasExtensionPre: bool hasExtensionSchema: bool - primaryItems: list + primaryItems: set def __init__(self, testModelXbrl): self.testModelXbrl = testModelXbrl diff --git a/arelle/plugin/validate/ESEF/Dimensions.py b/arelle/plugin/validate/ESEF/Dimensions.py index 5f20b26ca8..d6eb4bc8f7 100644 --- a/arelle/plugin/validate/ESEF/Dimensions.py +++ b/arelle/plugin/validate/ESEF/Dimensions.py @@ -7,8 +7,9 @@ @author: Mark V Systems Limited (c) Copyright 2018 Mark V Systems Limited, All rights reserved. ''' - +from __future__ import annotations from collections import defaultdict +from typing import Any from arelle.ModelDtsObject import ModelConcept from arelle.ModelObject import ModelObject from arelle.PrototypeDtsObject import PrototypeObject @@ -18,28 +19,40 @@ try: import regex as re except ImportError: - import re + import re # type: ignore[no-redef] +from collections.abc import Callable +from arelle.ValidateXbrl import ValidateXbrl +from arelle.ModelDtsObject import ModelConcept + +_: Callable[[str], str] # Handle gettext -def checkFilingDimensions(val): + +def checkFilingDimensions(val: ValidateXbrl) -> None: val.primaryItems = set() # concepts which are line items (should not also be dimension members - val.domainMembers = set() # concepts which are dimension domain members + val.domainMembers = set() # concepts which are dimension domain members + elrPrimaryItems = defaultdict(set) - hcPrimaryItems = set() - hcMembers = set() - - def addDomMbrs(sourceDomMbr, ELR, membersSet): + hcPrimaryItems: set[ModelConcept] = set() + hcMembers: set[Any] = set() + + def addDomMbrs(sourceDomMbr: ModelConcept, ELR: str, membersSet: set[ModelConcept]) -> None: 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): hasHypercubeRelationships = val.modelXbrl.relationshipSet(hasHypercubeArcrole).fromModelObjects() + + # fromModelObjects() has an optional return type, we need to handle that here + if hasHypercubeRelationships is None: + continue + for hasHcRels in hasHypercubeRelationships.values(): for hasHcRel in hasHcRels: - sourceConcept = hasHcRel.fromModelObject + sourceConcept: ModelConcept = hasHcRel.fromModelObject hcPrimaryItems.add(sourceConcept) # find associated primary items to source concept for domMbrRel in val.modelXbrl.relationshipSet(XbrlConst.domainMember).fromModelObject(sourceConcept): @@ -79,8 +92,8 @@ def addDomMbrs(sourceDomMbr, ELR, membersSet): elrPrimaryItems["*"].add(hcPrimaryItem) # members of any ELR hcPrimaryItems.clear() hcMembers.clear() - - # find primary items with other dimensions in + + # 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 @@ -100,7 +113,7 @@ def addDomMbrs(sourceDomMbr, ELR, membersSet): for qn, facts in val.modelXbrl.factsByQname.items() 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 + if concept is not None and concept not in elrPrimaryItems.get(LineItemsNotQualifiedLinkrole, set()) and concept not in elrPrimaryItems.get("*", set()) and (not nsExcl or not nsExclPat.match(qn.namespaceURI))) @@ -112,7 +125,7 @@ def addDomMbrs(sourceDomMbr, ELR, membersSet): # 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 + # if not any(not f.context.qnameDims # for f in val.modelXbrl.factsByQname.get(hcPrimaryItem.qname,()) # if f.context is not None)) #if i: @@ -151,13 +164,13 @@ def addDomMbrs(sourceDomMbr, ELR, membersSet): val.modelXbrl.error("ESEF.3.3.2.anchoringRelationshipsForConceptsDefinedInElrContainingDimensionalRelationships", _("Anchoring relationships for concepts MUST be defined in a dedicated extended link role (or roles if needed to properly represent the relationships), e.g. http://{issuer default pattern for roles}/Anchoring. %(anchoringDimensionalELR)s"), modelObject=rels, anchoringDimensionalELR=ELR) - + # check base set dimension default overrides in extension taxonomies for modelLink in val.modelXbrl.baseSets[XbrlConst.dimensionDefault, None, None, None]: - if isExtension(val, modelLink): + if isExtension(val, modelLink): for linkChild in modelLink: - if (isinstance(linkChild,(ModelObject,PrototypeObject)) and - linkChild.get("{http://www.w3.org/1999/xlink}type") == "arc" and + if (isinstance(linkChild,(ModelObject,PrototypeObject)) and + linkChild.get("{http://www.w3.org/1999/xlink}type") == "arc" and linkChild.get("{http://www.w3.org/1999/xlink}arcrole") == XbrlConst.dimensionDefault): fromLabel = linkChild.get("{http://www.w3.org/1999/xlink}from") for fromResource in modelLink.labeledResources[fromLabel]: @@ -169,4 +182,4 @@ def addDomMbrs(sourceDomMbr, ELR, membersSet): 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) - + diff --git a/pyproject.toml b/pyproject.toml index a37c6f5dea..b903e82617 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ ignore_errors = true [[tool.mypy.overrides]] module = [ 'arelle.plugin.validate.ESEF.Const', + 'arelle.plugin.validate.ESEF.Dimensions', 'arelle.plugin.validate.ESEF.DTS', 'arelle.plugin.validate.ESEF.Util' ] From 51ff5790312df3a8762cf71eb8852188b6dfd80a Mon Sep 17 00:00:00 2001 From: Derek Gengenbacher Date: Thu, 11 Aug 2022 17:07:33 -0600 Subject: [PATCH 006/111] Filer on deiDefaultPrefixedNamespaces instead of at the point of validation --- arelle/plugin/validate/EFM/Filing.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/arelle/plugin/validate/EFM/Filing.py b/arelle/plugin/validate/EFM/Filing.py index 3116e7ab30..35b351ef29 100644 --- a/arelle/plugin/validate/EFM/Filing.py +++ b/arelle/plugin/validate/EFM/Filing.py @@ -85,7 +85,7 @@ def validateFiling(val, modelXbrl, isEFM=False, isGFM=False): styleIxHiddenPattern = re.compile(r"(.*[^\w]|^)-sec-ix-hidden\s*:\s*([\w.-]+).*") efmRoleDefinitionPattern = re.compile(r"([0-9]+) - (Statement|Disclosure|Schedule|Document) - (.+)") messageKeySectionPattern = re.compile(r"(.*[{]efmSection[}]|[a-z]{2}-[0-9]{4})(.*)") - secDomainPattern = re.compile(r"((fasb)|(xbrl\.sec))\.((org)|(gov))") + secDomainPattern = re.compile(r"(fasb|xbrl\.sec)\.(org|gov)") val._isStandardUri = {} modelXbrl.modelManager.disclosureSystem.loadStandardTaxonomiesDict() @@ -1030,7 +1030,12 @@ def sevMessage(sev, messageKey=None, **kwargs): sevs = deiValidations["sub-type-element-validations"] deiCAxes = deiValidations["axis-validations"]["c"]["axes"] - deiDefaultPrefixedNamespaces = deiValidations["prefixed-namespaces"] + # Its possible that extension concepts could have prefixes that match `cef` or `vip` + # and EFM.6.5.55 or EFM.6.5.56 validations so we exclude all extension namespaces by + # filtering out prefix namespace combos where the namespace matches known SEC domains. + deiDefaultPrefixedNamespaces = { + prefix: qname for prefix, qname in deiValidations["prefixed-namespaces"].items() if secDomainPattern.search(qname) + } # called with sev, returns iterator of sev facts for names and axes matching # called with sev and name, returns single fact for name matching axesMembers (if any) def sevFacts(sev=None, name=None, otherFact=None, requiredContext=False, axisKey=None, deduplicate=False): @@ -1150,10 +1155,7 @@ def isADR(f): if name.endswith(":*") and validation == "(supported-taxonomy)": # taxonomy-prefix filter txPrefix = name[:-2] ns = deiDefaultPrefixedNamespaces.get(txPrefix) - # Its possible that extension concepts could have prefixes that match `cef` of `vip` - # and trip this validation so we exclude all extension namespaces by making sure the - # qname namespace matches known SEC domains. - if ns and secDomainPattern.match(ns): + if ns: unexpectedFacts = set() for qn, facts in modelXbrl.factsByQname.items(): if qn.namespaceURI == ns: From 2dfdd045bdd63e5a8be7b109c390b8dfa52d7df5 Mon Sep 17 00:00:00 2001 From: Derek Gengenbacher Date: Fri, 12 Aug 2022 09:43:14 -0600 Subject: [PATCH 007/111] Make regex more specific --- arelle/plugin/validate/EFM/Filing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arelle/plugin/validate/EFM/Filing.py b/arelle/plugin/validate/EFM/Filing.py index 35b351ef29..b38b46e56a 100644 --- a/arelle/plugin/validate/EFM/Filing.py +++ b/arelle/plugin/validate/EFM/Filing.py @@ -85,7 +85,7 @@ def validateFiling(val, modelXbrl, isEFM=False, isGFM=False): styleIxHiddenPattern = re.compile(r"(.*[^\w]|^)-sec-ix-hidden\s*:\s*([\w.-]+).*") efmRoleDefinitionPattern = re.compile(r"([0-9]+) - (Statement|Disclosure|Schedule|Document) - (.+)") messageKeySectionPattern = re.compile(r"(.*[{]efmSection[}]|[a-z]{2}-[0-9]{4})(.*)") - secDomainPattern = re.compile(r"(fasb|xbrl\.sec)\.(org|gov)") + secDomainPattern = re.compile(r"(fasb\.org|xbrl\.sec\.gov)") val._isStandardUri = {} modelXbrl.modelManager.disclosureSystem.loadStandardTaxonomiesDict() From 2d429259d8ea43aea6d35199d471cd781e479e99 Mon Sep 17 00:00:00 2001 From: Derek Gengenbacher Date: Fri, 12 Aug 2022 09:44:24 -0600 Subject: [PATCH 008/111] Update variable name --- arelle/plugin/validate/EFM/Filing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arelle/plugin/validate/EFM/Filing.py b/arelle/plugin/validate/EFM/Filing.py index b38b46e56a..673c42df1a 100644 --- a/arelle/plugin/validate/EFM/Filing.py +++ b/arelle/plugin/validate/EFM/Filing.py @@ -1034,7 +1034,7 @@ def sevMessage(sev, messageKey=None, **kwargs): # and EFM.6.5.55 or EFM.6.5.56 validations so we exclude all extension namespaces by # filtering out prefix namespace combos where the namespace matches known SEC domains. deiDefaultPrefixedNamespaces = { - prefix: qname for prefix, qname in deiValidations["prefixed-namespaces"].items() if secDomainPattern.search(qname) + prefix: namespace for prefix, namespace in deiValidations["prefixed-namespaces"].items() if secDomainPattern.search(namespace) } # called with sev, returns iterator of sev facts for names and axes matching # called with sev and name, returns single fact for name matching axesMembers (if any) From d2e7de14860bf393dd10ef5cb0b05fd490605af2 Mon Sep 17 00:00:00 2001 From: Patrik Lindgren <21142447+ggravlingen@users.noreply.github.com> Date: Fri, 12 Aug 2022 17:05:29 +0000 Subject: [PATCH 009/111] Don't check hasHypercubeRelationships for None --- arelle/plugin/validate/ESEF/Dimensions.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/arelle/plugin/validate/ESEF/Dimensions.py b/arelle/plugin/validate/ESEF/Dimensions.py index d6eb4bc8f7..9135dff9e1 100644 --- a/arelle/plugin/validate/ESEF/Dimensions.py +++ b/arelle/plugin/validate/ESEF/Dimensions.py @@ -46,10 +46,6 @@ def addDomMbrs(sourceDomMbr: ModelConcept, ELR: str, membersSet: set[ModelConcep for hasHypercubeArcrole in (XbrlConst.all, XbrlConst.notAll): hasHypercubeRelationships = val.modelXbrl.relationshipSet(hasHypercubeArcrole).fromModelObjects() - # fromModelObjects() has an optional return type, we need to handle that here - if hasHypercubeRelationships is None: - continue - for hasHcRels in hasHypercubeRelationships.values(): for hasHcRel in hasHcRels: sourceConcept: ModelConcept = hasHcRel.fromModelObject From 3170ade38df8575affc7782d851dd38392db8c94 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Aug 2022 10:28:03 +0000 Subject: [PATCH 010/111] Bump numpy from 1.23.1 to 1.23.2 Bumps [numpy](https://github.com/numpy/numpy) from 1.23.1 to 1.23.2. - [Release notes](https://github.com/numpy/numpy/releases) - [Changelog](https://github.com/numpy/numpy/blob/main/doc/RELEASE_WALKTHROUGH.rst) - [Commits](https://github.com/numpy/numpy/compare/v1.23.1...v1.23.2) --- updated-dependencies: - dependency-name: numpy dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 809996e552..d52b2d200f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ Cheroot==8.6.0 pycountry==22.3.5 # plugins # EdgarRenderer plugin -NumPy==1.23.1 +NumPy==1.23.2 Matplotlib==3.5.2 holidays==0.14.2 Tornado==6.2 From c0746dfe9a5d91967e844552decffbdae8d1eaaa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Aug 2022 14:49:27 +0000 Subject: [PATCH 011/111] Bump matplotlib from 3.5.2 to 3.5.3 Bumps [matplotlib](https://github.com/matplotlib/matplotlib) from 3.5.2 to 3.5.3. - [Release notes](https://github.com/matplotlib/matplotlib/releases) - [Commits](https://github.com/matplotlib/matplotlib/compare/v3.5.2...v3.5.3) --- updated-dependencies: - dependency-name: matplotlib dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index d52b2d200f..f674eb0007 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ pycountry==22.3.5 # plugins # EdgarRenderer plugin NumPy==1.23.2 -Matplotlib==3.5.2 +Matplotlib==3.5.3 holidays==0.14.2 Tornado==6.2 # security plugin From 1ce4af09069b88e972aba9bfddd7525a7f9802d4 Mon Sep 17 00:00:00 2001 From: Patrik Lindgren <21142447+ggravlingen@users.noreply.github.com> Date: Mon, 15 Aug 2022 16:59:37 +0000 Subject: [PATCH 012/111] Finalise adding type hints for arelle.plugin.validate.ESEF --- arelle/LeiUtil.py | 10 +- arelle/ModelDocument.py | 7 +- arelle/ModelDtsObject.py | 575 +++++++++++----------- arelle/ModelXbrl.py | 9 +- arelle/PackageManager.py | 62 +-- arelle/PythonUtil.py | 34 +- arelle/UrlUtil.py | 27 +- arelle/ValidateUtr.py | 27 +- arelle/ValidateXbrl.py | 7 +- arelle/ValidateXbrlCalcs.py | 59 +-- arelle/XhtmlValidate.py | 55 ++- arelle/plugin/validate/ESEF/DTS.py | 4 +- arelle/plugin/validate/ESEF/Dimensions.py | 4 +- arelle/plugin/validate/ESEF/Util.py | 4 +- arelle/plugin/validate/ESEF/__init__.py | 337 +++++++------ arelle/plugin/validate/ESEF/common.py | 5 + pyproject.toml | 5 +- 17 files changed, 644 insertions(+), 587 deletions(-) create mode 100644 arelle/plugin/validate/ESEF/common.py diff --git a/arelle/LeiUtil.py b/arelle/LeiUtil.py index ff1b2f7a3b..aa04773c25 100644 --- a/arelle/LeiUtil.py +++ b/arelle/LeiUtil.py @@ -22,7 +22,7 @@ validInvalidLeiPattern = re.compile("^(029200720E3M3A4D6D01|029200758D5M0AI3F601|315700X8JQ3IU0NGK501|3157007SCCESQAUH5Z01|315700TCC9NTEP7J8Z01|13250000000000000301|315700ADOXDR5PCY5400|315700A0UB9Q7DOQIZ00|315700BM9Z39TNTGQW00|315700BZ5F7DRYG2UM00|315700B8401GTFFY6X01|315700DJ07P6OX10FK01|315700DKCD4QSKLAMO01|315700D23JL5C1DZNT00|315700EIYO2TLSEGQ700|315700ET7M7VQ4C84R00|315700EZSEA51937KX01|315700GXBGM8DBYKHF01|315700GZA843JXKTJ400|315700G5G24XYL1TXH00|315700HS7WJ1B0SUWM01|315700HXOEOK58E58P01|315700HZU4SMI8LZTU00|315700I3W2AFHP8MNQ01|315700JICZ3SY5SAXX00|315700JKZH6I0067ND01|315700JO5E28SRE00Q01|315700JXUHL9H2C3P700|315700LDDN3RM7Y2MP00|315700LWYOZNQ7V1T100|315700MBYPT6PGKO7M01|315700MW2F0KFR45QW01|315700M5843O6DU83901|315700NNMGS8F3P2CN00|315700N0VEIBHP0NPQ01|315700OASRCM664PAW01|315700OFW4YCOBNX4U01|315700OVW93X0T3HP200|315700O666JVNCQU9X00|315700PLI0I7W8IOV400|315700PN3J57ZUNF1V00|315700PXKOSX7WQV4N00|315700P4N9VSLK5QZV01|315700P40OV6BT045900|315700P6TZOLP92KN801|315700P89WR82VNB8Z00|315700QGM4XWZE1I5N01|315700Q1S8O1UORF9700|315700RK8M4FAHMYAP01|315700RTEHY362KXWJ00|315700SNUXK41WMW5J00|315700S22RGYRIEEOT00|315700S3TF79ALV82F01|315700TF5Z7T28HZJK01|315700TWGZ89LLSRS000|315700TXNX10N8XH4K00|315700T2EEQAPBO0C301|315700T6T49EDM16YO01|315700UJ6N4LGKLNPB00|315700UKZXWXEO126601|315700UYFD5GF9R13F01|315700VG7PTE9EJJRX01|315700VITYR7AL4M9S01|315700VMAJZ9JZTXNQ00|315700WH3YMKHCVYW201|315700WKCDF4QGRRO200|315700WR4IHOO1M5LP00|315700WYOZ6994UATN00|315700WZPEIS41QDKE00|315700XEFYMA5EZ0P500|315700XE21UYOA3GAC01|315700XI4Z8GF5BDUJ01|315700XSCP1S8WOD8E01|315700X40GNCOUWJYR00|315700YS6RQ5TF3VBP01|315700Y1W7W1JHAUBW00|315700Y5JNQMMUF5ID01|3157000VAJWZ3P8ZED00|3157001KM8GOU7PXZY01|3157001K2LAL04D87901|3157001MLDD3SDFQA901|3157001TPR6K4GBTLN00|31570010000000006400|31570010000000009601|31570010000000019301|31570010000000025800|31570010000000029001|31570010000000035500|31570010000000038701|31570010000000045200|31570010000000048401|31570010000000054900|31570010000000064600|31570010000000067801|31570010000000077501|31570010000000084000|31570010000000087201|31570010000000096901|31570010000000103400|31570010000000106601|31570010000000116301|31570010000000122800|31570010000000126001|3157002CKDQOCIHE5H01|31570020000000005900|31570029808HJVCNFA01|3157003FQSSGS9OZ9E01|3157004PTVTDOKB46401|3157004R6CH6C1P4KX00|3157005RUI28M8FANK00|3157005VJE7A3MBUS201|3157005WT1SENAE17R00|31570058O0Z320C4GZ00|3157006B6JVZ5DFMSN00|3157006DE3SPNIUY9K01|3157006FR3JBBOLOMX01|3157006I3B6RSTPQLI00|3157006KT1EZ15OIXW00|315700659AALVVLVIO01|31570067WSCDST0S3F01|3157008VKYORMUNC5O01|3157008ZY7CW6LVU5J00|3157009FTHFDDK7FHW01|3157009OVCV07O4HXM00|315700T8U7IU4W8J3A01|315700BBRQHDWX6SHZ00|3157008KD17KROO7UT01)$") -def checkLei(lei): +def checkLei(lei: str) -> int: if validInvalidLeiPattern.match(lei): return LEI_VALID if not leiLexicalPattern.match(lei): @@ -34,12 +34,12 @@ def checkLei(lei): "S":"28", "T":"29", "U":"30", "V":"31", "W":"32", "X":"33", "Y":"34", "Z":"35" }[c] for c in lei) ) % 97 == 1: - return LEI_INVALID_CHECKSUM + return LEI_INVALID_CHECKSUM return LEI_VALID - + if __name__ == "__main__": # test cases - for lei, name in ( + for lei, name in ( ("001GPB6A9XPE8XJICC14", "Fidelity Advisor Series I"), ("004L5FPTUREIWK9T2N63", "Hutchin Hill Capital, LP"), ("00EHHQ2ZHDCFXJCPCL46", "Vanguard Russell 1000 Growth Index Trust"), @@ -55,7 +55,7 @@ def checkLei(lei): ("213800A9GT65GAE%2V60", "Error 2"), ("213800A9GT65GAES2V62", "Error 3"), ("1234", "Error 4"), - (""" + (""" 5299003M8JKHEFX58Y02""", "Error 4") ): print ("LEI {} result {} name {}".format(lei, LEI_RESULTS[checkLei(lei)], name) ) diff --git a/arelle/ModelDocument.py b/arelle/ModelDocument.py index f0215ca726..af27f3221b 100644 --- a/arelle/ModelDocument.py +++ b/arelle/ModelDocument.py @@ -4,6 +4,7 @@ @author: Mark V Systems Limited (c) Copyright 2010 Mark V Systems Limited, All rights reserved. ''' +from __future__ import annotations import os, io from collections import defaultdict from typing import Any @@ -24,7 +25,7 @@ creationSoftwareNames = None -def load(modelXbrl, uri, base=None, referringElement=None, isEntry=False, isDiscovered=False, isIncluded=None, isSupplemental=False, namespace=None, reloadCache=False, **kwargs): +def load(modelXbrl, uri, base=None, referringElement=None, isEntry=False, isDiscovered=False, isIncluded=None, isSupplemental=False, namespace=None, reloadCache=False, **kwargs) -> ModelDocument | None: """Returns a new modelDocument, performing DTS discovery for instance, inline XBRL, schema, linkbase, and versioning report entry urls. @@ -339,7 +340,7 @@ def load(modelXbrl, uri, base=None, referringElement=None, isEntry=False, isDisc modelXbrl.error("html:validationException", _("%(element)s error %(error)s, unable to process html."), modelObject=rootNode, element=rootNode.localName.title(), error=type(err).__name__) - return None # rootNode is not processed further to find any facts because there could be many recursion errors + return None # rootNode is not processed further to find any facts because there could be many recursion errors elif _type == Type.VERSIONINGREPORT: modelDocument.versioningReportDiscover(rootNode) elif _type == Type.TESTCASESINDEX: @@ -526,7 +527,7 @@ class Type: "fact dimensions infoset", "html non-XBRL") - def identify(filesource, filepath): + def identify(filesource, filepath) -> int: _type = Type.UnknownNonXML file, = filesource.file(filepath, stripDeclaration=True, binary=True) try: diff --git a/arelle/ModelDtsObject.py b/arelle/ModelDtsObject.py index 5cf5f7592e..0a93dd89c6 100644 --- a/arelle/ModelDtsObject.py +++ b/arelle/ModelDtsObject.py @@ -5,58 +5,59 @@ .. module:: arelle.ModelDtsObject :copyright: Copyright 2010-2012 Mark V Systems Limited, All rights reserved. :license: Apache-2. - :synopsis: This module contains DTS-specialized ModelObject classes: ModelRoleType (role and arcrole types), ModelSchemaObject (parent class for top-level named schema element, attribute, attribute groups, etc), ModelConcept (xs:elements that may be concepts, typed dimension elements, or just plain XML definitions), ModelAttribute (xs:attribute), ModelAttributeGroup, ModelType (both top level named and anonymous simple and complex types), ModelEnumeration, ModelLink (xlink link elements), ModelResource (xlink resource elements), ModelLocator (subclass of ModelResource for xlink locators), and ModelRelationship (not an lxml proxy object, but a resolved relationship that reflects an effective arc between one source and one target). + :synopsis: This module contains DTS-specialized ModelObject classes: ModelRoleType (role and arcrole types), ModelSchemaObject (parent class for top-level named schema element, attribute, attribute groups, etc), ModelConcept (xs:elements that may be concepts, typed dimension elements, or just plain XML definitions), ModelAttribute (xs:attribute), ModelAttributeGroup, ModelType (both top level named and anonymous simple and complex types), ModelEnumeration, ModelLink (xlink link elements), ModelResource (xlink resource elements), ModelLocator (subclass of ModelResource for xlink locators), and ModelRelationship (not an lxml proxy object, but a resolved relationship that reflects an effective arc between one source and one target). -XBRL processing requires element-level access to schema elements. Traditional XML processors, such as -lxml (based on libxml), and Xerces (not available in the Python environment), provide opaque schema -models that cannot be used by an XML processor. Arelle implements its own elment, attribute, and -type processing, in order to provide PSVI-validated element and attribute contents, and in order to +XBRL processing requires element-level access to schema elements. Traditional XML processors, such as +lxml (based on libxml), and Xerces (not available in the Python environment), provide opaque schema +models that cannot be used by an XML processor. Arelle implements its own elment, attribute, and +type processing, in order to provide PSVI-validated element and attribute contents, and in order to access XBRL features that would otherwise be inaccessible in the XML library opaque schema models. -ModelConcept represents a schema element, regardless whether an XBRL item or tuple, or non-concept -schema element. The common XBRL and schema element attributes are provided by Python properties, +ModelConcept represents a schema element, regardless whether an XBRL item or tuple, or non-concept +schema element. The common XBRL and schema element attributes are provided by Python properties, cached when needed for efficiency, somewhat isolating from the XML level implementation. -There is thought that a future SQL-based implementation may be able to utilize ModelObject proxy +There is thought that a future SQL-based implementation may be able to utilize ModelObject proxy objects to interface to SQL-obtained data. -ModelType represents an anonymous or explicit element type. It includes methods that determine -the base XBRL type (such as monetaryItemType), the base XML type (such as decimal), substitution +ModelType represents an anonymous or explicit element type. It includes methods that determine +the base XBRL type (such as monetaryItemType), the base XML type (such as decimal), substitution group chains, facits, and attributes. -ModelAttributeGroup and ModelAttribute provide sufficient mechanism to identify element attributes, +ModelAttributeGroup and ModelAttribute provide sufficient mechanism to identify element attributes, their types, and their default or fixed values. -There is also an inherently different model, modelRelationshipSet, which represents an individual -base or dimensional-relationship set, or a collection of them (such as labels independent of +There is also an inherently different model, modelRelationshipSet, which represents an individual +base or dimensional-relationship set, or a collection of them (such as labels independent of extended link role), based on the semantics of XLink arcs. -PSVI-validated instance data are determined during loading for instance documents, and on demand -for any other objects (such as when formula operations may access linkbase contents and need -PSVI-validated contents of some linkbase elements). These validated items are added to the +PSVI-validated instance data are determined during loading for instance documents, and on demand +for any other objects (such as when formula operations may access linkbase contents and need +PSVI-validated contents of some linkbase elements). These validated items are added to the ModelObject lxml custom proxy objects. -Linkbase objects include modelLink, representing extended link objects, modelResource, -representing resource objects, and modelRelationship, which is not a lxml proxy object, but +Linkbase objects include modelLink, representing extended link objects, modelResource, +representing resource objects, and modelRelationship, which is not a lxml proxy object, but represents a resolved and effective arc in a relationship set. -ModelRelationshipSets are populated on demand according to specific or general characteristics. -A relationship set can be a fully-specified base set, including arcrole, linkrole, link element -qname, and arc element qname. However by not specifying linkrole, link, or arc, a composite -relationship set can be produced for an arcrole accumulating relationships across all extended -link linkroles that have contributing arcs, which may be needed in building indexing or graphical +ModelRelationshipSets are populated on demand according to specific or general characteristics. +A relationship set can be a fully-specified base set, including arcrole, linkrole, link element +qname, and arc element qname. However by not specifying linkrole, link, or arc, a composite +relationship set can be produced for an arcrole accumulating relationships across all extended +link linkroles that have contributing arcs, which may be needed in building indexing or graphical topology top levels. -Relationship sets for dimensional arcroles will honor and traverse targetrole attributes across -linkroles. There is a pseudo-arcrole for dimensions that allows accumulating all dimensional -relationships regardless of arcrole, which is useful for constructing certain graphic tree views. +Relationship sets for dimensional arcroles will honor and traverse targetrole attributes across +linkroles. There is a pseudo-arcrole for dimensions that allows accumulating all dimensional +relationships regardless of arcrole, which is useful for constructing certain graphic tree views. -Relationship sets for table linkbases likewise have a pseudo-arcrole to accumulate all table +Relationship sets for table linkbases likewise have a pseudo-arcrole to accumulate all table relationships regardless of arcrole, for the same purpose. -Relationship sets can identify ineffective arcroles, which is a requirement for SEC and GFM +Relationship sets can identify ineffective arcroles, which is a requirement for SEC and GFM validation. """ +from __future__ import annotations from collections import defaultdict import os, sys from lxml import etree @@ -70,30 +71,30 @@ class ModelRoleType(ModelObject): """ .. class:: ModelRoleType(modelDocument) - + ModelRoleType represents both role type and arcrole type definitions - + :param modelDocument: owner document :type modelDocument: ModelDocument """ def init(self, modelDocument): super(ModelRoleType, self).init(modelDocument) - + @property def isArcrole(self): """(bool) -- True if ModelRoleType declares an arcrole type""" return self.localName == "arcroleType" - + @property def roleURI(self): """(str) -- Value of roleURI attribute""" return self.get("roleURI") - + @property def arcroleURI(self): """(str) -- Value of arcroleURI attribute""" return self.get("arcroleURI") - + @property def cyclesAllowed(self): """(str) -- Value of cyclesAllowed attribute""" @@ -114,9 +115,9 @@ def definitionNotStripped(self): """(str) -- Text of child definition element (not stripped)""" definition = XmlUtil.child(self, XbrlConst.link, "definition") return definition.textValue if definition is not None else None - + @property - def usedOns(self): + def usedOns(self): """( {QName} ) -- Set of PSVI QNames of descendant usedOn elements""" try: return self._usedOns @@ -126,7 +127,7 @@ def usedOns(self): for usedOn in self.iterdescendants("{http://www.xbrl.org/2003/linkbase}usedOn") if isinstance(usedOn,ModelObject)) return self._usedOns - + @property def tableCode(self): """ table code from structural model for presentable table by ELR""" @@ -138,7 +139,7 @@ def tableCode(self): from arelle import TableStructure TableStructure.evaluateRoleTypesTableCodes(self.modelXbrl) return self._tableCode - + @property def propertyView(self): if self.isArcrole: @@ -151,11 +152,11 @@ def propertyView(self): ("definition", self.definition), ("used on", self.usedOns), ("defined in", self.modelDocument.uri)) - + def __repr__(self): return ("{0}[{1}, uri: {2}, definition: {3}, {4} line {5}])" - .format('modelArcroleType' if self.isArcrole else 'modelRoleType', - self.objectIndex, + .format('modelArcroleType' if self.isArcrole else 'modelRoleType', + self.objectIndex, self.arcroleURI if self.isArcrole else self.roleURI, self.definition, self.modelDocument.basename, self.sourceline)) @@ -167,19 +168,19 @@ def viewConcept(self): # concept trees view roles as themselves class ModelNamableTerm(ModelObject): """ .. class:: ModelNamableTerm(modelDocument) - + Particle Model namable term (can have @name attribute) - + :param modelDocument: owner document :type modelDocument: ModelDocument """ def init(self, modelDocument): super(ModelNamableTerm, self).init(modelDocument) - + @property def name(self): return self.getStripped("name") - + @property def qname(self): try: @@ -196,7 +197,7 @@ def qname(self): else: self._xsdQname = None return self._xsdQname - + @property def isGlobalDeclaration(self): parent = self.getparent() @@ -204,7 +205,7 @@ def isGlobalDeclaration(self): def schemaNameQname(self, prefixedName, isQualifiedForm=True, prefixException=None): """Returns ModelValue.QName of prefixedName using this element and its ancestors' xmlns. - + :param prefixedName: A prefixed name string :type prefixedName: str :returns: QName -- the resolved prefixed name, or None if no prefixed name was provided @@ -261,19 +262,19 @@ def maxOccurs(self): self._maxOccurs = sys.maxsize else: self._maxOccurs = _INT(m) - if self._maxOccurs < 0: + if self._maxOccurs < 0: raise ValueError(_("maxOccurs must be positive").format(m)) else: self._maxOccurs = 1 return self._maxOccurs - + @property def maxOccursStr(self): """(str) -- String value of maxOccurs attribute""" if self.maxOccurs == sys.maxsize: return "unbounded" return str(self.maxOccurs) - + @property def minOccurs(self): """(int) -- Value of minOccurs attribute or 1 if absent""" @@ -283,25 +284,25 @@ def minOccurs(self): m = self.get("minOccurs") if m: self._minOccurs = _INT(m) - if self._minOccurs < 0: + if self._minOccurs < 0: raise ValueError(_("minOccurs must be positive").format(m)) else: self._minOccurs = 1 return self._minOccurs - + @property def minOccursStr(self): """(str) -- String value of minOccurs attribute""" - return str(self.minOccurs) + return str(self.minOccurs) anonymousTypeSuffix = "@anonymousType" class ModelConcept(ModelNamableTerm, ModelParticle): """ .. class:: ModelConcept(modelDocument) - + Particle Model element term - + :param modelDocument: owner document :type modelDocument: ModelDocument """ @@ -315,27 +316,27 @@ def init(self, modelDocument): if not self.isGlobalDeclaration: self.addToParticles() self._baseXsdAttrType = {} - + @property def abstract(self): """(str) -- Value of abstract attribute or 'false' if absent""" return self.get("abstract", 'false') - + @property def isAbstract(self): """(bool) -- True if abstract""" return self.abstract in ("true", "1") - + @property def periodType(self): """(str) -- Value of periodType attribute""" return self.get("{http://www.xbrl.org/2003/instance}periodType") - + @property def balance(self): """(str) -- Value of balance attribute""" return self.get("{http://www.xbrl.org/2003/instance}balance") - + @property def typeQname(self): """(QName) -- Value of type attribute, if any, or if type contains an annonymously-named @@ -364,11 +365,11 @@ def typeQname(self): else: self._typeQname = XbrlConst.qnXsdDefaultType return self._typeQname - + @property def niceType(self): - """Provides a type name suited for user interfaces: hypercubes as Table, dimensions as Axis, - types ending in ItemType have ItemType removed and first letter capitalized (e.g., + """Provides a type name suited for user interfaces: hypercubes as Table, dimensions as Axis, + types ending in ItemType have ItemType removed and first letter capitalized (e.g., stringItemType as String). Otherwise returns the type's localName portion. """ if self.isHypercubeItem: return "Table" @@ -378,7 +379,7 @@ def niceType(self): return self.typeQname.localName[0].upper() + self.typeQname.localName[1:-8] return self.typeQname.localName return None - + @property def baseXsdType(self): """(str) -- Value of localname of type (e.g., monetary for monetaryItemType)""" @@ -392,12 +393,12 @@ def baseXsdType(self): type = self.type self._baseXsdType = type.baseXsdType if type is not None else "anyType" return self._baseXsdType - + @property def facets(self): """(dict) -- Facets declared for element type""" return self.type.facets if self.type is not None else None - + ''' unused, remove??? def baseXsdAttrType(self,attrName): try: @@ -410,11 +411,11 @@ def baseXsdAttrType(self,attrName): self._baseXsdAttrType[attrName] = attrType return attrType ''' - + @property def baseXbrliType(self): - """(str) -- Attempts to return the base xsd type localName that this concept's type - is derived from. If not determinable anyType is returned. E.g., for monetaryItemType, + """(str) -- Attempts to return the base xsd type localName that this concept's type + is derived from. If not determinable anyType is returned. E.g., for monetaryItemType, decimal is returned.""" try: return self._baseXbrliType @@ -425,11 +426,11 @@ def baseXbrliType(self): else: self._baseXbrliType = self.type.baseXbrliType if self.type is not None else None return self._baseXbrliType - + @property def baseXbrliTypeQname(self): - """(qname) -- Attempts to return the base xsd type QName that this concept's type - is derived from. If not determinable anyType is returned. E.g., for monetaryItemType, + """(qname) -- Attempts to return the base xsd type QName that this concept's type + is derived from. If not determinable anyType is returned. E.g., for monetaryItemType, decimal is returned.""" try: return self._baseXbrliTypeQname @@ -440,7 +441,7 @@ def baseXbrliTypeQname(self): else: self._baseXbrliTypeQname = self.type.baseXbrliTypeQname if self.type is not None else None return self._baseXbrliTypeQname - + def instanceOfType(self, typeqname): """(bool) -- True if element is declared by, or derived from type of given qname or list of qnames""" if isinstance(typeqname, (tuple,list,set)): # union @@ -453,10 +454,10 @@ def instanceOfType(self, typeqname): if type is not None and type.isDerivedFrom(typeqname): # allows list or single type name return True subs = self.substitutionGroup - if subs is not None: + if subs is not None: return subs.instanceOfType(typeqname) return False - + @property def isNumeric(self): """(bool) -- True for elements of, or derived from, numeric base type (not including fractionItemType)""" @@ -465,7 +466,7 @@ def isNumeric(self): except AttributeError: self._isNumeric = XbrlConst.isNumericXsdType(self.baseXsdType) return self._isNumeric - + @property def isInteger(self): """(bool) -- True for elements of, or derived from, integer base type (not including fractionItemType)""" @@ -474,7 +475,7 @@ def isInteger(self): except AttributeError: self._isInteger = XbrlConst.isIntegerXsdType(self.baseXsdType) return self._isInteger - + @property def isFraction(self): """(bool) -- True if the baseXbrliType is fractionItemType""" @@ -483,7 +484,7 @@ def isFraction(self): except AttributeError: self._isFraction = self.baseXbrliType == "fractionItemType" return self._isFraction - + @property def isMonetary(self): """(bool) -- True if the baseXbrliType is monetaryItemType""" @@ -492,7 +493,7 @@ def isMonetary(self): except AttributeError: self._isMonetary = self.baseXbrliType == "monetaryItemType" return self._isMonetary - + @property def isShares(self): """(bool) -- True if the baseXbrliType is sharesItemType""" @@ -501,12 +502,12 @@ def isShares(self): except AttributeError: self._isShares = self.baseXbrliType == "sharesItemType" return self._isShares - + @property def isTextBlock(self): """(bool) -- Element's type.isTextBlock.""" return self.type is not None and self.type.isTextBlock - + @property def isLanguage(self): """(bool) -- True if the baseXbrliType is languageItemType""" @@ -515,7 +516,7 @@ def isLanguage(self): except AttributeError: self._isLanguage = self.baseXbrliType == "languageItemType" return self._isLanguage - + @property def type(self): """Element's modelType object (if any)""" @@ -524,7 +525,7 @@ def type(self): except AttributeError: self._type = self.modelXbrl.qnameTypes.get(self.typeQname) return self._type - + @property def substitutionGroup(self): """modelConcept object for substitution group (or None)""" @@ -532,7 +533,7 @@ def substitutionGroup(self): if subsgroupqname is not None: return self.modelXbrl.qnameConcepts.get(subsgroupqname) return None - + @property def substitutionGroupQname(self): """(QName) -- substitution group""" @@ -543,7 +544,7 @@ def substitutionGroupQname(self): if self.get("substitutionGroup"): self._substitutionGroupQname = self.schemaNameQname(self.get("substitutionGroup")) return self._substitutionGroupQname - + @property def substitutionGroupQnames(self): # ordered list of all substitution group qnames """([QName]) -- Ordered list of QNames of substitution groups (recursively)""" @@ -555,55 +556,55 @@ def substitutionGroupQnames(self): # ordered list of all substitution group qn subs = subNext subNext = subs.substitutionGroup return qnames - + @property def isQualifiedForm(self): # used only in determining qname, which itself is cached """(bool) -- True if element has form attribute qualified or its document default""" if self.get("form") is not None: # form is almost never used return self.get("form") == "qualified" return getattr(self.modelDocument, "isQualifiedElementFormDefault", False) # parent might not be a schema document - + @property def nillable(self): """(str) --Value of the nillable attribute or its default""" return self.get("nillable", 'false') - + @property def isNillable(self): """(bool) -- True if nillable""" return self.get("nillable") == 'true' - + @property def block(self): """(str) -- block attribute""" return self.get("block") - + @property def default(self): """(str) -- default attribute""" return self.get("default") - + @property def fixed(self): """(str) -- fixed attribute""" return self.get("fixed") - + @property def final(self): """(str) -- final attribute""" return self.get("final") - + @property def isRoot(self): """(bool) -- True if parent of element definition is xsd schema element""" return self.getparent().localName == "schema" - - def label(self,preferredLabel=None,fallbackToQname=True,lang=None,strip=False,linkrole=None,linkroleHint=None): - """Returns effective label for concept, using preferredLabel role (or standard label if None), - absent label falls back to element qname (prefixed name) if specified, lang falls back to - tool-config language if none, leading/trailing whitespace stripped (trimmed) if specified. + + def label(self,preferredLabel=None,fallbackToQname=True,lang=None,strip=False,linkrole=None,linkroleHint=None) -> str | None: + """Returns effective label for concept, using preferredLabel role (or standard label if None), + absent label falls back to element qname (prefixed name) if specified, lang falls back to + tool-config language if none, leading/trailing whitespace stripped (trimmed) if specified. Does not look for generic labels (use superclass genLabel for generic label). - + :param preferredLabel: label role (standard label if not specified) :type preferredLabel: str :param fallbackToQname: if True and no matching label, then element qname is returned @@ -626,24 +627,24 @@ def label(self,preferredLabel=None,fallbackToQname=True,lang=None,strip=False,li if strip: return label.strip() return Locale.rtlString(label, lang=_lang) return str(self.qname) if fallbackToQname else None - - def relationshipToResource(self, resourceObject, arcrole): - """For specified object and resource (all link roles), returns first - modelRelationshipObject that relates from this element to specified resourceObject. + + def relationshipToResource(self, resourceObject, arcrole): + """For specified object and resource (all link roles), returns first + modelRelationshipObject that relates from this element to specified resourceObject. :param resourceObject: resource to find relationship to :type resourceObject: ModelObject :param arcrole: specifies arcrole for search :type arcrole: str :returns: ModelRelationship - """ + """ relationshipSet = self.modelXbrl.relationshipSet(arcrole) if relationshipSet: for modelRel in relationshipSet.fromModelObject(self): if modelRel.toModelObject == resourceObject: return modelRel return None - + @property def isItem(self): """(bool) -- True for a substitution for xbrli:item but not xbrli:item itself""" @@ -654,14 +655,14 @@ def isItem(self): return self._isItem @property - def isTuple(self): + def isTuple(self): """(bool) -- True for a substitution for xbrli:tuple but not xbrli:tuple itself""" try: return self._isTuple except AttributeError: self._isTuple = self.subGroupHeadQname == XbrlConst.qnXbrliTuple and self.namespaceURI != XbrlConst.xbrli return self._isTuple - + @property def isLinkPart(self): """(bool) -- True for a substitution for link:part but not link:part itself""" @@ -670,7 +671,7 @@ def isLinkPart(self): except AttributeError: self._isLinkPart = self.subGroupHeadQname == XbrlConst.qnLinkPart and self.namespaceURI != XbrlConst.link return self._isLinkPart - + @property def isPrimaryItem(self): """(bool) -- True for a concept definition that is not a hypercube or dimension""" @@ -685,7 +686,7 @@ def isPrimaryItem(self): def isDomainMember(self): """(bool) -- Same as isPrimaryItem (same definition in XDT)""" return self.isPrimaryItem # same definition in XDT - + @property def isHypercubeItem(self): """(bool) -- True for a concept definition that is a hypercube""" @@ -694,7 +695,7 @@ def isHypercubeItem(self): except AttributeError: self._isHypercubeItem = self.substitutesForQname(XbrlConst.qnXbrldtHypercubeItem) return self._isHypercubeItem - + @property def isDimensionItem(self): """(bool) -- True for a concept definition that is a dimension""" @@ -703,7 +704,7 @@ def isDimensionItem(self): except AttributeError: self._isDimensionItem = self.substitutesForQname(XbrlConst.qnXbrldtDimensionItem) return self._isDimensionItem - + @property def isTypedDimension(self): """(bool) -- True for a concept definition that is a typed dimension""" @@ -712,12 +713,12 @@ def isTypedDimension(self): except AttributeError: self._isTypedDimension = self.isDimensionItem and self.get("{http://xbrl.org/2005/xbrldt}typedDomainRef") is not None return self._isTypedDimension - + @property def isExplicitDimension(self): """(bool) -- True for a concept definition that is an explicit dimension""" return self.isDimensionItem and not self.isTypedDimension - + @property def typedDomainRef(self): """(str) -- typedDomainRef attribute""" @@ -731,7 +732,7 @@ def typedDomainElement(self): except AttributeError: self._typedDomainElement = self.resolveUri(uri=self.typedDomainRef) return self._typedDomainElement - + @property def isEnumeration(self): """(bool) -- True if derived from enum:enumerationItemType or enum:enumerationsItemType or enum2:setValueDimensionType""" @@ -740,7 +741,7 @@ def isEnumeration(self): except AttributeError: self._isEnum = self.instanceOfType(XbrlConst.qnEnumerationTypes) return self._isEnum - + @property def isEnumeration2Item(self): """(bool) -- True if derived from enum2 item types""" @@ -749,7 +750,7 @@ def isEnumeration2Item(self): except AttributeError: self._isEnum = self.instanceOfType(XbrlConst.qnEnumeration2ItemTypes) return self._isEnum - + @property def enumDomainQname(self): """(QName) -- enumeration domain qname """ @@ -763,12 +764,12 @@ def enumDomain(self): except AttributeError: self._enumDomain = self.modelXbrl.qnameConcepts.get(self.enumDomainQname) return self._enumDomain - + @property def enumLinkrole(self): """(anyURI) -- enumeration linkrole """ return self.get(XbrlConst.attrEnumerationLinkrole2014) or self.get(XbrlConst.attrEnumerationLinkrole2020) or self.get(XbrlConst.attrEnumerationLinkroleYYYY) or self.get(XbrlConst.attrEnumerationLinkrole11YYYY) or self.get(XbrlConst.attrEnumerationLinkrole2016) - + @property def enumDomainUsable(self): """(string) -- enumeration usable attribute """ @@ -793,7 +794,7 @@ def substitutesForQname(self, subsQname): subs = subNext subNext = subs.substitutionGroup return False - + @property def subGroupHeadQname(self): """(QName) -- Head of substitution lineage of element (e.g., xbrli:item)""" @@ -835,10 +836,10 @@ def propertyView(self): for _refPart in _refRel.toModelObject.iterchildren()) _refProperty = ("references", _refsStrung, _refT) if _refT else () _facets = ("facets", ", ".join(sorted(self.facets.keys())), tuple( - (_name, sorted(_value.keys()), - tuple((eVal,eElt.genLabel()) + (_name, sorted(_value.keys()), + tuple((eVal,eElt.genLabel()) for eVal, eElt in sorted(_value.items(), key=lambda i:i[0])) - ) if isinstance(_value,dict) + ) if isinstance(_value,dict) else (_name, _value) for _name, _value in sorted(self.facets.items(), key=lambda i:i[0])) ) if self.facets else () @@ -854,7 +855,7 @@ def propertyView(self): ("balance", self.balance) if self.balance else (), _facets, _refProperty) - + def __repr__(self): return ("modelConcept[{0}, qname: {1}, type: {2}, abstract: {3}, {4}, line {5}]" .format(self.objectIndex, self.qname, self.typeQname, self.abstract, @@ -863,13 +864,13 @@ def __repr__(self): @property def viewConcept(self): return self - + class ModelAttribute(ModelNamableTerm): """ .. class:: ModelAttribute(modelDocument) - + Attribute term - + :param modelDocument: owner document :type modelDocument: ModelDocument """ @@ -879,7 +880,7 @@ def init(self, modelDocument): self.modelXbrl.qnameAttributes[self.qname] = self if not self.isQualifiedForm: self.modelXbrl.qnameAttributes[ModelValue.QName(None, None, self.name)] = self - + @property def typeQname(self): """(QName) -- QName of type of attribute""" @@ -901,7 +902,7 @@ def typeQname(self): return subs.typeQname ''' return None - + @property def type(self): """(ModelType) -- Attribute's modelType object (if any)""" @@ -910,10 +911,10 @@ def type(self): except AttributeError: self._type = self.modelXbrl.qnameTypes.get(self.typeQname) return self._type - + @property def baseXsdType(self): - """(str) -- Attempts to return the base xsd type localName that this attribute's type + """(str) -- Attempts to return the base xsd type localName that this attribute's type is derived from. If not determinable *anyType* is returned""" try: return self._baseXsdType @@ -926,7 +927,7 @@ def baseXsdType(self): type = self.type self._baseXsdType = type.baseXsdType if type is not None else None return self._baseXsdType - + @property def facets(self): """(dict) -- Returns self.type.facets or None (if type indeterminate)""" @@ -936,7 +937,7 @@ def facets(self): type = self.type self._facets = type.facets if type is not None else None return self._facets - + @property def isNumeric(self): """(bool) -- True for a numeric xsd base type (not including xbrl fractions)""" @@ -945,29 +946,29 @@ def isNumeric(self): except AttributeError: self._isNumeric = XbrlConst.isNumericXsdType(self.baseXsdType) return self._isNumeric - + @property def isQualifiedForm(self): # used only in determining qname, which itself is cached """(bool) -- True if attribute has form attribute qualified or its document default""" if self.get("form") is not None: # form is almost never used return self.get("form") == "qualified" return self.modelDocument.isQualifiedAttributeFormDefault - + @property def isRequired(self): """(bool) -- True if use is required""" return self.get("use") == "required" - + @property def default(self): """(str) -- default attribute""" return self.get("default") - + @property def fixed(self): """(str) -- fixed attribute or None""" return self.get("fixed") - + def dereference(self): """(ModelAttribute) -- If element is a ref (instead of name), provides referenced modelAttribute object, else self""" ref = self.get("ref") @@ -979,9 +980,9 @@ def dereference(self): class ModelAttributeGroup(ModelNamableTerm): """ .. class:: ModelAttributeGroup(modelDocument) - + Attribute Group term - + :param modelDocument: owner document :type modelDocument: ModelDocument """ @@ -989,12 +990,12 @@ def init(self, modelDocument): super(ModelAttributeGroup, self).init(modelDocument) if self.isGlobalDeclaration: self.modelXbrl.qnameAttributeGroups[self.qname] = self - + @property - def isQualifiedForm(self): + def isQualifiedForm(self): """(bool) -- True, always qualified""" return True - + @property def attributes(self): """(dict) -- Dict by attribute QName of ModelAttributes""" @@ -1017,7 +1018,7 @@ def attributes(self): if attrDecl is not None: self._attributes[attrDecl.qname] = attrDecl return self._attributes - + @property def attributeWildcards(self): try: @@ -1025,7 +1026,7 @@ def attributeWildcards(self): except AttributeError: self.attributes # loads attrWildcards return self._attributeWildcards - + def dereference(self): """(ModelAttributeGroup) -- If element is a ref (instead of name), provides referenced modelAttributeGroup object, else self""" ref = self.get("ref") @@ -1033,13 +1034,13 @@ def dereference(self): qn = self.schemaNameQname(ref) return self.modelXbrl.qnameAttributeGroups.get(ModelValue.qname(self, ref)) return self - + class ModelType(ModelNamableTerm): """ .. class:: ModelType(modelDocument) - + Type definition term - + :param modelDocument: owner document :type modelDocument: ModelDocument """ @@ -1047,7 +1048,7 @@ def init(self, modelDocument): super(ModelType, self).init(modelDocument) self.modelXbrl.qnameTypes.setdefault(self.qname, self) # don't redefine types nested in anonymous types self.particlesList = ParticlesList() - + @property def name(self): nameAttr = self.getStripped("name") @@ -1061,12 +1062,12 @@ def name(self): return nameAttr + anonymousTypeSuffix element = element.getparent() return None - + @property def isQualifiedForm(self): """(bool) -- True (for compatibility with other schema objects)""" return True - + @property def qnameDerivedFrom(self): """(QName) -- the type that this type is derived from""" @@ -1074,7 +1075,7 @@ def qnameDerivedFrom(self): if isinstance(typeOrUnion,list): # union return [self.schemaNameQname(t) for t in typeOrUnion] return self.schemaNameQname(typeOrUnion) - + @property def typeDerivedFrom(self): """(ModelType) -- type that this type is derived from""" @@ -1084,7 +1085,7 @@ def typeDerivedFrom(self): elif isinstance(qnameDerivedFrom, ModelValue.QName): return self.modelXbrl.qnameTypes.get(qnameDerivedFrom) return None - + @property def particles(self): """([ModelParticles]) -- Particles of this type""" @@ -1094,13 +1095,13 @@ def particles(self): if isinstance(typeDerivedFrom, ModelType): return typeDerivedFrom.particlesList return self.particlesList # empty list - + @property def baseXsdType(self): - """(str) -- The xsd type localName that this type is derived from or: - *noContent* for an element that may not have text nodes, - *anyType* for an element that may have text nodes but their type is not specified, - or one of several union types for schema validation purposes: *XBRLI_DATEUNION*, + """(str) -- The xsd type localName that this type is derived from or: + *noContent* for an element that may not have text nodes, + *anyType* for an element that may have text nodes but their type is not specified, + or one of several union types for schema validation purposes: *XBRLI_DATEUNION*, *XBRLI_DECIMALSUNION*, *XBRLI_PRECISIONUNION*, *XBRLI_NONZERODECIMAL*. """ try: @@ -1121,7 +1122,7 @@ def baseXsdType(self): #self._baseXsdType = "anyType" self._baseXsdType = "noContent" elif isinstance(qnameDerivedFrom,list): # union - if qnameDerivedFrom == XbrlConst.qnDateUnionXsdTypes: + if qnameDerivedFrom == XbrlConst.qnDateUnionXsdTypes: self._baseXsdType = "XBRLI_DATEUNION" elif len(qnameDerivedFrom) == 1: qn0 = qnameDerivedFrom[0] @@ -1132,7 +1133,7 @@ def baseXsdType(self): self._baseXsdType = typeDerivedFrom.baseXsdType if typeDerivedFrom is not None else "anyType" # TBD implement union types else: - self._baseXsdType = "anyType" + self._baseXsdType = "anyType" elif qnameDerivedFrom.namespaceURI == XbrlConst.xsd: self._baseXsdType = qnameDerivedFrom.localName else: @@ -1142,7 +1143,7 @@ def baseXsdType(self): if self._baseXsdType == "anyType" and XmlUtil.emptyContentModel(self): self._baseXsdType = "noContent" return self._baseXsdType - + @property def baseXbrliTypeQname(self): """(qname) -- The qname of the parent type in the xbrli namespace, if any, otherwise the localName of the parent in the xsd namespace.""" @@ -1155,7 +1156,7 @@ def baseXbrliTypeQname(self): else: qnameDerivedFrom = self.qnameDerivedFrom if isinstance(qnameDerivedFrom,list): # union - if qnameDerivedFrom == XbrlConst.qnDateUnionXsdTypes: + if qnameDerivedFrom == XbrlConst.qnDateUnionXsdTypes: self._baseXbrliTypeQname = qnameDerivedFrom # TBD implement union types elif len(qnameDerivedFrom) == 1: @@ -1176,7 +1177,7 @@ def baseXbrliTypeQname(self): else: self._baseXbrliType = None return self._baseXbrliTypeQname - + @property def baseXbrliType(self): """(str) -- The localName of the parent type in the xbrli namespace, if any, otherwise the localName of the parent in the xsd namespace.""" @@ -1185,17 +1186,17 @@ def baseXbrliType(self): except AttributeError: baseXbrliTypeQname = self.baseXbrliTypeQname if isinstance(baseXbrliTypeQname,list): # union - if baseXbrliTypeQname == XbrlConst.qnDateUnionXsdTypes: + if baseXbrliTypeQname == XbrlConst.qnDateUnionXsdTypes: self._baseXbrliType = "XBRLI_DATEUNION" # TBD implement union types else: - self._baseXbrliType = "anyType" + self._baseXbrliType = "anyType" elif baseXbrliTypeQname is not None: self._baseXbrliType = baseXbrliTypeQname.localName else: self._baseXbrliType = None return self._baseXbrliType - + @property def isTextBlock(self): """(str) -- True if type is, or is derived from, us-types:textBlockItemType or dtr-types:escapedItemType""" @@ -1209,7 +1210,7 @@ def isTextBlock(self): return False typeDerivedFrom = self.modelXbrl.qnameTypes.get(qnameDerivedFrom) return typeDerivedFrom.isTextBlock if typeDerivedFrom is not None else False - + @property def isOimTextFactType(self): """(str) -- True if type meets OIM requirements to be a text fact""" @@ -1247,12 +1248,12 @@ def isDomainItemType(self): return False typeDerivedFrom = self.modelXbrl.qnameTypes.get(qnameDerivedFrom) return typeDerivedFrom.isDomainItemType if typeDerivedFrom is not None else False - + @property def isMultiLanguage(self): """(bool) -- True if type is, or is derived from, stringItemType or normalizedStringItemType.""" return self.baseXbrliType in {"stringItemType", "normalizedStringItemType", "string", "normalizedString"} - + def isDerivedFrom(self, typeqname): """(bool) -- True if type is derived from type specified by QName. Type can be a single type QName or list of QNames""" qnamesDerivedFrom = self.qnameDerivedFrom # can be single qname or list of qnames if union @@ -1278,8 +1279,8 @@ def isDerivedFrom(self, typeqname): if typeDerivedFrom is not None and typeDerivedFrom.isDerivedFrom(typeqname): return True return False - - + + @property def attributes(self): """(dict) -- Dict of ModelAttribute attribute declarations keyed by attribute QName""" @@ -1307,7 +1308,7 @@ def attributes(self): self._attributes.update(t.attributes) self._attributeWildcards.extend(t.attributeWildcards) return self._attributes - + @property def attributeWildcards(self): """(dict) -- List of wildcard namespace strings (e.g., ##other)""" @@ -1325,7 +1326,7 @@ def requiredAttributeQnames(self): except AttributeError: self._requiredAttributeQnames = set(a.qname for a in self.attributes.values() if a.isRequired) return self._requiredAttributeQnames - + @property def defaultAttributeQnames(self): """(set) -- Set of attribute QNames which have a default specified""" @@ -1334,7 +1335,7 @@ def defaultAttributeQnames(self): except AttributeError: self._defaultAttributeQnames = set(a.qname for a in self.attributes.values() if a.default is not None) return self._defaultAttributeQnames - + @property def elements(self): """([QName]) -- List of element QNames that are descendants (content elements)""" @@ -1343,7 +1344,7 @@ def elements(self): except AttributeError: self._elements = XmlUtil.schemaDescendantsNames(self, XbrlConst.xsd, "element") return self._elements - + @property def facets(self): """(dict) -- Dict of facets by their facet name, all are strings except enumeration, which is a set of enumeration values.""" @@ -1353,16 +1354,16 @@ def facets(self): facets = self.constrainingFacets() self._facets = facets if facets else None return self._facets - + def constrainingFacets(self, facetValues=None): - """helper function for facets discovery""" + """helper function for facets discovery""" if facetValues is None: facetValues = {} for facetElt in XmlUtil.schemaFacets(self, ( - "{http://www.w3.org/2001/XMLSchema}length", "{http://www.w3.org/2001/XMLSchema}minLength", - "{http://www.w3.org/2001/XMLSchema}maxLength", - "{http://www.w3.org/2001/XMLSchema}pattern", "{http://www.w3.org/2001/XMLSchema}whiteSpace", - "{http://www.w3.org/2001/XMLSchema}maxInclusive", "{http://www.w3.org/2001/XMLSchema}minInclusive", - "{http://www.w3.org/2001/XMLSchema}maxExclusive", "{http://www.w3.org/2001/XMLSchema}minExclusive", + "{http://www.w3.org/2001/XMLSchema}length", "{http://www.w3.org/2001/XMLSchema}minLength", + "{http://www.w3.org/2001/XMLSchema}maxLength", + "{http://www.w3.org/2001/XMLSchema}pattern", "{http://www.w3.org/2001/XMLSchema}whiteSpace", + "{http://www.w3.org/2001/XMLSchema}maxInclusive", "{http://www.w3.org/2001/XMLSchema}minInclusive", + "{http://www.w3.org/2001/XMLSchema}maxExclusive", "{http://www.w3.org/2001/XMLSchema}minExclusive", "{http://www.w3.org/2001/XMLSchema}totalDigits", "{http://www.w3.org/2001/XMLSchema}fractionDigits")): facetValue = XmlValidate.validateFacet(self, facetElt) facetName = facetElt.localName @@ -1375,7 +1376,7 @@ def constrainingFacets(self, facetValues=None): if isinstance(typeDerivedFrom, ModelType): typeDerivedFrom.constrainingFacets(facetValues) return facetValues - + def fixedOrDefaultAttrValue(self, attrName): """(str) -- Descendant attribute declaration value if fixed or default, argument is attribute name (string), e.g., 'precision'.""" attr = XmlUtil.schemaDescendant(self, XbrlConst.xsd, "attribute", attrName) @@ -1389,7 +1390,7 @@ def fixedOrDefaultAttrValue(self, attrName): def dereference(self): """(ModelType) -- If element is a ref (instead of name), provides referenced modelType object, else self""" return self - + @property def propertyView(self): return (("namespace", self.qname.namespaceURI), @@ -1398,18 +1399,18 @@ def propertyView(self): ("xsd type", self.baseXsdType), ("derived from", self.qnameDerivedFrom), ("facits", self.facets)) - + def __repr__(self): return ("modelType[{0}, qname: {1}, derivedFrom: {2}, {3}, line {4}]" .format(self.objectIndex, self.qname, self.qnameDerivedFrom, self.modelDocument.basename, self.sourceline)) - + class ModelGroupDefinition(ModelNamableTerm, ModelParticle): """ .. class:: ModelGroupDefinition(modelDocument) - + Group definition particle term - + :param modelDocument: owner document :type modelDocument: ModelDocument """ @@ -1428,18 +1429,18 @@ def dereference(self): qn = self.schemaNameQname(ref) return self.modelXbrl.qnameGroupDefinitions.get(qn) return self - + @property def isQualifiedForm(self): """(bool) -- True (for compatibility with other schema objects)""" return True - + class ModelGroupCompositor(ModelObject, ModelParticle): """ .. class:: ModelGroupCompositor(modelDocument) - + Particle Model group compositor term (sequence, choice, or all) - + :param modelDocument: owner document :type modelDocument: ModelDocument """ @@ -1451,25 +1452,25 @@ def init(self, modelDocument): def dereference(self): """(ModelGroupCompositor) -- If element is a ref (instead of name), provides referenced ModelGroupCompositor object, else self""" return self - + class ModelAll(ModelGroupCompositor): """ .. class:: ModelAll(modelDocument) - + Particle Model all term - + :param modelDocument: owner document :type modelDocument: ModelDocument """ def init(self, modelDocument): super(ModelAll, self).init(modelDocument) - + class ModelChoice(ModelGroupCompositor): """ .. class:: ModelChoice(modelDocument) - + Particle Model choice term - + :param modelDocument: owner document :type modelDocument: ModelDocument """ @@ -1479,9 +1480,9 @@ def init(self, modelDocument): class ModelSequence(ModelGroupCompositor): """ .. class:: ModelSequence(modelDocument) - + Particle Model sequence term - + :param modelDocument: owner document :type modelDocument: ModelDocument """ @@ -1491,9 +1492,9 @@ def init(self, modelDocument): class ModelAny(ModelObject, ModelParticle): """ .. class:: ModelAny(modelDocument) - + Particle Model any term - + :param modelDocument: owner document :type modelDocument: ModelDocument """ @@ -1503,7 +1504,7 @@ def init(self, modelDocument): def dereference(self): return self - + def allowsNamespace(self, namespaceURI): try: if self._isAny: @@ -1518,7 +1519,7 @@ def allowsNamespace(self, namespaceURI): else: # not equal namespaces if "##other" in self._namespaces: return True - return False + return False except AttributeError: self._namespaces = self.get("namespace", '').split() self._isAny = (not self._namespaces) or "##any" in self._namespaces @@ -1527,15 +1528,15 @@ def allowsNamespace(self, namespaceURI): class ModelAnyAttribute(ModelObject): """ .. class:: ModelAnyAttribute(modelDocument) - + Any attribute definition term - + :param modelDocument: owner document :type modelDocument: ModelDocument """ def init(self, modelDocument): super(ModelAnyAttribute, self).init(modelDocument) - + def allowsNamespace(self, namespaceURI): try: if self._isAny: @@ -1550,7 +1551,7 @@ def allowsNamespace(self, namespaceURI): else: # not equal namespaces if "##other" in self._namespaces: return True - return False + return False except AttributeError: self._namespaces = self.get("namespace", '').split() self._isAny = (not self._namespaces) or "##any" in self._namespaces @@ -1559,32 +1560,32 @@ def allowsNamespace(self, namespaceURI): class ModelEnumeration(ModelNamableTerm): """ .. class:: ModelEnumeration(modelDocument) - + Facet enumeration term - + :param modelDocument: owner document :type modelDocument: ModelDocument """ def init(self, modelDocument): super(ModelEnumeration, self).init(modelDocument) - + @property def value(self): return self.get("value") - + class ModelLink(ModelObject): """ .. class:: ModelLink(modelDocument) - + XLink extended link element - + :param modelDocument: owner document :type modelDocument: ModelDocument """ def init(self, modelDocument): super(ModelLink, self).init(modelDocument) self.labeledResources = defaultdict(list) - + @property def role(self): return self.get("{http://www.w3.org/1999/xlink}role") @@ -1592,9 +1593,9 @@ def role(self): class ModelResource(ModelObject): """ .. class:: ModelResource(modelDocument) - + XLink resource element - + :param modelDocument: owner document :type modelDocument: ModelDocument """ @@ -1604,12 +1605,12 @@ def init(self, modelDocument): self.modelXbrl.langs.add(self.xmlLang) if self.localName == "label": self.modelXbrl.labelroles.add(self.role) - + @property def role(self): """(str) -- xlink:role attribute""" return self.get("{http://www.w3.org/1999/xlink}role") - + @property def xlinkLabel(self): """(str) -- xlink:label attribute""" @@ -1621,43 +1622,43 @@ def xmlLang(self): Note that xml.xsd specifies that an empty string xml:lang attribute is an un-declaration of the attribute, as if the attribute were not present. When absent or un-declared, returns None.""" return XmlUtil.ancestorOrSelfAttr(self, "{http://www.w3.org/XML/1998/namespace}lang") or None - + def viewText(self, labelrole=None, lang=None): - """(str) -- Text of contained (inner) text nodes except for any whose localName + """(str) -- Text of contained (inner) text nodes except for any whose localName starts with URI, for label and reference parts displaying purposes. (Footnotes, which return serialized html content of footnote.)""" if self.qname == XbrlConst.qnLinkFootnote: return XmlUtil.innerText(self, ixEscape="html", strip=True) # include HTML construct return " ".join([XmlUtil.text(resourceElt) for resourceElt in self.iter() - if isinstance(resourceElt,ModelObject) and + if isinstance(resourceElt,ModelObject) and not resourceElt.localName.startswith("URI")]) - + def roleRefPartSortKey(self): return "{} {}".format(self.role, " ".join("{} {}".format(_refPart.localName, _refPart.stringValue.strip()) for _refPart in self.iterchildren()))[:200] # limit length of sort - + def dereference(self): return self - + class ModelLocator(ModelResource): """ .. class:: ModelLocator(modelDocument) - + XLink locator element - + :param modelDocument: owner document :type modelDocument: ModelDocument """ def init(self, modelDocument): super(ModelLocator, self).init(modelDocument) - + def dereference(self): - """(ModelObject) -- Resolve loc's href if resource is a loc with href document and id modelHref a tuple with + """(ModelObject) -- Resolve loc's href if resource is a loc with href document and id modelHref a tuple with href's element, modelDocument, id""" return self.resolveUri(self.modelHref) - + @property def propertyView(self): global ModelFact @@ -1677,16 +1678,16 @@ class RelationStatus: OVERRIDDEN = 2 PROHIBITED = 3 INEFFECTIVE = 4 - + arcCustAttrsExclusions = {XbrlConst.xlink, "use","priority","order","weight","preferredLabel"} - + class ModelRelationship(ModelObject): """ .. class:: ModelRelationship(modelDocument, arcElement, fromModelObject, toModelObject) - - ModelRelationship is a ModelObject that does not proxy an lxml object (but instead references + + ModelRelationship is a ModelObject that does not proxy an lxml object (but instead references ModelObject arc elements, and the from and to ModelObject elements. - + :param modelDocument: Owning modelDocument object :type modelDocument: ModelDocument :param arcElement: lxml arc element that was resolved into this relationship object @@ -1695,16 +1696,16 @@ class ModelRelationship(ModelObject): :type fromModelObject: ModelObject :param toModelObject: the to lxml resource element of the target of the relationship :type toModelObject: ModelObject - + Includes properties that proxy the referenced modelArc: localName, namespaceURI, prefixedName, sourceline, tag, elementQname, qname, - and methods that proxy methods of modelArc: get() and itersiblings() + and methods that proxy methods of modelArc: get() and itersiblings() .. attribute:: arcElement - + ModelObject arc element of the effective relationship .. attribute:: fromModelObject - + ModelObject of the xlink:from (dereferenced if via xlink:locator) .. attribute:: toModelObject @@ -1717,69 +1718,69 @@ def __init__(self, modelDocument, arcElement, fromModelObject, toModelObject): self.init(modelDocument) self.fromModelObject = fromModelObject self.toModelObject = toModelObject - + def clear(self): self.__dict__.clear() # dereference here, not an lxml object, don't use superclass clear() - + # simulate etree operations def get(self, attrname): - """Method proxy for the arc element of the effective relationship so that the non-proxy + """Method proxy for the arc element of the effective relationship so that the non-proxy """ return self.arcElement.get(attrname) - + @property def localName(self): """(str) -- Property proxy for localName of arc element""" return self.arcElement.localName - + @property def namespaceURI(self): """(str) -- Property proxy for namespaceURI of arc element""" return self.arcElement.namespaceURI - + @property def prefixedName(self): """(str) -- Property proxy for prefixedName of arc element""" return self.arcElement.prefixedName - + @property def sourceline(self): """(int) -- Property proxy for sourceline of arc element""" return self.arcElement.sourceline - + @property def tag(self): """(str) -- Property proxy for tag of arc element (clark notation)""" return self.arcElement.tag - + @property def elementQname(self): """(QName) -- Property proxy for elementQName of arc element""" return self.arcElement.elementQname - + @property def qname(self): """(QName) -- Property proxy for qname of arc element""" return self.arcElement.qname - + def itersiblings(self, **kwargs): """Method proxy for itersiblings() of lxml arc element""" return self.arcElement.itersiblings(**kwargs) - + def getparent(self): """(_ElementBase) -- Method proxy for getparent() of lxml arc element""" return self.arcElement.getparent() - + @property def fromLabel(self): """(str) -- Value of xlink:from attribute""" return self.arcElement.get("{http://www.w3.org/1999/xlink}from") - + @property def toLabel(self): """(str) -- Value of xlink:to attribute""" return self.arcElement.get("{http://www.w3.org/1999/xlink}to") - + @property def fromLocator(self): """(ModelLocator) -- Value of locator surrogate of relationship source, if any""" @@ -1787,7 +1788,7 @@ def fromLocator(self): if isinstance(fromResource, ModelLocator) and self.fromModelObject is fromResource.dereference(): return fromResource return None - + @property def toLocator(self): """(ModelLocator) -- Value of locator surrogate of relationship target, if any""" @@ -1795,7 +1796,7 @@ def toLocator(self): if isinstance(toResource, ModelLocator) and self.toModelObject is toResource.dereference(): return toResource return None - + def locatorOf(self, dereferencedObject): """(ModelLocator) -- Value of locator surrogate of relationship target, if any""" fromLocator = self.fromLocator @@ -1805,7 +1806,7 @@ def locatorOf(self, dereferencedObject): if toLocator is not None and toLocator.dereference() == dereferencedObject: return toLocator return None - + @property def arcrole(self): """(str) -- Value of xlink:arcrole attribute""" @@ -1894,17 +1895,17 @@ def weightDecimal(self): def use(self): """(str) -- Value of use attribute""" return self.get("use") - + @property def isProhibited(self): """(bool) -- True if use is prohibited""" return self.use == "prohibited" - + @property def prohibitedUseSortKey(self): """(int) -- 2 if use is prohibited, else 1, for use in sorting effective arcs before prohibited arcs""" return 2 if self.isProhibited else 1 - + @property def preferredLabel(self): """(str) -- preferredLabel attribute or None if absent""" @@ -1925,37 +1926,37 @@ def variableQname(self): def linkrole(self): """(str) -- Value of xlink:role attribute of parent extended link element""" return self.arcElement.getparent().get("{http://www.w3.org/1999/xlink}role") - + @property def linkQname(self): """(QName) -- qname of the parent extended link element""" return self.arcElement.getparent().elementQname - + @property def contextElement(self): """(str) -- Value of xbrldt:contextElement attribute (on applicable XDT arcs)""" return self.get("{http://xbrl.org/2005/xbrldt}contextElement") - + @property def targetRole(self): """(str) -- Value of xbrldt:targetRole attribute (on applicable XDT arcs)""" return self.get("{http://xbrl.org/2005/xbrldt}targetRole") - + @property def consecutiveLinkrole(self): """(str) -- Value of xbrldt:targetRole attribute, if provided, else parent linkRole (on applicable XDT arcs)""" return self.targetRole or self.linkrole - + @property def isUsable(self): """(bool) -- True if xbrldt:usable is true (on applicable XDT arcs, defaults to True if absent)""" return self.get("{http://xbrl.org/2005/xbrldt}usable") in ("true","1", None) - + @property def closed(self): """(str) -- Value of xbrldt:closed (on applicable XDT arcs, defaults to 'false' if absent)""" return self.get("{http://xbrl.org/2005/xbrldt}closed") or "false" - + @property def isClosed(self): """(bool) -- True if xbrldt:closed is true (on applicable XDT arcs, defaults to False if absent)""" @@ -1985,7 +1986,7 @@ def isComplemented(self): except AttributeError: self._isComplemented = self.get("complement") in ("true","1") return self._isComplemented - + @property def isCovered(self): """(bool) -- True if cover is true (on applicable formula/rendering arcs, defaults to False if absent)""" @@ -1994,7 +1995,7 @@ def isCovered(self): except AttributeError: self._isCovered = self.get("cover") in ("true","1") return self._isCovered - + @property def axisDisposition(self): """(str) -- Value of axisDisposition (on applicable table linkbase arcs""" @@ -2009,33 +2010,33 @@ def axisDisposition(self): elif aType in ("zAxis","z"): self._axisDisposition = "z" else: self._axisDisposition = None return self._axisDisposition - + @property def equivalenceHash(self): # not exact, use equivalenceKey if hashes are the same - return hash((self.qname, + return hash((self.qname, self.linkQname, self.linkrole, # needed when linkrole=None merges multiple links - self.fromModelObject.objectIndex if isinstance(self.fromModelObject, ModelObject) else -1, - self.toModelObject.objectIndex if isinstance(self.toModelObject, ModelObject) else -1, - self.order, - self.weight, + self.fromModelObject.objectIndex if isinstance(self.fromModelObject, ModelObject) else -1, + self.toModelObject.objectIndex if isinstance(self.toModelObject, ModelObject) else -1, + self.order, + self.weight, self.preferredLabel)) - + @property def equivalenceKey(self): """(tuple) -- Key to determine relationship equivalence per 2.1 spec""" # cannot be cached because this is unique per relationship - return (self.qname, + return (self.qname, self.linkQname, self.linkrole, # needed when linkrole=None merges multiple links - self.fromModelObject.objectIndex if isinstance(self.fromModelObject, ModelObject) else -1, - self.toModelObject.objectIndex if isinstance(self.toModelObject, ModelObject) else -1, - self.order, - self.weight, + self.fromModelObject.objectIndex if isinstance(self.fromModelObject, ModelObject) else -1, + self.toModelObject.objectIndex if isinstance(self.toModelObject, ModelObject) else -1, + self.order, + self.weight, self.preferredLabel) + \ - XbrlUtil.attributes(self.modelXbrl, self.arcElement, + XbrlUtil.attributes(self.modelXbrl, self.arcElement, exclusions=arcCustAttrsExclusions, keyByTag=True) # use clark tag for key instead of qname - + def isIdenticalTo(self, otherModelRelationship): """(bool) -- Determines if relationship is identical to another, based on arc and identical from and to objects""" return (otherModelRelationship is not None and @@ -2058,7 +2059,7 @@ def priorityOver(self, otherModelRelationship): if otherModelRelationship.isProhibited: return False return True - + @property def propertyView(self): return self.toModelObject.propertyView + \ @@ -2066,10 +2067,10 @@ def propertyView(self): ("weight", self.weight) if self.arcrole == XbrlConst.summationItem else (), ("preferredLabel", self.preferredLabel) if self.arcrole == XbrlConst.parentChild and self.preferredLabel else (), ("contextElement", self.contextElement) if self.arcrole in (XbrlConst.all, XbrlConst.notAll) else (), - ("typedDomain", self.toModelObject.typedDomainElement.qname) + ("typedDomain", self.toModelObject.typedDomainElement.qname) if self.arcrole == XbrlConst.hypercubeDimension and isinstance(self.toModelObject,ModelConcept) and - self.toModelObject.isTypedDimension and + self.toModelObject.isTypedDimension and self.toModelObject.typedDomainElement is not None else (), ("closed", self.closed) if self.arcrole in (XbrlConst.all, XbrlConst.notAll) else (), ("usable", self.usable) if self.arcrole == XbrlConst.domainMember else (), @@ -2077,7 +2078,7 @@ def propertyView(self): ("order", self.order), ("priority", self.priority)) + \ (("from", self.fromModelObject.qname),) if isinstance(self.fromModelObject,ModelObject) else () - + def __repr__(self): return ("modelRelationship[{0}, linkrole: {1}, arcrole: {2}, from: {3}, to: {4}, {5}, line {6}]" .format(self.objectIndex, os.path.basename(self.linkrole), os.path.basename(self.arcrole), @@ -2092,7 +2093,7 @@ def viewConcept(self): elif isinstance(self.fromModelObject, ModelConcept): return self.fromModelObject return None - + from arelle.ModelObjectFactory import elementSubstitutionModelClass elementSubstitutionModelClass.update(( (XbrlConst.qnXlExtended, ModelLink), diff --git a/arelle/ModelXbrl.py b/arelle/ModelXbrl.py index 802824eb0f..b8ea46e8cc 100644 --- a/arelle/ModelXbrl.py +++ b/arelle/ModelXbrl.py @@ -7,7 +7,7 @@ from __future__ import annotations from collections import defaultdict import os, sys, re, traceback, uuid -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, cast import logging from decimal import Decimal from arelle import UrlUtil, XmlUtil, ModelValue, XbrlConst, XmlValidate @@ -266,6 +266,7 @@ class ModelXbrl: """ + ixdsHtmlElements: list uriDir: str def __init__(self, modelManager, errorCaptureLevel=None): @@ -412,11 +413,11 @@ def baseSetModelLink(self, linkElement): def roleUriTitle(self, roleURI): return re.sub(r"([A-Z])",r" \1", os.path.basename(roleURI)).title() - def roleTypeDefinition(self, roleURI, lang=None): + def roleTypeDefinition(self, roleURI, lang=None) -> str: modelRoles = self.roleTypes.get(roleURI, ()) if modelRoles: _roleType = modelRoles[0] - return _roleType.genLabel(lang=lang, strip=True) or _roleType.definition or self.roleUriTitle(roleURI) + return cast(str, _roleType.genLabel(lang=lang, strip=True) or _roleType.definition or self.roleUriTitle(roleURI)) return self.roleUriTitle(roleURI) def roleTypeName(self, roleURI, lang=None): @@ -1287,7 +1288,7 @@ def profileStat(self, name=None, stat=None): if stat is None: self._startedTimeStat = time.time() - def profileActivity(self, activityCompleted=None, minTimeToShow=0): + def profileActivity(self, activityCompleted=None, minTimeToShow=0) -> None: """Used to provide interactive GUI messages of long-running processes. When the time between last profileActivity and this profileActivity exceeds minTimeToShow, then diff --git a/arelle/PackageManager.py b/arelle/PackageManager.py index 595adb1679..4072d5b3cd 100644 --- a/arelle/PackageManager.py +++ b/arelle/PackageManager.py @@ -20,8 +20,8 @@ try: from collections import OrderedDict except ImportError: - OrderedDict = dict # python 3.0 lacks OrderedDict, json file will be in weird order - + OrderedDict = dict # python 3.0 lacks OrderedDict, json file will be in weird order + TP_XSD = "http://www.xbrl.org/2016/taxonomy-package.xsd" CAT_XSD = "http://www.xbrl.org/2016/taxonomy-package-catalog.xsd" @@ -40,7 +40,7 @@ def baseForElement(element): base = baseAttr + base baseElt = baseElt.getparent() return base - + def xmlLang(element): return (element.xpath('@xml:lang') + element.xpath('ancestor::*/@xml:lang') + [''])[0] @@ -57,7 +57,7 @@ def parsePackage(cntlr, filesource, metadataFile, fileBase, errors=[]): from arelle.FileSource import ArchiveFileIOError unNamedCounter = 1 - + txmyPkgNSes = ("http://www.corefiling.com/xbrl/taxonomypackage/v1", "http://xbrl.org/PWD/2014-01-15/taxonomy-package", "http://xbrl.org/PWD/2015-01-14/taxonomy-package", @@ -65,7 +65,7 @@ def parsePackage(cntlr, filesource, metadataFile, fileBase, errors=[]): "http://xbrl.org/2016/taxonomy-package", "http://xbrl.org/WGWD/YYYY-MM-DD/taxonomy-package") catalogNSes = ("urn:oasis:names:tc:entity:xmlns:xml:catalog",) - + pkg = {} currentLang = Locale.getLanguageCode() @@ -84,11 +84,11 @@ def parsePackage(cntlr, filesource, metadataFile, fileBase, errors=[]): level=logging.ERROR) errors.append("tpe:invalidMetaDataFile") return pkg - + root = tree.getroot() ns = root.tag.partition("}")[0][1:] nsPrefix = "{{{}}}".format(ns) - + if ns in txmyPkgNSes: # package file for eltName in ("identifier", "version", "license", "publisher", "publisherURL", "publisherCountry", "publicationDate"): pkg[eltName] = '' @@ -148,14 +148,14 @@ def parsePackage(cntlr, filesource, metadataFile, fileBase, errors=[]): del langElts # dereference else: # oasis catalog, use dirname as the package name - # metadataFile may be a File object (with name) or string filename - fileName = getattr(metadataFile, 'fileName', # for FileSource named objects + # metadataFile may be a File object (with name) or string filename + fileName = getattr(metadataFile, 'fileName', # for FileSource named objects getattr(metadataFile, 'name', # for io.file named objects metadataFile)) # for string pkg["name"] = os.path.basename(os.path.dirname(fileName)) pkg["description"] = "oasis catalog" pkg["version"] = "(none)" - + remappings = {} rewriteTree = tree catalogFile = metadataFile @@ -217,7 +217,7 @@ def parsePackage(cntlr, filesource, metadataFile, fileBase, errors=[]): for entryPointSpec in tree.iter(tag=nsPrefix + "entryPoint"): name = None closestLen = 0 - + # find closest match name node given xml:lang match to current language or no xml:lang for nameNode in entryPointSpec.iter(tag=nsPrefix + "name"): s = (nameNode.text or "").strip() @@ -241,9 +241,9 @@ def parsePackage(cntlr, filesource, metadataFile, fileBase, errors=[]): resolvedUrl = urljoin(base, epUrl) else: resolvedUrl = epUrl - + epDocCount += 1 - + #perform prefix remappings remappedUrl = resolvedUrl longestPrefix = 0 @@ -259,7 +259,7 @@ def parsePackage(cntlr, filesource, metadataFile, fileBase, errors=[]): longestPrefix = prefixLength if longestPrefix: remappedUrl = _remappedUrl.replace(os.sep, "/") # always used as FileSource select - + # find closest language description closest = '' closestLen = 0 @@ -304,14 +304,14 @@ def init(cntlr, loadPackagesConfig=True): packagesConfigChanged = False # don't save until something is added to pluginConfig pluginMethodsForClasses = {} # dict by class of list of ordered callable function objects _cntlr = cntlr - + def reset(): # force reloading modules and plugin infos packagesConfig.clear() # dict of loaded module pluginInfo objects by module names packagesMappings.clear() # dict by class of list of ordered callable function objects - + def orderedPackagesConfig(): return OrderedDict( - (('packages', [OrderedDict(sorted(_packageInfo.items(), + (('packages', [OrderedDict(sorted(_packageInfo.items(), key=lambda k: {'name': '01', 'status': '02', 'version': '03', @@ -319,17 +319,17 @@ def orderedPackagesConfig(): 'license': '05', 'URL': '06', 'description': '07', - "publisher": '08', + "publisher": '08', "publisherURL": '09', - "publisherCountry": '10', + "publisherCountry": '10', "publicationDate": '11', - "supersededTaxonomyPackages": '12', + "supersededTaxonomyPackages": '12', "versioningReports": '13', 'remappings': '14', }.get(k[0],k[0]))) for _packageInfo in packagesConfig['packages']]), ('remappings',OrderedDict(sorted(packagesConfig['remappings'].items()))))) - + def save(cntlr): global packagesConfigChanged if packagesConfigChanged and cntlr.hasFileSystem: @@ -337,13 +337,13 @@ def save(cntlr): jsonStr = _STR_UNICODE(json.dumps(orderedPackagesConfig(), ensure_ascii=False, indent=2)) # might not be unicode in 2.7 f.write(jsonStr) packagesConfigChanged = False - + def close(): # close all loaded methods packagesConfig.clear() packagesMappings.clear() global webCache webCache = None - + ''' packagesConfig structure { @@ -375,7 +375,7 @@ def packageNamesWithNewerFileDates(): pass return names -def validateTaxonomyPackage(cntlr, filesource, packageFiles=[], errors=[]): +def validateTaxonomyPackage(cntlr, filesource, packageFiles=[], errors=[]) -> bool: numErrorsOnEntry = len(errors) _dir = filesource.dir if not _dir: @@ -467,7 +467,7 @@ def packageInfo(cntlr, URL, reload=False, packageManifestName=None, errors=[]): packageFiles = [pf for pf in packageFiles if pf.startswith(_metaInf)] elif any(pf.startswith('META-INF/') for pf in packageFiles) and any(not pf.startswith('META-INF/') for pf in packageFiles): packageFiles = [pf for pf in packageFiles if pf.startswith('META-INF/')] - + for packageFile in packageFiles: packageFileUrl = filesource.url + os.sep + packageFile packageFilePrefix = os.sep.join(os.path.split(packageFile)[:-1]) @@ -482,7 +482,7 @@ def packageInfo(cntlr, URL, reload=False, packageManifestName=None, errors=[]): level=logging.ERROR) errors.append("tpe:invalidArchiveFormat") if (os.path.basename(filesource.url) in TAXONOMY_PACKAGE_FILE_NAMES or # individual manifest file - (os.path.basename(filesource.url) == "taxonomyPackage.xml" and + (os.path.basename(filesource.url) == "taxonomyPackage.xml" and os.path.basename(os.path.dirname(filesource.url)) == "META-INF")): packageFile = packageFileUrl = filesource.url packageFilePrefix = os.path.dirname(packageFile) @@ -522,11 +522,11 @@ def packageInfo(cntlr, URL, reload=False, packageManifestName=None, errors=[]): 'entryPoints': parsedPackage.get('entryPoints', {}), 'manifestName': packageManifestName, 'description': "; ".join(descriptions), - 'publisher': parsedPackage.get('publisher'), + 'publisher': parsedPackage.get('publisher'), 'publisherURL': parsedPackage.get('publisherURL'), - 'publisherCountry': parsedPackage.get('publisherCountry'), + 'publisherCountry': parsedPackage.get('publisherCountry'), 'publicationDate': parsedPackage.get('publicationDate'), - 'supersededTaxonomyPackages': parsedPackage.get('supersededTaxonomyPackages'), + 'supersededTaxonomyPackages': parsedPackage.get('supersededTaxonomyPackages'), 'versioningReports': parsedPackage.get('versioningReports'), 'remappings': remappings, } @@ -553,7 +553,7 @@ def rebuildRemappings(cntlr): _prefix, _packageURL, _rewrite = _remap for j in range(i-1, -1, -1): _prefix2, _packageURL2, _rewrite2 = remapOverlapUrls[j] - if (_packageURL != _packageURL2 and _prefix and _prefix2 and + if (_packageURL != _packageURL2 and _prefix and _prefix2 and (_prefix.startswith(_prefix2) or _prefix2.startswith(_prefix))): _url1 = os.path.basename(_packageURL) _url2 = os.path.basename(_packageURL2) @@ -567,7 +567,7 @@ def rebuildRemappings(cntlr): messageCode="arelle.packageRewriteOverlap", file=(_url1, _url2), level=logging.WARNING) - + def isMappedUrl(url): return (packagesConfig is not None and url is not None and diff --git a/arelle/PythonUtil.py b/arelle/PythonUtil.py index b7632dc4b2..1285728070 100644 --- a/arelle/PythonUtil.py +++ b/arelle/PythonUtil.py @@ -31,7 +31,7 @@ def noop(x): return x __builtins__['_STR_NUM_TYPES'] = (basestring,int,long,float,Decimal,Fraction) __builtins__['_RANGE'] = xrange __builtins__['_DICT_SET'] = set - + import math if sys.version >= "3.2": __builtins__['_ISFINITE'] = math.isfinite @@ -39,9 +39,9 @@ def noop(x): return x def simulatedIsFinite(num): return not math.isinf(num) and not math.isnan(num) __builtins__['_ISFINITE'] = simulatedIsFinite - - -# python 3 unquote, because py2 unquote doesn't do utf-8 correctly + + +# python 3 unquote, because py2 unquote doesn't do utf-8 correctly def py3unquote(string, encoding='utf-8', errors='replace'): """Replace %xx escapes by their single-character equivalent. The optional encoding and errors parameters specify how to decode percent-encoded @@ -106,7 +106,7 @@ def pyTypeName(object): return fullname except: return str(type(object)) - + def pyNamedObject(name, *args, **kwargs): try: if sys.version[0] >= '3': @@ -118,13 +118,13 @@ def pyNamedObject(name, *args, **kwargs): return objectConstructor(*args, **kwargs) except: return None - + def lcStr(value): # lower case first letter of string if len(value): return value[0].lower() + value[1:] return str - -def strTruncate(value, length): + +def strTruncate(value, length) -> str: _s = str(value).strip() if len(_s) <= length: return _s @@ -134,10 +134,10 @@ def normalizeSpace(s): if isinstance(s, str): return " ".join(s.split()) return s - + SEQUENCE_TYPES = (tuple,list,set,frozenset,MappingView) def flattenSequence(x, sequence=None): - if sequence is None: + if sequence is None: if not isinstance(x, SEQUENCE_TYPES): if x is None: return [] # none as atomic value is an empty sequence in xPath semantics @@ -151,7 +151,7 @@ def flattenSequence(x, sequence=None): return sequence def flattenToSet(x, _set=None): - if _set is None: + if _set is None: if not isinstance(x, SEQUENCE_TYPES): if x is None: return set() # none as atomic value is an empty sequence in xPath semantics @@ -174,7 +174,7 @@ class OrderedDefaultDict(OrderedDict): """ call with default factory and optional sorted initial entries e.g., OrderedDefaultDict(list, ((1,11),(2,22),...)) """ - def __init__(self, *args): + def __init__(self, *args): self.default_factory = None if len(args) > 0: # arg0 is default_factory @@ -184,7 +184,7 @@ def __init__(self, *args): super(OrderedDefaultDict, self).__init__(args[1]) else: super(OrderedDefaultDict, self).__init__() - + def __missing__(self, key): if self.default_factory is None: raise KeyError(key) @@ -195,7 +195,7 @@ def __missing__(self, key): class OrderedSet(MutableSet): def __init__(self, iterable=None): - self.end = end = [] + self.end = end = [] end += [None, end, end] # sentinel node for doubly linked list self.map = {} # key --> [key, prev, next] if iterable is not None: @@ -212,13 +212,13 @@ def add(self, key): end = self.end curr = end[1] curr[2] = end[1] = self.map[key] = [key, curr, end] - + def update(self, other): for key in other: self.add(key) def discard(self, key): - if key in self.map: + if key in self.map: key, prev, next = self.map.pop(key) prev[2] = next next[1] = prev @@ -253,7 +253,7 @@ def __eq__(self, other): if isinstance(other, OrderedSet): return len(self) == len(other) and list(self) == list(other) return set(self) == set(other) - + def Fraction(numerator,denominator=None): if denominator is None: if isinstance(numerator, (Fraction,_STR_UNICODE,Decimal)): diff --git a/arelle/UrlUtil.py b/arelle/UrlUtil.py index 476e717e77..1e2dae7410 100644 --- a/arelle/UrlUtil.py +++ b/arelle/UrlUtil.py @@ -4,6 +4,7 @@ @author: Mark V Systems Limited (c) Copyright 2010 Mark V Systems Limited, All rights reserved. ''' +from __future__ import annotations import os, sys try: import regex as re @@ -22,11 +23,11 @@ def authority(url, includeScheme=True): if url: - authSep = url.find(':') + authSep = url.find(':') if authSep > -1: scheme = url[0:authSep] authPart = authSep + (3 if scheme in ("http", "https", "ftp") else 1) # allow urn: - pathPart = url.find('/', authPart) + pathPart = url.find('/', authPart) if pathPart > -1: if includeScheme: return url[0:pathPart] @@ -36,7 +37,7 @@ def authority(url, includeScheme=True): return url[authPart:] return url #no path part of url -def scheme(url): # returns None if no scheme part +def scheme(url: str) -> str | None: # returns None if no scheme part return (url or "").rpartition(":")[0] or None absoluteUrlPattern = None @@ -56,7 +57,7 @@ def splitDecodeFragment(url): return (urlPart, unquote(fragPart, "utf-8", errors=None)) else: return _STR_UNICODE(urlPart), unquote(_STR_UNICODE(fragPart), "utf-8", errors=None) - + def anyUriQuoteForPSVI(uri): # only quote if quotable character found if any(c in {' ', '<', '>', '"', '{', '}', '|', '\\', '^', '~', '`'} or @@ -131,9 +132,9 @@ def isValidAbsolute(url): "|%[0-9A-F][0-9A-F]|[!$&'()*+,;=]|:|@)*)*)|(?P))(?:\\?(?P(?:(?:(?:[a-zA-Z0-9._~-]" "|[\xa0-\ud7ff\uf900-\ufdcf\ufdf0-\uffef])|%[0-9A-F][0-9A-F]|[!$&'()*+,;=]|:|@)|[\ue000-\uf8ff]" "|/|\\?)*))?(?:\\#(?P(?:(?:(?:[a-zA-Z0-9._~-]|[\xa0-\ud7ff\uf900-\ufdcf\ufdf0-\uffef])" - "|%[0-9A-F][0-9A-F]|[!$&'()*+,;=]|:|@)|/|\\?)*))?)" - ''' - + "|%[0-9A-F][0-9A-F]|[!$&'()*+,;=]|:|@)|/|\\?)*))?)" + ''' + ''' for Python 3.3 only "(?P[a-zA-Z][a-zA-Z0-9+.-]*):" "(?://(?P(?:(?P(?:(?:[a-zA-Z0-9._~-]|" @@ -251,7 +252,7 @@ def isValidAbsolute(url): "\U000b0000-\U000bfffd\U000c0000-\U000cfffd\U000d0000-\U000dfffd\U000e1000-\U000efffd])" "|%[0-9A-F][0-9A-F]|[!$&'()*+,;=]|:|@)|/|\\?)*))?)" ''' - + # note this pattern does not process urn: as valid!!! # regex to validate a full URL from http://stackoverflow.com/questions/827557/how-do-you-validate-a-url-with-a-regular-expression-in-python/835527#835527 r"(?:(http|https|ftp)://(?:(?:(?:(?:(?:[a-zA-Z\d](?:(?:[a-zA-Z\d]|-)*[a-zA-Z\d])?)\." @@ -360,13 +361,13 @@ def isValidAbsolute(url): r"\$\-_.!~*'(),])|(?:%[a-fA-F\d]{2})|[:@&=+])*)(?:/(?:(?:(?:[a-zA-Z\d\$\-" r"_.!~*'(),])|(?:%[a-fA-F\d]{2})|[:@&=+])*))*)?))|(?:(?:(?:(?:(?:[a-zA-" r"Z\d\$\-_.!~*'(),])|(?:%[a-fA-F\d]{2})|[:@&=+])*)(?:/(?:(?:(?:[a-zA-Z\d" - r"\$\-_.!~*'(),])|(?:%[a-fA-F\d]{2})|[:@&=+])*))*)?)))" + r"\$\-_.!~*'(),])|(?:%[a-fA-F\d]{2})|[:@&=+])*))*)?)))" ) return absoluteUrlPattern.match(url) is not None - + def isValidUriReference(url): return relativeUrlPattern.match(url) is not None - + def isAbsolute(url): if url: scheme, sep, path = url.partition(":") @@ -384,7 +385,7 @@ def ensureUrl(maybeUrl): return maybeUrl # probably a local file return urljoin('file:', pathname2url(maybeUrl)) - + def parseRfcDatetime(rfc2822date): from email.utils import parsedate from datetime import datetime @@ -393,7 +394,7 @@ def parseRfcDatetime(rfc2822date): if d: return datetime(d[0],d[1],d[2],d[3],d[4],d[5]) return None - + zipRelativeFilePattern = re.compile(r".*[.]zip[/\\](.*)$") def relativeUri(baseUri, relativeUri): # return uri relative to this modelDocument uri if isHttpUrl(relativeUri): diff --git a/arelle/ValidateUtr.py b/arelle/ValidateUtr.py index 01886ec948..fbcf423ab8 100644 --- a/arelle/ValidateUtr.py +++ b/arelle/ValidateUtr.py @@ -4,16 +4,17 @@ @author: Mark V Systems Limited (c) Copyright 2010-2017 Mark V Systems Limited, All rights reserved. ''' -import os, logging from lxml import etree from arelle import ModelDocument from collections import defaultdict +from arelle.ModelXbrl import ModelXbrl + DIVISOR = "*DIV*" class UtrEntry(): # use slotted class for execution efficiency __slots__ = ("id", "unitId", "nsUnit", "itemType", "nsItemType", "isSimple", - "numeratorItemType", "nsNumeratorItemType", + "numeratorItemType", "nsNumeratorItemType", "denominatorItemType", "nsDenominatorItemType", "symbol", "status") @@ -99,29 +100,29 @@ def loadUtr(modelXbrl, statusFilters=None): # Build a dictionary of item types t etree.clear_error_log() if file: file.close() - + def validateFacts(modelXbrl): ValidateUtr(modelXbrl).validateFacts() - + def utrEntries(modelType, modelUnit): return ValidateUtr(modelType.modelXbrl).utrEntries(modelType, modelUnit) def utrSymbol(modelType, unitMeasures): return ValidateUtr(modelType.modelXbrl).utrSymbol(unitMeasures[0], unitMeasures[1]) - + class ValidateUtr: - def __init__(self, modelXbrl, messageLevel="ERROR", messageCode="utre:error-NumericFactUtrInvalid"): + def __init__(self, modelXbrl: ModelXbrl, messageLevel: str="ERROR", messageCode: str="utre:error-NumericFactUtrInvalid") -> None: self.modelXbrl = modelXbrl self.messageLevel = messageLevel self.messageCode = messageCode - if getattr(modelXbrl.modelManager.disclosureSystem, "utrItemTypeEntries", None) is None: + if getattr(modelXbrl.modelManager.disclosureSystem, "utrItemTypeEntries", None) is None: loadUtr(modelXbrl) self.utrItemTypeEntries = modelXbrl.modelManager.disclosureSystem.utrItemTypeEntries - + def validateFacts(self): modelXbrl = self.modelXbrl if modelXbrl.modelDocument.type in (ModelDocument.Type.INSTANCE, ModelDocument.Type.INLINEXBRL): - modelXbrl.modelManager.cntlr.showStatus(_("Validating for Unit Type Registry").format()) + modelXbrl.modelManager.cntlr.showStatus(_("Validating for Unit Type Registry").format()) utrInvalidFacts = [] for f in modelXbrl.facts: concept = f.concept @@ -168,11 +169,11 @@ def measuresMatch(self, typeMatched, mulMeas, divMeas, typeName=None, typeNS=Non if self.measuresMatch(True, divMeas, mulMeas[:i] + mulMeas[i+1:], *divArgs)[0]: return True, True, u else: - if self.measuresMatch(True, mulMeas, divMeas, u.numeratorItemType, u.nsNumeratorItemType, + if self.measuresMatch(True, mulMeas, divMeas, u.numeratorItemType, u.nsNumeratorItemType, u.denominatorItemType, u.nsDenominatorItemType, None, DIVISOR, *divArgs)[0]: return True, True, u return False, typeMatched, None - + def utrEntries(self, modelType, unit): utrSatisfyingEntries = set() modelXbrl = self.modelXbrl @@ -209,8 +210,8 @@ def symbols(measures, wrapMult=True): if len(measures) > 1 and wrapMult: return "({})".format(measuresString) return measuresString - - if not multMeasures and divMeasures: + + if not multMeasures and divMeasures: return "per " + symbols(divMeasures) elif multMeasures: if divMeasures: diff --git a/arelle/ValidateXbrl.py b/arelle/ValidateXbrl.py index 785a6625e3..4284ad20e7 100644 --- a/arelle/ValidateXbrl.py +++ b/arelle/ValidateXbrl.py @@ -4,6 +4,7 @@ @author: Mark V Systems Limited (c) Copyright 2010 Mark V Systems Limited, All rights reserved. ''' +from __future__ import annotations try: import regex as re except ImportError: @@ -51,8 +52,9 @@ class ValidateXbrl: - authority: str + authority: str | None authParam: dict[str, Any] + consolidated: bool domainMembers: set extensionImportedUrls: set hasExtensionCal: bool @@ -60,6 +62,9 @@ class ValidateXbrl: hasExtensionLbl: bool hasExtensionPre: bool hasExtensionSchema: bool + unconsolidated: bool + validateESEFplugin: bool + priorFormulaOptionsRunIDs: str | None primaryItems: set def __init__(self, testModelXbrl): diff --git a/arelle/ValidateXbrlCalcs.py b/arelle/ValidateXbrlCalcs.py index 02f56388d1..5edeae8079 100644 --- a/arelle/ValidateXbrlCalcs.py +++ b/arelle/ValidateXbrlCalcs.py @@ -4,9 +4,11 @@ @author: Mark V Systems Limited (c) Copyright 2010 Mark V Systems Limited, All rights reserved. ''' +from __future__ import annotations from collections import defaultdict -from math import (log10, isnan, isinf, fabs, trunc, fmod, floor, pow) +from math import (log10, isnan, isinf, fabs, floor, pow) import decimal +from typing import TYPE_CHECKING try: from regex import compile as re_compile except ImportError: @@ -14,7 +16,9 @@ import hashlib from arelle import Locale, XbrlConst, XbrlUtil from arelle.ModelObject import ObjectPropertyViewWrapper -from arelle.XmlValidate import UNVALIDATED, VALID + +if TYPE_CHECKING: + from arelle.ModelInstanceObject import ModelInlineFact numberPattern = re_compile("[-+]?[0]*([1-9]?[0-9]*)([.])?(0*)([1-9]?[0-9]*)?([eE])?([-+]?[0-9]*)?") ZERO = decimal.Decimal(0) @@ -27,7 +31,7 @@ def validate(modelXbrl, inferDecimals=False, deDuplicate=False): ValidateXbrlCalcs(modelXbrl, inferDecimals, deDuplicate).validate() - + class ValidateXbrlCalcs: def __init__(self, modelXbrl, inferDecimals=False, deDuplicate=False): self.modelXbrl = modelXbrl @@ -47,14 +51,14 @@ def __init__(self, modelXbrl, inferDecimals=False, deDuplicate=False): self.conceptsInEssencesAlias = set() self.requiresElementFacts = defaultdict(list) self.conceptsInRequiresElement = set() - + def validate(self): if not self.modelXbrl.contexts and not self.modelXbrl.facts: return # skip if no contexts or facts - + if not self.inferDecimals: # infering precision is now contrary to XBRL REC section 5.2.5.2 self.modelXbrl.info("xbrl.5.2.5.2:inferringPrecision","Validating calculations inferring precision.") - + # identify equal contexts self.modelXbrl.profileActivity() uniqueContextHashes = {} @@ -79,7 +83,7 @@ def validate(self): uniqueUnitHashes[h] = unit del uniqueUnitHashes self.modelXbrl.profileActivity("... identify equal units", minTimeToShow=1.0) - + # identify concepts participating in essence-alias relationships # identify calcluation & essence-alias base sets (by key) for baseSetKey in self.modelXbrl.baseSets.keys(): @@ -96,7 +100,7 @@ def validate(self): self.bindFacts(self.modelXbrl.facts,[self.modelXbrl.modelDocument.xmlRootElement]) self.modelXbrl.profileActivity("... bind facts", minTimeToShow=1.0) - + # identify calcluation & essence-alias base sets (by key) for baseSetKey in self.modelXbrl.baseSets.keys(): arcrole, ELR, linkqname, arcqname = baseSetKey @@ -151,16 +155,16 @@ def validate(self): unreportedContribingItemQnames = [] # list the missing/unreported contributors in relationship order for modelRel in modelRels: itemConcept = modelRel.toModelObject - if (itemConcept is not None and + if (itemConcept is not None and (itemConcept, ancestor, contextHash, unit) not in self.itemFacts): unreportedContribingItemQnames.append(str(itemConcept.qname)) self.modelXbrl.log('INCONSISTENCY', "xbrl.5.2.5.2:calcInconsistency", _("Calculation inconsistent from %(concept)s in link role %(linkrole)s reported sum %(reportedSum)s computed sum %(computedSum)s context %(contextID)s unit %(unitID)s unreportedContributingItems %(unreportedContributors)s"), modelObject=wrappedSummationAndItems(fact, roundedSum, _boundSummationItems), - concept=sumConcept.qname, linkrole=ELR, + concept=sumConcept.qname, linkrole=ELR, linkroleDefinition=self.modelXbrl.roleTypeDefinition(ELR), reportedSum=Locale.format_decimal(self.modelXbrl.locale, roundedSum, 1, max(d,0)), - computedSum=Locale.format_decimal(self.modelXbrl.locale, roundedItemsSum, 1, max(d,0)), + computedSum=Locale.format_decimal(self.modelXbrl.locale, roundedItemsSum, 1, max(d,0)), contextID=fact.context.id, unitID=fact.unit.id, unreportedContributors=", ".join(unreportedContribingItemQnames) or "none") del unreportedContribingItemQnames[:] @@ -183,16 +187,16 @@ def validate(self): if essenceUnit != aliasUnit: self.modelXbrl.log('INCONSISTENCY', "xbrl.5.2.6.2.2:essenceAliasUnitsInconsistency", _("Essence-Alias inconsistent units from %(essenceConcept)s to %(aliasConcept)s in link role %(linkrole)s context %(contextID)s"), - modelObject=(modelRel, eF, aF), - essenceConcept=essenceConcept.qname, aliasConcept=aliasConcept.qname, - linkrole=ELR, + modelObject=(modelRel, eF, aF), + essenceConcept=essenceConcept.qname, aliasConcept=aliasConcept.qname, + linkrole=ELR, linkroleDefinition=self.modelXbrl.roleTypeDefinition(ELR), contextID=eF.context.id) if not XbrlUtil.vEqual(eF, aF): self.modelXbrl.log('INCONSISTENCY', "xbrl.5.2.6.2.2:essenceAliasUnitsInconsistency", _("Essence-Alias inconsistent value from %(essenceConcept)s to %(aliasConcept)s in link role %(linkrole)s context %(contextID)s"), - modelObject=(modelRel, eF, aF), - essenceConcept=essenceConcept.qname, aliasConcept=aliasConcept.qname, + modelObject=(modelRel, eF, aF), + essenceConcept=essenceConcept.qname, aliasConcept=aliasConcept.qname, linkrole=ELR, linkroleDefinition=self.modelXbrl.roleTypeDefinition(ELR), contextID=eF.context.id) @@ -204,13 +208,13 @@ def validate(self): not requiredConcept in self.requiresElementFacts: self.modelXbrl.log('INCONSISTENCY', "xbrl.5.2.6.2.4:requiresElementInconsistency", _("Requires-Element %(requiringConcept)s missing required fact for %(requiredConcept)s in link role %(linkrole)s"), - modelObject=sourceConcept, - requiringConcept=sourceConcept.qname, requiredConcept=requiredConcept.qname, + modelObject=sourceConcept, + requiringConcept=sourceConcept.qname, requiredConcept=requiredConcept.qname, linkrole=ELR, linkroleDefinition=self.modelXbrl.roleTypeDefinition(ELR)) self.modelXbrl.profileActivity("... find inconsistencies", minTimeToShow=1.0) self.modelXbrl.profileActivity() # reset - + def bindFacts(self, facts, ancestors): for f in facts: concept = f.concept @@ -246,7 +250,7 @@ def bindFacts(self, facts, ancestors): hasAccuracy = (p != 0) fIsMorePrecise = (p > pDup) if (hasAccuracy and - roundValue(f.value,precision=pMin,decimals=dMin) == + roundValue(f.value,precision=pMin,decimals=dMin) == roundValue(fDup.value,precision=pMin,decimals=dMin)): # consistent duplicate, f more precise than fDup, replace fDup with f if fIsMorePrecise: # works for inf and integer mixtures @@ -354,12 +358,12 @@ def roundFact(fact, inferDecimals=False, vDecimal=None): else: # no information available to do rounding (other errors xbrl.4.6.3 error) vRounded = vDecimal return vRounded - + def decimalRound(x, d, rounding): if x.is_normal() and -28 <= d <= 28: # prevent exception with excessive quantization digits if d >= 0: return x.quantize(ONE.scaleb(-d),rounding) - else: # quantize only seems to work on fractional part, convert integer to fraction at scaled point + else: # quantize only seems to work on fractional part, convert integer to fraction at scaled point return x.scaleb(d).quantize(ONE,rounding) * (TEN ** decimal.Decimal(-d)) # multiply by power of 10 to prevent scaleb scientific notatino return x # infinite, NaN, zero, or excessive decimal digits ( > 28 ) @@ -392,8 +396,8 @@ def inferredPrecision(fact): return 0 else: return p - -def inferredDecimals(fact): + +def inferredDecimals(fact: ModelInlineFact) -> float | int: vStr = fact.value dStr = fact.decimals pStr = fact.precision @@ -415,7 +419,7 @@ def inferredDecimals(fact): except ValueError: pass return floatNaN - + def roundValue(value, precision=None, decimals=None, scale=None): try: vDecimal = decimal.Decimal(value) @@ -467,7 +471,7 @@ def roundValue(value, precision=None, decimals=None, scale=None): vRounded = vDecimal return vRounded -def rangeValue(value, decimals=None): +def rangeValue(value, decimals=None) -> tuple[decimal.Decimal, decimal.Decimal]: try: vDecimal = decimal.Decimal(value) except (decimal.InvalidOperation, ValueError): # would have been a schema error reported earlier @@ -504,7 +508,7 @@ def insignificantDigits(value, precision=None, decimals=None, scale=None): precision = int(precision) except ValueError: # would be a schema error return None - if isinf(precision) or precision == 0 or isnan(precision) or vFloat == 0: + if isinf(precision) or precision == 0 or isnan(precision) or vFloat == 0: return None else: vAbs = fabs(vFloat) @@ -566,4 +570,3 @@ def wrappedSummationAndItems(fact, roundedSum, boundSummationItems): ("itemValuesHash", itemValuesHash), ("roundedSum", roundedSum) ))] + \ boundSummationItems - \ No newline at end of file diff --git a/arelle/XhtmlValidate.py b/arelle/XhtmlValidate.py index 862c1bfd66..2711e123b4 100644 --- a/arelle/XhtmlValidate.py +++ b/arelle/XhtmlValidate.py @@ -7,16 +7,17 @@ (originally part of XmlValidate, moved to separate module) ''' from arelle import XbrlConst, XmlUtil, XmlValidate, ValidateFilingText, UrlUtil -from arelle.ModelValue import qname from arelle.ModelObject import ModelObject, ModelComment from arelle.PythonUtil import normalizeSpace +from arelle.ModelXbrl import ModelXbrl +from arelle.ModelObject import ModelObject from lxml import etree import os, re, posixpath EMPTYDICT = {} XHTML_DTD = { # modified to have ixNestedContent elements as placeholders for ix footnote and nonnumeric/continuation elements - XbrlConst.ixbrl: "xhtml1-strict-ix.dtd", + XbrlConst.ixbrl: "xhtml1-strict-ix.dtd", XbrlConst.ixbrl11: "xhtml1_1-strict-ix.dtd" } @@ -25,7 +26,7 @@ "denominator", "exclude", "footnote", "fraction", "header", "hidden", "nonFraction", "nonNumeric", "numerator", "references", "resources", "tuple"}, XbrlConst.ixbrl11: { - "continuation", "denominator", "exclude", "footnote", "fraction", "header", "hidden", + "continuation", "denominator", "exclude", "footnote", "fraction", "header", "hidden", "nonFraction", "nonNumeric","numerator", "references", "relationship","resources", "tuple"} } @@ -125,13 +126,13 @@ "nonFraction": ("name", "contextRef", "unitRef"), "nonNumeric": ("name", "contextRef"), "tuple": ("name",)}, - XbrlConst.ixbrl11: { + XbrlConst.ixbrl11: { "continuation": ("id",), "footnote": ("id",), "fraction": ("name", "contextRef", "unitRef"), "nonFraction": ("name", "contextRef", "unitRef"), "nonNumeric": ("name", "contextRef"), - "tuple": ("name",)} + "tuple": ("name",)} } ixAttrDefined = { XbrlConst.ixbrl: { @@ -146,7 +147,7 @@ "footnoteRefs", "format", "escape"), "references": ("id", "target"), "tuple": ("id", "name", "target", "tupleID", "tupleRef", "order", "footnoteRefs")}, - XbrlConst.ixbrl11: { + XbrlConst.ixbrl11: { "continuation": ("id", "continuedAt"), "footnote": ("id", "continuedAt", "footnoteRole", "title"), "fraction": ("id", "name", "target", "contextRef", "unitRef", "tupleRef", "order"), @@ -158,7 +159,7 @@ "format", "escape", "continuedAt"), "references": ("id", "target"), "relationship": ("arcrole", "linkRole", "fromRefs", "toRefs", "order"), - "tuple": ("id", "name", "target", "tupleID", "tupleRef", "order")} + "tuple": ("id", "name", "target", "tupleID", "tupleRef", "order")} } allowedNonIxAttrNS = { "footnote": "http://www.w3.org/XML/1998/namespace", @@ -195,14 +196,14 @@ ("&child-choice", ('{http://www.xbrl.org/2003/linkbase}schemaRef', '{http://www.xbrl.org/2003/linkbase}linkbaseRef'))), "relationship": (("+parent",("resources",)),), "resources": (("+parent",("header",)), - ("&child-choice", ('relationship', - '{http://www.xbrl.org/2003/linkbase}roleRef', '{http://www.xbrl.org/2003/linkbase}arcroleRef', + ("&child-choice", ('relationship', + '{http://www.xbrl.org/2003/linkbase}roleRef', '{http://www.xbrl.org/2003/linkbase}arcroleRef', '{http://www.xbrl.org/2003/instance}context', '{http://www.xbrl.org/2003/instance}unit'))), "tuple": (("-child-choice",("continuation", "exclude", "denominator", "footnote", "numerator", "header", "hidden", "references", "relationship", "resources")),) } -ixSect = { +ixSect = { XbrlConst.ixbrl: { "footnote": {"constraint": "ix10.5.1.1", "validation": "ix10.5.1.2"}, "fraction": {"constraint": "ix10.6.1.1", "validation": "ix10.6.1.2"}, @@ -216,7 +217,7 @@ "resources": {"constraint": "ix10.12.1.1", "validation": "ix10.12.1.2"}, "tuple": {"constraint": "ix10.13.1.1", "validation": "ix10.13.1.2"}, "other": {"constraint": "ix10", "validation": "ix10"}}, - XbrlConst.ixbrl11: { + XbrlConst.ixbrl11: { "continuation": {"constraint": "ix11.4.1.1", "validation": "ix11.4.1.2"}, "exclude": {"constraint": "ix11.5.1.1", "validation": "ix11.5.1.2"}, "footnote": {"constraint": "ix11.6.1.1", "validation": "ix11.6.1.2"}, @@ -232,7 +233,7 @@ "resources": {"constraint": "ix11.14.1.1", "validation": "ix11.14.1.2"}, "tuple": {"constraint": "ix11.15.1.1", "validation": "ix11.15.1.2"}, "other": {"constraint": "ix11", "validation": "ix11"}} - } + } def ixMsgCode(codeName, elt=None, sect="constraint", ns=None, name=None): if elt is None: if ns is None: ns = XbrlConst.ixbrl11 @@ -248,7 +249,7 @@ def ixMsgCode(codeName, elt=None, sect="constraint", ns=None, name=None): name = "resources" return "{}:{}".format(ixSect[ns].get(name,"other")[sect], codeName) -def xhtmlValidate(modelXbrl, elt): +def xhtmlValidate(modelXbrl: ModelXbrl, elt: ModelObject) -> None: from lxml.etree import DTD, XMLSyntaxError from arelle import FunctionIxt ixNsStartTags = ["{" + ns + "}" for ns in XbrlConst.ixbrlAll] @@ -260,7 +261,7 @@ def xhtmlValidate(modelXbrl, elt): _ixNStag = "{{{}}}".format(_ixNS) _xhtmlDTD = XHTML_DTD[_ixNS] _customTransforms = modelXbrl.modelManager.customTransforms or {} - + def checkAttribute(elt, isIxElt, attrTag, attrValue): ixEltAttrDefs = ixAttrDefined.get(elt.namespaceURI, EMPTYDICT).get(elt.localName, ()) if attrTag.startswith("{"): @@ -278,7 +279,7 @@ def checkAttribute(elt, isIxElt, attrTag, attrValue): _("Inline XBRL element %(element)s has qualified attribute %(name)s"), modelObject=elt, element=str(elt.elementQname), name=attrTag) if ns == XbrlConst.xbrli and elt.localName in { - "fraction", "nonFraction", "nonNumeric", "references", "relationship", "tuple"}: + "fraction", "nonFraction", "nonNumeric", "references", "relationship", "tuple"}: modelXbrl.error(ixMsgCode("qualifiedAttributeDisallowed", elt), _("Inline XBRL element %(element)s has disallowed attribute %(name)s"), modelObject=elt, element=str(elt.elementQname), name=attrTag) @@ -301,7 +302,7 @@ def checkAttribute(elt, isIxElt, attrTag, attrValue): baseXsdType = _xsdType facets = None XmlValidate.validateValue(modelXbrl, elt, attrTag, baseXsdType, attrValue, facets=facets) - + if not (attrTag in ixEltAttrDefs or (localName in ixEltAttrDefs and (not ns or ns in XbrlConst.ixbrlAll))): raise KeyError @@ -328,7 +329,7 @@ def checkAttribute(elt, isIxElt, attrTag, attrValue): baseXsdType = _xsdType facets = None XmlValidate.validateValue(modelXbrl, elt, attrTag, baseXsdType, attrValue, facets=facets) - + def checkHierarchyConstraints(elt): constraints = ixHierarchyConstraints.get(elt.localName) if constraints: @@ -347,13 +348,13 @@ def checkHierarchyConstraints(elt): else: namespaceFilter = elt.namespaceURI namespacePrefix = elt.prefix - relations = {"ancestor": XmlUtil.ancestor, - "parent": XmlUtil.parent, - "child-choice": XmlUtil.children, + relations = {"ancestor": XmlUtil.ancestor, + "parent": XmlUtil.parent, + "child-choice": XmlUtil.children, "child-sequence": XmlUtil.children, "child-or-text": XmlUtil.children, "descendant": XmlUtil.descendants}[rel]( - elt, + elt, namespaceFilter, nameFilter) if rel in ("ancestor", "parent"): @@ -389,8 +390,8 @@ def checkHierarchyConstraints(elt): issue = " may only have 0 or 1 but {0} present ".format(len(relations)) if reqt == '+' and len(relations) == 0: issue = " must have at least 1 but none present " - disallowedChildText = bool(reqt == '&' and - rel in ("child-sequence", "child-choice") + disallowedChildText = bool(reqt == '&' and + rel in ("child-sequence", "child-choice") and elt.textValue.strip(" \t\r\n")) if ((reqt == '+' and not relations) or (reqt == '-' and relations) or @@ -423,7 +424,7 @@ def checkHierarchyConstraints(elt): ", ".join("{}:{}".format(namespacePrefix, n) for n in names), issue, " and no child text (\"{}\")".format(elt.textValue.strip(" \t\r\n")[:32]) if disallowedChildText else "") - modelXbrl.error(code, msg, + modelXbrl.error(code, msg, modelObject=[elt] + relations, requirement=reqt, messageCodes=("ix{ver.sect}:ancestorNode{Required|Disallowed}", "ix{ver.sect}:childNodesOrTextRequired", @@ -486,7 +487,7 @@ def checkHierarchyConstraints(elt): modelObject=elt, fact=elt.qname, transform=fmt, name=fmt.localName) elt.setInvalid() - + def ixToXhtml(fromRoot): toRoot = etree.Element(fromRoot.localName, nsmap={"ix": _ixNS}) copyNonIxChildren(fromRoot, toRoot) @@ -595,7 +596,7 @@ def dtdErrs(): _("%(element)s error %(error)s"), modelObject=dtdErrElts or elt, element=elt.localName.title(), error=', '.join(dtdErrMsgs)) if validateEntryText: - ValidateFilingText.validateHtmlContent(modelXbrl, elt, elt, "InlineXBRL", valHtmlContentMsgPrefix, isInline=True) + ValidateFilingText.validateHtmlContent(modelXbrl, elt, elt, "InlineXBRL", valHtmlContentMsgPrefix, isInline=True) except XMLSyntaxError as err: modelXbrl.error("html:syntaxError", _("%(element)s error %(error)s"), @@ -614,7 +615,7 @@ def resolveHtmlUri(elt, name, value): value = UrlUtil.authority(base) + value elif value.startswith("#"): # add anchor to base document value = base + value - else: + else: value = os.path.dirname(base) + "/" + value # canonicalize ../ and ./ scheme, sep, pathpart = value.rpartition("://") diff --git a/arelle/plugin/validate/ESEF/DTS.py b/arelle/plugin/validate/ESEF/DTS.py index f21dd154cc..04a6544a49 100644 --- a/arelle/plugin/validate/ESEF/DTS.py +++ b/arelle/plugin/validate/ESEF/DTS.py @@ -26,9 +26,9 @@ linkbaseRefTypes, filenamePatterns, filenameRegexes) from .Util import isExtension from arelle.ValidateXbrl import ValidateXbrl -from collections.abc import Callable +from .common import TypeGetText -_: Callable[[str], str] # Handle gettext +_: TypeGetText # Handle gettext def checkFilingDTS(val: ValidateXbrl, modelDocument: ModelDocument, visited: list[ModelDocument], hrefXlinkRole: str | None =None) -> None: diff --git a/arelle/plugin/validate/ESEF/Dimensions.py b/arelle/plugin/validate/ESEF/Dimensions.py index d6eb4bc8f7..95503f4e2a 100644 --- a/arelle/plugin/validate/ESEF/Dimensions.py +++ b/arelle/plugin/validate/ESEF/Dimensions.py @@ -20,11 +20,11 @@ import regex as re except ImportError: import re # type: ignore[no-redef] -from collections.abc import Callable from arelle.ValidateXbrl import ValidateXbrl from arelle.ModelDtsObject import ModelConcept +from .common import TypeGetText -_: Callable[[str], str] # Handle gettext +_: TypeGetText # Handle gettext def checkFilingDimensions(val: ValidateXbrl) -> None: diff --git a/arelle/plugin/validate/ESEF/Util.py b/arelle/plugin/validate/ESEF/Util.py index 998207e80c..2f5b77a197 100644 --- a/arelle/plugin/validate/ESEF/Util.py +++ b/arelle/plugin/validate/ESEF/Util.py @@ -18,10 +18,10 @@ from arelle.ModelXbrl import ModelXbrl from arelle.ValidateXbrl import ValidateXbrl from typing import Any, Union, cast -from collections.abc import Callable from arelle.ModelDocument import ModelDocument +from .common import TypeGetText -_: Callable[[str], str] # Handle gettext +_: TypeGetText # Handle gettext # check if a modelDocument URI is an extension URI (document URI) # also works on a uri passed in as well as modelObject diff --git a/arelle/plugin/validate/ESEF/__init__.py b/arelle/plugin/validate/ESEF/__init__.py index c7c22cea82..c528e8a0b8 100644 --- a/arelle/plugin/validate/ESEF/__init__.py +++ b/arelle/plugin/validate/ESEF/__init__.py @@ -1,13 +1,13 @@ ''' Created on June 6, 2018 -Filer Guidelines: +Filer Guidelines: RTS: https://eur-lex.europa.eu/legal-content/EN/TXT/?qid=1563538104990&uri=CELEX:32019R0815 ESEF Filer Manual https://www.esma.europa.eu/sites/default/files/library/esma32-60-254_esef_reporting_manual.pdf - -Taxonomy Architecture: -Taxonomy package expected to be installed: +Taxonomy Architecture: + +Taxonomy package expected to be installed: @author: Workiva (c) Copyright 2022 Workiva, All rights reserved. @@ -15,7 +15,7 @@ GUI operation install plugin validate/ESEF and optionally applicable taxonomy packages - + Under tools->formula add parameters eps_threshold and optionally authority Command line operation: @@ -26,7 +26,7 @@ --parameters "eps_threshold=.01" Dimensional validations required by some auditors may require --import http://www.esma.europa.eu/taxonomy/2020-03-16/esef_all-for.xml - and likely --skipLoading *esef_all-cal.xml + and likely --skipLoading *esef_all-cal.xml because the esef_all-cal.xml calculations are reported to be problematical for some filings Authority specific validations are enabled by formula parameter authority, e.g. for Denmark or UKSEF and eps_threshold specify: @@ -36,21 +36,22 @@ Using arelle as a web server: arelleCmdLine.exe --webserver localhost:8080:cheroot --plugins validate/ESEF --packages {my-package-directory}/esef_taxonomy_2019.zip - + Client with curl: curl -X POST "-HContent-type: application/zip" -T TC1_valid.zip "http://localhost:8080/rest/xbrl/validation?disclosureSystem=esef&media=text" ''' +from __future__ import annotations import os, base64 try: import regex as re except ImportError: - import re + import re # type: ignore[no-redef] from collections import defaultdict from math import isnan -from lxml.etree import _ElementTree, _Comment, _ProcessingInstruction, EntityBase -from arelle import LeiUtil, ModelDocument, XbrlConst, XhtmlValidate, XmlUtil +from lxml.etree import _ElementTree, _Comment, _ProcessingInstruction, EntityBase, _Element +from arelle import LeiUtil, ModelDocument, XbrlConst, XhtmlValidate from arelle.FunctionIxt import ixtNamespaces from arelle.ModelDtsObject import ModelResource from arelle.ModelInstanceObject import ModelFact, ModelInlineFact, ModelInlineFootnote @@ -62,16 +63,30 @@ from arelle.XmlValidate import VALID, lexicalPatterns from arelle.ValidateXbrlCalcs import inferredDecimals, rangeValue -from arelle.XbrlConst import (ixbrl11, xhtml, link, parentChild, summationItem, standardLabel, - all as hc_all, notAll as hc_notAll, hypercubeDimension, dimensionDomain, domainMember, +from arelle.XbrlConst import (ixbrl11, xhtml, parentChild, summationItem, standardLabel, + all as hc_all, notAll as hc_notAll, dimensionDomain, domainMember, qnLinkLoc, qnLinkFootnoteArc, qnLinkFootnote, qnIXbrl11Footnote, iso17442) from arelle.XmlValidate import VALID from arelle.ValidateUtr import ValidateUtr -from .Const import (browserMaxBase64ImageLength, mandatory, untransformableTypes, +from .Const import (mandatory, untransformableTypes, esefPrimaryStatementPlaceholderNames, esefStatementsOfMonetaryDeclarationNames, esefMandatoryElementNames2020) from .Dimensions import checkFilingDimensions from .DTS import checkFilingDTS from .Util import isExtension, checkImageContents, loadAuthorityValidations +from .common import TypeGetText +from arelle.ModelObject import ModelObject +from arelle.DisclosureSystem import DisclosureSystem +from arelle.ModelDtsObject import ModelConcept +from arelle.ModelXbrl import ModelXbrl +from arelle.ValidateXbrl import ValidateXbrl +from arelle.XPathContext import XPathContext +from arelle.ModelRelationshipSet import ModelRelationshipSet +from arelle.ModelInstanceObject import ModelInlineFootnote +from arelle.ModelInstanceObject import ModelContext +from typing import Any, cast +from collections.abc import Generator + +_: TypeGetText # Handle gettext styleIxHiddenPattern = re.compile(r"(.*[^\w]|^)-esef-ix-hidden\s*:\s*([\w.-]+).*") styleCssHiddenPattern = re.compile(r"(.*[^\w]|^)display\s*:\s*none([^\w].*|$)") @@ -82,55 +97,61 @@ docTypeXhtmlPattern = re.compile(r"^$", re.IGNORECASE) FOOTNOTE_LINK_CHILDREN = {qnLinkLoc, qnLinkFootnoteArc, qnLinkFootnote, qnIXbrl11Footnote} -PERCENT_TYPE = qname("{http://www.xbrl.org/dtr/type/numeric}num:percentItemType") +PERCENT_TYPE = qname("{http://www.xbrl.org/dtr/type/numeric}num:percentItemType") # type: ignore[no-untyped-call] IXT_NAMESPACES = {ixtNamespaces["ixt v4"], # only tr4 or newer REC is currently recommended ixtNamespaces["ixt v5"]} -def etreeIterWithDepth(node, depth=0): +def etreeIterWithDepth(node: ModelObject | _Element, depth: int=0) -> Generator[tuple[ModelObject | _Element, int], None, None]: yield (node, depth) for child in node.iterchildren(): for n_d in etreeIterWithDepth(child, depth+1): yield n_d - -def dislosureSystemTypes(disclosureSystem, *args, **kwargs): + +def dislosureSystemTypes(disclosureSystem: DisclosureSystem, *args: Any, **kwargs: Any) -> tuple[tuple[str, str]]: # return ((disclosure system name, variable name), ...) return (("ESEF", "ESEFplugin"),) -def disclosureSystemConfigURL(disclosureSystem, *args, **kwargs): +def disclosureSystemConfigURL(disclosureSystem: DisclosureSystem, *args: Any, **kwargs: Any) -> str: return os.path.join(os.path.dirname(__file__), "config.xml") -def modelXbrlBeforeLoading(modelXbrl, normalizedUri, filepath, isEntry=False, **kwargs): +def modelXbrlBeforeLoading(modelXbrl: ModelXbrl, normalizedUri: str, filepath: str, isEntry: bool=False, **kwargs: Any) -> ModelDocument.LoadingException | None: if getattr(modelXbrl.modelManager.disclosureSystem, "ESEFplugin", False): if isEntry and not any("unconsolidated" in n for n in modelXbrl.modelManager.disclosureSystem.names): if modelXbrl.fileSource.isArchive: if (not isinstance(modelXbrl.fileSource.selection, list) and - modelXbrl.fileSource.selection.endswith(".xml") and - ModelDocument.Type.identify(modelXbrl.fileSource, modelXbrl.fileSource.url) in ( + modelXbrl.fileSource.selection is not None and + modelXbrl.fileSource.selection.endswith(".xml") and + # Ignoring for now: Argument 1 to "identify" of "Type" has incompatible type "FileSource"; expected "Type". + # It is not entirely clear why self isn't used in the identify-method. + ModelDocument.Type.identify(modelXbrl.fileSource, modelXbrl.fileSource.url) in ( # type: ignore[arg-type] ModelDocument.Type.TESTCASESINDEX, ModelDocument.Type.TESTCASE)): return None # allow zipped test case to load normally if not validateTaxonomyPackage(modelXbrl.modelManager.cntlr, modelXbrl.fileSource): modelXbrl.error("ESEF.RTS.Annex.III.3.missingOrInvalidTaxonomyPackage", _("Single reporting package with issuer's XBRL extension taxonomy files and Inline XBRL instance document must be compliant with the latest recommended version of the Taxonomy Packages specification (1.0)"), - modelObject=modelXbrl) - return ModelDocument.LoadingException("Invalid taxonomy package") - return None + modelObject=modelXbrl) + return ModelDocument.LoadingException("Invalid taxonomy package") + return None -def modelXbrlLoadComplete(modelXbrl): +def modelXbrlLoadComplete(modelXbrl: ModelXbrl) -> None: if (getattr(modelXbrl.modelManager.disclosureSystem, "ESEFplugin", False) and (modelXbrl.modelDocument is None or modelXbrl.modelDocument.type not in (ModelDocument.Type.TESTCASESINDEX, ModelDocument.Type.TESTCASE, ModelDocument.Type.REGISTRY, ModelDocument.Type.RSSFEED))): if any("unconsolidated" in n for n in modelXbrl.modelManager.disclosureSystem.names): + + assert modelXbrl.modelDocument is not None + htmlElement = modelXbrl.modelDocument.xmlRootElement - if htmlElement.namespaceURI == xhtml: + if htmlElement.namespaceURI == xhtml: # xhtml is validated by ModelDocument inine, plain xhtml still needs validating XhtmlValidate.xhtmlValidate(modelXbrl, htmlElement) if modelXbrl.facts: modelXbrl.warning("arelle-ESEF:unconsolidatedContainsInline", - _("Inline XBRL not expected in unconsolidated xhtml document."), + _("Inline XBRL not expected in unconsolidated xhtml document."), modelObject=modelXbrl) return if modelXbrl.modelDocument is None: modelXbrl.error("ESEF.3.1.3.missingOrInvalidTaxonomyPackage", - _("RTS Annex III Par 3 and ESEF 3.1.3 requires an XBRL Report Package but one could not be loaded."), + _("RTS Annex III Par 3 and ESEF 3.1.3 requires an XBRL Report Package but one could not be loaded."), modelObject=modelXbrl) if (modelXbrl.modelDocument is None or not modelXbrl.facts and "ESEF.RTS.Art.6.a.noInlineXbrlTags" not in modelXbrl.errors): @@ -138,7 +159,7 @@ def modelXbrlLoadComplete(modelXbrl): _("RTS on ESEF requires inline XBRL, no facts were reported."), modelObject=modelXbrl) -def validateXbrlStart(val, parameters=None, *args, **kwargs): +def validateXbrlStart(val: ValidateXbrl, parameters: dict[Any, Any] | None=None, *args: Any, **kwargs: Any) -> None: val.validateESEFplugin = val.validateDisclosureSystem and getattr(val.disclosureSystem, "ESEFplugin", False) if not (val.validateESEFplugin): return @@ -148,18 +169,22 @@ def validateXbrlStart(val, parameters=None, *args, **kwargs): val.consolidated = not val.unconsolidated val.authority = None if parameters: - p = parameters.get(qname("authority",noPrefixIsNoNamespace=True)) + p = parameters.get(qname("authority",noPrefixIsNoNamespace=True)) # type: ignore[no-untyped-call] if p and len(p) == 2 and p[1] not in ("null", "None", None): v = p[1] # formula dialog and cmd line formula parameters may need type conversion val.authority = v - + authorityValidations = loadAuthorityValidations(val.modelXbrl) + # loadAuthorityValidations returns either a list or a dict but in this context, we expect a dict. + # By using cast, we let mypy know that a list is _not_ expected here. + authorityValidations = cast(dict[Any, Any], authorityValidations) + val.authParam = authorityValidations["default"] val.authParam.update(authorityValidations.get(val.authority, {})) for convertListIntoSet in ("outdatedTaxonomyURLs", "effectiveTaxonomyURLs", "standardTaxonomyURIs", "additionalMandatoryTags"): if convertListIntoSet in val.authParam: val.authParam[convertListIntoSet] = set(val.authParam[convertListIntoSet]) - + # add in formula messages if not loaded formulaMsgsUrls = val.authParam.get("formulaMessagesAdditionalURLs", ()) _reCacheRelationships = False @@ -174,15 +199,15 @@ def validateXbrlStart(val, parameters=None, *args, **kwargs): _reCacheRelationships = True if _reCacheRelationships: modelXbrl.relationshipSets.clear() # relationships have to be re-cached - + formulaOptions = val.modelXbrl.modelManager.formulaOptions # skip formula IDs as needed per authority if no formula runIDs provided by environment val.priorFormulaOptionsRunIDs = formulaOptions.runIDs if not formulaOptions.runIDs and val.authParam["formulaRunIDs"]: formulaOptions.runIDs = val.authParam["formulaRunIDs"] - - -def validateXbrlFinally(val, *args, **kwargs): + + +def validateXbrlFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None: if not (val.validateESEFplugin): return @@ -196,9 +221,9 @@ def validateXbrlFinally(val, *args, **kwargs): _statusMsg = _("validating {0} filing rules").format(val.disclosureSystem.name) modelXbrl.profileActivity() modelXbrl.modelManager.showStatus(_statusMsg) - + prefixedNamespaces = modelXbrl.prefixedNamespaces - + reportPackageMaxMB = val.authParam["reportPackageMaxMB"] if reportPackageMaxMB is not None and modelXbrl.fileSource.fs: # must be a zip to be a report package maxMB = float(reportPackageMaxMB) @@ -209,18 +234,18 @@ def validateXbrlFinally(val, *args, **kwargs): # not usable because zip may be posted or streamed: _size = os.path.getsize(modelXbrl.fileSource.basefile) if _size > maxMB * 1048576: modelXbrl.error("arelle.ESEF.maximumReportPackageSize", - _("The authority %(authority)s requires a report package size under %(maxSize)s MB, size is %(size)s."), + _("The authority %(authority)s requires a report package size under %(maxSize)s MB, size is %(size)s."), modelObject=modelXbrl, authority=val.authority, maxSize=reportPackageMaxMB, size=_size) - + if val.authority == "UKFRC": if modelXbrl.fileSource and modelXbrl.fileSource.taxonomyPackage and modelXbrl.fileSource.taxonomyPackage["publisherCountry"] != "GB": modelXbrl.error("UKFRC.1.2.publisherCountrySetting", - _("The \"Publisher Country\" element of the report package metadata for a UKSEF report MUST be set to \"GB\" but was \"%(publisherCountry)s\"."), + _("The \"Publisher Country\" element of the report package metadata for a UKSEF report MUST be set to \"GB\" but was \"%(publisherCountry)s\"."), modelObject=modelXbrl, publisherCountry=modelXbrl.fileSource.taxonomyPackage["publisherCountry"] ) - + reportXmlLang = None firstRootmostXmlLangDepth = 9999999 - + _ifrsNses = [] _ifrsNs = None for targetNs in modelXbrl.namespaceDocs.keys(): @@ -229,30 +254,33 @@ def validateXbrlFinally(val, *args, **kwargs): if val.consolidated: if not _ifrsNses: modelXbrl.warning("ESEF.RTS.ifrsRequired", - _("RTS on ESEF requires IFRS taxonomy."), + _("RTS on ESEF requires IFRS taxonomy."), modelObject=modelXbrl) return if len(_ifrsNses) > 1: modelXbrl.error("Arelle.ESEF.multipleIfrsTaxonomies", - _("Multuple IFRS taxonomies were imported %(ifrsNamespaces)s."), + _("Multuple IFRS taxonomies were imported %(ifrsNamespaces)s."), modelObject=modelXbrl, ifrsNamespaces=", ".join(_ifrsNses)) if _ifrsNses: _ifrsNs = _ifrsNses[0] - - esefPrimaryStatementPlaceholders = set(qname(_ifrsNs, n) for n in esefPrimaryStatementPlaceholderNames) - esefStatementsOfMonetaryDeclaration = set(qname(_ifrsNs, n) for n in esefStatementsOfMonetaryDeclarationNames) - esefMandatoryElements2020 = set(qname(_ifrsNs, n) for n in esefMandatoryElementNames2020) - + + esefPrimaryStatementPlaceholders = set(qname(_ifrsNs, n) for n in esefPrimaryStatementPlaceholderNames) # type: ignore[no-untyped-call] + esefStatementsOfMonetaryDeclaration = set(qname(_ifrsNs, n) for n in esefStatementsOfMonetaryDeclarationNames) # type: ignore[no-untyped-call] + esefMandatoryElements2020 = set(qname(_ifrsNs, n) for n in esefMandatoryElementNames2020) # type: ignore[no-untyped-call] + if modelDocument.type == ModelDocument.Type.INSTANCE and not val.unconsolidated: modelXbrl.error("ESEF.I.1.instanceShallBeInlineXBRL", - _("RTS on ESEF requires inline XBRL instances."), + _("RTS on ESEF requires inline XBRL instances."), modelObject=modelXbrl) - + checkFilingDimensions(val) # sets up val.primaryItems and val.domainMembers val.hasExtensionSchema = val.hasExtensionPre = val.hasExtensionCal = val.hasExtensionDef = val.hasExtensionLbl = False + + # ModelDocument.load has None as a return type. For typing reasons, we need to guard against that here. + assert modelXbrl.modelDocument is not None checkFilingDTS(val, modelXbrl.modelDocument, []) modelXbrl.profileActivity("... filer DTS checks", minTimeToShow=1.0) - + if val.consolidated and not (val.hasExtensionSchema and val.hasExtensionPre and val.hasExtensionCal and val.hasExtensionDef and val.hasExtensionLbl): missingFiles = [] if not val.hasExtensionSchema: missingFiles.append("schema file") @@ -264,19 +292,18 @@ def validateXbrlFinally(val, *args, **kwargs): _("Extension taxonomies MUST consist of at least a schema file and presentation, calculation, definition and label linkbases" ": missing %(missingFiles)s"), modelObject=modelXbrl, missingFiles=", ".join(missingFiles)) - + #if modelDocument.type == ModelDocument.Type.INLINEXBRLDOCUMENTSET: # # reports only under reports, none elsewhere # modelXbrl.fileSource.dir - if modelDocument.type in (ModelDocument.Type.INLINEXBRL, ModelDocument.Type.INLINEXBRLDOCUMENTSET, ModelDocument.Type.INSTANCE, ModelDocument.Type.UnknownXML): footnotesRelationshipSet = modelXbrl.relationshipSet("XBRL-footnotes") orphanedFootnotes = set() noLangFootnotes = set() factLangFootnotes = defaultdict(set) footnoteRoleErrors = set() - transformRegistryErrors = set() - def checkFootnote(elt, text): + transformRegistryErrors: set[ModelInlineFact] = set() + def checkFootnote(elt: ModelInlineFootnote | ModelResource, text: str) -> None: if text: # non-empty footnote must be linked to a fact if not empty if not any(isinstance(rel.fromModelObject, ModelFact) for rel in footnotesRelationshipSet.toModelObject(elt)): @@ -292,9 +319,9 @@ def checkFootnote(elt, text): rel.arcrole == XbrlConst.factFootnote and rel.linkrole == XbrlConst.defaultLinkRole for rel in footnotesRelationshipSet.toModelObject(elt)): footnoteRoleErrors.add(elt) - + # check file name of each inline document (which might be below a top-level IXDS) - ixdsDocDirs = set() + ixdsDocDirs: set[str] = set() for doc in modelXbrl.urlDocs.values(): if doc.type in (ModelDocument.Type.INLINEXBRL, ModelDocument.Type.UnknownXML): _baseName, _baseExt = os.path.splitext(doc.basename) @@ -357,7 +384,7 @@ def checkFootnote(elt, text): modelXbrl.error("ESEF.RTS.Art.3.htmlDoctype", _("Doctype SHALL NOT specify html validation: %(doctype)s"), modelObject=doc, doctype=docinfo.doctype) - + if len(ixdsDocDirs) > 1 and val.consolidated: modelXbrl.error("ESEF.2.6.2.reportSetIncorrectlyPlacedInPackage", @@ -371,8 +398,11 @@ def checkFootnote(elt, text): presentedHiddenEltIds = defaultdict(list) eligibleForTransformHiddenFacts = [] requiredToDisplayFacts = [] - requiredToDisplayFactIds = {} + requiredToDisplayFactIds: dict[Any, Any] = {} firstIxdsDoc = True + + # ModelDocument.load has None as a return type. For typing reasons, we need to guard against that here. + assert modelXbrl.modelDocument is not None for ixdsHtmlRootElt in (modelXbrl.ixdsHtmlElements if val.consolidated else # ix root elements for all ix docs in IXDS (modelXbrl.modelDocument.xmlRootElement,)): # plain xhtml filing ixNStag = getattr(ixdsHtmlRootElt.modelDocument, "ixNStag", ixbrl11) @@ -381,7 +411,10 @@ def checkFootnote(elt, text): ixExcludeTag = ixNStag + "exclude" ixTupleTag = ixNStag + "tuple" ixFractionTag = ixNStag + "fraction" - for elt, depth in etreeIterWithDepth(ixdsHtmlRootElt): + + for uncast_elt, depth in etreeIterWithDepth(ixdsHtmlRootElt): + elt = cast(Any, uncast_elt) + eltTag = elt.tag if isinstance(elt, (_ElementTree, _Comment, _ProcessingInstruction, EntityBase)): continue # comment or other non-parsed element @@ -410,7 +443,7 @@ def checkFootnote(elt, text): if _ancestorElt.tag in ixTextTags: hasParentIxTextTag = True break - _ancestorElt = _ancestorElt.getparent() + _ancestorElt = _ancestorElt.getparent() if scheme(src) in ("http", "https", "ftp"): modelXbrl.error("ESEF.4.1.6.xHTMLDocumentContainsExternalReferences" if val.unconsolidated else "ESEF.3.5.1.inlineXbrlDocumentContainsExternalReferences", @@ -468,9 +501,9 @@ def checkFootnote(elt, text): modelXbrl.error("ESEF.2.5.1.embeddedImageNotUsingBase64Encoding", _("Base64 encoding error %(err)s in image source: %(src)s."), modelObject=elt, err=str(err), src=src[:128]) - # links to external documents are allowed as of 2021 per G.2.5.1 - # Since ESEF is a format requirement and is not expected to impact the 'human readable layer' of a report, - # this guidance should not be seen as limiting the inclusion of links to external websites, to other documents + # links to external documents are allowed as of 2021 per G.2.5.1 + # Since ESEF is a format requirement and is not expected to impact the 'human readable layer' of a report, + # this guidance should not be seen as limiting the inclusion of links to external websites, to other documents # or to other sections of the annual financial report.''' #elif eltTag == "a": # href = elt.get("href","").strip() @@ -490,11 +523,11 @@ def checkFootnote(elt, text): _("For XHTML stand-alone documents, the CSS SHOULD be embedded within the document."), modelObject=elt, element=eltTag) elif len(modelXbrl.ixdsHtmlElements) > 1: - f = elt.get("href") - if not f or isHttpUrl(f) or os.path.isabs(f): + _file = elt.get("href") + if not _file or isHttpUrl(_file) or os.path.isabs(_file): modelXbrl.warning("ESEF.2.5.4.externalCssReportPackage", _("The CSS file should be physically stored within the report package: %{file}s."), - modelObject=elt, file=f) + modelObject=elt, file=_file) else: modelXbrl.warning("ESEF.2.5.4.externalCssFileForSingleIXbrlDocument", _("Where an Inline XBRL document set contains a single document, the CSS SHOULD be embedded within the document."), @@ -506,13 +539,13 @@ def checkFootnote(elt, text): modelXbrl.warning("ESEF.2.5.4.embeddedCssForMultiHtmlIXbrlDocumentSets", _("Where an Inline XBRL document set contains multiple documents, the CSS SHOULD be defined in a separate file."), modelObject=elt, element=eltTag) - - + + if eltTag in ixTags and elt.get("target") and ixTargetUsage != "allowed": modelXbrl.log(ixTargetUsage.upper(), "ESEF.2.5.3.targetAttributeUsedForESEFContents", _("Target attribute %(severityVerb)s not be used unless explicitly required by local jurisdictions: element %(localName)s, target attribute %(target)s."), - modelObject=elt, localName=elt.elementQname, target=elt.get("target"), + modelObject=elt, localName=elt.elementQname, target=elt.get("target"), severityVerb={"warning":"SHOULD","error":"MUST"}[ixTargetUsage]) if eltTag == ixTupleTag: modelXbrl.error("ESEF.2.4.1.tupleElementUsed", @@ -557,7 +590,7 @@ def checkFootnote(elt, text): _("\"display:none\" style applies to a fact that is not in an ix:hidden section."), modelObject=ixElt) del ixHiddenFacts - + firstIxdsDoc = False if val.unconsolidated: @@ -567,7 +600,7 @@ def checkFootnote(elt, text): modelXbrl.error("ESEF.2.4.1.transformableElementIncludedInHiddenSection", _("The ix:hidden section of Inline XBRL document MUST not include elements eligible for transformation. " "%(countEligible)s fact(s) were eligible for transformation: %(elements)s"), - modelObject=eligibleForTransformHiddenFacts, + modelObject=eligibleForTransformHiddenFacts, countEligible=len(eligibleForTransformHiddenFacts), elements=", ".join(sorted(set(str(f.qname) for f in eligibleForTransformHiddenFacts)))) for ixdsHtmlRootElt in modelXbrl.ixdsHtmlElements: @@ -589,29 +622,33 @@ def checkFootnote(elt, text): if requiredToDisplayFacts: modelXbrl.error("ESEF.2.4.1.factInHiddenSectionNotInReport", _("The ix:hidden section contains %(countUnreferenced)s fact(s) whose @id is not applied on any \"-esef-ix- hidden\" style: %(elements)s"), - modelObject=requiredToDisplayFacts, + modelObject=requiredToDisplayFacts, countUnreferenced=len(requiredToDisplayFacts), elements=", ".join(sorted(set(str(f.qname) for f in requiredToDisplayFacts)))) del eligibleForTransformHiddenFacts, hiddenEltIds, presentedHiddenEltIds, requiredToDisplayFacts elif modelDocument.type == ModelDocument.Type.INSTANCE: - for elt in modelDocument.xmlRootElement.iter(): + for uncast_elt in modelDocument.xmlRootElement.iter(): + elt = cast(Any, uncast_elt) + if elt.qname == XbrlConst.qnLinkFootnote: # for now assume no private elements extend link:footnote checkFootnote(elt, elt.stringValue) - + if val.unconsolidated: modelXbrl.modelManager.showStatus(None) return # no more checks apply - + contextsWithDisallowedOCEs = [] contextsWithDisallowedOCEcontent = [] - contextsWithPeriodTime = [] - contextsWithPeriodTimeZone = [] + contextsWithPeriodTime: list[ModelContext] = [] + contextsWithPeriodTimeZone: list[ModelContext] = [] contextIdentifiers = defaultdict(list) - nonStandardTypedDimensions = defaultdict(set) + nonStandardTypedDimensions: dict[Any, Any] = defaultdict(set) for context in modelXbrl.contexts.values(): - for elt in context.iterdescendants("{http://www.xbrl.org/2003/instance}startDate", + for uncast_elt in context.iterdescendants("{http://www.xbrl.org/2003/instance}startDate", "{http://www.xbrl.org/2003/instance}endDate", "{http://www.xbrl.org/2003/instance}instant"): + elt = cast(Any, uncast_elt) + m = datetimePattern.match(elt.stringValue) if m: if m.group(1): @@ -624,13 +661,13 @@ def checkFootnote(elt, text): for elt in context.iterdescendants("{http://www.xbrl.org/2003/instance}scenario"): if isinstance(elt,ModelObject): if any(True for child in elt.iterchildren() - if isinstance(child,ModelObject) and + if isinstance(child,ModelObject) and child.tag not in ("{http://xbrl.org/2006/xbrldi}explicitMember", "{http://xbrl.org/2006/xbrldi}typedMember")): contextsWithDisallowedOCEcontent.append(context) # check periods here contextIdentifiers[context.entityIdentifier].append(context) - + if contextsWithDisallowedOCEs: modelXbrl.error("ESEF.2.1.3.segmentUsed", _("xbrli:segment container MUST NOT be used in contexts: %(contextIds)s"), @@ -667,11 +704,11 @@ def checkFootnote(elt, text): modelXbrl.error("ESEF.2.1.2.periodWithTimeZone", _("The xbrli:startDate, xbrli:endDate and xbrli:instant elements MUST identify periods using whole days (i.e. specified without a time zone): %(contextIds)s"), modelObject=contextsWithPeriodTimeZone, contextIds=", ".join(c.id for c in contextsWithPeriodTimeZone)) - + # identify unique contexts and units mapContext = {} mapUnit = {} - uniqueContextHashes = {} + uniqueContextHashes: dict[Any, Any] = {} for context in modelXbrl.contexts.values(): h = context.contextDimAwareHash if h in uniqueContextHashes: @@ -680,7 +717,7 @@ def checkFootnote(elt, text): else: uniqueContextHashes[h] = context del uniqueContextHashes - uniqueUnitHashes = {} + uniqueUnitHashes: dict[Any, Any] = {} utrValidator = ValidateUtr(modelXbrl) utrUnitIds = set(u.unitId for unitItemType in utrValidator.utrItemTypeEntries.values() @@ -702,17 +739,17 @@ def checkFootnote(elt, text): _("Custom measure SHOULD NOT duplicate a UnitID of UTR: %(measure)s"), modelObject=unit, measure=measure) del uniqueUnitHashes - + reportedMandatory = set() precisionFacts = set() numFactsByConceptContextUnit = defaultdict(list) textFactsByConceptContext = defaultdict(list) footnotesRelationshipSet = modelXbrl.relationshipSet(XbrlConst.factFootnote, XbrlConst.defaultLinkRole) noLangFacts = [] - textFactsMissingReportLang = [] + textFactsMissingReportLang: list[Any] = [] conceptsUsed = set() langsUsedByTextFacts = set() - + hasNoFacts = True for qn, facts in modelXbrl.factsByQname.items(): hasNoFacts = False @@ -744,16 +781,16 @@ def checkFootnote(elt, text): conceptsUsed.add(dim.dimension) if dim.isExplicit: conceptsUsed.add(dim.member) - #don't consider typed member as a used concept which needs to be in pre LB + #don't consider typed member as a used concept which needs to be in pre LB #elif dim.isTyped: # conceptsUsed.add(dim.typedMember) ''' - + if noLangFacts: modelXbrl.error("ESEF.2.5.2.undefinedLanguageForTextFact", _("Each tagged text fact MUST have the 'xml:lang' attribute assigned or inherited."), modelObject=noLangFacts) - + # missing report lang text facts if reportXmlLang: for fList in textFactsByConceptContext.values(): @@ -761,8 +798,8 @@ def checkFootnote(elt, text): modelXbrl.error("ESEF.2.5.2.taggedTextFactOnlyInLanguagesOtherThanLanguageOfAReport", _("Each tagged text fact MUST have the 'xml:lang' provided in at least the language of the report: %(element)s"), modelObject=fList, element=fList[0].qname) - - + + # 2.2.4 test decVals = {} for fList in numFactsByConceptContextUnit.values(): @@ -799,21 +836,21 @@ def checkFootnote(elt, text): if precisionFacts: modelXbrl.error("ESEF.2.2.1.precisionAttributeUsed", - _("The accuracy of numeric facts MUST be defined with the 'decimals' attribute rather than the 'precision' attribute: %(elements)s."), + _("The accuracy of numeric facts MUST be defined with the 'decimals' attribute rather than the 'precision' attribute: %(elements)s."), modelObject=precisionFacts, elements=", ".join(sorted(str(e.qname) for e in precisionFacts))) - - missingElements = (mandatory - reportedMandatory) + + missingElements = (mandatory - reportedMandatory) if missingElements: modelXbrl.error("ESEF.???.missingRequiredElements", - _("Required elements missing from document: %(elements)s."), + _("Required elements missing from document: %(elements)s."), modelObject=modelXbrl, elements=", ".join(sorted(str(qn) for qn in missingElements))) - + if transformRegistryErrors: modelXbrl.error("ESEF.2.2.3.incorrectTransformationRuleApplied", _("ESMA recommends applying the Transformation Rules Registry 4, as published by XBRL International or any more recent versions of the Transformation Rules Registry provided with a 'Recommendation' status, for these elements: %(elements)s."), - modelObject=transformRegistryErrors, + modelObject=transformRegistryErrors, elements=", ".join(sorted(str(fact.qname) for fact in transformRegistryErrors))) - + if orphanedFootnotes: modelXbrl.warning("ESEF.2.3.1.unusedFootnote", _("Every nonempty link:footnote element SHOULD be linked to at least one fact."), @@ -845,10 +882,12 @@ def checkFootnote(elt, text): modelXbrl.error("ESEF.2.3.1.nonStandardRoleForFootnote", _("The xlink:role attribute of a link:footnote and link:footnoteLink element as well as xlink:arcrole attribute of a link:footnoteArc MUST be defined in the XBRL Specification 2.1."), modelObject=footnoteRoleErrors) - + nonStdFootnoteElts = list() for modelLink in modelXbrl.baseSets[("XBRL-footnotes",None,None,None)]: - for elt in modelLink.iterchildren(): + for uncast_elt in modelLink.iterchildren(): + elt = cast(Any, uncast_elt) + if isinstance(elt, (_ElementTree, _Comment, _ProcessingInstruction)): continue # comment or other non-parsed element if elt.qname not in FOOTNOTE_LINK_CHILDREN: @@ -858,11 +897,11 @@ def checkFootnote(elt, text): modelXbrl.error("ESEF.2.3.2.nonStandardElementInFootnote", _("A link:footnoteLink element MUST have no children other than link:loc, link:footnote, and link:footnoteArc."), modelObject=nonStdFootnoteElts) - + conceptsUsedByFacts = conceptsUsed.copy() #for qn in modelXbrl.qnameDimensionDefaults.values(): # conceptsUsed.add(modelXbrl.qnameConcepts.get(qn)) - + # 3.1.1 test hasOutdatedUrl = False for e in val.authParam["outdatedTaxonomyURLs"]: @@ -871,14 +910,14 @@ def checkFootnote(elt, text): _("The issuer's extension taxonomies MUST import the applicable version of the taxonomy files prepared by ESMA. Outdated entry point: %(url)s"), modelObject=modelDocument, url=e) hasOutdatedUrl = True - + if ("authorityRequiredTaxonomyURLs" in val.authParam and not any(e in val.extensionImportedUrls for e in val.authParam["authorityRequiredTaxonomyURLs"])): val.modelXbrl.error( "UKFRC22.3.requiredFrcEntryPointNotImported", _("The issuer's extension taxonomies MUST import the FRC entry point of the taxonomy files prepared by %(authority)s."), modelObject=modelDocument, authority=val.authParam["authorityName"]) - + if not hasOutdatedUrl and not any(e in val.extensionImportedUrls for e in val.authParam["effectiveTaxonomyURLs"]): val.modelXbrl.error( "UKFRC22.1.requiredUksefEntryPointNotImported" if val.authority == "UKFRC" else @@ -886,10 +925,10 @@ def checkFootnote(elt, text): _("The issuer's extension taxonomies MUST import the entry point of the taxonomy files prepared by %(authority)s."), modelObject=modelDocument, authority=val.authParam["authorityName"]) - + # unused elements in linkbases unreportedLbElts = set() - for arcroles, err, checkRoots, lbType in ( + for arcroles, error, checkRoots, lbType in ( ((parentChild,), "elements{}UsedForTagging{}AppliedInPresentationLinkbase", True, "presentation"), ((summationItem,), "elements{}UsedForTagging{}AppliedInCalculationLinkbase", False, "calculation"), ((hc_all, hc_notAll, dimensionDomain,domainMember), "elements{}UsedForTagging{}AppliedInDefinitionLinkbase", False, "definition")): @@ -914,7 +953,7 @@ def checkFootnote(elt, text): unreportedLbElts.add(to) reportedEltsNotInLb.discard(fr) reportedEltsNotInLb.discard(to) - + if reportedEltsNotInLb and lbType == "presentation": # reported pri items excluded from having to be in pre LB nsExcl = val.authParam.get("lineItemsMustBeInPreLbExclusionNsPattern") @@ -929,15 +968,15 @@ def checkFootnote(elt, text): modelXbrl.error("ESEF.3.4.6.UsableConceptsNotAppliedByTaggedFacts", _("All usable concepts in extension taxonomy relationships MUST be applied by tagged facts: %(elements)s."), modelObject=unreportedLbElts, elements=", ".join(sorted((str(c.qname) for c in unreportedLbElts)))) - + # 3.4.4 check for presentation preferred labels missingConceptLabels = defaultdict(set) # by role pfsConceptsRootInPreLB = set() # Annex II para 1 check of monetary declaration statementMonetaryUnitReportedConcepts = defaultdict(set) # index is unit, set is concepts - statementMonetaryUnitFactCounts = {} - - def checkLabels(parent, relSet, labelrole, visited): + statementMonetaryUnitFactCounts: dict[Any, Any] = {} + + def checkLabels(parent: ModelConcept, relSet: ModelRelationshipSet, labelrole: str | None, visited: set[ModelConcept]) -> None: if not parent.label(labelrole,lang=reportXmlLang,fallbackToQname=False): if (not labelrole or labelrole == standardLabel) and isExtension(val, parent): missingConceptLabels[labelrole].add(parent) @@ -957,8 +996,8 @@ def checkLabels(parent, relSet, labelrole, visited): _("Preferred label role SHOULD be used when concept is duplicated in same presentation tree location: %(qname)s."), modelObject=rels+[concept], qname=concept.qname) visited.remove(parent) - - def checkMonetaryUnits(parent, relSet, visited): + + def checkMonetaryUnits(parent: ModelConcept, relSet: ModelRelationshipSet, visited: set[ModelConcept]) -> None: if parent.isMonetary: for f in modelXbrl.factsByQname.get(parent.qname,()): u = f.unit @@ -996,10 +1035,12 @@ def checkMonetaryUnits(parent, relSet, visited): modelXbrl.warning("ESEF.3.4.7.singleExtendedLinkRoleUsedForAllPFSs", _("Separate Extended Link Roles are required by %(elr)s for hierarchies: %(roots)s."), modelObject=roots, elr=modelXbrl.roleTypeDefinition(ELR), roots=", ".join(sorted((str(c.qname) for c in roots)))) + + concepts: set[ModelConcept] for labelrole, concepts in missingConceptLabels.items(): modelXbrl.warning("ESEF.3.4.5.missingLabelForRoleInReportLanguage", _("Label for %(role)s role SHOULD be available in report language for concepts: %(qnames)s."), - modelObject=concepts, qnames=", ".join(str(c.qname) for c in concepts), + modelObject=concepts, qnames=", ".join(str(c.qname) for c in concepts), role=os.path.basename(labelrole) if labelrole else "standard") if not pfsConceptsRootInPreLB: # no PFS statements were recognized @@ -1008,11 +1049,11 @@ def checkMonetaryUnits(parent, relSet, visited): modelObject=modelXbrl) # dereference del missingConceptLabels, pfsConceptsRootInPreLB - + # facts in declared units RTS Annex II para 1 # assume declared currency is one with majority of concepts monetaryItemsNotInDeclaredCurrency = [] - unitCounts = sorted(statementMonetaryUnitFactCounts.items(), key=lambda uc:uc[1], reverse=True) + unitCounts = sorted(statementMonetaryUnitFactCounts.items(), key=lambda uc:uc[1], reverse=True) # type: ignore[no-any-return] if unitCounts: # must have a monetary statement fact for this check _declaredCurrency = unitCounts[0][0] for facts in modelXbrl.factsByQname.values(): @@ -1033,49 +1074,50 @@ def checkMonetaryUnits(parent, relSet, visited): _("Numbers SHALL be marked up in declared currency %(currency)s: %(qnames)s."), modelObject=monetaryItemsNotInDeclaredCurrency, currency=_declaredCurrency, qnames=", ".join(sorted(str(c.qname) for c in monetaryItemsNotInDeclaredCurrency))) - + # mandatory facts RTS Annex II missingMandatoryElements = esefMandatoryElements2020 - modelXbrl.factsByQname.keys() if missingMandatoryElements: modelXbrl.warning("ESEF.RTS.Annex.II.Par.2.missingMandatoryMarkups", _("Mandatory elements to be marked up are missing: %(qnames)s."), modelObject=missingMandatoryElements, qnames=", ".join(sorted(str(qn) for qn in missingMandatoryElements))) - + # supplemental authority required tags - additionalTagQnames = set(qname(n, prefixedNamespaces) + additionalTagQnames = set(qname(n, prefixedNamespaces) # type: ignore[no-untyped-call] for n in val.authParam.get("additionalMandatoryTags", ()) - if qname(n, prefixedNamespaces)) + if qname(n, prefixedNamespaces)) # type: ignore[no-untyped-call] missingAuthorityElements = additionalTagQnames - modelXbrl.factsByQname.keys() if missingAuthorityElements: modelXbrl.warning("arelle.ESEF.missingAuthorityMandatoryMarkups", _("Mandatory authority elements to be marked up are missing: %(qnames)s."), modelObject=missingAuthorityElements, qnames=", ".join(sorted(str(qn) for qn in missingAuthorityElements))) - - - # duplicated core taxonomy elements + + # duplicated core taxonomy elements for name, concepts in modelXbrl.nameConcepts.items(): if len(concepts) > 1: - i = None # ifrs Concept + # Note 2022-08-12: i was being used as an int somewhere else, causing mypy some confusion. + # I renamed i to _i to handle that. + _i = None # ifrs Concept for c in concepts: if c.qname.namespaceURI == _ifrsNs: - i = c + _i = c break - if i is not None: + if _i is not None: for c in concepts: - if (c.qname.namespaceURI not in _ifrsNses + if (c.qname.namespaceURI not in _ifrsNses and isExtension(val, c.qname.namespaceURI) # may be a authority-specific duplication such as UK-FRC - and c.balance == i.balance and c.periodType == i.periodType): + and c.balance == _i.balance and c.periodType == _i.periodType): modelXbrl.error("ESEF.RTS.Annex.IV.Par.4.1.extensionElementDuplicatesCoreElement", - _("Extension elements must not duplicate the existing elements from the core taxonomy and be identifiable %(qname)s."), - modelObject=(c,i), qname=c.qname) - + _("Extension elements must not duplicate the existing elements from the core taxonomy and be identifiable %(qname)s."), + modelObject=(c,_i), qname=c.qname) + modelXbrl.profileActivity(_statusMsg, minTimeToShow=0.0) modelXbrl.modelManager.showStatus(None) -def validateFinally(val, *args, **kwargs): # runs all inline checks +def validateFinally(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None: # runs all inline checks if not (val.validateESEFplugin): return - + if val.unconsolidated: return @@ -1090,18 +1132,18 @@ def validateFinally(val, *args, **kwargs): # runs all inline checks numXbrlErrors = sum(ixErrorPattern.match(e) is not None for e in modelXbrl.errors if isinstance(e,str)) if numXbrlErrors: modelXbrl.error("ESEF.RTS.Annex.III.Par.1.invalidInlineXBRL", - _("RTS on ESEF requires valid XBRL instances, %(numXbrlErrors)s errors were reported."), + _("RTS on ESEF requires valid XBRL instances, %(numXbrlErrors)s errors were reported."), modelObject=modelXbrl, numXbrlErrors=numXbrlErrors) - + # force reporting of unsatisfied assertions for which there are no messages traceUnmessagedUnsatisfiedAssertions = True - -def validateFormulaCompiled(modelXbrl, xpathContext): + +def validateFormulaCompiled(modelXbrl: ModelXbrl, xpathContext: XPathContext) -> None: # request unsatisfied assertions without a message to print a trace # this is not conditional on validateESEFplugin so the flag is set even if DisclosureSystemChecks not requested upon compiling but set later in workflow xpathContext.formulaOptions.traceUnmessagedUnsatisfiedAssertions = True - -def validateFormulaFinished(val, *args, **kwargs): # runs *after* formula (which is different for test suite from other operation + +def validateFormulaFinished(val: ValidateXbrl, *args: Any, **kwargs: Any) -> None: # runs *after* formula (which is different for test suite from other operation if not (val.validateESEFplugin): return @@ -1116,19 +1158,18 @@ def validateFormulaFinished(val, *args, **kwargs): # runs *after* formula (which sumErrMsgs += numErrMsgs if sumErrMsgs: modelXbrl.error("ESEF.2.7.1.targetXBRLDocumentWithFormulaErrors", - _("Target XBRL document MUST be valid against the assertions specified in ESEF taxonomy, %(numUnsatisfied)s with errors."), + _("Target XBRL document MUST be valid against the assertions specified in ESEF taxonomy, %(numUnsatisfied)s with errors."), modelObject=modelXbrl, numUnsatisfied=sumErrMsgs) if sumWrnMsgs: modelXbrl.warning("ESEF.2.7.1.targetXBRLDocumentWithFormulaWarnings", - _("Target XBRL document SHOULD be valid against the assertions specified in ESEF taxonomy, %(numUnsatisfied)s with warnings."), + _("Target XBRL document SHOULD be valid against the assertions specified in ESEF taxonomy, %(numUnsatisfied)s with warnings."), modelObject=modelXbrl, numUnsatisfied=sumWrnMsgs) - -def testcaseVariationReportPackageIxdsOptions(validate, rptPkgIxdsOptions): +def testcaseVariationReportPackageIxdsOptions(validate: ValidateXbrl, rptPkgIxdsOptions: dict[str, bool]) -> None: if getattr(validate.modelXbrl.modelManager.disclosureSystem, "ESEFplugin", False): rptPkgIxdsOptions["lookOutsideReportsDirectory"] = True rptPkgIxdsOptions["combineIntoSingleIxds"] = True - + __pluginInfo__ = { # Do not use _( ) in pluginInfo itself (it is applied later, after loading 'name': 'Validate ESMA ESEF', diff --git a/arelle/plugin/validate/ESEF/common.py b/arelle/plugin/validate/ESEF/common.py new file mode 100644 index 0000000000..7a67fd804e --- /dev/null +++ b/arelle/plugin/validate/ESEF/common.py @@ -0,0 +1,5 @@ +"""Shared functions and type hints.""" +from __future__ import annotations +from collections.abc import Callable + +TypeGetText = Callable[[str], str] diff --git a/pyproject.toml b/pyproject.toml index b903e82617..3f177e8a99 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,10 +23,7 @@ ignore_errors = true # when the library is converted, the above entry will be replaced (strict is demanded) [[tool.mypy.overrides]] module = [ - 'arelle.plugin.validate.ESEF.Const', - 'arelle.plugin.validate.ESEF.Dimensions', - 'arelle.plugin.validate.ESEF.DTS', - 'arelle.plugin.validate.ESEF.Util' + 'arelle.plugin.validate.ESEF', ] ignore_errors = false strict = true From 956cfcca1aaa18dd2521172a851da3375abd2978 Mon Sep 17 00:00:00 2001 From: Patrik Lindgren <21142447+ggravlingen@users.noreply.github.com> Date: Tue, 16 Aug 2022 16:37:46 +0000 Subject: [PATCH 013/111] Cleanup whitespace --- arelle/CntlrCmdLine.py | 166 +- arelle/CntlrComServer.py | 4 +- arelle/CntlrProfiler.py | 8 +- arelle/CntlrQuickBooks.py | 234 +-- arelle/CntlrWebMain.py | 212 +-- arelle/CntlrWinMain.py | 238 +-- arelle/CntlrWinTooltip.py | 22 +- arelle/DialogAbout.py | 14 +- arelle/DialogArcroleGroup.py | 46 +- arelle/DialogFind.py | 70 +- arelle/DialogFormulaParameters.py | 114 +- arelle/DialogLanguage.py | 32 +- arelle/DialogNewFactItem.py | 56 +- arelle/DialogOpenArchive.py | 134 +- arelle/DialogOpenTaxonomyPackage.py | 2 +- arelle/DialogPackageManager.py | 74 +- arelle/DialogPluginManager.py | 78 +- arelle/DialogRssWatch.py | 58 +- arelle/DialogURL.py | 14 +- arelle/DialogUserPassword.py | 26 +- arelle/DisclosureSystem.py | 38 +- arelle/FileSource.py | 104 +- arelle/FormulaConsisAsser.py | 10 +- arelle/FormulaEvaluator.py | 306 ++-- arelle/FunctionCustom.py | 12 +- arelle/FunctionFn.py | 86 +- arelle/FunctionIxt.py | 152 +- arelle/FunctionUtil.py | 6 +- arelle/FunctionXfi.py | 128 +- arelle/FunctionXs.py | 104 +- arelle/HashUtil.py | 10 +- arelle/HtmlUtil.py | 2 +- arelle/InstanceAspectsEvaluator.py | 10 +- arelle/LocalViewer.py | 24 +- arelle/Locale.py | 24 +- arelle/ModelFormulaObject.py | 838 ++++----- arelle/ModelInstanceObject.py | 408 ++--- arelle/ModelManager.py | 92 +- arelle/ModelObject.py | 148 +- arelle/ModelObjectFactory.py | 18 +- arelle/ModelRenderingObject.py | 422 ++--- arelle/ModelRssItem.py | 42 +- arelle/ModelRssObject.py | 12 +- arelle/ModelTestcaseObject.py | 66 +- arelle/ModelValue.py | 86 +- arelle/ModelVersObject.py | 130 +- arelle/ModelVersReport.py | 236 +-- arelle/PluginManager.py | 2 +- arelle/PrototypeDtsObject.py | 58 +- arelle/PrototypeInstanceObject.py | 24 +- arelle/RenderingEvaluator.py | 50 +- arelle/RenderingResolver.py | 118 +- arelle/TableStructure.py | 50 +- arelle/TkTableWrapper.py | 4 +- arelle/UITkTable.py | 18 +- arelle/UiUtil.py | 156 +- arelle/Updater.py | 18 +- arelle/Validate.py | 100 +- arelle/ValidateFilingText.py | 80 +- arelle/ValidateFormula.py | 346 ++-- arelle/ValidateInfoset.py | 24 +- arelle/ValidateUtr.py | 2 +- arelle/ValidateVersReport.py | 74 +- arelle/ValidateXbrlDTS.py | 180 +- arelle/ValidateXbrlDimensions.py | 50 +- arelle/ViewFile.py | 34 +- arelle/ViewFileConcepts.py | 8 +- arelle/ViewFileDTS.py | 6 +- arelle/ViewFileFactList.py | 8 +- arelle/ViewFileFactTable.py | 52 +- arelle/ViewFileFormulae.py | 10 +- arelle/ViewFileRelationshipSet.py | 36 +- arelle/ViewFileRenderedGrid.py | 82 +- arelle/ViewFileRoleTypes.py | 8 +- arelle/ViewFileRssFeed.py | 4 +- arelle/ViewFileTests.py | 32 +- arelle/ViewUtil.py | 6 +- arelle/ViewUtilFormulae.py | 8 +- arelle/ViewWinConcepts.py | 12 +- arelle/ViewWinDTS.py | 12 +- arelle/ViewWinDiffs.py | 32 +- arelle/ViewWinFactGrid.py | 64 +- arelle/ViewWinFactList.py | 18 +- arelle/ViewWinFactTable.py | 48 +- arelle/ViewWinFormulae.py | 18 +- arelle/ViewWinGrid.py | 2 +- arelle/ViewWinList.py | 24 +- arelle/ViewWinPane.py | 18 +- arelle/ViewWinProperties.py | 16 +- arelle/ViewWinRelationshipSet.py | 48 +- arelle/ViewWinRenderedGrid.py | 142 +- arelle/ViewWinRoleTypes.py | 14 +- arelle/ViewWinRssFeed.py | 14 +- arelle/ViewWinTests.py | 26 +- arelle/ViewWinTkTable.py | 2 +- arelle/ViewWinTree.py | 76 +- arelle/ViewWinTupleGrid.py | 48 +- arelle/ViewWinVersReport.py | 44 +- arelle/ViewWinXml.py | 4 +- arelle/WatchRss.py | 68 +- arelle/WebCache.py | 128 +- arelle/XPathContext.py | 85 +- arelle/XPathParser.py | 144 +- arelle/XbrlConst.py | 110 +- arelle/XbrlUtil.py | 14 +- arelle/XmlUtil.py | 106 +- arelle/XmlValidate.py | 44 +- arelle/XmlValidateParticles.py | 18 +- arelle/XmlValidateSchema.py | 44 +- arelle/examples/CustomLogger.py | 8 +- arelle/examples/LoadEFMvalidate.py | 8 +- arelle/examples/LoadSavePreLbCsv.py | 6 +- arelle/examples/LoadValidate.py | 10 +- arelle/examples/SaveTableToExelle.py | 22 +- arelle/examples/TR3toTR4.py | 176 +- arelle/examples/plugin/bigInstance.py | 84 +- .../examples/plugin/cmdWebServerExtension.py | 10 +- arelle/examples/plugin/crashTest.py | 6 +- arelle/examples/plugin/functionsCustom.py | 4 +- arelle/examples/plugin/hello_dolly.py | 12 +- arelle/examples/plugin/hello_i18n.py | 4 +- arelle/examples/plugin/importTestChild1.py | 40 +- arelle/examples/plugin/importTestChild2.py | 42 +- .../examples/plugin/importTestGrandchild1.py | 40 +- .../examples/plugin/importTestGrandchild2.py | 40 +- arelle/examples/plugin/importTestImported1.py | 44 +- .../examples/plugin/importTestImported11.py | 42 +- arelle/examples/plugin/importTestParent.py | 94 +- .../plugin/packagedImportTest/__init__.py | 92 +- .../packagedImportTest/importTestChild1.py | 40 +- .../packagedImportTest/importTestChild2.py | 42 +- .../importTestGrandchild1.py | 40 +- .../importTestGrandchild2.py | 40 +- .../packagedImportTest/importTestImported1.py | 46 +- .../importTestImported11.py | 40 +- .../subdir/importTestImported111.py | 40 +- .../subsubdir/importTestImported1111.py | 40 +- arelle/examples/plugin/sakaCalendar.py | 36 +- arelle/examples/plugin/saveInstanceInfoset.py | 26 +- arelle/examples/plugin/streamingExtensions.py | 30 +- .../plugin/testcaseIxExpectedHtmlFixup.py | 6 +- arelle/examples/plugin/updateTableLB.py | 52 +- arelle/examples/plugin/validateSchemaLxml.py | 34 +- .../examples/plugin/validateTableInfoset.py | 16 +- arelle/plugin/EdgarRendererAllReports.py | 14 +- arelle/plugin/SECCorrespondenceLoader.py | 40 +- arelle/plugin/TDnetLoader.py | 28 +- arelle/plugin/UKCompaniesHouseLoader.py | 26 +- arelle/plugin/formulaLoader.py | 436 ++--- arelle/plugin/formulaSaver.py | 80 +- arelle/plugin/formulaXPathChecker.py | 62 +- arelle/plugin/functionsMath.py | 48 +- arelle/plugin/functionsXmlCreation.py | 20 +- arelle/plugin/inlineXbrlDocumentSet.py | 116 +- arelle/plugin/instanceInfo.py | 28 +- .../internet/proxyNTLM/HTTPNtlmAuthHandler.py | 296 ++-- arelle/plugin/internet/proxyNTLM/U32.py | 226 +-- arelle/plugin/internet/proxyNTLM/__init__.py | 8 +- arelle/plugin/internet/proxyNTLM/des.py | 188 +- arelle/plugin/internet/proxyNTLM/des_c.py | 664 +++---- arelle/plugin/internet/proxyNTLM/des_data.py | 698 ++++---- arelle/plugin/internet/proxyNTLM/ntlm.py | 938 +++++----- arelle/plugin/loadFromExcel.py | 342 ++-- arelle/plugin/loadFromOIM-2018.py | 186 +- arelle/plugin/loadFromOIM.py | 364 ++-- arelle/plugin/logging/dpmSignature.py | 4 +- arelle/plugin/logging/dqcParameters.py | 18 +- arelle/plugin/logging/saveMessages.py | 40 +- arelle/plugin/objectmaker.py | 54 +- arelle/plugin/profileCmdLine.py | 12 +- arelle/plugin/profileFormula.py | 30 +- arelle/plugin/saveCHComponentFile.py | 28 +- arelle/plugin/saveDTS.py | 12 +- arelle/plugin/saveHtmlEBAtables.py | 72 +- arelle/plugin/saveLoadableExcel.py | 100 +- arelle/plugin/saveLoadableOIM.py | 124 +- arelle/plugin/saveSKOS.py | 124 +- arelle/plugin/saveSampleInstance.py | 100 +- arelle/plugin/security/cryptAES_CBC.py | 14 +- arelle/plugin/security/cryptAES_EAX.py | 14 +- arelle/plugin/sphinx/FormulaGenerator.py | 110 +- arelle/plugin/sphinx/SphinxContext.py | 78 +- arelle/plugin/sphinx/SphinxEvaluator.py | 140 +- arelle/plugin/sphinx/SphinxMethods.py | 122 +- arelle/plugin/sphinx/SphinxParser.py | 220 +-- arelle/plugin/sphinx/SphinxValidator.py | 46 +- arelle/plugin/sphinx/__init__.py | 90 +- arelle/plugin/streamingExtensions.py | 62 +- arelle/plugin/transforms/SEC/__init__.py | 622 +++---- arelle/plugin/transforms/SEC/text2num.py | 6 +- arelle/plugin/transforms/tester.py | 90 +- arelle/plugin/unpackSecEisFile.py | 14 +- arelle/plugin/validate/CIPC/Const.py | 14 +- arelle/plugin/validate/CIPC/__init__.py | 62 +- arelle/plugin/validate/EBA/__init__.py | 1518 ++++++++--------- arelle/plugin/validate/EFM-htm/Const.py | 2 +- arelle/plugin/validate/EFM-htm/__init__.py | 32 +- arelle/plugin/validate/EFM/Consts.py | 12 +- arelle/plugin/validate/EFM/DTS.py | 88 +- arelle/plugin/validate/EFM/Dimensions.py | 38 +- arelle/plugin/validate/EFM/Document.py | 38 +- arelle/plugin/validate/EFM/Filing.py | 482 +++--- arelle/plugin/validate/EFM/PreCalAlignment.py | 174 +- arelle/plugin/validate/EFM/Util.py | 122 +- arelle/plugin/validate/EFM/__init__.py | 130 +- arelle/plugin/validate/ESEF/Const.py | 2 +- arelle/plugin/validate/FERC/__init__.py | 32 +- arelle/plugin/validate/GFM/__init__.py | 6 +- arelle/plugin/validate/HMRC/__init__.py | 86 +- arelle/plugin/validate/ROS/__init__.py | 90 +- arelle/plugin/validate/SBRnl/CustomLoader.py | 4 +- arelle/plugin/validate/SBRnl/DTS.py | 28 +- arelle/plugin/validate/SBRnl/Dimensions.py | 54 +- arelle/plugin/validate/SBRnl/Document.py | 84 +- arelle/plugin/validate/SBRnl/Filing.py | 76 +- arelle/plugin/validate/SBRnl/__init__.py | 10 +- arelle/plugin/validate/USBestPractices.py | 124 +- arelle/plugin/validate/USCorpAction.py | 122 +- arelle/plugin/validate/USSecTagging.py | 34 +- arelle/plugin/validate/XDC/__init__.py | 24 +- arelle/plugin/validate/XFsyntax/__init__.py | 8 +- arelle/plugin/validate/__init__.py | 2 +- arelle/plugin/validate/calc2.py | 84 +- arelle/plugin/validateSBRnl.py | 52 +- .../plugin/xbrlDB/DialogRssWatchExtender.py | 8 +- arelle/plugin/xbrlDB/SqlDb.py | 198 +-- arelle/plugin/xbrlDB/XbrlDpmSqlDB.py | 230 +-- arelle/plugin/xbrlDB/XbrlOpenSqlDB.py | 518 +++--- arelle/plugin/xbrlDB/XbrlPublicPostgresDB.py | 266 +-- arelle/plugin/xbrlDB/XbrlSemanticGraphDB.py | 214 +-- arelle/plugin/xbrlDB/XbrlSemanticJsonDB.py | 190 +-- arelle/plugin/xbrlDB/XbrlSemanticRdfDB.py | 232 +-- arelle/plugin/xbrlDB/XbrlSemanticSqlDB.py | 438 ++--- arelle/plugin/xbrlDB/__init__.py | 88 +- arelle/plugin/xbrlDB/entityInformation.py | 8 +- arelle/plugin/xbrlDB/ext/china.py | 22 +- arelle/plugin/xbrlDB/ext/edgar.py | 30 +- arelle/plugin/xbrlDB/ext/xdc.py | 58 +- arelle/plugin/xbrlDB/primaryDocumentFacts.py | 28 +- arelle/plugin/xbrlDB/tableFacts.py | 10 +- arelle/pyparsing/pyparsing_py3.py | 34 +- arelle/webserver/bottle-no2to3.py | 10 +- arelle/webserver/bottle.py | 2 +- 243 files changed, 11445 insertions(+), 11446 deletions(-) diff --git a/arelle/CntlrCmdLine.py b/arelle/CntlrCmdLine.py index 09bf34e340..2d0a196150 100644 --- a/arelle/CntlrCmdLine.py +++ b/arelle/CntlrCmdLine.py @@ -12,8 +12,8 @@ import gettext, time, datetime, os, shlex, sys, traceback, fnmatch, threading, json, logging, platform from optparse import OptionParser, SUPPRESS_HELP import re -from arelle import (Cntlr, FileSource, ModelDocument, RenderingEvaluator, XmlUtil, XbrlConst, Version, - ViewFileDTS, ViewFileFactList, ViewFileFactTable, ViewFileConcepts, +from arelle import (Cntlr, FileSource, ModelDocument, RenderingEvaluator, XmlUtil, XbrlConst, Version, + ViewFileDTS, ViewFileFactList, ViewFileFactTable, ViewFileConcepts, ViewFileFormulae, ViewFileRelationshipSet, ViewFileTests, ViewFileRssFeed, ViewFileRoleTypes, ModelManager) @@ -33,7 +33,7 @@ def main(): """Main program to initiate application from command line or as a separate process (e.g, java Runtime.getRuntime().exec). May perform a command line request, or initiate a web server on specified local port. - + :param argv: Command line arguments. (Currently supported arguments can be displayed by the parameter *--help*.) :type message: [str] """ @@ -42,13 +42,13 @@ def main(): args = shlex.split(envArgs) else: args = sys.argv[1:] - + gettext.install("arelle") # needed for options messages parseAndRun(args) - + def wsgiApplication(extraArgs=[]): # for example call wsgiApplication(["--plugins=EdgarRenderer"]) return parseAndRun( ["--webserver=::wsgi"] + extraArgs ) - + def parseAndRun(args): """interface used by Main program and py.test (arelle_test.py) """ @@ -68,8 +68,8 @@ def parseAndRun(args): break usage = "usage: %prog [options]" - - parser = OptionParser(usage, + + parser = OptionParser(usage, version="Arelle(r) {0} ({1}bit)".format(Version.__version__, cntlr.systemWordSize), conflict_handler="resolve") # allow reloading plug-in options without errors parser.add_option("-f", "--file", dest="entrypointFile", @@ -137,7 +137,7 @@ def parseAndRun(args): help=_("Language for labels in following file options (override system settings)")) parser.add_option("--labellang", action="store", dest="labelLang", help=SUPPRESS_HELP) parser.add_option("--disableRtl", action="store_true", dest="disableRtl", default=False, - help=_("Flag to disable reversing string read order for right to left languages, useful for some locale settings")) + help=_("Flag to disable reversing string read order for right to left languages, useful for some locale settings")) parser.add_option("--labelRole", action="store", dest="labelRole", help=_("Label role for labels in following file options (instead of standard label)")) parser.add_option("--labelrole", action="store", dest="labelRole", help=SUPPRESS_HELP) @@ -199,18 +199,18 @@ def parseAndRun(args): help=_("Skip loading discovered or schemaLocated files matching pattern (unix-style file name patterns separated by '|'), useful when not all linkbases are needed.")) parser.add_option("--skiploading", action="store", dest="skipLoading", help=SUPPRESS_HELP) parser.add_option("--logFile", action="store", dest="logFile", - help=_("Write log messages into file, otherwise they go to standard output. " + help=_("Write log messages into file, otherwise they go to standard output. " "If file ends in .xml it is xml-formatted, otherwise it is text. ")) parser.add_option("--logfile", action="store", dest="logFile", help=SUPPRESS_HELP) parser.add_option("--logFormat", action="store", dest="logFormat", help=_("Logging format for messages capture, otherwise default is \"[%(messageCode)s] %(message)s - %(file)s\".")) parser.add_option("--logformat", action="store", dest="logFormat", help=SUPPRESS_HELP) parser.add_option("--logLevel", action="store", dest="logLevel", - help=_("Minimum level for messages capture, otherwise the message is ignored. " + help=_("Minimum level for messages capture, otherwise the message is ignored. " "Current order of levels are debug, info, info-semantic, warning, warning-semantic, warning, assertion-satisfied, inconsistency, error-semantic, assertion-not-satisfied, and error. ")) parser.add_option("--loglevel", action="store", dest="logLevel", help=SUPPRESS_HELP) parser.add_option("--logLevelFilter", action="store", dest="logLevelFilter", - help=_("Regular expression filter for logLevel. " + help=_("Regular expression filter for logLevel. " "(E.g., to not match *-semantic levels, logLevelFilter=(?!^.*-semantic$)(.+). ")) parser.add_option("--loglevelfilter", action="store", dest="logLevelFilter", help=SUPPRESS_HELP) parser.add_option("--logCodeFilter", action="store", dest="logCodeFilter", @@ -219,10 +219,10 @@ def parseAndRun(args): parser.add_option("--logTextMaxLength", action="store", dest="logTextMaxLength", type="int", help=_("Log file text field max length override.")) parser.add_option("--logtextmaxlength", action="store", dest="logTextMaxLength", type="int", help=SUPPRESS_HELP) - parser.add_option("--logRefObjectProperties", action="store_true", dest="logRefObjectProperties", + parser.add_option("--logRefObjectProperties", action="store_true", dest="logRefObjectProperties", help=_("Log reference object properties (default)."), default=True) parser.add_option("--logrefobjectproperties", action="store_true", dest="logRefObjectProperties", help=SUPPRESS_HELP) - parser.add_option("--logNoRefObjectProperties", action="store_false", dest="logRefObjectProperties", + parser.add_option("--logNoRefObjectProperties", action="store_false", dest="logRefObjectProperties", help=_("Do not log reference object properties.")) parser.add_option("--lognorefobjectproperties", action="store_false", dest="logRefObjectProperties", help=SUPPRESS_HELP) parser.add_option("--statusPipe", action="store", dest="statusPipe", help=SUPPRESS_HELP) @@ -233,7 +233,7 @@ def parseAndRun(args): parser.add_option("--parameters", action="store", dest="parameters", help=_("Specify parameters for formula and validation (name=value[,name=value]).")) parser.add_option("--parameterSeparator", action="store", dest="parameterSeparator", help=_("Specify parameters separator string (if other than comma).")) parser.add_option("--parameterseparator", action="store", dest="parameterSeparator", help=SUPPRESS_HELP) - parser.add_option("--formula", choices=("validate", "run", "none"), dest="formulaAction", + parser.add_option("--formula", choices=("validate", "run", "none"), dest="formulaAction", help=_("Specify formula action: " "validate - validate only, without running, " "run - validate and run, or " @@ -296,33 +296,33 @@ def parseAndRun(args): help=_("Language for user interface (override system settings, such as program messages). Does not save setting.")) parser.add_option("--uilang", action="store", dest="uiLang", help=SUPPRESS_HELP) parser.add_option("--proxy", action="store", dest="proxy", - help=_("Modify and re-save proxy settings configuration. " + help=_("Modify and re-save proxy settings configuration. " "Enter 'system' to use system proxy setting, 'none' to use no proxy, " "'http://[user[:password]@]host[:port]' " " (e.g., http://192.168.1.253, http://example.com:8080, http://joe:secret@example.com:8080), " " or 'show' to show current setting, ." )) - parser.add_option("--internetConnectivity", choices=("online", "offline"), dest="internetConnectivity", + parser.add_option("--internetConnectivity", choices=("online", "offline"), dest="internetConnectivity", help=_("Specify internet connectivity: online or offline")) parser.add_option("--internetconnectivity", action="store", dest="internetConnectivity", help=SUPPRESS_HELP) - parser.add_option("--internetTimeout", type="int", dest="internetTimeout", + parser.add_option("--internetTimeout", type="int", dest="internetTimeout", help=_("Specify internet connection timeout in seconds (0 means unlimited).")) parser.add_option("--internettimeout", type="int", action="store", dest="internetTimeout", help=SUPPRESS_HELP) - parser.add_option("--internetRecheck", choices=("weekly", "daily", "never", "hourly", "quarter-hourly"), action="store", dest="internetRecheck", + parser.add_option("--internetRecheck", choices=("weekly", "daily", "never", "hourly", "quarter-hourly"), action="store", dest="internetRecheck", help=_("Specify rechecking for newer cache files 'daily', 'weekly', 'monthly' or 'never' ('weekly' is default)")) parser.add_option("--internetrecheck", choices=("weekly", "daily", "never"), action="store", dest="internetRecheck", help=SUPPRESS_HELP) - parser.add_option("--internetLogDownloads", action="store_true", dest="internetLogDownloads", + parser.add_option("--internetLogDownloads", action="store_true", dest="internetLogDownloads", help=_("Log info message for downloads to web cache.")) parser.add_option("--internetlogdownloads", action="store_true", dest="internetLogDownloads", help=SUPPRESS_HELP) - parser.add_option("--noCertificateCheck", action="store_true", dest="noCertificateCheck", + parser.add_option("--noCertificateCheck", action="store_true", dest="noCertificateCheck", help=_("Specify no checking of internet secure connection certificate")) parser.add_option("--nocertificatecheck", action="store_true", dest="noCertificateCheck", help=SUPPRESS_HELP) parser.add_option("--httpsRedirectCache", action="store_true", dest="httpsRedirectCache", help=_("Treat http and https schemes interchangeably when looking up files from the webcache")) parser.add_option("--httpsredirectcache", action="store_true", dest="httpsRedirectCache", help=SUPPRESS_HELP) - parser.add_option("--httpUserAgent", action="store", dest="httpUserAgent", + parser.add_option("--httpUserAgent", action="store", dest="httpUserAgent", help=_("Specify non-standard http header User-Agent value")) parser.add_option("--httpuseragent", action="store", dest="httpUserAgent", help=SUPPRESS_HELP) - parser.add_option("--xdgConfigHome", action="store", dest="xdgConfigHome", + parser.add_option("--xdgConfigHome", action="store", dest="xdgConfigHome", help=_("Specify non-standard location for configuration and cache files (overrides environment parameter XDG_CONFIG_HOME).")) parser.add_option("--plugins", action="store", dest="plugins", help=_("Specify plug-in configuration for this invocation. " @@ -382,7 +382,7 @@ def parseAndRun(args): parser.add_option("-a", "--about", action="store_true", dest="about", help=_("Show product version, copyright, and license.")) - + if not args and cntlr.isGAE: args = ["--webserver=::gae"] elif cntlr.isCGI: @@ -410,7 +410,7 @@ def parseAndRun(args): arg = arg.replace(r'\"', '"') args.append(arg) priorArg = arg - + (options, leftoverArgs) = parser.parse_args(args) if options.about: print(_("\narelle(r) {0} ({1}bit {6})\n\n" @@ -443,7 +443,7 @@ def parseAndRun(args): print(text.encode("ascii", "replace").decode("ascii")) elif len(leftoverArgs) != 0 and (not hasWebServer or options.webserver is None): parser.error(_("unrecognized arguments: {}").format(', '.join(leftoverArgs))) - elif (options.entrypointFile is None and + elif (options.entrypointFile is None and ((not options.proxy) and (not options.plugins) and (not any(pluginOption for pluginOption in parser.option_list[pluginOptionsIndex:pluginLastOptionIndex])) and (not hasWebServer or options.webserver is None))): @@ -474,9 +474,9 @@ def parseAndRun(args): logTextMaxLength=options.logTextMaxLength, # e.g., used by EdgarRenderer to require buffered logging logRefObjectProperties=options.logRefObjectProperties) cntlr.run(options) - + return cntlr - + def filesourceEntrypointFiles(filesource, entrypointFiles=[]): if filesource.isArchive: if filesource.isTaxonomyPackage: # if archive is also a taxonomy package, activate mappings @@ -504,7 +504,7 @@ def filesourceEntrypointFiles(filesource, entrypointFiles=[]): for pluginXbrlMethod in pluginClassMethods("InlineDocumentSet.Discovery"): pluginXbrlMethod(filesource, entrypointFiles) # group into IXDS if plugin feature is available break # found inline (or non-inline) entrypoint files, don't look for any other type - + elif os.path.isdir(filesource.url): del entrypointFiles[:] # clear list for _file in os.listdir(filesource.url): @@ -512,7 +512,7 @@ def filesourceEntrypointFiles(filesource, entrypointFiles=[]): if os.path.isfile(_path) and ModelDocument.Type.identify(filesource, _path) in (ModelDocument.Type.INSTANCE, ModelDocument.Type.INLINEXBRL): entrypointFiles.append({"file":_path}) return entrypointFiles - + class ParserForDynamicPlugins: def __init__(self, options): self.options = options @@ -521,28 +521,28 @@ def add_option(self, *args, **kwargs): _dest = kwargs['dest'] if not hasattr(self.options, _dest): setattr(self.options, _dest, kwargs.get('default',None)) - + class CntlrCmdLine(Cntlr.Cntlr): """ .. class:: CntlrCmdLin() - + Initialization sets up for platform via Cntlr.Cntlr. """ def __init__(self, logFileName=None): super(CntlrCmdLine, self).__init__(hasGui=False) self.preloadedPlugins = {} - + def run(self, options, sourceZipStream=None, responseZipStream=None): """Process command line arguments or web service request, such as to load and validate an XBRL document, or start web server. - + When a web server has been requested, this method may be called multiple times, once for each web service (REST) request that requires processing. Otherwise (when called for a command line request) this method is called only once for the command line arguments request. - + :param options: OptionParser options from parse_args of main argv arguments (when called from command line) or corresponding arguments from web service (REST) request. :type options: optparse.Values """ - + if options.statusPipe or options.monitorParentProcess: try: global win32file, win32api, win32process, pywintypes @@ -552,7 +552,7 @@ def run(self, options, sourceZipStream=None, responseZipStream=None): options.statusPipe = options.monitorParentProcess = None if options.statusPipe: try: - self.statusPipe = win32file.CreateFile("\\\\.\\pipe\\{}".format(options.statusPipe), + self.statusPipe = win32file.CreateFile("\\\\.\\pipe\\{}".format(options.statusPipe), win32file.GENERIC_READ | win32file.GENERIC_WRITE, 0, None, win32file.OPEN_EXISTING, win32file.FILE_FLAG_NO_BUFFERING, None) self.showStatus = self.showStatusOnPipe self.lastStatusTime = 0.0 @@ -624,7 +624,7 @@ def monitorParentProcess(): elif cmd.startswith("+"): moduleInfo = PluginManager.addPluginModule(cmd[1:]) if moduleInfo: - self.addToLog(_("Addition of plug-in {0} successful.").format(moduleInfo.get("name")), + self.addToLog(_("Addition of plug-in {0} successful.").format(moduleInfo.get("name")), messageCode="info", file=moduleInfo.get("moduleURL")) resetPlugins = True if "CntlrCmdLine.Options" in moduleInfo["classMethods"]: @@ -651,12 +651,12 @@ def monitorParentProcess(): moduleInfo = PluginManager.addPluginModule(cmd) if moduleInfo: resetPlugins = True - if moduleInfo: - self.addToLog(_("Activation of plug-in {0} successful, version {1}.").format(moduleInfo.get("name"), moduleInfo.get("version")), + if moduleInfo: + self.addToLog(_("Activation of plug-in {0} successful, version {1}.").format(moduleInfo.get("name"), moduleInfo.get("version")), messageCode="info", file=moduleInfo.get("moduleURL")) else: self.addToLog(_("Unable to load \"%(name)s\" as a plug-in or \"%(name)s\" is not recognized as a plugin command. "), - messageCode="arelle:pluginParameterError", + messageCode="arelle:pluginParameterError", messageArgs={"name": cmd, "file": cmd}, level=logging.ERROR) if resetPlugins: PluginManager.reset() @@ -689,7 +689,7 @@ def monitorParentProcess(): elif cmd.startswith("+"): packageInfo = PackageManager.addPackage(self, cmd[1:], options.packageManifestName) if packageInfo: - self.addToLog(_("Addition of package {0} successful.").format(packageInfo.get("name")), + self.addToLog(_("Addition of package {0} successful.").format(packageInfo.get("name")), messageCode="info", file=packageInfo.get("URL")) else: self.addToLog(_("Unable to load package."), messageCode="info", file=cmd[1:]) @@ -707,12 +707,12 @@ def monitorParentProcess(): savePackagesChanges = False packageInfo = PackageManager.addPackage(self, cmd, options.packageManifestName) if packageInfo: - self.addToLog(_("Activation of package {0} successful.").format(packageInfo.get("name")), + self.addToLog(_("Activation of package {0} successful.").format(packageInfo.get("name")), messageCode="info", file=packageInfo.get("URL")) resetPlugins = True else: self.addToLog(_("Unable to load package \"%(name)s\". "), - messageCode="arelle:packageLoadingError", + messageCode="arelle:packageLoadingError", messageArgs={"name": cmd, "file": cmd}, level=logging.ERROR) if PackageManager.packagesConfigChanged: PackageManager.rebuildRemappings(self) @@ -727,7 +727,7 @@ def monitorParentProcess(): packageInfo.get("name"), packageInfo.get("version"), packageInfo.get("status"), packageInfo.get("fileDate"), packageInfo.get("description")), messageCode="info", file=packageInfo.get("URL")) - + if options.showEnvironment: self.addToLog(_("Config directory: {0}").format(self.configDir)) self.addToLog(_("Cache directory: {0}").format(self.userAppDir)) @@ -735,10 +735,10 @@ def monitorParentProcess(): if envVar in os.environ: self.addToLog(_("XDG_CONFIG_HOME={0}").format(os.environ[envVar])) return True - + self.modelManager.customTransforms = None # clear out prior custom transforms self.modelManager.loadCustomTransforms() - + self.username = options.username self.password = options.password if options.disclosureSystemName: @@ -758,13 +758,13 @@ def monitorParentProcess(): self.modelManager.validateDisclosureSystem = False if options.utrUrl: # override disclosureSystem utrUrl self.modelManager.disclosureSystem.utrUrl = [options.utrUrl] - # can be set now because the utr is first loaded at validation time + # can be set now because the utr is first loaded at validation time if options.skipDTS: # skip DTS loading, discovery, etc self.modelManager.skipDTS = True if options.skipLoading: # skip loading matching files (list of unix patterns) self.modelManager.skipLoading = re.compile( '|'.join(fnmatch.translate(f) for f in options.skipLoading.split('|'))) - + # disclosure system sets logging filters, override disclosure filters, if specified by command line if options.logLevelFilter: self.setLogLevelFilter(options.logLevelFilter) @@ -804,8 +804,8 @@ def monitorParentProcess(): fo = FormulaOptions() if options.parameters: parameterSeparator = (options.parameterSeparator or ',') - fo.parameterValues = dict(((qname(key, noPrefixIsNoNamespace=True),(None,value)) - for param in options.parameters.split(parameterSeparator) + fo.parameterValues = dict(((qname(key, noPrefixIsNoNamespace=True),(None,value)) + for param in options.parameters.split(parameterSeparator) for key,sep,value in (param.partition('='),) ) ) if options.formulaParamExprResult: fo.traceParameterExpressionResult = True @@ -856,13 +856,13 @@ def monitorParentProcess(): if options.testcaseResultOptions: fo.testcaseResultOptions = options.testcaseResultOptions if options.formulaRunIDs: - fo.runIDs = options.formulaRunIDs + fo.runIDs = options.formulaRunIDs if options.formulaCompileOnly: fo.compileOnly = True if options.formulaAction: fo.formulaAction = options.formulaAction self.modelManager.formulaOptions = fo - + # run utility command line options that don't depend on entrypoint Files hasUtilityPlugin = False for pluginXbrlMethod in pluginClassMethods("CntlrCmdLine.Utility.Run"): @@ -871,7 +871,7 @@ def monitorParentProcess(): pluginXbrlMethod(self, options, sourceZipStream=sourceZipStream, responseZipStream=responseZipStream) except SystemExit: # terminate operation, plug in has terminated all processing return True # success - + # if no entrypointFile is applicable, quit now if options.proxy or options.plugins or hasUtilityPlugin: if not (options.entrypointFile or sourceZipStream): @@ -894,7 +894,7 @@ def monitorParentProcess(): messageCode="FileNameFormatError", level=logging.ERROR) success = False - else: # try as file names separated by '|' + else: # try as file names separated by '|' for f in (_f or '').split('|'): if not sourceZipStream and not isHttpUrl(f) and not os.path.isabs(f): f = os.path.normpath(os.path.join(os.getcwd(), f)) # make absolute normed path @@ -916,7 +916,7 @@ def monitorParentProcess(): if filesource and filesource.isArchive: filesource.select(_entrypointFile) else: - filesource = FileSource.openFileSource(_entrypointFile, self, sourceZipStream) + filesource = FileSource.openFileSource(_entrypointFile, self, sourceZipStream) self.entrypointFile = _entrypointFile timeNow = XmlUtil.dateunionValue(datetime.datetime.now()) firstStartedAt = startedAt = time.time() @@ -937,9 +937,9 @@ def monitorParentProcess(): if modelXbrl and modelXbrl.modelDocument: loadTime = time.time() - startedAt modelXbrl.profileStat(_("load"), loadTime) - self.addToLog(format_string(self.modelManager.locale, - _("loaded in %.2f secs at %s"), - (loadTime, timeNow)), + self.addToLog(format_string(self.modelManager.locale, + _("loaded in %.2f secs at %s"), + (loadTime, timeNow)), messageCode="info", file=self.entrypointFile) if modelXbrl.hasTableRendering: RenderingEvaluator.init(modelXbrl) @@ -950,9 +950,9 @@ def monitorParentProcess(): fileName = os.path.dirname(modelXbrl.uri) + os.sep + fileName # make relative to sourceZipStream ModelDocument.load(modelXbrl, fileName, isSupplemental=True) loadTime = time.time() - startedAt - self.addToLog(format_string(self.modelManager.locale, - _("import in %.2f secs at %s"), - (loadTime, timeNow)), + self.addToLog(format_string(self.modelManager.locale, + _("import in %.2f secs at %s"), + (loadTime, timeNow)), messageCode="info", file=importFile) modelXbrl.profileStat(_("import"), loadTime) if modelXbrl.errors: @@ -977,17 +977,17 @@ def monitorParentProcess(): else: loadTime = time.time() - startedAt modelXbrl.profileStat(_("load"), loadTime) - self.addToLog(format_string(self.modelManager.locale, - _("diff comparison DTS loaded in %.2f secs"), - loadTime), + self.addToLog(format_string(self.modelManager.locale, + _("diff comparison DTS loaded in %.2f secs"), + loadTime), messageCode="info", file=self.entrypointFile) startedAt = time.time() modelDiffReport = self.modelManager.compareDTSes(options.versReportFile) diffTime = time.time() - startedAt modelXbrl.profileStat(_("diff"), diffTime) - self.addToLog(format_string(self.modelManager.locale, - _("compared in %.2f secs"), - diffTime), + self.addToLog(format_string(self.modelManager.locale, + _("compared in %.2f secs"), + diffTime), messageCode="info", file=self.entrypointFile) except ModelDocument.LoadingException: success = False @@ -1011,8 +1011,8 @@ def monitorParentProcess(): self.modelManager.validate() if options.formulaAction: # restore setting modelXbrl.hasFormulae = hasFormulae - self.addToLog(format_string(self.modelManager.locale, - _("validated in %.2f secs"), + self.addToLog(format_string(self.modelManager.locale, + _("validated in %.2f secs"), time.time() - startedAt), messageCode="info", file=self.entrypointFile) if (modelXbrl.modelDocument.type not in ModelDocument.Type.TESTCASETYPES and @@ -1025,20 +1025,20 @@ def monitorParentProcess(): # setup fresh parameters from formula optoins modelXbrl.parameters = fo.typedParameters(modelXbrl.prefixedNamespaces) ValidateFormula.validate(modelXbrl, compileOnly=(options.formulaAction != "run")) - self.addToLog(format_string(self.modelManager.locale, + self.addToLog(format_string(self.modelManager.locale, _("formula validation and execution in %.2f secs") if options.formulaAction == "run" - else _("formula validation only in %.2f secs"), + else _("formula validation only in %.2f secs"), time.time() - startedAt), messageCode="info", file=self.entrypointFile) - - + + if options.testReport: ViewFileTests.viewTests(self.modelManager.modelXbrl, options.testReport, options.testReportCols) - + if options.rssReport: ViewFileRssFeed.viewRssFeed(self.modelManager.modelXbrl, options.rssReport, options.rssReportCols) - + if options.DTSFile: ViewFileDTS.viewDTS(modelXbrl, options.DTSFile) if options.factsFile: @@ -1067,19 +1067,19 @@ def monitorParentProcess(): ViewFileRoleTypes.viewRoleTypes(modelXbrl, options.arcroleTypesFile, "Arcrole Types", isArcrole=True, lang=options.labelLang) for pluginXbrlMethod in pluginClassMethods("CntlrCmdLine.Xbrl.Run"): pluginXbrlMethod(self, options, modelXbrl, _entrypoint, responseZipStream=responseZipStream) - + except (IOError, EnvironmentError) as err: self.addToLog(_("[IOError] Failed to save output:\n {0}").format(err), - messageCode="IOError", - file=options.entrypointFile, + messageCode="IOError", + file=options.entrypointFile, level=logging.CRITICAL) success = False except Exception as err: self.addToLog(_("[Exception] Failed to complete request: \n{0} \n{1}").format( err, traceback.format_tb(sys.exc_info()[2])), - messageCode=err.__class__.__name__, - file=options.entrypointFile, + messageCode=err.__class__.__name__, + file=options.entrypointFile, level=logging.CRITICAL) success = False if modelXbrl: @@ -1111,11 +1111,11 @@ def monitorParentProcess(): # default web authentication password def internet_user_password(self, host, realm): return (self.username, self.password) - + # special show status for named pipes def showStatusOnPipe(self, message, clearAfter=None): # now = time.time() # seems ok without time-limiting writes to the pipe - if self.statusPipe is not None: # max status updates 3 per second now - 0.3 > self.lastStatusTime and + if self.statusPipe is not None: # max status updates 3 per second now - 0.3 > self.lastStatusTime and # self.lastStatusTime = now try: if self.parentProcessHandle is not None: diff --git a/arelle/CntlrComServer.py b/arelle/CntlrComServer.py index 16d8bf09e4..f80029d080 100644 --- a/arelle/CntlrComServer.py +++ b/arelle/CntlrComServer.py @@ -30,7 +30,7 @@ def main(): from win32com.server.dispatcher import DefaultDebugDispatcher useDispatcher = DefaultDebugDispatcher win32com.server.register.UseCommandLine(CntlrComServer, debug=debugging) - + class CntlrComServer(Cntlr.Cntlr): _public_methods_ = [ 'Load' ] _public_attrs_ = [ ] @@ -47,7 +47,7 @@ def __init__(self, logFileName=None): self.startedAt = datetime.datetime.now().microsecond self.last = "({0})".format(self.startedAt) pass - + def Load(self, url): last = self.last diff --git a/arelle/CntlrProfiler.py b/arelle/CntlrProfiler.py index a64bb1b313..6b329888a7 100644 --- a/arelle/CntlrProfiler.py +++ b/arelle/CntlrProfiler.py @@ -7,22 +7,22 @@ def main(): CntlrProfiler().run() - + class CntlrProfiler(Cntlr.Cntlr): def __init__(self): super(CntlrProfiler, self).__init__() - + def run(self): self.filename = r"C:\Users\Herm Fischer\Documents\mvsl\projects\SEC\Local.Conformance\conformance\Private\Formula\Extension-Conformance\root\efm-15-101007\conf\616-definition-syntax\616-03-dimension-domain-is-domain\e61603000gd-20081231.xml" filesource = FileSource.FileSource(self.filename) self.modelManager.validateEFM = True self.modelManager.load(filesource, _("views loading")) self.modelManager.validate() - + def addToLog(self, message): print(message) - + def showStatus(self, message, clearAfter=None): pass diff --git a/arelle/CntlrQuickBooks.py b/arelle/CntlrQuickBooks.py index dfc1904c7d..63fe8a5d4d 100644 --- a/arelle/CntlrQuickBooks.py +++ b/arelle/CntlrQuickBooks.py @@ -19,8 +19,8 @@ cntlr = None # report in url path request and type of query to QB -supportedQbReports = {'trialBalance':'GeneralSummary', - 'generalLedger':'GeneralDetail', +supportedQbReports = {'trialBalance':'GeneralSummary', + 'generalLedger':'GeneralDetail', 'journal':'GeneralDetail' } # some reports don't provide the needed columns, request explicitly @@ -38,8 +38,8 @@ ''', 'journal': '' } -glEntriesType = {'trialBalance':'trialbalance', - 'generalLedger':'balance', +glEntriesType = {'trialBalance':'trialbalance', + 'generalLedger':'balance', 'journal':'journal' } @@ -82,7 +82,7 @@ def server(_cntlr, soapFile, requestUrlParts): if cntlr is None: cntlr = _cntlr soapDocument = etree.parse(soapFile) soapBody = soapDocument.find("{http://schemas.xmlsoap.org/soap/envelope/}Body") - if soapBody is None: + if soapBody is None: return "" else: for request in soapBody.iterchildren(): @@ -105,7 +105,7 @@ def server(_cntlr, soapFile, requestUrlParts): sessions[ticket] = qbRequests qbRequests = [] else: - # to start an interactive session automatically from QB side, uncomment + # to start an interactive session automatically from QB side, uncomment #response = [ticket, "" if not sessions else "none"] # don't start session if one already there #sessions[ticket] = [{"request":"StartInteractiveMode"}] response = [ticket, "none"] # response to not start interactive mode @@ -132,13 +132,13 @@ def server(_cntlr, soapFile, requestUrlParts): {4} -''').format(action[0].upper() + action[1:], +''').format(action[0].upper() + action[1:], supportedQbReports[action], _qbRequest["fromDate"], _qbRequest["toDate"], includeQbColumns[action], ).replace("&","&").replace("<","<").replace(">",">") - + elif request.tag == "{http://developer.intuit.com/}connectionError": ticket = request.find("{http://developer.intuit.com/}ticket").text hresult = request.find("{http://developer.intuit.com/}hresult").text @@ -177,7 +177,7 @@ def server(_cntlr, soapFile, requestUrlParts): elif request.tag == "{http://developer.intuit.com/}getInteractiveURL": ticket = request.find("{http://developer.intuit.com/}wcTicket").text response = "{0}://{1}/quickbooks/server.html?ticket={2}".format( - requestUrlParts.scheme, + requestUrlParts.scheme, requestUrlParts.netloc, ticket) sessions[ticket] = [{"request":"WaitForInput"}] @@ -199,30 +199,30 @@ def server(_cntlr, soapFile, requestUrlParts): sessions.pop(ticket, None) elif request.tag == "{http://developer.intuit.com/}closeConnection": response = "OK" - + soapResponse = qbResponse(requestName, response) return soapResponse - + def qbRequest(qbReport, fromDate, toDate, file): ticket = str(uuid.uuid1()) - qbRequests.append({"ticket":ticket, + qbRequests.append({"ticket":ticket, "request":qbReport, "fromDate":fromDate, - "toDate":toDate, + "toDate":toDate, "xbrlFile":file}) qbRequestStatus[ticket] = _("Waiting for QuickBooks") return ticket - + def qbResponse(responseName, content=None): if not content: result = "" elif isinstance(content, list): result = '<{0}Result>{1}'.format( - responseName, + responseName, '\n'.join("{0}".format(l) for l in content)) else: result = '<{0}Result>{1}'.format(responseName, content) - + return ('' '' '' @@ -231,12 +231,12 @@ def qbResponse(responseName, content=None): '' '' ''.format(responseName, result)) - + def docEltText(doc, tag, defaultValue=""): for elt in doc.iter(tag): return elt.text return defaultValue - + def processQbResponse(qbRequest, responseXml): from arelle import ModelXbrl, XbrlConst from arelle.ModelValue import qname @@ -247,7 +247,7 @@ def processQbResponse(qbRequest, responseXml): fromDate = qbRequest["fromDate"] toDate = qbRequest["toDate"] strHCPResponse = qbRequest.get("strHCPResponse", "") - + # uncomment to dump out QB responses ''' with open("c:/temp/test.xml", "w") as fh: @@ -256,7 +256,7 @@ def processQbResponse(qbRequest, responseXml): fh.write(strHCPResponse) # qb responses dump ''' - + companyQbDoc = etree.parse(io.StringIO(initial_value=strHCPResponse)) responseQbDoc = etree.parse(io.StringIO(initial_value=responseXml)) # columns table @@ -275,7 +275,7 @@ def processQbResponse(qbRequest, responseXml): break colTypeId[colType] = colID colIdType[colID] = colType - + # open new result instance document # load GL palette file (no instance) @@ -286,66 +286,66 @@ def processQbResponse(qbRequest, responseXml): else: saveInstance = True instance.createInstance(xbrlFile) # creates an instance as this modelXbrl's entrypoing - newCntx = instance.createContext("http://www.xbrl.org/xbrlgl/sample", "SAMPLE", + newCntx = instance.createContext("http://www.xbrl.org/xbrlgl/sample", "SAMPLE", "instant", None, datetime.date.today() + datetime.timedelta(1), # today midnight None, {}, [], [], afterSibling=ModelXbrl.AUTO_LOCATE_ELEMENT) - + monetaryUnit = qname(XbrlConst.iso4217, "iso4217:USD") newUnit = instance.createUnit([monetaryUnit],[], afterSibling=ModelXbrl.AUTO_LOCATE_ELEMENT) - + nonNumAttr = [("contextRef", newCntx.id)] monetaryAttr = [("contextRef", newCntx.id), ("unitRef", newUnit.id), ("decimals", "2")] isoLanguage = qname("{http://www.xbrl.org/2005/iso639}iso639:en") - + # root of GL is accounting entries tuple xbrlElt = instance.modelDocument.xmlRootElement - - '''The container for XBRL GL, accountingEntries, is not the root of an XBRL GL file - the root, - as with all XBRL files, is xbrl. This means that a single XBRL GL file can store one or more - virtual XBRL GL files, through one or more accountingEntries structures with data inside. - The primary key to understanding an XBRL GL file is the entriesType. A single physical XBRL GL - file can have multiple accountingEntries structures to represent both transactions and + + '''The container for XBRL GL, accountingEntries, is not the root of an XBRL GL file - the root, + as with all XBRL files, is xbrl. This means that a single XBRL GL file can store one or more + virtual XBRL GL files, through one or more accountingEntries structures with data inside. + The primary key to understanding an XBRL GL file is the entriesType. A single physical XBRL GL + file can have multiple accountingEntries structures to represent both transactions and master files; the differences are signified by the appropriate entriesType enumerated values.''' accountingEntries = instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:accountingEntries")) - + # Because entriesType is strongly suggested, documentInfo will be required docInfo = instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:documentInfo"), parent=accountingEntries) # This field, entriesType, provides the automated guidance on the purpose of the XBRL GL information. - instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:entriesType"), parent=docInfo, attributes=nonNumAttr, + instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:entriesType"), parent=docInfo, attributes=nonNumAttr, text=glEntriesType[qbReport]) - '''Like a serial number, this field, uniqueID, provides a place to uniquely identify/track - a series of entries. It is like less relevant for ad-hoc reports. XBRL GL provides for later + '''Like a serial number, this field, uniqueID, provides a place to uniquely identify/track + a series of entries. It is like less relevant for ad-hoc reports. XBRL GL provides for later correction through replacement or augmentation of transferred information.''' - instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:uniqueID"), parent=docInfo, attributes=nonNumAttr, + instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:uniqueID"), parent=docInfo, attributes=nonNumAttr, text="001") - instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:language"), parent=docInfo, attributes=nonNumAttr, + instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:language"), parent=docInfo, attributes=nonNumAttr, text=XmlUtil.addQnameValue(xbrlElt, isoLanguage)) - '''The date associated with the creation of the data reflected within the associated + '''The date associated with the creation of the data reflected within the associated accountingEntries section. Somewhat like a "printed date" on a paper report''' - instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:creationDate"), parent=docInfo, attributes=nonNumAttr, + instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:creationDate"), parent=docInfo, attributes=nonNumAttr, text=str(datetime.date.today())) - instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:periodCoveredStart"), parent=docInfo, attributes=nonNumAttr, + instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:periodCoveredStart"), parent=docInfo, attributes=nonNumAttr, text=fromDate) - instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:periodCoveredEnd"), parent=docInfo, attributes=nonNumAttr, + instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:periodCoveredEnd"), parent=docInfo, attributes=nonNumAttr, text=toDate) - instance.createFact(qname("{http://www.xbrl.org/int/gl/bus/2006-10-25}gl-bus:sourceApplication"), parent=docInfo, attributes=nonNumAttr, + instance.createFact(qname("{http://www.xbrl.org/int/gl/bus/2006-10-25}gl-bus:sourceApplication"), parent=docInfo, attributes=nonNumAttr, text=docEltText(companyQbDoc, "ProductName","QuickBooks (version not known)")) - instance.createFact(qname("{http://www.xbrl.org/int/gl/muc/2006-10-25}gl-muc:defaultCurrency"), parent=docInfo, attributes=nonNumAttr, + instance.createFact(qname("{http://www.xbrl.org/int/gl/muc/2006-10-25}gl-muc:defaultCurrency"), parent=docInfo, attributes=nonNumAttr, text=XmlUtil.addQnameValue(xbrlElt, monetaryUnit)) - - '''Typically, an export from an accounting system does not carry with it information - specifically about the company. However, the name of the company would be a very good + + '''Typically, an export from an accounting system does not carry with it information + specifically about the company. However, the name of the company would be a very good thing to include with the file, making the entityInformation tuple necessary.''' entityInfo = instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:entityInformation"), parent=accountingEntries) - '''The name of the company would be a very good thing to include with the file; + '''The name of the company would be a very good thing to include with the file; this structure and its content are where that would be stored.''' orgIds = instance.createFact(qname("{http://www.xbrl.org/int/gl/bus/2006-10-25}gl-bus:organizationIdentifiers"), parent=entityInfo) - instance.createFact(qname("{http://www.xbrl.org/int/gl/bus/2006-10-25}gl-bus:organizationIdentifier"), parent=orgIds, attributes=nonNumAttr, + instance.createFact(qname("{http://www.xbrl.org/int/gl/bus/2006-10-25}gl-bus:organizationIdentifier"), parent=orgIds, attributes=nonNumAttr, text=docEltText(companyQbDoc, "CompanyName")) - instance.createFact(qname("{http://www.xbrl.org/int/gl/bus/2006-10-25}gl-bus:organizationDescription"), parent=orgIds, attributes=nonNumAttr, + instance.createFact(qname("{http://www.xbrl.org/int/gl/bus/2006-10-25}gl-bus:organizationDescription"), parent=orgIds, attributes=nonNumAttr, text=docEltText(companyQbDoc, "LegalCompanyName")) - + if qbReport == "trialBalance": qbTxnType = "trialbalance" else: @@ -361,7 +361,7 @@ def processQbResponse(qbRequest, responseXml): cols = dict((colIdType[colElt.get("colID")], colElt.get("value")) for colElt in dataRowElt.iter("ColData")) if qbReport == "trialBalance" and "Label" in cols: cols["SplitAccount"] = cols["Label"] - + hasRowDataAccount = False for rowDataElt in dataRowElt.iter("RowData"): rowType = rowDataElt.get("rowType") @@ -392,7 +392,7 @@ def processQbResponse(qbRequest, responseXml): qbDebitAmount = cols.get("Debit") qbCreditAmount = cols.get("Credit") runningBalance = cols.get("RunningBalance") - + if qbAmount is not None: drCrCode = None amt = qbAmount @@ -405,117 +405,117 @@ def processQbResponse(qbRequest, responseXml): else: # no amount, skip this transaction continue - + if isFirst or qbTxnNumber: - '''Journal entries require entry in entryHeader and entryDetail. - Few files can be represented using only documentInfo and entityInformation sections, + '''Journal entries require entry in entryHeader and entryDetail. + Few files can be represented using only documentInfo and entityInformation sections, but it is certainly possible.''' entryHdr = instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:entryHeader"), parent=accountingEntries) #instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:enteredBy"), parent=entryHdr, attributes=nonNumAttr, text="") - instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:enteredDate"), parent=entryHdr, attributes=nonNumAttr, + instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:enteredDate"), parent=entryHdr, attributes=nonNumAttr, text=str(datetime.date.today())) - '''This is an enumerated entry that ties the source journal from the reporting + '''This is an enumerated entry that ties the source journal from the reporting organization to a fixed list that helps in data interchange.''' - instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:sourceJournalID"), parent=entryHdr, attributes=nonNumAttr, + instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:sourceJournalID"), parent=entryHdr, attributes=nonNumAttr, text="gj") - '''Since sourceJournalID is enumerated (you must pick one of the entries already - identified within XBRL GL), sourceJournalDescription lets you capture the actual + '''Since sourceJournalID is enumerated (you must pick one of the entries already + identified within XBRL GL), sourceJournalDescription lets you capture the actual code or term used to descibe the source journal by the organization.''' # instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:sourceJournalDescription"), parent=entryHdr, attributes=nonNumAttr, text="JE") - '''An enumerated field to differentiate between details that represent actual accounting - entries - as opposed to entries for budget purposes, planning purposes, or other entries + '''An enumerated field to differentiate between details that represent actual accounting + entries - as opposed to entries for budget purposes, planning purposes, or other entries that may not contribute to the financial statements.''' - instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:entryType"), parent=entryHdr, attributes=nonNumAttr, + instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:entryType"), parent=entryHdr, attributes=nonNumAttr, text="standard") - '''When capturing journal entries, you have a series of debits and credits that (normally) - add up to zero. The hierarchical nature of XBRL GL keeps the entry detail lines associated - with the entry header by a parent-child relationship. The unique identifier of each entry + '''When capturing journal entries, you have a series of debits and credits that (normally) + add up to zero. The hierarchical nature of XBRL GL keeps the entry detail lines associated + with the entry header by a parent-child relationship. The unique identifier of each entry is entered here.''' - instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:entryNumber"), parent=entryHdr, attributes=nonNumAttr, + instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:entryNumber"), parent=entryHdr, attributes=nonNumAttr, text=str(entryNumber)) entryNumber += 1 # The reason for making an entry goes here. if qbRefNumber: - instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:entryComment"), parent=entryHdr, attributes=nonNumAttr, + instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:entryComment"), parent=entryHdr, attributes=nonNumAttr, text=qbRefNumber) - - '''Individual lines of journal entries will normally require their own entryDetail section - - one primary amount per entryDetail line. However, you can list different accounts within - the same entryDetail line that are associated with that amount. For example, if you + + '''Individual lines of journal entries will normally require their own entryDetail section - + one primary amount per entryDetail line. However, you can list different accounts within + the same entryDetail line that are associated with that amount. For example, if you capitalize for US GAAP and expense for IFRS''' entryDetail = instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:entryDetail"), parent=entryHdr) # A unique identifier for each entry detail line within an entry header, this should at the least be a counter. - instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:lineNumber"), parent=entryDetail, attributes=nonNumAttr, + instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:lineNumber"), parent=entryDetail, attributes=nonNumAttr, text=str(lineNumber)) lineNumber += 1 - - '''If account information is represented elsewhere or as a master file, some of the + + '''If account information is represented elsewhere or as a master file, some of the fields below would not need to be here (signified by *)''' account = instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:account"), parent=entryDetail) - '''The account number is the basis for posting journal entries. In some cases, - accounting systems used by small organizations do not use account numbers/codes, + '''The account number is the basis for posting journal entries. In some cases, + accounting systems used by small organizations do not use account numbers/codes, but only use a descriptive name for the account.''' # QB does not have account numbers # instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:accountMainID"), parent=account, attributes=nonNumAttr, text="10100") - '''In most cases, the description is given to help a human reader; the accountMainID would - be sufficient for data exchange purposes. As noted previously, some implementations use the + '''In most cases, the description is given to help a human reader; the accountMainID would + be sufficient for data exchange purposes. As noted previously, some implementations use the description as the primary identifier of the account.''' if qbAccount: - instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:accountMainDescription"), parent=account, attributes=nonNumAttr, + instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:accountMainDescription"), parent=account, attributes=nonNumAttr, text=qbAccount) - '''Accounts serve many purposes, and in a large company using more sophisticated software, - the company may wish to record the account used for the original entry and a separate - consolidating account. The Japanese system may require a counterbalancing account for - each line item. And an entry may be recorded differently for US GAAP, IFRS and other purposes. + '''Accounts serve many purposes, and in a large company using more sophisticated software, + the company may wish to record the account used for the original entry and a separate + consolidating account. The Japanese system may require a counterbalancing account for + each line item. And an entry may be recorded differently for US GAAP, IFRS and other purposes. This code is an enumerated code to help identify accounts for those purposes.''' - instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:accountPurposeCode"), parent=account, attributes=nonNumAttr, + instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:accountPurposeCode"), parent=account, attributes=nonNumAttr, text="usgaap") - '''In an international environment, the "chart of accounts" will include not only - traditional accounts, like Cash, Accounts Payable/Due to Creditors or Retained Earnings, - but also extensions to some of the accounts. Accounts Payable may be extended to - include the creditors/vendors themselves. Therefore, in XBRL GL, accounts can be - specifically identified as the "traditional" accountm or to identify a customer, - vendor, employee, bank, job or fixed asset. While this may overlap with the customers, + '''In an international environment, the "chart of accounts" will include not only + traditional accounts, like Cash, Accounts Payable/Due to Creditors or Retained Earnings, + but also extensions to some of the accounts. Accounts Payable may be extended to + include the creditors/vendors themselves. Therefore, in XBRL GL, accounts can be + specifically identified as the "traditional" accountm or to identify a customer, + vendor, employee, bank, job or fixed asset. While this may overlap with the customers, vendors and employees of the identifier structure, fixed-assets in the measurable - structure, jobs in the jobInfo structure and other representations, they can also be + structure, jobs in the jobInfo structure and other representations, they can also be represented here as appropriate to the jurisidiction.''' instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:accountType"), parent=account, attributes=nonNumAttr, text="account") - - '''What is a journal entry without a (monetary) amount? While XBRL GL may usher in journal - entries that also incorporate quantities, to reflect the detail of business metrics, the - (monetary) amount is another key and obvious fields. XBRL GL has been designed to reflect - how popular accounting systems store amounts - some combination of a signed amount (e.g., 5, -10), - a separate sign (entered into signOfAmount) and a separate place to indicate the number is + + '''What is a journal entry without a (monetary) amount? While XBRL GL may usher in journal + entries that also incorporate quantities, to reflect the detail of business metrics, the + (monetary) amount is another key and obvious fields. XBRL GL has been designed to reflect + how popular accounting systems store amounts - some combination of a signed amount (e.g., 5, -10), + a separate sign (entered into signOfAmount) and a separate place to indicate the number is associated with a debit or credit (debitCreditCode).''' - instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:amount"), parent=entryDetail, attributes=monetaryAttr, + instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:amount"), parent=entryDetail, attributes=monetaryAttr, text=amt) - '''Depending on the originating system, this field may contain whether the amount is - associated with a debit or credit. Interpreting the number correctly for import requires + '''Depending on the originating system, this field may contain whether the amount is + associated with a debit or credit. Interpreting the number correctly for import requires an understanding of the three related amount fields - amount, debitCreditCode and sign of amount.''' if drCrCode: - instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:debitCreditCode"), parent=entryDetail, attributes=nonNumAttr, + instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:debitCreditCode"), parent=entryDetail, attributes=nonNumAttr, text=drCrCode) - '''Depending on the originating system, this field may contain whether the amount is - signed (+ or -) separately from the amount field itself. Interpreting the number correctly - for import requires an understanding of the three related amount fields - amount, + '''Depending on the originating system, this field may contain whether the amount is + signed (+ or -) separately from the amount field itself. Interpreting the number correctly + for import requires an understanding of the three related amount fields - amount, debitCreditCode and sign of amount.''' # instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:signOfAmount"), parent=entryDetail, attributes=nonNumAttr, text="+") # This date is the accounting significance date, not the date that entries were actually entered or posted to the system. if qbDate: - instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:postingDate"), parent=entryDetail, attributes=nonNumAttr, + instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:postingDate"), parent=entryDetail, attributes=nonNumAttr, text=qbDate) - if qbName or qbMemo: + if qbName or qbMemo: identRef = instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:identifierReference"), parent=entryDetail) if qbMemo: - instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:identifierCode"), parent=identRef, attributes=nonNumAttr, + instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:identifierCode"), parent=identRef, attributes=nonNumAttr, text=qbMemo) if qbName: - instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:identifierDescription"), parent=identRef, attributes=nonNumAttr, + instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:identifierDescription"), parent=identRef, attributes=nonNumAttr, text=qbName) - #instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:identifierType"), parent=identRef, attributes=nonNumAttr, + #instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:identifierType"), parent=identRef, attributes=nonNumAttr, # text="V") - + if qbReport != "trialBalance": if qbTxnType: # not exactly same enumerations as expected by QB cleanedQbTxnType = qbTxnType.replace(" ","").lower() @@ -526,18 +526,18 @@ def processQbResponse(qbRequest, responseXml): # TBD add more QB transations here as they are discovered and not in table else: glDocType = qbTxnType # if all else fails pass through QB TxnType, it will fail GL validation and be noticed! - instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:documentType"), parent=entryDetail, attributes=nonNumAttr, + instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:documentType"), parent=entryDetail, attributes=nonNumAttr, text=glDocType) - - '''This enumerated field is used to specifically state whether the entries have been + + '''This enumerated field is used to specifically state whether the entries have been posted to the originating system or not.''' - instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:postingStatus"), parent=entryDetail, attributes=nonNumAttr, + instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:postingStatus"), parent=entryDetail, attributes=nonNumAttr, text="posted") # A comment at the individual entry detail level. # instance.createFact(qname("{http://www.xbrl.org/int/gl/cor/2006-10-25}gl-cor:detailComment"), parent=entryDetail, attributes=nonNumAttr, text="Comment...") - + isFirst = False - + if saveInstance: qbRequestStatus[ticket] = _("Saving XBRL-GL instance") instance.saveInstance() diff --git a/arelle/CntlrWebMain.py b/arelle/CntlrWebMain.py index e9cf230fd7..3f7eee956c 100644 --- a/arelle/CntlrWebMain.py +++ b/arelle/CntlrWebMain.py @@ -14,7 +14,7 @@ from arelle.FileSource import FileNamedStringIO from arelle.PluginManager import pluginClassMethods _os_pid = os.getpid() - + GETorPOST = ('GET', 'POST') GET = 'GET' POST = 'POST' @@ -22,7 +22,7 @@ def startWebserver(_cntlr, options): """Called once from main program in CmtlrCmdLine to initiate web server on specified local port. To test WebServer run from source in IIS, use an entry like this: c:\python33\python.exe c:\\users\\myname\\mySourceFolder\\arelleCmdLine.py %s - + :param options: OptionParser options from parse_args of main argv arguments (the argument *webserver* provides hostname and port), port being used to startup the webserver on localhost. :type options: optparse.Values """ @@ -42,7 +42,7 @@ def startWebserver(_cntlr, options): # install REST API interfaces # if necessary to support CGI hosted servers below root, add as first part of routes # and corresponding arguments to the handler methods - + # allow plugins to replace or add to default REST API "routes" pluginResult = None for pluginMethod in pluginClassMethods("CntlrWebMain.StartWebServer"): @@ -104,7 +104,7 @@ def startWebserver(_cntlr, options): app.run(host=host, port=port or 80, server=server) else: app.run(host=host, port=port or 80) - + def cgiInterface(cgiAppPath): # route request according to content #with open(r"c:\temp\tracecgi.log", "at", encoding="utf-8") as fh: @@ -119,12 +119,12 @@ def cgiInterface(cgiAppPath): return image(request.query.image) else: return indexPageCGI() - - + + def login_form(): - """Request for a login form (get to */rest/login*). Corresponds to login from other providers of XBRL validation services, but + """Request for a login form (get to */rest/login*). Corresponds to login from other providers of XBRL validation services, but this version of Arelle does not perform accounting or charges for validation requests, so the login is ignored. - + :returns: str -- HTML login form to enter and submit via method=POST these fields: name, password """ return _('''
@@ -132,10 +132,10 @@ def login_form():
Password:
 
''') - + def login_submit(): """Login of fields from login form (post to */rest/login*). Saves user ID for future use. - + :param name: User ID :param password: Password """ @@ -145,10 +145,10 @@ def login_submit(): return _("

You are logged in as user: {0}

").format(name) else: return _("

Login failed

") - + def checkLogin(_user, _password): """Save user ID for future use. Password not currently processed. - + :returns: bool -- True (for now, future user may interact with authentication and accounting services.) """ global user @@ -157,7 +157,7 @@ def checkLogin(_user, _password): def logout(): """Request to log out (get */rest/logout*). Removes any proior user ID from session. - + :returns: html -- Message that user has logged out """ global user @@ -166,14 +166,14 @@ def logout(): def arelleIcon(): """Request for icon for URL display (get */favicon.ico*). - + :returns: ico -- Icon file for browsers """ return static_file("arelle.ico", root=imagesDir, mimetype='image/vnd.microsoft.icon') def image(imgFile): """Request for an image file for URL display (get */images/*). - + :returns: image file -- Requested image file from images directory of application for browsers """ return static_file(imgFile, root=imagesDir) @@ -207,14 +207,14 @@ class Options(): def __init__(self): for option, defaultValue in optionsPrototype.items(): setattr(self, option, defaultValue) - + supportedViews = {'DTS', 'concepts', 'pre', 'table', 'cal', 'dim', 'facts', 'factTable', 'formulae', 'roleTypes', 'arcroleTypes'} def validation(file=None): """REST request to validate, by *get* or *post*, to URL patterns including */rest/xbrl//{open|close|validation|DTS...}*, and */rest/xbrl/{view|open|close}*. Sets up CntrlCmdLine options for request, performed by runOptionsAndGetResult using CntlrCmdLine.run with get or post arguments. - + :returns: html, xhtml, xml, json, text -- Return per media type argument and request arguments """ errors = [] @@ -249,11 +249,11 @@ def validation(file=None): elif view or viewArcrole: if media not in ('xml', 'xhtml', 'html', 'csv', 'xlsx', 'json'): errors.append(_("Media '{0}' is not supported for view (please select xhtml, html, xml, csv, xlsx or json)").format(media)) - elif requestPathParts[-1] not in ("open", "close"): + elif requestPathParts[-1] not in ("open", "close"): errors.append(_("Neither validation nor view requested, nothing to do.")) if (flavor not in ('standard', 'standard-except-formula', 'formula-compile-only', 'formula-compile-and-run') and not flavor.startswith('edgar') and not flavor.startswith('sec')): - errors.append(_("Flavor '{0}' is not supported").format(flavor)) + errors.append(_("Flavor '{0}' is not supported").format(flavor)) if view and view not in supportedViews: errors.append(_("View '{0}' is not supported").format(view)) if errors: @@ -301,17 +301,17 @@ def validation(file=None): setattr(options, "viewArcrole", viewArcrole) setattr(options, "viewFile", viewFile) return runOptionsAndGetResult(options, media, viewFile, sourceZipStream) - + def runOptionsAndGetResult(options, media, viewFile, sourceZipStream=None): """Execute request according to options, for result in media, with *post*ed file in sourceZipStream, if any. - + :returns: html, xml, csv, text -- Return per media type argument and request arguments """ addLogToZip = False if media == "zip" and not viewFile: responseZipStream = io.BytesIO() # add any needed plugins to load from OIM or save into OIM - if (hasattr(options, "saveOIMinstance") or + if (hasattr(options, "saveOIMinstance") or (getattr(options, "entrypointFile", "") or "").rpartition(".")[2] in ("json", "csv", "xlsx")): plugins = (getattr(options, "plugins", "") or "").split("|") if getattr(options, "entrypointFile", "").rpartition(".")[2] in ("json", "csv", "xlsx"): @@ -373,7 +373,7 @@ def runOptionsAndGetResult(options, media, viewFile, sourceZipStream=None): def diff(): """Execute versioning diff request for *get* request to */rest/xbrl/diff*. - + :returns: xml -- Versioning report. """ if not request.query.fromDTS or not request.query.toDTS or not request.query.report: @@ -391,7 +391,7 @@ def diff(): def configure(): """Set up features for *get* requests to */rest/configure*, e.g., proxy or plug-ins. - + :returns: html -- Status of configuration request (e.g., proxy or plug-ins). """ if not request.query.proxy and not request.query.plugins and not request.query.packages and 'environment' not in request.query: @@ -411,7 +411,7 @@ def configure(): def stopWebServer(): """Stop the web server by *get* requests to */rest/stopWebServer*. - + """ def stopSoon(delaySeconds): time.sleep(delaySeconds) @@ -420,19 +420,19 @@ def stopSoon(delaySeconds): threading.Thread(target=stopSoon, args=(2.5,), daemon=True).start() response.content_type = 'text/html; charset=UTF-8' return htmlBody(tableRows((time.strftime("Received at %Y-%m-%d %H:%M:%S"), - "Good bye...",), + "Good bye...",), header=_("Stop Request"))) - + def testTest(): return "Results attached:\n" + multipartResponse(( ("file1", "text/plain", "test text 1"), ("file2", "text/plain", "test text 2"), ("file3", "text/plain", "test text 3"), )) - + def quickbooksServer(): """Interface to QuickBooks server responding to *post* requests to */quickbooks/server.asmx*. - + (Part of QuickBooks protocol, see module CntlrQuickBooks.) """ from arelle import CntlrQuickBooks @@ -442,10 +442,10 @@ def quickbooksServer(): def quickbooksGLrequest(qbReport=None, file=None): """Initiate request to QuickBooks server for *get* requests to */rest/quickbooks//xbrl-gl/...*. - + :returns: html, xml, csv, text -- Return per media type argument and request arguments """ - from arelle.CntlrQuickBooks import supportedQbReports, qbRequest + from arelle.CntlrQuickBooks import supportedQbReports, qbRequest from arelle.ModelValue import dateTime errors = [] requestPathParts = request.urlparts[2].split('/') @@ -467,17 +467,17 @@ def quickbooksGLrequest(qbReport=None, file=None): ticket = qbRequest(qbReport, fromDate, toDate, file) result = htmlBody(tableRows([_("Request queued for QuickBooks...")], header=_("Quickbooks Request")), script=''' '''.format(ticket, media, viewRequested)) return result def quickbooksGLresponse(): """Poll for QuickBooks protocol responses for *get* requests to */rest/quickbooks/response*. - + :returns: html, xml, csv, text -- Return per media type argument and request arguments, if response is ready, otherwise javascript to requery this *get* request periodicially. """ from arelle import CntlrQuickBooks @@ -491,18 +491,18 @@ def quickbooksGLresponse(): CntlrQuickBooks.qbRequestStatus.pop(ticket, None) return errorReport([status[24:]], media) if status != "Done" or ticket not in CntlrQuickBooks.xbrlInstances: - return htmlBody(tableRows([_("{0}, Waiting 20 seconds...").format(status)], - header=_("Quickbooks Request")), + return htmlBody(tableRows([_("{0}, Waiting 20 seconds...").format(status)], + header=_("Quickbooks Request")), script=''' ''') CntlrQuickBooks.qbRequestStatus.pop(ticket) - + instanceUuid = CntlrQuickBooks.xbrlInstances[ticket] CntlrQuickBooks.xbrlInstances.pop(ticket) options = Options() @@ -520,9 +520,9 @@ def quickbooksWebPage(): def localhostCertificate(): """Interface to QuickBooks server responding to *get* requests for a host certificate */quickbooks/localhost.crt* or */localhost.crt*. - + (Supports QuickBooks protocol.) - + :returns: self-signed certificate """ return ''' @@ -549,10 +549,10 @@ def localhostCertificate(): +3cu//C8LvhjkQ== -----END CERTIFICATE----- ''' - + def helpREST(): """Help web page for *get* requests to */help*. - + :returns: html - Table of CntlrWebMain web API """ return htmlBody(_(''' @@ -565,7 +565,7 @@ def helpREST(): ''') + (_(''' - @@ -575,7 +575,7 @@ def helpREST(): ''') if cntlr.isGAE else _(''' - @@ -585,26 +585,26 @@ def helpREST(): document at c:/a/b/c.xbrl (on local drive) and return structured xml results. ''')) + _(''' - +
formula-compile-and-run: Formulas will be compiled and run. (No XBRL 2.1, XDT, or disclosure system validation.) - - - - - - - - + + + + + + + + - - - - - - - + + + + + + + - - - + + + - - - - + + + - - - - - - +
json: JSON text results. + + + + +Example: factListCols=Label,unitRef,Dec,Value,EntityScheme,EntityIdentifier,Period,Dimensions - + - +
text: Plain text results (no markup). - ''') + @@ -747,20 +747,20 @@ def helpREST(): def about(arelleImgFile=None): from lxml import etree """About web page for *get* requests to */about*. - + :returns: html - About web page """ return htmlBody(_('''
\u00A0For an http POST of a zip file (mime type application/zip), {file} is the relative file path inside the zip file.
\u00A0For an http GET request, {file} may be a web url, and may have "/" characters replaced by ";" characters +
\u00A0For an http GET request, {file} may be a web url, and may have "/" characters replaced by ";" characters (but that is not necessary).
Example:/rest/xbrl/c.xbrl/validation/xbrl?media=xml: Validate entry instance document in the POSTed zip archived file c.xbrl and return structured xml results.
\u00A0For a browser request or http GET request, {file} may be local or web url, and may have "/" characters replaced by ";" characters +
\u00A0For a browser request or http GET request, {file} may be local or web url, and may have "/" characters replaced by ";" characters (but that is not necessary).
Example:/rest/xbrl/c:/a/b/c.xbrl/validation/xbrl?media=xml: Validate entry instance document at c:/a/b/c.xbrl (on local drive) and return structured xml results.
Parameters are optional after "?" character, and are separated by "&" characters, +
Parameters are optional after "?" character, and are separated by "&" characters, as follows:
flavorstandard: XBRL 2.1 and XDT validation. (If formulas are present they will also be compiled and run.) (default)
{sec*|edgar*}: SEC Edgar Filer Manual validation. (If formulas are present they will also be compiled and run.)
standard-except-formula: XBRL 2.1 and XDT validation. (If formulas are present they will be ignored.)
formula-compile-only: Formulas will be compiled but not run. (No XBRL 2.1, XDT, or disclosure system validation.) -
formula-compile-and-run: Formulas will be compiled and run. (No XBRL 2.1, XDT, or disclosure system validation.)
mediahtml or xhtml: Html text results. (default)
xml: XML structured results.
json: JSON results. -
text: Plain text results (no markup).
fileAlternate way to specify file name or url by a parameter. Files ending in .json will be loaded as xBRL-JSON.
importA list of files to import to the DTS, such as additional formula -or label linkbases. Multiple file names are separated by a '|' character.
labelLangLabel language to override system settings, e.g., &labelLang=ja.
labelRoleLabel role instead of standard label, e.g., &labelRole=http://www.xbrl.org/2003/role/verboseLabel. To use the concept QName instead of a label, specify &labelRole=XBRL-concept-name.
uiLangUser interface language to override system settings, e.g., &uiLang=fr. Changes setting for current session (but not saved setting).
calcDecimalsSpecify calculation linkbase validation inferring decimals.
calcPrecisionSpecify calculation linkbase validation inferring precision.
efm-*Select Edgar Filer Manual (U.S. SEC) disclosure system validation. (Alternative to flavor parameter.):
+
text: Plain text results (no markup).
fileAlternate way to specify file name or url by a parameter. Files ending in .json will be loaded as xBRL-JSON.
importA list of files to import to the DTS, such as additional formula +or label linkbases. Multiple file names are separated by a '|' character.
labelLangLabel language to override system settings, e.g., &labelLang=ja.
labelRoleLabel role instead of standard label, e.g., &labelRole=http://www.xbrl.org/2003/role/verboseLabel. To use the concept QName instead of a label, specify &labelRole=XBRL-concept-name.
uiLangUser interface language to override system settings, e.g., &uiLang=fr. Changes setting for current session (but not saved setting).
calcDecimalsSpecify calculation linkbase validation inferring decimals.
calcPrecisionSpecify calculation linkbase validation inferring precision.
efm-*Select Edgar Filer Manual (U.S. SEC) disclosure system validation. (Alternative to flavor parameter.):
efm-pragmatic: SEC-required rules, currently-allowed years
efm-strict: SEC-semantic additional rules, currently-allowed years
efm-pragmatic-all-years: SEC-required rules, all years
@@ -612,60 +612,60 @@ def helpREST():
ifrsSpecify IFRS Global Filer Manual validation.
hmrcSpecify HMRC validation.
sbr-nlSpecify SBR-NL taxonomy validation.
utrSelect validation with respect to Unit Type Registry.
infosetSelect validation with respect to testcase infoset.
parametersSpecify parameters for validation or formula (comma separated name=value[,name2=value2]).
formulaAsserResultCountsReport formula assertion counts.
formulaVarSetExprResultTrace variable set formula value, assertion test results.
formulaVarSetTimingTrace variable set execution times.
formulaVarFilterWinnowingTrace variable set filter winnowing.
utrSelect validation with respect to Unit Type Registry.
infosetSelect validation with respect to testcase infoset.
parametersSpecify parameters for validation or formula (comma separated name=value[,name2=value2]).
formulaAsserResultCountsReport formula assertion counts.
formulaVarSetExprResultTrace variable set formula value, assertion test results.
formulaVarSetTimingTrace variable set execution times.
formulaVarFilterWinnowingTrace variable set filter winnowing.
{other}Other detailed formula trace parameters:
formulaParamExprResult, formulaParamInputValue, formulaCallExprSource, formulaCallExprCode, formulaCallExprEval, formulaCallExprResult, formulaVarSetExprEval, formulaFormulaRules, formulaVarsOrder, formulaVarExpressionSource, formulaVarExpressionCode, formulaVarExpressionEvaluation, formulaVarExpressionResult, formulaVarFiltersResult, and formulaRunIDs.
abortOnMajorErrorAbort process on major error, such as when load is unable to find an entry or discovered file.
saveOIMinstanceSpecify output instance filename to save (name.json, name.xml), for example if loading from xBRL-JSON.one would save to .xml otherwise to .json. Media must be zip. Returns a zip of instance and logFile.
collectProfileStatsCollect profile statistics, such as timing of validation activities and formulae.
abortOnMajorErrorAbort process on major error, such as when load is unable to find an entry or discovered file.
saveOIMinstanceSpecify output instance filename to save (name.json, name.xml), for example if loading from xBRL-JSON.one would save to .xml otherwise to .json. Media must be zip. Returns a zip of instance and logFile.
collectProfileStatsCollect profile statistics, such as timing of validation activities and formulae.
pluginsActivate plug-ins, specify '|' separated .py modules (relative to plug-in directory).
packagesActivate taxonomy packages, specify '|' separated .zip packages (absolute URLs or file paths).
Versioning Report (diff of two DTSes)
/rest/xbrl/diffDiff two DTSes, producing an XBRL versioning report relative to report directory.
Parameters are requred "?" character, and are separated by "&" characters, +
Parameters are requred "?" character, and are separated by "&" characters, as follows:
fromDTSFile name or url of from DTS.
toDTSFile name or url of to DTS.
reportFile name or url of to report (to for relative path construction). The report is not written out, but its contents are returned by the web request to be saved by the requestor.
fromDTSFile name or url of from DTS.
toDTSFile name or url of to DTS.
reportFile name or url of to report (to for relative path construction). The report is not written out, but its contents are returned by the web request to be saved by the requestor.
Example:/rest/diff?fromDTS=c:/a/prev/old.xsd&toDTS=c:/a/next/new.xsd&report=c:/a/report/report.xml: Diff two DTSes and produce versioning report.
Views
/rest/xbrl/{file}/{view}View document at {file}.
\u00A0{file} may be local or web url, and may have "/" characters replaced by ";" characters (but that is not necessary).
\u00A0{view} may be DTS, concepts, pre, table, cal, dim, facts, factTable, formulae, roleTypes, or arcroleTypes.
Example:/rest/xbrl/c:/a/b/c.xbrl/dim?media=html: View dimensions of +
Example:/rest/xbrl/c:/a/b/c.xbrl/dim?media=html: View dimensions of document at c:/a/b/c.xbrl (on local drive) and return html result.
/rest/xbrl/view(Alternative syntax) View document, file and view are provided as parameters (see below).
Example:/rest/xbrl/view?file=c:/a/b/c.xbrl&view=dim&media=xml: Validate entry instance document at c:/a/b/c.xbrl (on local drive) and return structured xml results.
Parameters are optional after "?" character, and are separated by "&" characters, +
Parameters are optional after "?" character, and are separated by "&" characters, as follows:
mediahtml or xhtml: Html text results. (default)
xml: XML structured results.
csv: CSV text results (no markup).
xslx: Excel results. -
json: JSON text results.
fileAlternate way to specify file name or url by a parameter.
viewAlternate way to specify view by a parameter.
viewArcroleAlternate way to specify view by indicating arcrole desired.
importA list of files to import to the DTS, such as additional formula -or label linkbases. Multiple file names are separated by a '|' character.
fileAlternate way to specify file name or url by a parameter.
viewAlternate way to specify view by a parameter.
viewArcroleAlternate way to specify view by indicating arcrole desired.
importA list of files to import to the DTS, such as additional formula +or label linkbases. Multiple file names are separated by a '|' character.
factListColsA list of column names for facts list. Multiple names are separated by a space or comma characters. -Example: factListCols=Label,unitRef,Dec,Value,EntityScheme,EntityIdentifier,Period,Dimensions
Excel interface
GUI operation:Select data tab.
Click Get External Data From Web.
-New Web Query dialog, enter rest URI to Address (example, for instance with indicated fact columns: +New Web Query dialog, enter rest URI to Address (example, for instance with indicated fact columns: http://localhost:8080/rest/xbrl/C:/Users/John Doe/Documents/eu/instance.xbrl/facts?media=xhtml&factListCols=Label,unitRef,Dec,Value,EntityScheme,EntityIdentifier,Period,Dimensions
Before clicking Go, click Options, on Options dialog select Full HTML Formatting, then Ok to Options dialog.
Click Go.
@@ -708,34 +708,34 @@ def helpREST(): QBWC polls once a minute, if impatient, in the QBWC window, click its Arelle checkbox and press the update button.
(If you get the error [8004041A] from Quickbooks, enable the company file for Arelle access in Quickbooks: Edit->Preferences...->Integrated Applications->Company Preferences->click allow web access for ArelleWebService)
-
Example:http://localhost:8080/rest/quickbooks/generalLedger/xbrl-gl/C:/mystuff/xbrlGeneralLedger.xbrl/view?fromDate=2011-01-01&toDate=2011-12-31 +
Example:http://localhost:8080/rest/quickbooks/generalLedger/xbrl-gl/C:/mystuff/xbrlGeneralLedger.xbrl/view?fromDate=2011-01-01&toDate=2011-12-31 (You may omit /view.)
Parameters follow "?" character, and are separated by "&" characters, +
Parameters follow "?" character, and are separated by "&" characters, as follows:
mediahtml or xhtml: Html text results. (default)
xml: XML structured results.
json: JSON results. -
text: Plain text results (no markup).
fromDate, toDateFrom & to dates for GL transactions
Management
/rest/configureConfigure settings:
Parameters are required following "?" character, and are separated by "&" characters, +
Parameters are required following "?" character, and are separated by "&" characters, as follows:
proxyShow or modify and re-save proxy settings:
Enter 'show' to view current setting, 'system' to configure to use system proxy setting, 'none' to configure for no proxy, or 'http://[user[:password]@]host[:port]' (e.g., http://192.168.1.253, http://example.com:8080, http://joe:secret@example.com:8080)." ))
pluginsShow or modify and re-save plug-ins configuration:
-Enter 'show' to view plug-ins configuration, , or '|' separated modules: -+url to add plug-in by its url or filename (relative to plug-in directory else absolute), ~name to reload a plug-in by its name, -name to remove a plug-in by its name, +Enter 'show' to view plug-ins configuration, , or '|' separated modules: ++url to add plug-in by its url or filename (relative to plug-in directory else absolute), ~name to reload a plug-in by its name, -name to remove a plug-in by its name, (e.g., '+http://arelle.org/files/hello_web.py', '+C:\Program Files\Arelle\examples\plugin\hello_dolly.py' to load, -~Hello Dolly to reload, -Hello Dolly to remove). (Note that plug-ins are transient on Google App Engine, specify with &plugins to other rest commands.) +~Hello Dolly to reload, -Hello Dolly to remove). (Note that plug-ins are transient on Google App Engine, specify with &plugins to other rest commands.)
packagesShow or modify and re-save taxonomy packages configuration:
-Enter 'show' to view packages configuration, , or '|' separated package URLs: -+url to add package by its full url or filename, ~name to reload a package by its name, -name to remove a package by its name. -(Note that packages are transient on Google App Engine, specify with &packages to other rest commands.) +Enter 'show' to view packages configuration, , or '|' separated package URLs: ++url to add package by its full url or filename, ~name to reload a package by its name, -name to remove a package by its name. +(Note that packages are transient on Google App Engine, specify with &packages to other rest commands.)
environmentShow host environment (config and cache directories).
- - @@ -769,12 +769,12 @@ def about(arelleImgFile=None):
About arelle
arelle® %s (%sbit). An open source XBRL platform
© 2010-%s Mark V Systems Limited. All rights reserved.
Web site: http://www.arelle.org. +
Web site: http://www.arelle.org. E-mail support: support@arelle.org.
Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file +
Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. -Unless required by applicable law or agreed to in writing, software distributed under the License -is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +Unless required by applicable law or agreed to in writing, software distributed under the License +is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
Includes:
Python® %s.%s.%s © 2001-2010 Python Software Foundation
Bottle © 2011 Marcel Hellkamp
''') % (arelleImgFile or '/images/arelle32.gif', Version.__version__, cntlr.systemWordSize, Version.copyrightLatestYear, - sys.version_info[0],sys.version_info[1],sys.version_info[2], + sys.version_info[0],sys.version_info[1],sys.version_info[2], etree.LXML_VERSION[0],etree.LXML_VERSION[1],etree.LXML_VERSION[2]) ) def indexPageREST(): """Index (default) web page for *get* requests to */*. - + :returns: html - Web page of choices to navigate to */help* or */about*. """ return htmlBody(_(''' @@ -785,7 +785,7 @@ def indexPageREST(): def indexPageCGI(): """Default web page response for *get* CGI request with no parameters. - + :returns: html - Web page of choices to navigate to *?help* or *?about*. """ return htmlBody(_('''
@@ -798,7 +798,7 @@ def indexPageCGI(): def htmlBody(body, script=""): """Wraps body html string in a css-styled html web page - + :param body: Contents for the ** element :type body: html str :param script: Script to insert in generated html web page (such as a timed reload script) @@ -828,7 +828,7 @@ def htmlBody(body, script=""): def tableRows(lines, header=None): """Wraps lines of text into a one-column table (for display of text results of operations, such as processing messages and status, to web browser). Replaces any *&* with *&* and *<* with *<*. - + :param lines: Sequence (list or tuple) of line strings. :type lines: [str] :param header: Optional header text for top row of table. @@ -836,12 +836,12 @@ def tableRows(lines, header=None): :returns: html -
html string. """ return '
%s\n
' % ( - ("%s" % header if header else "") + + ("%s" % header if header else "") + "\n".join("%s" % line.replace("&","&").replace("<","<") for line in lines)) def errorReport(errors, media="html"): """Wraps lines of error text into specified media type for return of result to a request. - + :param errors: Sequence (list or tuple) of error strings. :type errors: [str] :param media: Type of result requestd. @@ -854,13 +854,13 @@ def errorReport(errors, media="html"): else: response.content_type = 'text/html; charset=UTF-8' return htmlBody(tableRows(errors, header=_("Messages"))) - + def multipartResponse(parts): # call with ( (filename, contentType, content), ...) boundary='----multipart-boundary-%s----' % (uuid.uuid1(),) response.content_type = 'multipart/mixed; boundary=%s' % (boundary,) buf = [] - + for filename, contentType, content in parts: buf.append("\r\n" + boundary + "\r\n") buf.append('Content-Disposition: attachment; filename="{0}";\r\n'.format(filename)) diff --git a/arelle/CntlrWinMain.py b/arelle/CntlrWinMain.py index d9a063f692..362126c003 100644 --- a/arelle/CntlrWinMain.py +++ b/arelle/CntlrWinMain.py @@ -9,11 +9,11 @@ from arelle import PythonUtil # define 2.x or 3.x string types import os, sys, subprocess, pickle, time, locale, re, fnmatch, platform -if sys.platform == 'win32' and getattr(sys, 'frozen', False): +if sys.platform == 'win32' and getattr(sys, 'frozen', False): # need the .dll directory in path to be able to access Tk and Tcl DLLs efore importinng Tk, etc. os.environ['PATH'] = os.path.dirname(sys.executable) + ";" + os.environ['PATH'] -from tkinter import (Tk, Tcl, TclError, Toplevel, Menu, PhotoImage, StringVar, BooleanVar, N, S, E, W, EW, +from tkinter import (Tk, Tcl, TclError, Toplevel, Menu, PhotoImage, StringVar, BooleanVar, N, S, E, W, EW, HORIZONTAL, VERTICAL, END, font as tkFont) try: from tkinter.ttk import Frame, Button, Label, Combobox, Separator, PanedWindow, Notebook @@ -81,9 +81,9 @@ def __init__(self, parent): else: toolbarButtonPadding = 4 - tkinter.CallWrapper = TkinterCallWrapper + tkinter.CallWrapper = TkinterCallWrapper + - imgpath = self.imagesDir + os.sep if self.isMSW: @@ -101,7 +101,7 @@ def __init__(self, parent): self.menubar = Menu(self.parent) self.parent["menu"] = self.menubar - + self.fileMenu = Menu(self.menubar, tearoff=0) self.fileMenuLength = 1 for label, command, shortcut_text, shortcut in ( @@ -135,9 +135,9 @@ def __init__(self, parent): self.fileMenuLength += 1 self.loadFileMenuHistory() self.menubar.add_cascade(label=_("File"), menu=self.fileMenu, underline=0) - + toolsMenu = Menu(self.menubar, tearoff=0) - + validateMenu = Menu(self.menubar, tearoff=0) toolsMenu.add_cascade(label=_("Validation"), menu=validateMenu, underline=0) validateMenu.add_command(label=_("Validate"), underline=0, command=self.validate) @@ -173,7 +173,7 @@ def __init__(self, parent): toolsMenu.add_command(label=_("Compare DTSes..."), underline=0, command=self.compareDTSes) cacheMenu = Menu(self.menubar, tearoff=0) - + rssWatchMenu = Menu(self.menubar, tearoff=0) rssWatchMenu.add_command(label=_("Options..."), underline=0, command=self.rssWatchOptionsDialog) rssWatchMenu.add_command(label=_("Start"), underline=0, command=lambda: self.rssWatchControl(start=True)) @@ -214,7 +214,7 @@ def __init__(self, parent): for (_opt_label, _opt_val) in _internetRecheckEntries: internetCacheRecheckMenu.add_checkbutton( label=_opt_label, - variable=self.internetRecheckVar, + variable=self.internetRecheckVar, underline=0, onvalue=_opt_val ) @@ -242,18 +242,18 @@ def __init__(self, parent): logmsgMenu.add_checkbutton(label=_("Show debug messages"), underline=0, variable=self.showDebugMessages, onvalue=True, offvalue=False) toolsMenu.add_command(label=_("Language..."), underline=0, command=lambda: DialogLanguage.askLanguage(self)) - + for pluginMenuExtender in pluginClassMethods("CntlrWinMain.Menu.Tools"): pluginMenuExtender(self, toolsMenu) self.menubar.add_cascade(label=_("Tools"), menu=toolsMenu, underline=0) - + # view menu only if any plug-in additions provided if any (pluginClassMethods("CntlrWinMain.Menu.View")): viewMenu = Menu(self.menubar, tearoff=0) for pluginMenuExtender in pluginClassMethods("CntlrWinMain.Menu.View"): pluginMenuExtender(self, viewMenu) self.menubar.add_cascade(label=_("View"), menu=viewMenu, underline=0) - + helpMenu = Menu(self.menubar, tearoff=0) for label, command, shortcut_text, shortcut in ( (_("Check for updates"), lambda: Updater.checkForUpdates(self), None, None), @@ -281,7 +281,7 @@ def __init__(self, parent): self.statusbar = Label(windowFrame, text=_("Ready..."), anchor=W) self.statusbarTimerId = self.statusbar.after(5000, self.uiClearStatusTimerEvent) self.statusbar.grid(row=2, column=0, columnspan=2, sticky=EW) - + #self.balloon = tkinter.tix.Balloon(windowFrame, statusbar=self.statusbar) self.toolbar_images = [] toolbar = Frame(windowFrame) @@ -354,9 +354,9 @@ def __init__(self, parent): logViewMenu.add_command(label=_("Save to file"), underline=0, command=self.logSaveToFile) if self.hasClipboard: logViewMenu.add_command(label=_("Copy to clipboard"), underline=0, command=lambda: self.logView.copyToClipboard(cntlr=self)) - + windowFrame.grid(row=0, column=0, sticky=(N,S,E,W)) - + windowFrame.columnconfigure(0, weight=999) windowFrame.columnconfigure(1, weight=1) windowFrame.rowconfigure(0, weight=1) @@ -372,12 +372,12 @@ def __init__(self, parent): self.tabWinTopRt.rowconfigure(0, weight=1) self.tabWinBtm.columnconfigure(0, weight=1) self.tabWinBtm.rowconfigure(0, weight=1) - - + + window = self.parent.winfo_toplevel() window.columnconfigure(0, weight=1) window.rowconfigure(0, weight=1) - + priorState = self.config.get('windowState') screenW = self.parent.winfo_screenwidth() - 16 # allow for window edge screenH = self.parent.winfo_screenheight() - 64 # allow for caption and menus @@ -422,11 +422,11 @@ def __init__(self, parent): self.tabWinTopLeft.config(width=topLeftW) if 10 < topLeftH < h - 60: self.tabWinTopLeft.config(height=topLeftH) - + self.parent.title(_("arelle - Unnamed")) - + self.logFile = None - + self.uiThreadQueue = queue.Queue() # background processes communicate with ui thread self.uiThreadChecker(self.statusbar) # start background queue @@ -434,7 +434,7 @@ def __init__(self, parent): if not self.modelManager.disclosureSystem.select(self.config.setdefault("disclosureSystem", None)): self.validateDisclosureSystem.set(False) self.modelManager.validateDisclosureSystem = False - + # load argv overrides for modelManager options lastArg = None for arg in sys.argv: @@ -442,11 +442,11 @@ def __init__(self, parent): if lastArg == "--skipLoading": # skip loading matching files (list of unix patterns) self.modelManager.skipLoading = re.compile('|'.join(fnmatch.translate(f) for f in arg.split('|'))) elif arg == "--skipDTS": # skip DTS loading, discovery, etc - self.modelManager.skipDTS = True + self.modelManager.skipDTS = True lastArg = arg self.setValidateTooltipText() - - + + def onTabChanged(self, event, *args): try: widgetIndex = event.widget.index("current") @@ -464,14 +464,14 @@ def loadFileMenuHistory(self): self.recentFilesMenu = Menu(self.menubar, tearoff=0) for i in range( min( len(fileHistory), 10 ) ): self.recentFilesMenu.add_command( - label=fileHistory[i], + label=fileHistory[i], command=lambda j=i: self.fileOpenFile(self.config["fileHistory"][j])) self.fileMenu.add_cascade(label=_("Recent files"), menu=self.recentFilesMenu, underline=0) importHistory = self.config.setdefault("importHistory", []) self.recentAttachMenu = Menu(self.menubar, tearoff=0) for i in range( min( len(importHistory), 10 ) ): self.recentAttachMenu.add_command( - label=importHistory[i], + label=importHistory[i], command=lambda j=i: self.fileOpenFile(self.config["importHistory"][j],importToDTS=True)) self.fileMenu.add_cascade(label=_("Recent imports"), menu=self.recentAttachMenu, underline=0) self.packagesMenu = Menu(self.menubar, tearoff=0) @@ -486,15 +486,15 @@ def loadFileMenuHistory(self): URL = packageInfo.get("URL") if name and URL and packageInfo.get("status") == "enabled": self.packagesMenu.add_command( - label=name, + label=name, command=lambda url=URL: self.fileOpenFile(url)) hasPackages = True if hasPackages: self.fileMenu.add_cascade(label=_("Packages"), menu=self.packagesMenu, underline=0) - + def onPackageEnablementChanged(self): self.loadFileMenuHistory() - + def fileNew(self, *ignore): if not self.okayToContinue(): return @@ -504,7 +504,7 @@ def fileNew(self, *ignore): self.data = {} self.parent.title(_("arelle - Unnamed")); self.modelManager.load(None); - + def getViewAndModelXbrl(self): view = getattr(self, "currentView", None) if view: @@ -531,13 +531,13 @@ def okayToContinue(self): return True reply = tkinter.messagebox.askokcancel( _("arelle - Unsaved Changes"), - _("Are you sure to close the current instance without saving?\n (OK will discard changes.)"), + _("Are you sure to close the current instance without saving?\n (OK will discard changes.)"), parent=self.parent) if reply is None: return False else: return reply - + def fileSave(self, event=None, view=None, fileType=None, filenameFromInstance=False, *ignore): if view is None: view = getattr(self, "currentView", None) @@ -636,7 +636,7 @@ def fileSave(self, event=None, view=None, fileType=None, filenameFromInstance=Fa filename, err), parent=self.parent) return True - + elif isinstance(view, ViewWinXml.ViewXml) and self.modelManager.modelXbrl.formulaOutputInstance: filename = self.uiFileDialog("save", title=_("arelle - Save Formula Result Instance Document"), @@ -656,7 +656,7 @@ def fileSave(self, event=None, view=None, fileType=None, filenameFromInstance=Fa self.filename, err), parent=self.parent) return True - tkinter.messagebox.showwarning(_("arelle - Save what?"), + tkinter.messagebox.showwarning(_("arelle - Save what?"), _("Nothing has been selected that can be saved. \nPlease select a view pane that can be saved."), parent=self.parent) ''' @@ -687,13 +687,13 @@ def fileSave(self, event=None, view=None, fileType=None, filenameFromInstance=Fa parent=self.parent) return True; ''' - + def fileSaveExistingFile(self, event=None, view=None, fileType=None, *ignore): return self.fileSave(view=view, fileType=fileType, filenameFromInstance=True) def saveDTSpackage(self): self.modelManager.saveDTSpackage(allDTSes=True) - + def fileOpen(self, *ignore): if not self.okayToContinue(): return @@ -708,9 +708,9 @@ def fileOpen(self, *ignore): return if os.sep == "\\": filename = filename.replace("/", "\\") - + self.fileOpenFile(filename) - + def importFileOpen(self, *ignore): if not self.modelManager.modelXbrl or self.modelManager.modelXbrl.modelDocument.type not in ( ModelDocument.Type.SCHEMA, ModelDocument.Type.LINKBASE, ModelDocument.Type.INSTANCE, ModelDocument.Type.INLINEXBRL): @@ -728,10 +728,10 @@ def importFileOpen(self, *ignore): return if os.sep == "\\": filename = filename.replace("/", "\\") - + self.fileOpenFile(filename, importToDTS=True) - - + + def updateFileHistory(self, url, importToDTS): if isinstance(url, list): # may be multi-doc ixds if len(url) != 1: @@ -747,7 +747,7 @@ def updateFileHistory(self, url, importToDTS): self.config[key] = fileHistory self.loadFileMenuHistory() self.saveConfig() - + def fileOpenFile(self, filename, importToDTS=False, selectTopView=False): if filename: for xbrlLoadedMethod in pluginClassMethods("CntlrWinMain.Xbrl.Open"): @@ -776,7 +776,7 @@ def fileOpenFile(self, filename, importToDTS=False, selectTopView=False): elif len(filename) == 1: self.updateFileHistory(filename[0], importToDTS) thread = threading.Thread(target=self.backgroundLoadXbrl, args=(filesource,importToDTS,selectTopView), daemon=True).start() - + def webOpen(self, *ignore): if not self.okayToContinue(): return @@ -791,7 +791,7 @@ def webOpen(self, *ignore): url = DialogOpenArchive.askArchiveFile(self, filesource) self.updateFileHistory(url, False) thread = threading.Thread(target=self.backgroundLoadXbrl, args=(filesource,False,False), daemon=True).start() - + def importWebOpen(self, *ignore): if not self.modelManager.modelXbrl or self.modelManager.modelXbrl.modelDocument.type not in ( ModelDocument.Type.SCHEMA, ModelDocument.Type.LINKBASE, ModelDocument.Type.INSTANCE, ModelDocument.Type.INLINEXBRL): @@ -801,8 +801,8 @@ def importWebOpen(self, *ignore): url = DialogURL.askURL(self.parent, buttonSEC=False, buttonRSS=False) if url: self.fileOpenFile(url, importToDTS=True) - - + + def backgroundLoadXbrl(self, filesource, importToDTS, selectTopView): startedAt = time.time() try: @@ -834,8 +834,8 @@ def backgroundLoadXbrl(self, filesource, importToDTS, selectTopView): if modelXbrl and modelXbrl.modelDocument: statTime = time.time() - startedAt modelXbrl.profileStat(profileStat, statTime) - self.addToLog(format_string(self.modelManager.locale, - _("%s in %.2f secs"), + self.addToLog(format_string(self.modelManager.locale, + _("%s in %.2f secs"), (action, statTime))) if modelXbrl.hasTableRendering: self.showStatus(_("Initializing table rendering")) @@ -844,8 +844,8 @@ def backgroundLoadXbrl(self, filesource, importToDTS, selectTopView): self.waitForUiThreadQueue() # force status update self.uiThreadQueue.put((self.showLoadedXbrl, [modelXbrl, importToDTS, selectTopView])) else: - self.addToLog(format_string(self.modelManager.locale, - _("not successfully %s in %.2f secs"), + self.addToLog(format_string(self.modelManager.locale, + _("not successfully %s in %.2f secs"), (action, time.time() - startedAt))) self.showStatus(_("Loading terminated"), 15000) @@ -937,7 +937,7 @@ def showLoadedXbrl(self, modelXbrl, attach, selectTopView=False): currentAction = "log view creation time" viewTime = time.time() - startedAt modelXbrl.profileStat("view", viewTime) - self.addToLog(format_string(self.modelManager.locale, + self.addToLog(format_string(self.modelManager.locale, _("views %.2f secs"), viewTime)) if selectTopView and topView: topView.select() @@ -953,7 +953,7 @@ def showLoadedXbrl(self, modelXbrl, attach, selectTopView=False): tkinter.messagebox.showwarning(_("Exception preparing view"),msg, parent=self.parent) self.addToLog(msg); self.showStatus(_("Ready..."), 2000) - + def showFormulaOutputInstance(self, priorOutputInstance, currentOutputInstance): currentAction = "closing prior formula output instance" try: @@ -970,17 +970,17 @@ def showFormulaOutputInstance(self, priorOutputInstance, currentOutputInstance): tkinter.messagebox.showwarning(_("Exception preparing view"),msg, parent=self.parent) self.addToLog(msg); self.showStatus(_("Ready..."), 2000) - + def showProfileStats(self): modelXbrl = self.modelManager.modelXbrl if modelXbrl and self.modelManager.collectProfileStats: modelXbrl.logProfileStats() - + def clearProfileStats(self): modelXbrl = self.modelManager.modelXbrl if modelXbrl and self.modelManager.collectProfileStats: modelXbrl.profileStats.clear() - + def fileClose(self, *ignore): if not self.okayToContinue(): return @@ -988,7 +988,7 @@ def fileClose(self, *ignore): self.parent.title(_("arelle - Unnamed")) self.setValidateTooltipText() self.currentView = None - + def fileReopen(self, *ignore): self.fileClose() fileHistory = self.config.setdefault("fileHistory", []) @@ -998,7 +998,7 @@ def fileReopen(self, *ignore): def validate(self): modelXbrl = self.modelManager.modelXbrl if modelXbrl and modelXbrl.modelDocument: - if (modelXbrl.modelManager.validateDisclosureSystem and + if (modelXbrl.modelManager.validateDisclosureSystem and not modelXbrl.modelManager.disclosureSystem.selection): tkinter.messagebox.showwarning(_("arelle - Warning"), _("Validation - disclosure system checks is requested but no disclosure system is selected, please select one by validation - select disclosure system."), @@ -1008,19 +1008,19 @@ def validate(self): for pluginXbrlMethod in pluginClassMethods("Testcases.Start"): pluginXbrlMethod(self, None, modelXbrl) thread = threading.Thread(target=self.backgroundValidate, daemon=True).start() - + def backgroundValidate(self): startedAt = time.time() modelXbrl = self.modelManager.modelXbrl priorOutputInstance = modelXbrl.formulaOutputInstance modelXbrl.formulaOutputInstance = None # prevent closing on background thread by validateFormula self.modelManager.validate() - self.addToLog(format_string(self.modelManager.locale, - _("validated in %.2f secs"), + self.addToLog(format_string(self.modelManager.locale, + _("validated in %.2f secs"), time.time() - startedAt)) if not modelXbrl.isClosed and (priorOutputInstance or modelXbrl.formulaOutputInstance): self.uiThreadQueue.put((self.showFormulaOutputInstance, [priorOutputInstance, modelXbrl.formulaOutputInstance])) - + self.uiThreadQueue.put((self.logSelect, [])) def compareDTSes(self): @@ -1040,16 +1040,16 @@ def compareDTSes(self): self.config["versioningReportDir"] = os.path.dirname(versReportFile) self.saveConfig() thread = threading.Thread(target=self.backgroundCompareDTSes, args=(versReportFile,), daemon=True).start() - + def backgroundCompareDTSes(self, versReportFile): startedAt = time.time() modelVersReport = self.modelManager.compareDTSes(versReportFile) if modelVersReport and modelVersReport.modelDocument: - self.addToLog(format_string(self.modelManager.locale, - _("compared in %.2f secs"), + self.addToLog(format_string(self.modelManager.locale, + _("compared in %.2f secs"), time.time() - startedAt)) self.uiThreadQueue.put((self.showComparedDTSes, [modelVersReport])) - + def showComparedDTSes(self, modelVersReport): # close prior DTS displays modelVersReport.modelDocument.fromDTS.closeViews() @@ -1070,14 +1070,14 @@ def loadFile(self, filename): self.filename), clearAfter=5000) self.parent.title(_("arelle - {0}").format( os.path.basename(self.filename))) - + except (EnvironmentError, pickle.PickleError) as err: tkinter.messagebox.showwarning(_("arelle - Error"), _("Failed to load {0}\n{1}").format( self.filename, err), parent=self.parent) - + def quit(self, event=None, restartAfterQuit=False): if self.okayToContinue(): self.modelManager.close() @@ -1100,10 +1100,10 @@ def quit(self, event=None, restartAfterQuit=False): if self.logFile: self.logFile.close() self.logFile = None - + def restart(self, event=None): self.quit(event, restartAfterQuit=True) - + def setWorkOffline(self, *args): self.webCache.workOffline = self.workOffline.get() self.config["workOffline"] = self.webCache.workOffline @@ -1125,18 +1125,18 @@ def setNoCertificateCheck(self, *args): self.webCache.noCertificateCheck = self.noCertificateCheck.get() # resets proxy handlers self.config["noCertificateCheck"] = self.webCache.noCertificateCheck self.saveConfig() - + def confirmClearWebCache(self): if tkinter.messagebox.askyesno( _("arelle - Clear Internet Cache"), - _("Are you sure you want to clear the internet cache?"), + _("Are you sure you want to clear the internet cache?"), parent=self.parent): def backgroundClearCache(): self.showStatus(_("Clearing internet cache")) self.webCache.clear() self.showStatus(_("Internet cache cleared"), 5000) thread = threading.Thread(target=backgroundClearCache, daemon=True).start() - + def manageWebCache(self): if sys.platform.startswith("win"): command = 'explorer' @@ -1148,7 +1148,7 @@ def manageWebCache(self): subprocess.Popen([command,self.webCache.cacheDir]) except: pass - + def setupProxy(self): from arelle.DialogUserPassword import askProxy proxySettings = askProxy(self.parent, self.config.get("proxySettings")) @@ -1156,7 +1156,7 @@ def setupProxy(self): self.webCache.resetProxies(proxySettings) self.config["proxySettings"] = proxySettings self.saveConfig() - + def setupUserAgent(self): httpUserAgent = tkinter.simpledialog.askstring( _("HTTP header User-Agent value"), @@ -1169,7 +1169,7 @@ def setupUserAgent(self): else: self.config["httpUserAgent"] = httpUserAgent self.saveConfig() - + def setValidateDisclosureSystem(self, *args): self.modelManager.validateDisclosureSystem = self.validateDisclosureSystem.get() self.config["validateDisclosureSystem"] = self.modelManager.validateDisclosureSystem @@ -1178,21 +1178,21 @@ def setValidateDisclosureSystem(self, *args): if not self.modelManager.disclosureSystem or not self.modelManager.disclosureSystem.selection: self.selectDisclosureSystem() self.setValidateTooltipText() - + def selectDisclosureSystem(self, *args): from arelle import DialogOpenArchive self.config["disclosureSystem"] = DialogOpenArchive.selectDisclosureSystem(self, self.modelManager.disclosureSystem) self.saveConfig() self.setValidateTooltipText() - + def formulaParametersDialog(self, *args): DialogFormulaParameters.getParameters(self) self.setValidateTooltipText() - + def rssWatchOptionsDialog(self, *args): from arelle import DialogRssWatch DialogRssWatch.getOptions(self) - + # find or open rssWatch view def rssWatchControl(self, start=False, stop=False, close=False): from arelle.ModelDocument import Type @@ -1207,7 +1207,7 @@ def rssWatchControl(self, start=False, stop=False, close=False): if (loadedModelXbrl.modelDocument.type == Type.RSSFEED and loadedModelXbrl.modelDocument.uri == self.modelManager.rssWatchOptions.get("feedSourceUri")): rssModelXbrl = loadedModelXbrl - break + break #not loaded if start: if not rssModelXbrl: @@ -1223,14 +1223,14 @@ def rssWatchControl(self, start=False, stop=False, close=False): # for ui thread option updating def rssWatchUpdateOption(self, latestPubDate=None): self.uiThreadQueue.put((self.uiRssWatchUpdateOption, [latestPubDate])) - + # ui thread addToLog - def uiRssWatchUpdateOption(self, latestPubDate): + def uiRssWatchUpdateOption(self, latestPubDate): if latestPubDate: self.modelManager.rssWatchOptions["latestPubDate"] = latestPubDate self.config["rssWatchOptions"] = self.modelManager.rssWatchOptions self.saveConfig() - + def languagesDialog(self, *args): override = self.lang if self.lang != self.modelManager.defaultLang else "" import tkinter.simpledialog @@ -1250,7 +1250,7 @@ def languagesDialog(self, *args): if self.modelManager.modelXbrl and self.modelManager.modelXbrl.modelDocument: self.showLoadedXbrl(self.modelManager.modelXbrl, True) # reload views self.saveConfig() - + def setValidateTooltipText(self): if self.modelManager.modelXbrl and not self.modelManager.modelXbrl.isClosed and self.modelManager.modelXbrl.modelDocument is not None: valType = self.modelManager.modelXbrl.modelDocument.type @@ -1282,44 +1282,44 @@ def setValidateTooltipText(self): else: v = _("Validate") self.validateTooltipText.set(v) - + def setValidateCalcLB(self, *args): self.modelManager.validateCalcLB = self.validateCalcLB.get() self.config["validateCalcLB"] = self.modelManager.validateCalcLB self.saveConfig() self.setValidateTooltipText() - + def setValidateInferDecimals(self, *args): self.modelManager.validateInferDecimals = self.validateInferDecimals.get() self.config["validateInferDecimals"] = self.modelManager.validateInferDecimals self.saveConfig() self.setValidateTooltipText() - + def setValidateDedupCalcs(self, *args): self.modelManager.validateDedupCalcs = self.validateDedupCalcs.get() self.config["validateDedupCalcs"] = self.modelManager.validateDedupCalcs self.saveConfig() self.setValidateTooltipText() - + def setValidateUtr(self, *args): self.modelManager.validateUtr = self.validateUtr.get() self.config["validateUtr"] = self.modelManager.validateUtr self.saveConfig() self.setValidateTooltipText() - + def setCollectProfileStats(self, *args): self.modelManager.collectProfileStats = self.collectProfileStats.get() self.config["collectProfileStats"] = self.modelManager.collectProfileStats self.saveConfig() - + def setShowDebugMessages(self, *args): self.config["showDebugMessages"] = self.showDebugMessages.get() self.saveConfig() - + def find(self, *args): from arelle.DialogFind import find find(self) - + def helpAbout(self, event=None): from arelle import DialogAbout, Version from lxml import etree @@ -1354,7 +1354,7 @@ def helpAbout(self, event=None): platform.machine() )) - # worker threads addToLog + # worker threads addToLog def addToLog(self, message, messageCode="", messageArgs=None, file="", refs=[], level=logging.INFO): if level < logging.INFO and not self.showDebugMessages.get(): return # skip DEBUG and INFO-RESULT messages @@ -1373,20 +1373,20 @@ def addToLog(self, message, messageCode="", messageArgs=None, file="", refs=[], except (KeyError, TypeError, ValueError) as ex: message += " \nMessage log error: " + str(ex) + " \nMessage arguments: " + str(messageArgs) self.uiThreadQueue.put((self.uiAddToLog, [message])) - + # ui thread addToLog def uiAddToLog(self, message): try: self.logView.append(message) except: pass - + def logClear(self, *ignore): self.logView.clear() - + def logSelect(self, *ignore): self.logView.select() - + def logSaveToFile(self, *ignore): filename = self.uiFileDialog("save", title=_("arelle - Save Messages Log"), @@ -1403,43 +1403,43 @@ def logSaveToFile(self, *ignore): filename, err), parent=self.parent) return True; - - # worker threads viewModelObject + + # worker threads viewModelObject def viewModelObject(self, modelXbrl, objectId): self.waitForUiThreadQueue() # force prior ui view updates if any self.uiThreadQueue.put((self.uiViewModelObject, [modelXbrl, objectId])) - + # ui thread viewModelObject def uiViewModelObject(self, modelXbrl, objectId): modelXbrl.viewModelObject(objectId) - # worker threads viewModelObject + # worker threads viewModelObject def reloadViews(self, modelXbrl): self.uiThreadQueue.put((self.uiReloadViews, [modelXbrl])) - + # ui thread viewModelObject def uiReloadViews(self, modelXbrl): for view in modelXbrl.views: view.view() - # worker threads showStatus + # worker threads showStatus def showStatus(self, message, clearAfter=None): self.uiThreadQueue.put((self.uiShowStatus, [message, clearAfter])) - + # ui thread showStatus def uiClearStatusTimerEvent(self): if self.statusbarTimerId: # if timer still wanted, clear status self.statusbar["text"] = "" self.statusbarTimerId = None - + def uiShowStatus(self, message, clearAfter=None): if self.statusbarTimerId: # ignore timer self.statusbarTimerId = None self.statusbar["text"] = message if clearAfter is not None and clearAfter > 0: self.statusbarTimerId = self.statusbar.after(clearAfter, self.uiClearStatusTimerEvent) - + # web authentication password request def internet_user_password(self, host, realm): from arelle.DialogUserPassword import askUserPassword @@ -1448,7 +1448,7 @@ def internet_user_password(self, host, realm): self.uiThreadQueue.put((askUserPassword, [self.parent, host, realm, untilDone, result])) untilDone.wait() return result[0] - + # web file login requested def internet_logon(self, url, quotedUrl, dialogCaption, dialogText): from arelle.DialogUserPassword import askInternetLogon @@ -1457,7 +1457,7 @@ def internet_logon(self, url, quotedUrl, dialogCaption, dialogText): self.uiThreadQueue.put((askInternetLogon, [self.parent, url, quotedUrl, dialogCaption, dialogText, untilDone, result])) untilDone.wait() return result[0] - + def waitForUiThreadQueue(self): for i in range(40): # max 2 secs if self.uiThreadQueue.empty(): @@ -1474,7 +1474,7 @@ def uiThreadChecker(self, widget, delayMsecs=100): # 10x per second else: callback(*args) widget.after(delayMsecs, lambda: self.uiThreadChecker(widget)) - + def uiFileDialog(self, action, title=None, initialdir=None, filetypes=[], defaultextension=None, owner=None, multiple=False, parent=None): if parent is None: parent = self.parent if multiple and action == "open": # return as simple list of file names @@ -1494,7 +1494,7 @@ def uiFileDialog(self, action, title=None, initialdir=None, filetypes=[], defaul try: filename, filter, flags = {"open":win32gui.GetOpenFileNameW, "save":win32gui.GetSaveFileNameW}[action]( - hwndOwner=(owner if owner else parent).winfo_id(), + hwndOwner=(owner if owner else parent).winfo_id(), hInstance=win32gui.GetModuleHandle(None), Filter='\0'.join(e for t in filetypes+['\0'] for e in t), MaxFile=4096, @@ -1534,13 +1534,13 @@ def emit(self, logRecord): if self.logRecordBuffer is not None: self.logRecordBuffer.append(logRecord) # add to logView - msg = self.format(logRecord) - try: + msg = self.format(logRecord) + try: self.cntlr.addToLog(msg, level=logRecord.levelno) except: pass -class TkinterCallWrapper: +class TkinterCallWrapper: """Replacement for internal tkinter class. Stores function to call when some user defined Tcl function is called e.g. after an event occurred.""" def __init__(self, func, subst, widget): @@ -1561,9 +1561,9 @@ def __call__(self, *args): exc_type, exc_value, exc_traceback = sys.exc_info() msg = ''.join(traceback.format_exception_only(exc_type, exc_value)) tracebk = ''.join(traceback.format_tb(exc_traceback, limit=30)) - tkinter.messagebox.showerror(_("Exception"), + tkinter.messagebox.showerror(_("Exception"), _("{0}\nCall trace\n{1}").format(msg, tracebk)) - + def main(): @@ -1576,7 +1576,7 @@ def main(): _d = _resourcesDir while len(_d) > 3: # stop at root directory _tcltkDir = os.path.join(_d, _tcltk + _tcltkVer) - if os.path.exists(_tcltkDir): + if os.path.exists(_tcltkDir): os.environ[_tcltk.upper() + "_LIBRARY"] = _tcltkDir break _d = os.path.dirname(_d) @@ -1590,7 +1590,7 @@ def close(self): pass sys.stdout = dummyFrozenStream() sys.stderr = dummyFrozenStream() sys.stdin = dummyFrozenStream() - + global restartMain while restartMain: restartMain = False @@ -1604,7 +1604,7 @@ def close(self): pass application.call('wm', 'attributes', '.', '-topmost', True) cntlrWinMain.uiThreadQueue.put((application.call, ['wm', 'attributes', '.', '-topmost', False])) os.system('''/usr/bin/osascript -e 'tell app "Finder" to set frontmost of process "Python" to true' ''') - application.mainloop() + application.mainloop() except Exception: # unable to start Tk or other fatal error exc_type, exc_value, exc_traceback = sys.exc_info() msg = ''.join(traceback.format_exception_only(exc_type, exc_value)) diff --git a/arelle/CntlrWinTooltip.py b/arelle/CntlrWinTooltip.py index a8ac5dbeb2..97ff653710 100644 --- a/arelle/CntlrWinTooltip.py +++ b/arelle/CntlrWinTooltip.py @@ -56,7 +56,7 @@ def __init__(self, master, text='Your text here', delay=500, **opts): if self._opts['follow_mouse']: self._id4 = self.master.bind("", self.motion, '+') self._follow_mouse = 1 - + def configure(self, **opts): for key in opts: if key in self._opts: @@ -64,24 +64,24 @@ def configure(self, **opts): else: KeyError = 'KeyError: Unknown option: "%s"' %key raise KeyError - + ##----these methods handle the callbacks on "", "" and ""---------------## ##----events on the parent widget; override them if you want to change the widget's behavior--## - + def enter(self, event=None): self._schedule() - + def leave(self, event=None): self._unschedule() self._hide() - + def motion(self, event=None): if self._tipwindow and self._follow_mouse: x, y = self.coords() self._tipwindow.wm_geometry("+%d+%d" % (x, y)) - + ##------the methods that do the work:---------------------------------------------------------## - + def _schedule(self): self._unschedule() if self._opts['state'] == 'disabled': @@ -112,15 +112,15 @@ def _show(self): x, y = self.coords() tw.wm_geometry("+%d+%d" % (x, y)) tw.deiconify() - + def _hide(self): tw = self._tipwindow self._tipwindow = None if tw: tw.destroy() - + ##----these methods might be overridden in derived classes:----------------------------------## - + def coords(self): # The tip window must be completely outside the master widget; # otherwise when the mouse enters the tip window we get @@ -170,4 +170,4 @@ def demo(): root.mainloop() if __name__ == '__main__': - demo() + demo() diff --git a/arelle/DialogAbout.py b/arelle/DialogAbout.py index 4b32cb5f7c..aac2a3b97d 100644 --- a/arelle/DialogAbout.py +++ b/arelle/DialogAbout.py @@ -30,7 +30,7 @@ def __init__(self, parent, title, imageFile, body): dialogY = int(parentGeometry.group(4)) self.transient(self.parent) self.title(title) - + frame = Frame(self) image = PhotoImage(file=imageFile) aboutImage = Label(frame, image=image) @@ -40,24 +40,24 @@ def __init__(self, parent, title, imageFile, body): aboutImage.grid(row=0, column=0, sticky=NW, pady=20, padx=16) aboutBody.grid(row=0, column=1, columnspan=2, sticky=EW, pady=3, padx=0) okButton.grid(row=1, column=2, sticky=EW, pady=3) - + frame.grid(row=0, column=0, sticky=(N,S,E,W)) frame.columnconfigure(1, weight=1) window = self.winfo_toplevel() window.columnconfigure(0, weight=1) self.geometry("+{0}+{1}".format(dialogX+200,dialogY+200)) - + self.bind("", lambda *ignore: okButton.focus_set()) self.bind("", self.ok) self.bind("", self.close) - + self.protocol("WM_DELETE_WINDOW", self.close) self.grab_set() self.wait_window(self) - + def ok(self, event=None): self.close() - + def close(self, event=None): self.parent.focus_set() - self.destroy() \ No newline at end of file + self.destroy() diff --git a/arelle/DialogArcroleGroup.py b/arelle/DialogArcroleGroup.py index cc9e7e8f45..f5841ec6b8 100644 --- a/arelle/DialogArcroleGroup.py +++ b/arelle/DialogArcroleGroup.py @@ -21,7 +21,7 @@ def getArcroleGroup(mainWin, modelXbrl): dialog = DialogArcroleGroup(mainWin, modelXbrl) return dialog.selectedGroup - + class DialogArcroleGroup(Toplevel): def __init__(self, mainWin, modelXbrl): parent = mainWin.parent @@ -36,7 +36,7 @@ def __init__(self, mainWin, modelXbrl): self.transient(self.parent) self.title(_("Select Arcrole Group")) - + frame = Frame(self) ''' @@ -45,9 +45,9 @@ def __init__(self, mainWin, modelXbrl): dialogFrame.rowconfigure(0, weight=1) dialogFrame.grid(row=0, column=0, columnspan=4, sticky=(N, S, E, W), padx=3, pady=3) ''' - + # mainWin.showStatus(_("loading formula options and parameters")) - + # load grid groupLabel = label(frame, 1, 0, _("Group:")) self.arcroleGroups = mainWin.config.get("arcroleGroups", {}) @@ -57,16 +57,16 @@ def __init__(self, mainWin, modelXbrl): else: arcroleGroup = [] arcroleGroupSelected = None - self.groupName = gridCombobox(frame, 2, 0, + self.groupName = gridCombobox(frame, 2, 0, value=arcroleGroupSelected, - values=sorted(self.arcroleGroups.keys()), + values=sorted(self.arcroleGroups.keys()), comboboxselected=self.comboBoxSelected) groupToolTipMessage = _("Select an existing arcrole group, or enter a name for a new arcrole group. " "If selecting an existing group, it can be edited, and changes will be saved in the config file. " "If nothing is changed for an existing group, the saved setting is not disturbed. " "Arcroles with checkboxes below are shown only for arcroles that have relationships in the loaded DTS, " "but if an existing group is selected with more arcroles (that were not in the current DTS) then " - "the prior setting with not-present arcroles is preserved. ") + "the prior setting with not-present arcroles is preserved. ") ToolTip(self.groupName, text=groupToolTipMessage, wraplength=360) ToolTip(groupLabel, text=groupToolTipMessage, wraplength=360) clearImage = PhotoImage(file=os.path.join(mainWin.imagesDir, "toolbarDelete.gif")) @@ -74,10 +74,10 @@ def __init__(self, mainWin, modelXbrl): clearGroupNameButton.grid(row=0, column=3, sticky=W) ToolTip(clearGroupNameButton, text=_("Remove the currently selected arcrole group from the config file. " "After removing, you may select another arcrole, but must select 'OK' for the " - "removal to be saved. "), + "removal to be saved. "), wraplength=240) arcrolesLabel = label(frame, 1, 1, _("Arcroles:")) - ToolTip(arcrolesLabel, text=_("Shows all the arcroles that are present in this DTS. "), + ToolTip(arcrolesLabel, text=_("Shows all the arcroles that are present in this DTS. "), wraplength=240) from arelle.ModelRelationshipSet import baseSetArcroles self.options = {} @@ -86,32 +86,32 @@ def __init__(self, mainWin, modelXbrl): for name, arcrole in baseSetArcroles(self.modelXbrl): if arcrole.startswith("http://"): self.options[arcrole] = arcrole in arcroleGroup - self.checkboxes.append( - checkbox(frame, 2, y, - name[1:], - arcrole, + self.checkboxes.append( + checkbox(frame, 2, y, + name[1:], + arcrole, columnspan=2) ) y += 1 - + mainWin.showStatus(None) self.options[XbrlConst.arcroleGroupDetect] = XbrlConst.arcroleGroupDetect in arcroleGroup self.autoOpen = checkbox(frame, 1, y, _("detect"), XbrlConst.arcroleGroupDetect) self.autoOpen.grid(sticky=W, columnspan=2) self.checkboxes.append(self.autoOpen) - ToolTip(self.autoOpen, text=_("If checked, this arcrole group will be detected if any arcrole of the group is present in a DTS, for example to open a treeview pane. "), + ToolTip(self.autoOpen, text=_("If checked, this arcrole group will be detected if any arcrole of the group is present in a DTS, for example to open a treeview pane. "), wraplength=240) okButton = Button(frame, text=_("OK"), width=8, command=self.ok) cancelButton = Button(frame, text=_("Cancel"), width=8, command=self.close) cancelButton.grid(row=y, column=1, sticky=E, columnspan=3, pady=3, padx=3) okButton.grid(row=y, column=1, sticky=E, columnspan=3, pady=3, padx=64) ToolTip(okButton, text=_("Open a treeview with named arcrole group and selected arcroles. " - "If any changes were made to checkboxes or name, save in the config. "), + "If any changes were made to checkboxes or name, save in the config. "), wraplength=240) - ToolTip(cancelButton, text=_("Close this dialog, without saving arcrole group changes or opening a view pane. "), + ToolTip(cancelButton, text=_("Close this dialog, without saving arcrole group changes or opening a view pane. "), wraplength=240) - + frame.grid(row=0, column=0, sticky=(N,S,E,W)) frame.columnconfigure(1, weight=3) frame.columnconfigure(2, weight=1) @@ -119,14 +119,14 @@ def __init__(self, mainWin, modelXbrl): window = self.winfo_toplevel() window.columnconfigure(0, weight=1) self.geometry("+{0}+{1}".format(dialogX+50,dialogY+100)) - + #self.bind("", self.ok) #self.bind("", self.close) - + self.protocol("WM_DELETE_WINDOW", self.close) self.grab_set() self.wait_window(self) - + def ok(self, event=None): groupName = self.groupName.value arcrolesSelected = [checkbox.attr for checkbox in self.checkboxes if checkbox.value] @@ -138,7 +138,7 @@ def ok(self, event=None): self.mainWin.saveConfig() self.selectedGroup = (groupName, arcrolesSelected) self.close() - + def close(self, event=None): self.parent.focus_set() self.destroy() @@ -148,7 +148,7 @@ def comboBoxSelected(self, *args): for checkbox in self.checkboxes: checkbox.valueVar.set( checkbox.attr in arcroles ) checkbox.isChanged = False - + def clearGroupName(self): groupName = self.groupName.value if groupName and groupName in self.arcroleGroups: diff --git a/arelle/DialogFind.py b/arelle/DialogFind.py index cb3e94d402..de4b0f9797 100644 --- a/arelle/DialogFind.py +++ b/arelle/DialogFind.py @@ -26,7 +26,7 @@ ''' reMetaChars = '[]\\^$.|?*+(){}' - + newFindOptions = { "direction": "down", "exprType": "text", @@ -49,7 +49,7 @@ def find(mainWin): dialog = DialogFind(mainWin, mainWin.config.setdefault("findOptions", newFindOptions)) - + class DialogFind(Toplevel): def __init__(self, mainWin, options): parent = mainWin.parent @@ -68,9 +68,9 @@ def __init__(self, mainWin, options): self.transient(self.parent) self.title(_("Find")) - + self.objsList = [] # next may be tried before anything is found - + frame = Frame(self) # load grid @@ -81,7 +81,7 @@ def __init__(self, mainWin, options): ToolTip(self.cbExpr, text=_("Enter expression to find, or select from combo box drop down history list."), wraplength=240) y = 2 - + # checkbox entries label(frame, 1, y, "Direction:") label(frame, 1, y + 3, "Match:") @@ -98,7 +98,7 @@ def __init__(self, mainWin, options): rbRegex = radiobutton(frame, 1, y+5, "Regular expression", "regex", "exprType", rbText.valueVar) ToolTip(rbRegex, text=_('A regular expression to match, anywhere in the scope, ignoring case. ' 'For example, "cash" would match cash anywhere in a string (like cash on hand), ' - 'whereas "^cash$" would match a full string to only contain cash. ' + 'whereas "^cash$" would match a full string to only contain cash. ' 'Use regular expression metacharacters, e.g., "." for any single character, ' '".*" for any number of wild characters, .{3} for exactly 3 wild characters. '), wraplength=360) rbXPath = radiobutton(frame, 1, y+6, "XPath 2 expression", "xpath", "exprType", rbText.valueVar) @@ -125,9 +125,9 @@ def __init__(self, mainWin, options): checkbox(frame, 3, y + 4, " context", "factCntx"), checkbox(frame, 3, y + 5, " unit", "factUnit"), checkbox(frame, 3, y + 6, "Messages", "messagesLog"), - + # Note: if adding to this list keep Finder.FindOptions in sync - + ) y += 7 resultLabel = gridHdr(frame, 1, y, "Result:", anchor="w") @@ -136,7 +136,7 @@ def __init__(self, mainWin, options): self.resultText.grid(columnspan=3, padx=8) self.resultText.config(state="readonly") y += 2 - + mainWin.showStatus(None) buttonFrame = Frame(frame) @@ -150,7 +150,7 @@ def __init__(self, mainWin, options): findButton.grid(row=1, column=1, pady=3) nextButton.grid(row=1, column=2, pady=3) closeButton.grid(row=1, column=3, padx=3) - + frame.grid(row=0, column=0, sticky=(N,S,E,W)) frame.columnconfigure(1, weight=1) frame.columnconfigure(2, weight=1) @@ -161,27 +161,27 @@ def __init__(self, mainWin, options): self.geometry(self.options["geometry"]) else: self.geometry("+{0}+{1}".format(dialogX+50,dialogY+100)) - + #self.bind("", self.ok) #self.bind("", self.close) - + self.protocol("WM_DELETE_WINDOW", self.close) - + # make this dialog non-modal self.focus_set() #self.grab_set() #self.wait_window(self) - + def setOptions(self): # set formula options for optionControl in self.optionControls: self.options[optionControl.attr] = optionControl.value - + def find(self, event=None): self.setOptions() self.accepted = True # self.close() - + docType = self.modelManager.modelXbrl.modelDocument.type if self.modelManager.modelXbrl else None if self.options["messagesLog"]: if docType == ModelDocument.Type.RSSFEED and self.options["exprType"] == "xpath": @@ -195,12 +195,12 @@ def find(self, event=None): messagebox.showerror(_("Find cannot be completed"), _("Find requires an opened DTS or RSS Feed"), parent=self.parent) return - + if docType == ModelDocument.Type.RSSFEED and self.options["exprType"] == "xpath": messagebox.showerror(_("Find cannot be completed"), _("XPath matching is not available for an RSS Feed, please choose text or regular expression. "), parent=self) return - + self.modelXbrl = self.modelManager.modelXbrl expr = self.cbExpr.value # update find expressions history @@ -211,7 +211,7 @@ def find(self, event=None): self.options["priorExpressions"].insert(0, expr) self.cbExpr.config(values=self.options["priorExpressions"]) self.saveConfig() - + import threading thread = threading.Thread(target=lambda expr=self.cbExpr.value, @@ -236,17 +236,17 @@ def backgroundFind(self, expr, logViewLines): inFactUnit = self.options["factUnit"] inMessagesLog = self.options["messagesLog"] nextIsDown = self.options["direction"] == "down" - + objsFound = set() - + self.result = "Found " - + try: if exprType == "text": # escape regex metacharacters pattern = re.compile(''.join( - [(('\\' + c) if c in reMetaChars else c) for c in expr]), + [(('\\' + c) if c in reMetaChars else c) for c in expr]), re.IGNORECASE) isRE = True isXP = False @@ -260,16 +260,16 @@ def backgroundFind(self, expr, logViewLines): self.resultText.setValue(_("Compiling xpath expression...")) XPathParser.initializeParser(self.modelManager) self.modelManager.showStatus(_("Compiling xpath expression...")) - xpProg= XPathParser.parse(self, - expr, - XPathParser.staticExpressionFunctionContext(), - "find expression", + xpProg= XPathParser.parse(self, + expr, + XPathParser.staticExpressionFunctionContext(), + "find expression", Trace.CALL) xpCtx = XPathContext.create(self.modelXbrl, sourceElement=None) else: return # nothing to do - + if inMessagesLog: for lineNumber, line in enumerate(logViewLines): if pattern.search(line): @@ -277,7 +277,7 @@ def backgroundFind(self, expr, logViewLines): elif self.modelXbrl.modelDocument.type == ModelDocument.Type.RSSFEED: for rssItem in self.modelXbrl.modelDocument.items: if any(pattern.search(str(value)) for name, value in rssItem.propertyView): - objsFound.add(rssItem) + objsFound.add(rssItem) else: # DTS search if inConceptLabel or inConceptName or inConceptType or inConceptSubs or inConceptPer or inConceptBal: self.modelManager.cntlr.uiThreadQueue.put((self.resultText.setValue, [_("Matching concepts...")])) @@ -294,7 +294,7 @@ def backgroundFind(self, expr, logViewLines): (inConceptBal and concept.balance and pattern.search(concept.balance)) ) ): - objsFound.add(concept) + objsFound.add(concept) if inFactLabel or inFactName or inFactValue or inFactCntx or inFactUnit: self.modelManager.cntlr.uiThreadQueue.put((self.resultText.setValue, [_("Matching facts...")])) self.modelManager.showStatus(_("Matching facts...")) @@ -311,13 +311,13 @@ def backgroundFind(self, expr, logViewLines): objsFound.add(fact) except (XPathContext.XPathException, TypeError, ValueError, OverflowError, IndexError, KeyError, re.error) as err: err = _("Find expression error: {0} \n{1}").format( - str(err), + str(err), getattr(err, "sourceErrorIndication",getattr(err, "pattern",""))) self.modelManager.addToLog(err) self.modelManager.cntlr.uiThreadQueue.put((self.resultText.setValue, [err])) self.modelManager.showStatus(_("Completed with errors"), 5000) self.result = err + "\n" - + numConcepts = 0 numFacts = 0 numRssItems = 0 @@ -354,7 +354,7 @@ def backgroundFind(self, expr, logViewLines): self.foundIndex = 0 if nextIsDown else (len(self.objsList) - 1) self.modelManager.cntlr.uiThreadQueue.put((self.next, [])) self.modelManager.showStatus(_("Ready..."), 2000) - + def next(self): self.setOptions() # refresh options nextIsDown = self.options["direction"] == "down" @@ -371,7 +371,7 @@ def next(self): messagebox.showwarning(_("Next cannot be completed"), _("No matches were found. Please try a different search."), parent=self) return - + if self.foundIndex < 0 and nextIsDown: self.foundIndex += 1 elif self.foundIndex >= lenObjsList and not nextIsDown: @@ -395,7 +395,7 @@ def close(self, event=None): self.saveConfig() self.parent.focus_set() self.destroy() - + def saveConfig(self): self.modelManager.cntlr.config["findOptions"] = self.options self.modelManager.cntlr.saveConfig() diff --git a/arelle/DialogFormulaParameters.py b/arelle/DialogFormulaParameters.py index e84a5c97e3..e7db235e83 100644 --- a/arelle/DialogFormulaParameters.py +++ b/arelle/DialogFormulaParameters.py @@ -25,7 +25,7 @@ def getParameters(mainWin): mainWin.config["formulaParameters"] = dialog.options mainWin.saveConfig() - + class DialogFormulaParameters(Toplevel): def __init__(self, mainWin, options): parent = mainWin.parent @@ -40,7 +40,7 @@ def __init__(self, mainWin, options): self.transient(self.parent) self.title(_("Formula Parameters and Trace Options")) - + frame = Frame(self) ''' @@ -49,15 +49,15 @@ def __init__(self, mainWin, options): dialogFrame.rowconfigure(0, weight=1) dialogFrame.grid(row=0, column=0, columnspan=4, sticky=(N, S, E, W), padx=3, pady=3) ''' - + # mainWin.showStatus(_("loading formula options and parameters")) - + # load grid gridHdr(frame, 1, 0, "Parameters", columnspan=3) gridHdr(frame, 1, 1, "QName") gridHdr(frame, 2, 1, "Type") gridHdr(frame, 3, 1, "Value") - + self.gridCells = [] y = 2 dataTypes = ("xs:string", "xs:integer", "xs:decimal", "xs:boolean", "xs:date", "xs:datetime", "xs:QName") @@ -81,7 +81,7 @@ def __init__(self, mainWin, options): gridCell(frame, 3, y)) ) y += 1 y += 1 - + # checkbox entries label(frame, 1, y, "Parameter Trace:") label(frame, 1, y + 3, "API Calls Trace:") @@ -89,91 +89,91 @@ def __init__(self, mainWin, options): label(frame, 2, y, "Variable Set Trace:") label(frame, 3, y, "Variables Trace:") self.checkboxes = ( - checkbox(frame, 1, y + 1, - "Expression Result", + checkbox(frame, 1, y + 1, + "Expression Result", "traceParameterExpressionResult"), checkbox(frame, 1, y + 2, - "Input Value", + "Input Value", "traceParameterInputValue"), - checkbox(frame, 1, y + 4, - "Expression Source", + checkbox(frame, 1, y + 4, + "Expression Source", "traceCallExpressionSource"), - checkbox(frame, 1, y + 5, - "Expression Code", + checkbox(frame, 1, y + 5, + "Expression Code", "traceCallExpressionCode"), - checkbox(frame, 1, y + 6, - "Expression Evaluation", + checkbox(frame, 1, y + 6, + "Expression Evaluation", "traceCallExpressionEvaluation"), checkbox(frame, 1, y + 7, - "Expression Result", + "Expression Result", "traceCallExpressionResult"), checkbox(frame, 1, y + 9, - "Capture Warnings", + "Capture Warnings", "testcaseResultsCaptureWarnings"), gridCombobox(frame, 1, y + 10, padx=24, - attr="testcaseResultOptions", + attr="testcaseResultOptions", values=("match-any", "match-all")), - checkbox(frame, 2, y + 1, - "Expression Source", + checkbox(frame, 2, y + 1, + "Expression Source", "traceVariableSetExpressionSource"), - checkbox(frame, 2, y + 2, - "Expression Code", + checkbox(frame, 2, y + 2, + "Expression Code", "traceVariableSetExpressionCode"), - checkbox(frame, 2, y + 3, - "Expression Evaluation", + checkbox(frame, 2, y + 3, + "Expression Evaluation", "traceVariableSetExpressionEvaluation"), checkbox(frame, 2, y + 4, - "Expression Result", + "Expression Result", "traceVariableSetExpressionResult"), checkbox(frame, 2, y + 5, - "Assertion Result Counts", + "Assertion Result Counts", "traceAssertionResultCounts"), checkbox(frame, 2, y + 6, - "Assertion Satisfied [info]", + "Assertion Satisfied [info]", "traceSatisfiedAssertions"), checkbox(frame, 2, y + 7, - "Assertion Unsatisfied [error]", + "Assertion Unsatisfied [error]", "errorUnsatisfiedAssertions"), checkbox(frame, 2, y + 8, - "Assertion Unsatisfied [info]", + "Assertion Unsatisfied [info]", "traceUnsatisfiedAssertions"), checkbox(frame, 2, y + 9, - "Formula Rules", + "Formula Rules", "traceFormulaRules"), checkbox(frame, 2, y + 10, - "Evaluation Timing", + "Evaluation Timing", "timeVariableSetEvaluation"), - checkbox(frame, 3, y + 1, - "Variable Dependencies", + checkbox(frame, 3, y + 1, + "Variable Dependencies", "traceVariablesDependencies"), - checkbox(frame, 3, y + 2, - "Variables Order", + checkbox(frame, 3, y + 2, + "Variables Order", "traceVariablesOrder"), - checkbox(frame, 3, y + 3, - "Expression Source", + checkbox(frame, 3, y + 3, + "Expression Source", "traceVariableExpressionSource"), - checkbox(frame, 3, y + 4, - "Expression Code", + checkbox(frame, 3, y + 4, + "Expression Code", "traceVariableExpressionCode"), - checkbox(frame, 3, y + 5, - "Expression Evaluation", + checkbox(frame, 3, y + 5, + "Expression Evaluation", "traceVariableExpressionEvaluation"), - checkbox(frame, 3, y + 6, - "Expression Result", + checkbox(frame, 3, y + 6, + "Expression Result", "traceVariableExpressionResult"), - checkbox(frame, 3, y + 7, - "Filter Winnowing", + checkbox(frame, 3, y + 7, + "Filter Winnowing", "traceVariableFilterWinnowing"), - checkbox(frame, 3, y + 8, - "Filters Result", + checkbox(frame, 3, y + 8, + "Filters Result", "traceVariableFiltersResult") - + # Note: if adding to this list keep ModelFormulaObject.FormulaOptions in sync - + ) y += 11 - + mainWin.showStatus(None) label(frame, 1, y, "IDs:") @@ -184,7 +184,7 @@ def __init__(self, mainWin, options): cancelButton = Button(frame, text=_("Cancel"), width=_w, command=self.close) okButton.grid(row=y, column=3, sticky=W, pady=3) cancelButton.grid(row=y, column=3, sticky=E, pady=3, padx=3) - + frame.grid(row=0, column=0, sticky=(N,S,E,W)) frame.columnconfigure(1, weight=3) frame.columnconfigure(2, weight=1) @@ -192,14 +192,14 @@ def __init__(self, mainWin, options): window = self.winfo_toplevel() window.columnconfigure(0, weight=1) self.geometry("+{0}+{1}".format(dialogX+50,dialogY+100)) - + #self.bind("", self.ok) #self.bind("", self.close) - + self.protocol("WM_DELETE_WINDOW", self.close) self.grab_set() self.wait_window(self) - + def setOptions(self): # set formula options for checkbox in self.checkboxes: @@ -212,13 +212,13 @@ def setOptions(self): parameterValues[qnameCell.value] = (typeCell.value, valueCell.value) self.options["parameterValues"] = parameterValues self.options["runIDs"] = self.idsEntry.value - + def ok(self, event=None): self.setOptions() self.accepted = True self.close() - + def close(self, event=None): self.parent.focus_set() self.destroy() - + diff --git a/arelle/DialogLanguage.py b/arelle/DialogLanguage.py index 08fa916720..9e0132324d 100644 --- a/arelle/DialogLanguage.py +++ b/arelle/DialogLanguage.py @@ -39,7 +39,7 @@ def __init__(self, mainWin): self.languageCodes = languageCodes() langs = (["System default language ({0})".format(mainWin.modelManager.defaultLang)] + sorted(self.languageCodes.keys() if self.mainWin.isMSW else - [k + [k for avail in [availableLocales()] # unix/Mac locale -a supported locale codes for k, v in self.languageCodes.items() if v.partition(" ")[0] in avail] @@ -62,9 +62,9 @@ def __init__(self, mainWin): if i > 0 and self.labelLang in self.languageCodes[langName]: self.labelLangIndex = i break - + frame = Frame(self) - + defaultLanguage = mainWin.modelManager.defaultLang for langName, langCodes in self.languageCodes.items(): if mainWin.modelManager.defaultLang in langCodes: @@ -92,14 +92,14 @@ def __init__(self, mainWin): window = self.winfo_toplevel() window.columnconfigure(0, weight=1) self.geometry("+{0}+{1}".format(dialogX+50,dialogY+100)) - + self.bind("", self.ok) self.bind("", self.close) - + self.protocol("WM_DELETE_WINDOW", self.close) self.grab_set() self.wait_window(self) - + def ok(self, event=None): self.mainWin.disableRtl= self.cbDisableRtl.value self.mainWin.config['disableRtl']= self.cbDisableRtl.value @@ -122,14 +122,14 @@ def ok(self, event=None): langCode, sep, localeCode = self.languageCodes[self.cbUiLang.value].partition(" ") if not self.mainWin.isMSW: # Windows uses string language codes localeCode = langCode.replace("-", "_") + ".UTF-8" # Unix and Mac uses en_US.UTF-8 - + newLocale = getUserLocale(localeCode) if newLocale is not None: self.mainWin.modelManager.locale = newLocale else: - messagebox.showerror(_("User interface locale error"), + messagebox.showerror(_("User interface locale error"), _("Locale setting {0} ({1}) is not supported on this system") - .format(langCode, localeCode), + .format(langCode, localeCode), parent=self) return if localeCode != "": # not the system default @@ -139,20 +139,20 @@ def ok(self, event=None): self.mainWin.config.pop("userInterfaceLangOverride", None) self.mainWin.config.pop("userInterfaceLocaleOverride", None) self.mainWin.setUiLanguage(langCode) - + if messagebox.askyesno( - _("User interface language changed"), - _("Should Arelle restart with changed user interface language, if there are any unsaved changes they would be lost!"), + _("User interface language changed"), + _("Should Arelle restart with changed user interface language, if there are any unsaved changes they would be lost!"), parent=self): self.mainWin.uiThreadQueue.put((self.mainWin.quit, [None, True])) else: messagebox.showwarning( - _("User interface language changed"), - _("Please restart Arelle for the change in user interface language."), + _("User interface language changed"), + _("Please restart Arelle for the change in user interface language."), parent=self) self.close() - + def close(self, event=None): self.parent.focus_set() self.destroy() - + diff --git a/arelle/DialogNewFactItem.py b/arelle/DialogNewFactItem.py index b31a801cf8..5e5a981705 100644 --- a/arelle/DialogNewFactItem.py +++ b/arelle/DialogNewFactItem.py @@ -23,7 +23,7 @@ caller checks accepted, if True, caller retrieves url ''' def getNewFactItemOptions(mainWin, newInstanceOptions=None): - if newInstanceOptions is None: newInstanceOptions = NewFactItemOptions() + if newInstanceOptions is None: newInstanceOptions = NewFactItemOptions() # use prior prevOptionValues for those keys not in existing newInstanceOptions for prevOptionKey, prevOptionValue in mainWin.config.get("newFactItemOptions",{}).items(): if not getattr(newInstanceOptions, prevOptionKey, None): @@ -35,26 +35,26 @@ def getNewFactItemOptions(mainWin, newInstanceOptions=None): mainWin.saveConfig() return True return False - -monetaryUnits = ("AED", "AFN", "ALL", "AMD", "ANG", "AON", "ARP", "ATS", "AUD", "AWF", "AZM", - "AZN", "BAK", "BBD", "BDT", "BEF", "BGL", "BHD", "BIF", "BMD", "BND", "BOB", - "BRR", "BSD", "BTR", "BWP", "BYR", "BZD", "CAD", "CHF", "CLP", "CNY", "COP", - "CRC", "CSK", "CUP", "CVE", "CYP", "DEM", "DJF", "DKK", "DOP", "DZD", "ECS", - "EEK", "EGP", "ERN", "ESP", "ETB", "EUR", "FIM", "FJD", "FKP", "FRF", "GBP", - "GEL", "GHC", "GIP", "GMD", "GNF", "GRD", "GTQ", "GYD", "HKD", "HNL", "HRK", - "HTG", "HUF", "IDR", "IEP", "IEP", "ILS", "INR", "IQD", "IRR", "ISK", "ITL", - "JMD", "JOD", "JPY", "KES", "KGS", "KHR", "KMF", "KPW", "KRW", "KWD", "KYD", - "KZT", "LAK", "LBP", "LKR", "LRD", "LSL", "LTL", "LUF", "LVL", "LYD", "MAD", - "MDL", "MGF", "MKD", "MMK", "MNT", "MOP", "MRO", "MTL", "MUR", "MVR", "MWK", - "MXP", "MYR", "MZM", "NAD", "NGN", "NIO", "NLG", "NOK", "NPR", "NZD", "OMR", - "PAB", "PEN", "PGK", "PHP", "PKR", "PLN", "PTE", "PYG", "QAR", "ROL", "RSD", - "RUR", "RWF", "SAR", "SBD", "SBL", "SCR", "SDD", "SEK", "SGD", "SHP", "SIT", - "SKK", "SLL", "SOS", "SRG", "STD", "SVC", "SYP", "SZL", "THB", "TJR", "TMM", - "TND", "TOP", "TRL", "TTD", "TWD", "TZS", "UAH", "UGX", "USD", "UYU", "UZS", - "VEB", "VND", "VUV", "WST", "XAF", "XAG", "XAU", "XCD", "XDR", "XOF", "XPD", + +monetaryUnits = ("AED", "AFN", "ALL", "AMD", "ANG", "AON", "ARP", "ATS", "AUD", "AWF", "AZM", + "AZN", "BAK", "BBD", "BDT", "BEF", "BGL", "BHD", "BIF", "BMD", "BND", "BOB", + "BRR", "BSD", "BTR", "BWP", "BYR", "BZD", "CAD", "CHF", "CLP", "CNY", "COP", + "CRC", "CSK", "CUP", "CVE", "CYP", "DEM", "DJF", "DKK", "DOP", "DZD", "ECS", + "EEK", "EGP", "ERN", "ESP", "ETB", "EUR", "FIM", "FJD", "FKP", "FRF", "GBP", + "GEL", "GHC", "GIP", "GMD", "GNF", "GRD", "GTQ", "GYD", "HKD", "HNL", "HRK", + "HTG", "HUF", "IDR", "IEP", "IEP", "ILS", "INR", "IQD", "IRR", "ISK", "ITL", + "JMD", "JOD", "JPY", "KES", "KGS", "KHR", "KMF", "KPW", "KRW", "KWD", "KYD", + "KZT", "LAK", "LBP", "LKR", "LRD", "LSL", "LTL", "LUF", "LVL", "LYD", "MAD", + "MDL", "MGF", "MKD", "MMK", "MNT", "MOP", "MRO", "MTL", "MUR", "MVR", "MWK", + "MXP", "MYR", "MZM", "NAD", "NGN", "NIO", "NLG", "NOK", "NPR", "NZD", "OMR", + "PAB", "PEN", "PGK", "PHP", "PKR", "PLN", "PTE", "PYG", "QAR", "ROL", "RSD", + "RUR", "RWF", "SAR", "SBD", "SBL", "SCR", "SDD", "SEK", "SGD", "SHP", "SIT", + "SKK", "SLL", "SOS", "SRG", "STD", "SVC", "SYP", "SZL", "THB", "TJR", "TMM", + "TND", "TOP", "TRL", "TTD", "TWD", "TZS", "UAH", "UGX", "USD", "UYU", "UZS", + "VEB", "VND", "VUV", "WST", "XAF", "XAG", "XAU", "XCD", "XDR", "XOF", "XPD", "XPF", "XPT", "YER", "YUN", "ZAR", "ZMK", "ZRN", "ZWD",) -decimalsPattern = re.compile(r"^(INF|-?[0-9]+)$") +decimalsPattern = re.compile(r"^(INF|-?[0-9]+)$") class DialogNewFactItemOptions(Toplevel): def __init__(self, mainWin, options): @@ -70,7 +70,7 @@ def __init__(self, mainWin, options): self.transient(self.parent) self.title(_("New Fact Item Options")) - + frame = Frame(self) label(frame, 1, 1, "Entity scheme:") @@ -101,20 +101,20 @@ def __init__(self, mainWin, options): ToolTip(okButton, text=_("Accept the options as entered above")) cancelButton.grid(row=8, column=1, columnspan=3, sticky=E, pady=3, padx=3) okButton.grid(row=8, column=1, columnspan=3, sticky=E, pady=3, padx=86) - + frame.grid(row=0, column=0, sticky=(N,S,E,W)) frame.columnconfigure(2, weight=1) window = self.winfo_toplevel() window.columnconfigure(0, weight=1) self.geometry("+{0}+{1}".format(dialogX+50,dialogY+100)) - + #self.bind("", self.ok) #self.bind("", self.close) - + self.protocol("WM_DELETE_WINDOW", self.close) self.grab_set() self.wait_window(self) - + def checkEntries(self): errors = [] if not self.cellEntityIdentScheme.value: @@ -136,7 +136,7 @@ def checkEntries(self): "\n ".join(errors), parent=self) return False return True - + def setOptions(self): # set formula options self.options.entityIdentScheme = self.cellEntityIdentScheme.value @@ -147,15 +147,15 @@ def setOptions(self): self.options.monetaryUnit = self.cellMonetaryUnit.value self.options.monetaryDecimals = self.cellMonetaryDecimals.value self.options.nonMonetaryDecimals = self.cellNonMonetaryDecimals.value - + def ok(self, event=None): if not self.checkEntries(): return self.setOptions() self.accepted = True self.close() - + def close(self, event=None): self.parent.focus_set() self.destroy() - + diff --git a/arelle/DialogOpenArchive.py b/arelle/DialogOpenArchive.py index 508c0f1b2f..b34a5e135d 100644 --- a/arelle/DialogOpenArchive.py +++ b/arelle/DialogOpenArchive.py @@ -38,21 +38,21 @@ def askArchiveFile(parent, filesource, multiselect=False): try: filenames = filesource.dir if filenames is not None: # an IO or other error can return None - if filesource.isTaxonomyPackage: - dialog = DialogOpenArchive(parent, - ENTRY_POINTS, - filesource, + if filesource.isTaxonomyPackage: + dialog = DialogOpenArchive(parent, + ENTRY_POINTS, + filesource, filenames, - _("Select Entry Point"), + _("Select Entry Point"), _("File"), showAltViewButton=True, multiselect=multiselect) else: - dialog = DialogOpenArchive(parent, - ARCHIVE, - filesource, + dialog = DialogOpenArchive(parent, + ARCHIVE, + filesource, filenames, - _("Select Archive File"), + _("Select Archive File"), _("File"), multiselect=multiselect) if dialog.accepted: @@ -64,49 +64,49 @@ def askArchiveFile(parent, filesource, multiselect=False): return None def selectDisclosureSystem(parent, disclosureSystem): - + disclosureSystemSelections = disclosureSystem.dir - + # if no disclosure system to select, user may need to enable applicable plugin(s) if not disclosureSystemSelections and messagebox.askokcancel( - _("Load disclosure systems"), + _("Load disclosure systems"), _("Disclosure systems are provided by plug-ins, no applicable plug-in(s) have been enabled. \n\n" "Press OK to open the plug-in manager and select plug-in(s) (e.g., validate or EdgarRenderer).")): from arelle import DialogPluginManager DialogPluginManager.dialogPluginManager(parent) return None - dialog = DialogOpenArchive(parent, - DISCLOSURE_SYSTEM, - disclosureSystem, - disclosureSystemSelections, - _("Select Disclosure System"), + dialog = DialogOpenArchive(parent, + DISCLOSURE_SYSTEM, + disclosureSystem, + disclosureSystemSelections, + _("Select Disclosure System"), _("Disclosure System")) if dialog and dialog.accepted: return disclosureSystem.selection return None def selectPlugin(parent, pluginChoices): - + filesource = attrdict(isRss=False, url="Plug-ins", selection="") # emulates a filesource object for the selection return - dialog = DialogOpenArchive(parent, - PLUGIN, - filesource, - pluginChoices, - _("File"), + dialog = DialogOpenArchive(parent, + PLUGIN, + filesource, + pluginChoices, + _("File"), _("Select Plug-in Module")) if dialog and dialog.accepted: return filesource.selection return None def selectPackage(parent, packageChoices): - + filesource = attrdict(isRss=False, url="Packages", selection="") # emulates a filesource object for the selection return - dialog = DialogOpenArchive(parent, - PACKAGE, - filesource, - packageChoices, - _("Name"), + dialog = DialogOpenArchive(parent, + PACKAGE, + filesource, + packageChoices, + _("Name"), _("Select Package")) if dialog and dialog.accepted: return filesource.selection @@ -129,7 +129,7 @@ def __init__(self, parent, openType, filesource, filenames, title, colHeader, sh self.accepted = False self.transient(self.parent) - + frame = Frame(self) treeFrame = Frame(frame, width=500) @@ -146,7 +146,7 @@ def __init__(self, parent, openType, filesource, filenames, title, colHeader, sh treeFrame.rowconfigure(0, weight=1) treeFrame.grid(row=0, column=0, columnspan=4, sticky=(N, S, E, W), padx=3, pady=3) self.treeView.focus_set() - + if openType not in (PLUGIN, PACKAGE): cntlr.showStatus(_("loading archive {0}").format(filesource.url)) self.filesource = filesource @@ -172,11 +172,11 @@ def __init__(self, parent, openType, filesource, filenames, title, colHeader, sh self.taxonomyPkgMetaInf = '{}/META-INF/'.format( os.path.splitext(os.path.basename(filesource.url))[0]) - + self.taxonomyPackage = parsePackage(cntlr, filesource, metadata, os.sep.join(os.path.split(metadata)[:-1]) + os.sep) - - + + if self.taxonomyPackage["entryPoints"]: # may have instance documents too self.packageContainedInstances = [] @@ -200,7 +200,7 @@ def __init__(self, parent, openType, filesource, filenames, title, colHeader, sh packageContentInstanceCounts[potentialInstance] = packageContentInstanceCounts.get(potentialInstance, 0) + 1 packageContentTypeCounts[_type] = packageContentTypeCounts.get(_type, 0) + 1 if self.packageContainedInstances: - break + break if self.packageContainedInstances: # add sequences to any duplicated entry types for _type, count in packageContentTypeCounts.items(): if count > 1: @@ -216,7 +216,7 @@ def __init__(self, parent, openType, filesource, filenames, title, colHeader, sh if self.packageContainedInstances[i][0] == _instance: _dupNo += 1 self.packageContainedInstances[i][0] = "{} {}".format(_instance, _dupNo) - + else: # may be a catalog file with no entry oint names openType = ARCHIVE # no entry points to show, just archive @@ -227,10 +227,10 @@ def __init__(self, parent, openType, filesource, filenames, title, colHeader, sh messagebox.showerror(_("Malformed taxonomy package"), err) cntlr.addToLog(err) return - + if openType not in (PLUGIN, PACKAGE): cntlr.showStatus(None) - + if openType in (DISCLOSURE_SYSTEM, PLUGIN, PACKAGE): y = 3 else: @@ -240,11 +240,11 @@ def __init__(self, parent, openType, filesource, filenames, title, colHeader, sh cancelButton = Button(frame, text=_("Cancel"), command=self.close) okButton.grid(row=y, column=2, sticky=(S,E,W), pady=3) cancelButton.grid(row=y, column=3, sticky=(S,E,W), pady=3, padx=3) - + if self.showAltViewButton: self.altViewButton = Button(frame, command=self.showAltView) self.altViewButton.grid(row=y, column=0, sticky=(S,W), pady=3, padx=3) - + self.loadTreeView(openType, colHeader, title) self.geometry("+{0}+{1}".format(dialogX+50,dialogY+100)) @@ -254,37 +254,37 @@ def __init__(self, parent, openType, filesource, filenames, title, colHeader, sh window = self.winfo_toplevel() window.columnconfigure(0, weight=1) window.rowconfigure(0, weight=1) - + self.bind("", self.ok) self.bind("", self.close) - + self.toolTipText = StringVar() if self.hasToolTip: self.treeView.bind("", self.motion, '+') self.treeView.bind("", self.leave, '+') self.toolTipText = StringVar() - self.toolTip = ToolTip(self.treeView, - textvariable=self.toolTipText, - wraplength=640, + self.toolTip = ToolTip(self.treeView, + textvariable=self.toolTipText, + wraplength=640, follow_mouse=True, state="disabled") self.toolTipRowId = None self.protocol("WM_DELETE_WINDOW", self.close) self.grab_set() - + self.wait_window(self) - - - + + + def loadTreeView(self, openType, title, colHeader): self.title(title) self.openType = openType selectedNode = None # clear previous treeview entries - for previousNode in self.treeView.get_children(""): + for previousNode in self.treeView.get_children(""): self.treeView.delete(previousNode) # set up treeView widget and tabbed pane @@ -325,7 +325,7 @@ def loadTreeView(self, openType, title, colHeader): self.treeView.heading("license", text="License") else: self.treeView["columns"] = tuple() - + loadedPaths = [] for i, filename in enumerate(self.filenames): if isinstance(filename,tuple): @@ -343,7 +343,7 @@ def loadTreeView(self, openType, title, colHeader): if not self.isRss and len(path) > 1 and path[:-1] in loadedPaths: parent = "file{0}".format(loadedPaths.index(path[:-1])) else: - parent = "" + parent = "" node = self.treeView.insert(parent, "end", "file{0}".format(i), text=path[-1]) if self.isRss: self.treeView.set(node, "descr", form) @@ -365,20 +365,20 @@ def loadTreeView(self, openType, title, colHeader): elif openType == ENTRY_POINTS: self.treeView.column("#0", width=200, anchor="w") self.treeView.heading("#0", text="Name") - + self.treeView["columns"] = ("url",) self.treeView.column("url", width=300, anchor="w") self.treeView.heading("url", text="URL") - + for fileType, fileUrl in getattr(self, "packageContainedInstances", ()): - self.treeView.insert("", "end", fileUrl, - values=fileType, + self.treeView.insert("", "end", fileUrl, + values=fileType, text=fileUrl or urls[0][2]) for name, urls in sorted(self.taxonomyPackage["entryPoints"].items(), key=lambda i:i[0][2]): - self.treeView.insert("", "end", name, - values="\n".join(url[1] for url in urls), + self.treeView.insert("", "end", name, + values="\n".join(url[1] for url in urls), text=name or urls[0][2]) - + self.hasToolTip = True else: # unknown openType return None @@ -389,7 +389,7 @@ def loadTreeView(self, openType, title, colHeader): if self.showAltViewButton: self.altViewButton.config(text=_("Show Files") if openType == ENTRY_POINTS else _("Show Entries")) - + def ok(self, event=None): selection = self.treeView.selection() if len(selection) > 0: @@ -425,7 +425,7 @@ def ok(self, event=None): filenames.append(_url) if not filenames: # else if it's a named taxonomy entry point of an installed package for url in self.taxonomyPackage["entryPoints"][epName]: - filename = url[1] # use unmapped file name + filename = url[1] # use unmapped file name if not filename.endswith("/"): # check if it's an absolute URL rather than a path into the archive if not isHttpUrl(filename) and self.metadataFilePrefix != self.taxonomyPkgMetaInf: @@ -463,18 +463,18 @@ def ok(self, event=None): self.filesource.select(filename) self.accepted = True self.close() - - + + def close(self, event=None): self.parent.focus_set() self.destroy() - + def showAltView(self, event=None): if self.openType == ENTRY_POINTS: self.loadTreeView(ARCHIVE, _("Select Entry Point"), _("File")) else: self.loadTreeView(ENTRY_POINTS, _("Select Archive File"), _("File")) - + def leave(self, *args): self.toolTipRowId = None @@ -493,12 +493,12 @@ def motion(self, *args): pass elif self.openType == ENTRY_POINTS: try: - text = "{0}\n{1}".format(tvRowId, + text = "{0}\n{1}".format(tvRowId, "\n".join(url[1] for url in self.taxonomyPackage["entryPoints"][tvRowId])) except KeyError: pass self.setToolTip(text) - + def setToolTip(self, text): self.toolTip._hide() if text: diff --git a/arelle/DialogOpenTaxonomyPackage.py b/arelle/DialogOpenTaxonomyPackage.py index e39348e90d..1a3de81721 100644 --- a/arelle/DialogOpenTaxonomyPackage.py +++ b/arelle/DialogOpenTaxonomyPackage.py @@ -1 +1 @@ -# module merged to DialogOpenArchive.py \ No newline at end of file +# module merged to DialogOpenArchive.py diff --git a/arelle/DialogPackageManager.py b/arelle/DialogPackageManager.py index a2816dbaa0..142abc9a4a 100644 --- a/arelle/DialogPackageManager.py +++ b/arelle/DialogPackageManager.py @@ -17,7 +17,7 @@ import regex as re except ImportError: import re - + STANDARD_PACKAGES_URL = "https://taxonomies.xbrl.org/api/v0/taxonomy" def dialogPackageManager(mainWin): @@ -35,31 +35,31 @@ def backgroundCheckForUpdates(cntlr): .format(', '.join(packageNamesWithNewerFileDates)), clearAfter=5000) else: cntlr.showStatus(_("No updates found for packages."), clearAfter=5000) - time.sleep(0.1) # Mac locks up without this, may be needed for empty ui queue? + time.sleep(0.1) # Mac locks up without this, may be needed for empty ui queue? cntlr.uiThreadQueue.put((DialogPackageManager, [cntlr, packageNamesWithNewerFileDates])) class DialogPackageManager(Toplevel): def __init__(self, mainWin, packageNamesWithNewerFileDates): super(DialogPackageManager, self).__init__(mainWin.parent) - + self.ENABLE = _("Enable") self.DISABLE = _("Disable") self.parent = mainWin.parent self.cntlr = mainWin self.webCache = mainWin.webCache - + # copy plugins for temporary display self.packagesConfig = PackageManager.packagesConfig self.packagesConfigChanged = False self.packageNamesWithNewerFileDates = packageNamesWithNewerFileDates - + parentGeometry = re.match("(\d+)x(\d+)[+]?([-]?\d+)[+]?([-]?\d+)", self.parent.geometry()) dialogX = int(parentGeometry.group(3)) dialogY = int(parentGeometry.group(4)) self.title(_("Taxonomy Packages Manager")) frame = Frame(self) - + # left button frame buttonFrame = Frame(frame, width=40) buttonFrame.columnconfigure(0, weight=1) @@ -91,7 +91,7 @@ def __init__(self, mainWin, packageNamesWithNewerFileDates): manifestNameButton.grid(row=selBtnRow, column=0, pady=4) selBtnRow += 1 buttonFrame.grid(row=0, column=0, rowspan=3, sticky=(N, S, W), padx=3, pady=3) - + # right tree frame (packages already known to arelle) packagesFrame = Frame(frame, width=700) vScrollbar = Scrollbar(packagesFrame, orient=VERTICAL) @@ -135,18 +135,18 @@ def __init__(self, mainWin, packageNamesWithNewerFileDates): remappingsFrame.rowconfigure(0, weight=1) remappingsFrame.grid(row=1, column=1, columnspan=4, sticky=(N, S, E, W), padx=3, pady=3) self.remappingsView.focus_set() - + self.remappingsView.column("#0", width=200, anchor="w") self.remappingsView.heading("#0", text=_("Prefix")) self.remappingsView["columns"] = ("remapping") self.remappingsView.column("remapping", width=500, anchor="w", stretch=False) self.remappingsView.heading("remapping", text=_("Remapping")) - + # bottom frame package info details packageInfoFrame = Frame(frame, width=700) packageInfoFrame.columnconfigure(1, weight=1) - - self.packageNameLabel = Label(packageInfoFrame, wraplength=600, justify="left", + + self.packageNameLabel = Label(packageInfoFrame, wraplength=600, justify="left", font=font.Font(family='Helvetica', size=12, weight='bold')) self.packageNameLabel.grid(row=0, column=0, columnspan=6, sticky=W) self.packageVersionHdr = Label(packageInfoFrame, text=_("version:"), state=DISABLED) @@ -203,14 +203,14 @@ def __init__(self, mainWin, packageNamesWithNewerFileDates): self.packageRemoveButton.grid(row=9, column=5, sticky=E) packageInfoFrame.grid(row=2, column=0, columnspan=5, sticky=(N, S, E, W), padx=3, pady=3) packageInfoFrame.config(borderwidth=4, relief="groove") - + okButton = Button(frame, text=_("Close"), command=self.ok) ToolTip(okButton, text=_("Accept and changes (if any) and close dialog."), wraplength=240) cancelButton = Button(frame, text=_("Cancel"), command=self.close) ToolTip(cancelButton, text=_("Cancel changes (if any) and close dialog."), wraplength=240) okButton.grid(row=3, column=3, sticky=(S,E), pady=3) cancelButton.grid(row=3, column=4, sticky=(S,E), pady=3, padx=3) - + enableDisableFrame = Frame(frame) enableDisableFrame.grid(row=3, column=1, sticky=(S,W), pady=3) enableAllButton = Button(enableDisableFrame, text=_("Enable All"), command=self.enableAll) @@ -219,7 +219,7 @@ def __init__(self, mainWin, packageNamesWithNewerFileDates): ToolTip(disableAllButton, text=_("Disable all packages."), wraplength=240) enableAllButton.grid(row=1, column=1) disableAllButton.grid(row=1, column=2) - + self.loadTreeViews() self.geometry("+{0}+{1}".format(dialogX+50,dialogY+100)) @@ -230,19 +230,19 @@ def __init__(self, mainWin, packageNamesWithNewerFileDates): window = self.winfo_toplevel() window.columnconfigure(0, weight=1) window.rowconfigure(0, weight=1) - + self.bind("", self.ok) self.bind("", self.close) - + self.protocol("WM_DELETE_WINDOW", self.close) self.grab_set() self.wait_window(self) - + def loadTreeViews(self): self.selectedModule = None # clear previous treeview entries - for previousNode in self.packagesView.get_children(""): + for previousNode in self.packagesView.get_children(""): self.packagesView.delete(previousNode) for i, packageInfo in enumerate(self.packagesConfig.get("packages", [])): @@ -254,16 +254,16 @@ def loadTreeViews(self): if name in self.packageNamesWithNewerFileDates: self.packagesView.set(node, "update", _("available")) self.packagesView.set(node, "descr", packageInfo.get("description")) - + # clear previous treeview entries - for previousNode in self.remappingsView.get_children(""): + for previousNode in self.remappingsView.get_children(""): self.remappingsView.delete(previousNode) for i, remappingItem in enumerate(sorted(self.packagesConfig.get("remappings", {}).items())): prefix, remapping = remappingItem node = self.remappingsView.insert("", "end", prefix, text=prefix) self.remappingsView.set(node, "remapping", remapping) - + self.packageSelect() # clear out prior selection def ok(self, event=None): @@ -272,7 +272,7 @@ def ok(self, event=None): PackageManager.packagesConfigChanged = True self.cntlr.onPackageEnablementChanged() self.close() - + def close(self, event=None): self.parent.focus_set() self.destroy() @@ -359,7 +359,7 @@ def langLabel(labels): for label in labels: return label["Label"] return "" - try: + try: with open(self.webCache.getfilename(STANDARD_PACKAGES_URL, reload=True), 'r', errors='replace') as fh: regPkgs = json.load(fh) # always reload for pkgTxmy in regPkgs.get("taxonomies", []): @@ -378,8 +378,8 @@ def langLabel(labels): _("Standard packages URL is not accessible, please check if online:\n\n{0}.") .format(STANDARD_PACKAGES_URL), parent=self) - - + + def findLocally(self): initialdir = self.cntlr.pluginDir # default plugin directory #if not self.cntlr.isMac: # can't navigate within app easily, always start in default directory @@ -398,17 +398,17 @@ def findLocally(self): self.cntlr.config["packageOpenDir"] = os.path.dirname(filename) packageInfo = PackageManager.packageInfo(self.cntlr, filename, packageManifestName=self.manifestNamePattern) self.loadFoundPackageInfo(packageInfo, filename) - + def findOnWeb(self): self.loadPackageUrl(DialogURL.askURL(self)) - + def loadPackageUrl(self, url): if url: # url is the in-cache or local file packageInfo = PackageManager.packageInfo(self.cntlr, url, packageManifestName=self.manifestNamePattern) self.cntlr.showStatus("") # clear web loading status self.loadFoundPackageInfo(packageInfo, url) - + def manifestName(self): self.manifestNamePattern = simpledialog.askstring(_("Archive manifest file name pattern"), _("Provide non-standard archive manifest file name pattern (e.g., *taxonomyPackage.xml). \n" @@ -417,7 +417,7 @@ def manifestName(self): "(If blank, search for either .taxonomyPackage.xml or catalog.xml). "), initialvalue=self.manifestNamePattern, parent=self) - + def loadFoundPackageInfo(self, packageInfo, url): if packageInfo and packageInfo.get("name"): self.addPackageInfo(packageInfo) @@ -428,7 +428,7 @@ def loadFoundPackageInfo(self, packageInfo, url): "If opening an archive file, the manifest file search pattern currently is \"\", please press \"Manifest\" to change manifest file name pattern, e.g.,, \"*.taxonomyPackage.xml\", if needed. ") .format(url), parent=self) - + def removePackageInfo(self, name, version): # find package entry packagesList = self.packagesConfig["packages"] @@ -462,7 +462,7 @@ def packageEnable(self): self.packagesConfigChanged = True PackageManager.rebuildRemappings(self.cntlr) self.loadTreeViews() - + def packageMoveUp(self): if 1 <= self.selectedPackageIndex < len(self.packagesConfig["packages"]): packages = self.packagesConfig["packages"] @@ -472,7 +472,7 @@ def packageMoveUp(self): self.packagesConfigChanged = True PackageManager.rebuildRemappings(self.cntlr) self.loadTreeViews() - + def packageMoveDown(self): if 0 <= self.selectedPackageIndex < len(self.packagesConfig["packages"]) - 1: packages = self.packagesConfig["packages"] @@ -482,7 +482,7 @@ def packageMoveDown(self): self.packagesConfigChanged = True PackageManager.rebuildRemappings(self.cntlr) self.loadTreeViews() - + def packageReload(self): if 0 <= self.selectedPackageIndex < len(self.packagesConfig["packages"]): packageInfo = self.packagesConfig["packages"][self.selectedPackageIndex] @@ -507,13 +507,13 @@ def packageRemove(self): self.packagesConfigChanged = True PackageManager.rebuildRemappings(self.cntlr) self.loadTreeViews() - + def enableAll(self): self.enableDisableAll(True) - + def disableAll(self): self.enableDisableAll(False) - + def enableDisableAll(self, doEnable): for iPkg in range(len(self.packagesConfig["packages"])): packageInfo = self.packagesConfig["packages"][iPkg] @@ -526,4 +526,4 @@ def enableDisableAll(self, doEnable): self.packagesConfigChanged = True PackageManager.rebuildRemappings(self.cntlr) self.loadTreeViews() - + diff --git a/arelle/DialogPluginManager.py b/arelle/DialogPluginManager.py index 36328a9a6a..1df647b8f2 100644 --- a/arelle/DialogPluginManager.py +++ b/arelle/DialogPluginManager.py @@ -38,18 +38,18 @@ def backgroundCheckForUpdates(cntlr): .format(', '.join(modulesWithNewerFileDates)), clearAfter=5000) else: cntlr.showStatus(_("No updates found for plug-ins."), clearAfter=5000) - time.sleep(0.1) # Mac locks up without this, may be needed for empty ui queue? + time.sleep(0.1) # Mac locks up without this, may be needed for empty ui queue? cntlr.uiThreadQueue.put((DialogPluginManager, [cntlr, modulesWithNewerFileDates])) class DialogPluginManager(Toplevel): def __init__(self, mainWin, modulesWithNewerFileDates): super(DialogPluginManager, self).__init__(mainWin.parent) - + self.ENABLE = _("Enable") self.DISABLE = _("Disable") self.parent = mainWin.parent self.cntlr = mainWin - + # copy plugins for temporary display self.pluginConfig = PluginManager.pluginConfig self.pluginConfigChanged = False @@ -59,14 +59,14 @@ def __init__(self, mainWin, modulesWithNewerFileDates): self.disclosureSystemTypesChanged = False self.hostSystemFeaturesChanged = False self.modulesWithNewerFileDates = modulesWithNewerFileDates - + parentGeometry = re.match("(\d+)x(\d+)[+]?([-]?\d+)[+]?([-]?\d+)", self.parent.geometry()) dialogX = int(parentGeometry.group(3)) dialogY = int(parentGeometry.group(4)) self.title(_("Plug-in Manager")) frame = Frame(self) - + # left button frame buttonFrame = Frame(frame, width=40) buttonFrame.columnconfigure(0, weight=1) @@ -82,7 +82,7 @@ def __init__(self, mainWin, modulesWithNewerFileDates): addBrowseLocalButton.grid(row=2, column=0, pady=4) addWebButton.grid(row=3, column=0, pady=4) buttonFrame.grid(row=0, column=0, rowspan=3, sticky=(N, S, W), padx=3, pady=3) - + # right tree frame (plugins already known to arelle) modulesFrame = Frame(frame, width=720) vScrollbar = Scrollbar(modulesFrame, orient=VERTICAL) @@ -130,18 +130,18 @@ def __init__(self, mainWin, modulesWithNewerFileDates): classesFrame.rowconfigure(0, weight=1) classesFrame.grid(row=1, column=1, columnspan=4, sticky=(N, S, E, W), padx=3, pady=3) self.classesView.focus_set() - + self.classesView.column("#0", width=200, anchor="w") self.classesView.heading("#0", text=_("Class")) self.classesView["columns"] = ("modules",) self.classesView.column("modules", width=500, anchor="w", stretch=False) self.classesView.heading("modules", text=_("Modules")) - + # bottom frame module info details moduleInfoFrame = Frame(frame, width=700) moduleInfoFrame.columnconfigure(1, weight=1) - - self.moduleNameLabel = Label(moduleInfoFrame, wraplength=600, justify="left", + + self.moduleNameLabel = Label(moduleInfoFrame, wraplength=600, justify="left", font=font.Font(family='Helvetica', size=12, weight='bold')) self.moduleNameLabel.grid(row=0, column=0, columnspan=4, sticky=W) self.moduleAuthorHdr = Label(moduleInfoFrame, text=_("author:"), state=DISABLED) @@ -191,14 +191,14 @@ def __init__(self, mainWin, modulesWithNewerFileDates): self.moduleRemoveButton.grid(row=9, column=3, sticky=E) moduleInfoFrame.grid(row=2, column=0, columnspan=5, sticky=(N, S, E, W), padx=3, pady=3) moduleInfoFrame.config(borderwidth=4, relief="groove") - + okButton = Button(frame, text=_("Close"), command=self.ok) ToolTip(okButton, text=_("Accept and changes (if any) and close dialog."), wraplength=240) cancelButton = Button(frame, text=_("Cancel"), command=self.close) ToolTip(cancelButton, text=_("Cancel changes (if any) and close dialog."), wraplength=240) okButton.grid(row=3, column=3, sticky=(S,E), pady=3) cancelButton.grid(row=3, column=4, sticky=(S,E), pady=3, padx=3) - + enableDisableFrame = Frame(frame) enableDisableFrame.grid(row=3, column=1, sticky=(S,W), pady=3) enableAllButton = Button(enableDisableFrame, text=_("Enable All"), command=self.enableAll) @@ -207,7 +207,7 @@ def __init__(self, mainWin, modulesWithNewerFileDates): ToolTip(disableAllButton, text=_("Disable all plug ins."), wraplength=240) enableAllButton.grid(row=1, column=1) disableAllButton.grid(row=1, column=2) - + self.loadTreeViews() self.geometry("+{0}+{1}".format(dialogX+50,dialogY+100)) @@ -218,21 +218,21 @@ def __init__(self, mainWin, modulesWithNewerFileDates): window = self.winfo_toplevel() window.columnconfigure(0, weight=1) window.rowconfigure(0, weight=1) - + self.bind("", self.ok) self.bind("", self.close) - + self.protocol("WM_DELETE_WINDOW", self.close) self.grab_set() self.wait_window(self) - + def loadTreeViews(self): self.selectedModule = None # clear previous treeview entries - for previousNode in self.modulesView.get_children(""): + for previousNode in self.modulesView.get_children(""): self.modulesView.delete(previousNode) - + def loadSubtree(parentNode, moduleItems): for moduleItem in sorted(moduleItems, key=lambda item: item[0]): moduleInfo = moduleItem[1] @@ -253,18 +253,18 @@ def loadSubtree(parentNode, moduleItems): if moduleInfo.get("imports"): loadSubtree(node, [(importModuleInfo["name"],importModuleInfo) for importModuleInfo in moduleInfo["imports"]]) - + loadSubtree("", self.pluginConfig.get("modules", {}).items()) - + # clear previous treeview entries - for previousNode in self.classesView.get_children(""): + for previousNode in self.classesView.get_children(""): self.classesView.delete(previousNode) for i, classItem in enumerate(sorted(self.pluginConfig.get("classes", {}).items())): className, moduleList = classItem node = self.classesView.insert("", "end", className, text=className) self.classesView.set(node, "modules", ', '.join(moduleList)) - + self.moduleSelect() # clear out prior selection def ok(self, event=None): @@ -281,7 +281,7 @@ def ok(self, event=None): self.pluginConfigChanged = True for _orphanedClassName in _orphanedClassNames: del self.pluginConfig["classes"][_orphanedClassName] - + if self.pluginConfigChanged: PluginManager.pluginConfig = self.pluginConfig PluginManager.pluginConfigChanged = True @@ -307,7 +307,7 @@ def ok(self, event=None): affectedItems += _(" and ") affectedItems += _("host system features") if messagebox.askyesno(_("User interface plug-in change"), - _("A change in plug-in class methods may have affected {0}. " + _("A change in plug-in class methods may have affected {0}. " "Please restart Arelle to due to these changes. \n\n" "Should Arelle restart itself now " "(if there are any unsaved changes they would be lost!)?" @@ -315,11 +315,11 @@ def ok(self, event=None): parent=self): self.cntlr.uiThreadQueue.put((self.cntlr.quit, [None, True])) self.close() - + def close(self, event=None): self.parent.focus_set() self.destroy() - + def moduleSelect(self, *args): node = (self.modulesView.selection() or (None,))[0] if node: @@ -397,12 +397,12 @@ def selectChoices(dir, indent=""): ((os.path.isfile(fPath) and f.endswith(".py")))): moduleInfo = PluginManager.moduleModuleInfo(fPath) if moduleInfo: - choices.append((indent + f, + choices.append((indent + f, "name: {}\ndescription: {}\nversion: {}\nlicense: {}".format( moduleInfo["name"], moduleInfo.get("description"), moduleInfo.get("version"), - moduleInfo.get("license")), + moduleInfo.get("license")), fPath, moduleInfo["name"], moduleInfo.get("version"), moduleInfo.get("description"), moduleInfo.get("license"))) dirHasEntries = True if os.path.isdir(fPath) and f not in ("DQC_US_Rules",) and not f.startswith("ixviewer"): @@ -414,7 +414,7 @@ def selectChoices(dir, indent=""): if selectedPath: moduleInfo = PluginManager.moduleModuleInfo(selectedPath[len(self.cntlr.pluginDir)+1:]) self.loadFoundModuleInfo(moduleInfo, selectedPath) - + def browseLocally(self): initialdir = self.cntlr.pluginDir # default plugin directory if not self.cntlr.isMac: # can't navigate within app easily, always start in default directory @@ -433,7 +433,7 @@ def browseLocally(self): self.cntlr.config["pluginOpenDir"] = os.path.dirname(filename) moduleInfo = PluginManager.moduleModuleInfo(filename) self.loadFoundModuleInfo(moduleInfo, filename) - + def findOnWeb(self): url = DialogURL.askURL(self) @@ -441,7 +441,7 @@ def findOnWeb(self): moduleInfo = PluginManager.moduleModuleInfo(url) self.cntlr.showStatus("") # clear web loading status self.loadFoundModuleInfo(moduleInfo, url) - + def loadFoundModuleInfo(self, moduleInfo, url): if moduleInfo and moduleInfo.get("name"): self.addPluginConfigModuleInfo(moduleInfo) @@ -451,7 +451,7 @@ def loadFoundModuleInfo(self, moduleInfo, url): _("File does not itself contain a python program with an appropriate __pluginInfo__ declaration: \n\n{0}") .format(url), parent=self) - + def checkIfImported(self, moduleInfo): if moduleInfo.get("isImported"): messagebox.showwarning(_("Plug-in is imported by a parent plug-in. "), @@ -460,7 +460,7 @@ def checkIfImported(self, moduleInfo): parent=self) return True return False - + def checkClassMethodsChanged(self, moduleInfo): for classMethod in moduleInfo["classMethods"]: if classMethod.startswith("CntlrWinMain.Menu"): @@ -473,7 +473,7 @@ def checkClassMethodsChanged(self, moduleInfo): self.disclosureSystemTypesChanged = True # disclosure system types changed elif classMethod.startswith("Proxy."): self.hostSystemFeaturesChanged = True # system features (e.g., proxy) changed - + def removePluginConfigModuleInfo(self, name): moduleInfo = self.pluginConfig["modules"].get(name) if moduleInfo: @@ -540,7 +540,7 @@ def _moduleEnable(moduleInfo): self.moduleEnableButton['text'] = self.ENABLE self.pluginConfigChanged = True self.loadTreeViews() - + def moduleReload(self): if self.selectedModule in self.pluginConfig["modules"]: url = self.pluginConfig["modules"][self.selectedModule].get("moduleURL") @@ -563,13 +563,13 @@ def moduleRemove(self): self.removePluginConfigModuleInfo(self.selectedModule) self.pluginConfigChanged = True self.loadTreeViews() - + def enableAll(self): self.enableDisableAll(True) - + def disableAll(self): self.enableDisableAll(False) - + def enableDisableAll(self, doEnable): for module in self.pluginConfig["modules"]: moduleInfo = self.pluginConfig["modules"][module] @@ -588,4 +588,4 @@ def _enableDisableAll(moduleInfo): self.moduleEnableButton['text'] = self.ENABLE self.pluginConfigChanged = True self.loadTreeViews() - + diff --git a/arelle/DialogRssWatch.py b/arelle/DialogRssWatch.py index 1766a801b6..529a58d655 100644 --- a/arelle/DialogRssWatch.py +++ b/arelle/DialogRssWatch.py @@ -39,11 +39,11 @@ def getOptions(mainWin): "US SEC Mutual Fund Risk/Return Filings": "http://www.sec.gov/Archives/edgar/xbrl-rr.rss.xml", "US SEC All Filings": "http://www.sec.gov/Archives/edgar/xbrlrss.all.xml", } - + emailPattern = re.compile( - r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom - r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string - r')@(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?$', re.IGNORECASE) + r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom + r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string + r')@(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?$', re.IGNORECASE) class DialogRssWatch(Toplevel): @@ -60,7 +60,7 @@ def __init__(self, mainWin, options): self.transient(self.parent) self.title(_("RSS Feed Processing Control")) - + frame = Frame(self) # checkbox entries @@ -116,17 +116,17 @@ def __init__(self, mainWin, options): label(frame, 2, row, "Validate:") row += 1 self.checkboxes = ( - checkbox(frame, 2, row, - "XBRL 2.1 and Dimensions rules", + checkbox(frame, 2, row, + "XBRL 2.1 and Dimensions rules", "validateXbrlRules"), - checkbox(frame, 2, row+1, - "Selected disclosure system rules", + checkbox(frame, 2, row+1, + "Selected disclosure system rules", "validateDisclosureSystemRules"), checkbox(frame, 2, row+2, - "Calculation linkbase roll-up", + "Calculation linkbase roll-up", "validateCalcLinkbase"), checkbox(frame, 2, row+3, - "Formula assertions", + "Formula assertions", "validateFormulaAssertions"), # Note: if adding to this list keep ModelFormulaObject.FormulaOptions in sync ) @@ -137,19 +137,19 @@ def __init__(self, mainWin, options): label(frame, 2, row, "Alert on:") row += 1 self.checkboxes += ( - checkbox(frame, 2, row, - "Facts with matching text", + checkbox(frame, 2, row, + "Facts with matching text", "alertMatchedFactText"), checkbox(frame, 2, row+1, - "Unsuccessful formula assertions", + "Unsuccessful formula assertions", "alertAssertionUnsuccessful"), - checkbox(frame, 2, row+2, - "Validation errors", + checkbox(frame, 2, row+2, + "Validation errors", "alertValiditionError"), # Note: if adding to this list keep ModelFormulaObject.FormulaOptions in sync ) row += 3 - + mainWin.showStatus(None) cancelButton = Button(frame, text=_("Cancel"), width=8, command=self.close) @@ -158,20 +158,20 @@ def __init__(self, mainWin, options): ToolTip(okButton, text=_("Accept the options as entered above")) cancelButton.grid(row=row, column=1, columnspan=3, sticky=E, pady=3, padx=3) okButton.grid(row=row, column=1, columnspan=3, sticky=E, pady=3, padx=86) - + frame.grid(row=0, column=0, sticky=(N,S,E,W)) frame.columnconfigure(2, weight=1) window = self.winfo_toplevel() window.columnconfigure(0, weight=1) self.geometry("+{0}+{1}".format(dialogX+50,dialogY+100)) - + #self.bind("", self.ok) #self.bind("", self.close) - + self.protocol("WM_DELETE_WINDOW", self.close) self.grab_set() self.wait_window(self) - + def chooseFormulaFile(self): filename = tkinter.filedialog.askopenfilename( title=_("Choose formula file for RSS Watch"), @@ -182,7 +182,7 @@ def chooseFormulaFile(self): if filename: self.options["rssWatchFormulaFileDir"] = os.path.dirname(filename) self.cellFormulaFile.setValue(filename) - + def chooseLogFile(self): filename = tkinter.filedialog.asksaveasfilename( title=_("Choose log file for RSS Watch"), @@ -193,16 +193,16 @@ def chooseLogFile(self): if filename: self.options["rssWatchLogFileDir"] = os.path.dirname(filename) self.cellLogFile.setValue(filename) - + def setupSmtp(self): from arelle.DialogUserPassword import askSmtp smtpSettings = askSmtp(self, self.options.get("smtpEmailSettings",())) if smtpSettings: self.options["smtpEmailSettings"] = smtpSettings - + def clearPubDate(self): self.cellLatestPubDate.setValue("") - + def checkEntries(self): errors = [] if not self.cellFeed.value in rssFeeds and not isValidAbsolute(self.cellFeed.value): @@ -228,7 +228,7 @@ def checkEntries(self): "\n ".join(errors), parent=self) return False return True - + def setOptions(self): # set formula options self.options["feedSource"] = self.cellFeed.value @@ -247,15 +247,15 @@ def setOptions(self): self.options["latestPubDate"] = None for checkbox in self.checkboxes: self.options[checkbox.attr] = checkbox.value - + def ok(self, event=None): if not self.checkEntries(): return self.setOptions() self.accepted = True self.close() - + def close(self, event=None): self.parent.focus_set() self.destroy() - + diff --git a/arelle/DialogURL.py b/arelle/DialogURL.py index edf481f7f5..3470a557f5 100644 --- a/arelle/DialogURL.py +++ b/arelle/DialogURL.py @@ -38,7 +38,7 @@ def __init__(self, parent, url=None, buttonSEC=False, buttonRSS=False): self.title("Enter URL") self.urlVar = StringVar() self.urlVar.set(url if url is not None else "") - + frame = Frame(self) urlLabel = Label(frame, text=_("URL:"), underline=0) urlEntry = Entry(frame, textvariable=self.urlVar, width=60) @@ -68,32 +68,32 @@ def __init__(self, parent, url=None, buttonSEC=False, buttonRSS=False): ToolTip(okButton, text=_("Opens above URL from web cache, downloading to cache if necessary"), wraplength=240) cancelButton.grid(row=1, column=3, sticky=EW, pady=3, padx=3) ToolTip(cancelButton, text=_("Cancel operation")) - + frame.grid(row=0, column=0, sticky=(N,S,E,W)) frame.columnconfigure(1, weight=1) window = self.winfo_toplevel() window.columnconfigure(0, weight=1) self.geometry("+{0}+{1}".format(dialogX+50,dialogY+100)) - + self.bind("", lambda *ignore: urlEntry.focus_set()) self.bind("", self.ok) self.bind("", self.close) - + self.protocol("WM_DELETE_WINDOW", self.close) self.grab_set() self.wait_window(self) - + def ok(self, event=None): self.url = self.urlVar.get().strip() if self.url and self.url[0] == '"' and self.url[-1] == '"': self.url = self.url[1:-1] # strip double quotes (from cut and paste from database etc self.accepted = True self.close() - + def close(self, event=None): self.parent.focus_set() self.destroy() - + def usSec(self, event=None): import webbrowser webbrowser.open("http://www.sec.gov/edgar/searchedgar/companysearch.html") diff --git a/arelle/DialogUserPassword.py b/arelle/DialogUserPassword.py index 1375c0b0d9..317d67e4de 100644 --- a/arelle/DialogUserPassword.py +++ b/arelle/DialogUserPassword.py @@ -54,7 +54,7 @@ def askParams(parent, title, prompt1, prompt2): userLabel=prompt1, passwordLabel=prompt2, hidePassword=False) if dialog.accepted: return (dialog.user, dialog.password) - + DBTypes = ("postgres", "mssqlSemantic", "mysqlSemantic", "orclSemantic", "pgSemantic", "sqliteSemantic", "pgOpenDB", "sqliteDpmDB", "rexster", "rdfDB", "json") DBDescriptions = ("XBRL-US Postgres SQL", @@ -98,8 +98,8 @@ def askInternetLogon(parent, url, quotedUrl, dialogCaption, dialogText, untilDon untilDoneEvent.set() class DialogUserPassword(Toplevel): - def __init__(self, parent, title, host=None, realm=None, useOsProxy=None, urlAddr=None, urlPort=None, - user=None, password=None, database=None, timeout=None, dbType=None, + def __init__(self, parent, title, host=None, realm=None, useOsProxy=None, urlAddr=None, urlPort=None, + user=None, password=None, database=None, timeout=None, dbType=None, showUrl=False, showUser=False, showHost=True, showRealm=True, showDatabase=False, userLabel=None, passwordLabel=None, hidePassword=True): super(DialogUserPassword, self).__init__(parent) @@ -122,7 +122,7 @@ def __init__(self, parent, title, host=None, realm=None, useOsProxy=None, urlAdd self.databaseVar.set(database if database else "") self.timeoutVar = StringVar() self.timeoutVar.set(timeout if timeout else "") - + frame = Frame(self) y = 0 if showHost: @@ -210,7 +210,7 @@ def __init__(self, parent, title, host=None, realm=None, useOsProxy=None, urlAdd y += 1 dbTypeLabel = Label(frame, text=_("DB type:"), underline=0) dbTypeLabel.grid(row=y, column=0, sticky=W, pady=3, padx=3) - self.cbDbType = gridCombobox(frame, 1, y, values=DBDescriptions, + self.cbDbType = gridCombobox(frame, 1, y, values=DBDescriptions, selectindex=DBTypes.index(dbType) if dbType in DBTypes else None) self.cbDbType.grid(columnspan=4, pady=3, padx=3) y += 1 @@ -219,7 +219,7 @@ def __init__(self, parent, title, host=None, realm=None, useOsProxy=None, urlAdd okButton.grid(row=y, column=2, sticky=E, pady=3) cancelButton.grid(row=y, column=3, columnspan=2, sticky=EW, pady=3, padx=3) y += 1 - + if useOsProxy is not None: self.setEnabledState() @@ -228,14 +228,14 @@ def __init__(self, parent, title, host=None, realm=None, useOsProxy=None, urlAdd window = self.winfo_toplevel() window.columnconfigure(0, weight=1) self.geometry("+{0}+{1}".format(dialogX+50,dialogY+100)) - + self.bind("", self.ok) self.bind("", self.close) - + self.protocol("WM_DELETE_WINDOW", self.close) self.grab_set() self.wait_window(self) - + def checkEntries(self): errors = [] if self.urlPort and not self.urlPort.isdigit(): @@ -249,7 +249,7 @@ def checkEntries(self): "\n ".join(errors), parent=self) return False return True - + def ok(self, event=None): if hasattr(self, "useOsProxyCb"): self.useOsProxy = self.useOsProxyCb.value @@ -265,13 +265,13 @@ def ok(self, event=None): return self.accepted = True self.close() - + def close(self, event=None): self.parent.focus_set() self.destroy() - + def setEnabledState(self, *args): if hasattr(self, "useOsProxyCb"): state = DISABLED if self.useOsProxyCb.value else NORMAL for widget in self.enabledWidgets: - widget.config(state=state) \ No newline at end of file + widget.config(state=state) diff --git a/arelle/DisclosureSystem.py b/arelle/DisclosureSystem.py index cfa1d55cc1..9b275aa583 100644 --- a/arelle/DisclosureSystem.py +++ b/arelle/DisclosureSystem.py @@ -13,7 +13,7 @@ def compileAttrPattern(elt, attrName, flags=None, patternIfNoAttr=""): attr = elt.get(attrName) - if attr is None: + if attr is None: # pattern to match if no attribute provided if patternIfNoAttr is None: return None # if None, then there is no pattern if attribute missing @@ -36,7 +36,7 @@ class DisclosureSystem: def __init__(self, modelManager): self.modelManager = modelManager self.clear() - + def clear(self): self.selection = None self.standardTaxonomiesDict = {} @@ -104,7 +104,7 @@ def clear(self): @property def dir(self): return self.dirlist("dir") - + @property def urls(self): _urls = [os.path.join(self.modelManager.cntlr.configDir, "disclosuresystems.xml")] @@ -112,11 +112,11 @@ def urls(self): for pluginXbrlMethod in pluginClassMethods("DisclosureSystem.ConfigURL"): _urls.insert(0, pluginXbrlMethod(self)) return _urls - + @property def url(self): # needed for status messages (not used in this module) return ", ".join(os.path.basename(url) for url in self.urls) - + def dirlist(self, listFormat): self.modelManager.cntlr.showStatus(_("parsing disclosuresystems.xml")) namepaths = [] @@ -133,7 +133,7 @@ def dirlist(self, listFormat): namepaths.append('{0}: {1}'.format(entryName,names[0])) elif listFormat == "help-verbose": namepaths.append('{0}: {1}\n{2}\n'.format(entryName, - names[0], + names[0], dsElt.get("description").replace('\\n','\n'))) elif listFormat == "dir": namepaths.append((names[0], @@ -142,8 +142,8 @@ def dirlist(self, listFormat): except (EnvironmentError, etree.LxmlError) as err: self.modelManager.cntlr.addToLog(_("Disclosure System listing, import error: %(error)s"), - messageCode="arelle:disclosureSystemListingError", - messageArgs={"error": str(err)}, + messageCode="arelle:disclosureSystemListingError", + messageArgs={"error": str(err)}, level=logging.ERROR) self.modelManager.cntlr.showStatus("") return namepaths @@ -244,20 +244,20 @@ def select(self, name): else: status = _("unable to load disclosure system {}").format(name) self.modelManager.cntlr.addToLog(_("Disclosure System \"%(name)s\" not recognized (a plug-in may be needed)."), - messageCode="arelle:disclosureSystemName", + messageCode="arelle:disclosureSystemName", messageArgs={"name": name}, level=logging.ERROR) - + except (EnvironmentError, etree.LxmlError) as err: status = _("exception during loading") result = False self.modelManager.cntlr.addToLog(_("Disclosure System \"%(name)s\" loading error: %(error)s"), - messageCode="arelle:disclosureSystemLoadingError", + messageCode="arelle:disclosureSystemLoadingError", messageArgs={"error": str(err), "name": name}, level=logging.ERROR) etree.clear_error_log() self.modelManager.cntlr.showStatus(_("Disclosure system and mappings {0}: {1}").format(status,name), 3500) return result - + def loadStandardTaxonomiesDict(self): if self.selection: self.standardTaxonomiesDict = defaultdict(set) @@ -271,7 +271,7 @@ def loadStandardTaxonomiesDict(self): self.modelManager.cntlr.showStatus(_("parsing {0}").format(basename)) try: from arelle.FileSource import openXmlFileStream - for filepath in (self.standardTaxonomiesUrl, + for filepath in (self.standardTaxonomiesUrl, os.path.join(self.modelManager.cntlr.configDir,"xbrlschemafiles.xml")): xmldoc = etree.parse(filepath) # must open with file path for xinclude to know base of file xmldoc.xinclude() # to include elements below root use xpointer(/*/*) @@ -330,8 +330,8 @@ def loadStandardTaxonomiesDict(self): except (EnvironmentError, etree.LxmlError) as err: self.modelManager.cntlr.addToLog(_("Disclosure System \"%(name)s\" import %(importFile)s, error: %(error)s"), - messageCode="arelle:disclosureSystemImportError", - messageArgs={"error": str(err), "name": self.name, "importFile": basename}, + messageCode="arelle:disclosureSystemImportError", + messageArgs={"error": str(err), "name": self.name, "importFile": basename}, level=logging.ERROR) etree.clear_error_log() @@ -348,11 +348,11 @@ def loadMappings(self): except (EnvironmentError, etree.LxmlError) as err: self.modelManager.cntlr.addToLog(_("Disclosure System \"%(name)s\" import %(importFile)s, error: %(error)s"), - messageCode="arelle:disclosureSystemImportError", - messageArgs={"error": str(err), "name": self.name, "importFile": basename}, + messageCode="arelle:disclosureSystemImportError", + messageArgs={"error": str(err), "name": self.name, "importFile": basename}, level=logging.ERROR) etree.clear_error_log() - + def mappedUrl(self, url): if url in self.mappedFiles: mappedUrl = self.mappedFiles[url] @@ -368,7 +368,7 @@ def uriAuthorityValid(self, uri): if self.standardTaxonomiesUrl: return UrlUtil.authority(uri) in self.standardAuthorities return True # no standard authorities to test - + def disallowedHrefOfNamespace(self, href, namespaceUri): if self.standardTaxonomiesUrl: if namespaceUri in self.standardTaxonomiesDict: diff --git a/arelle/FileSource.py b/arelle/FileSource.py index 926fa06f82..05a648a7c9 100644 --- a/arelle/FileSource.py +++ b/arelle/FileSource.py @@ -14,7 +14,7 @@ archivePathSeparators = (".zip" + os.sep, ".tar.gz" + os.sep, ".eis" + os.sep, ".xml" + os.sep, ".xfd" + os.sep, ".frm" + os.sep, '.taxonomyPackage.xml' + os.sep) + \ ((".zip/", ".tar.gz/", ".eis/", ".xml/", ".xfd/", ".frm/", '.taxonomyPackage.xml/') if os.sep != "/" else ()) #acomodate windows and http styles - + archiveFilenameSuffixes = {".zip", ".tar.gz", ".eis", ".xml", ".xfd", ".frm"} POST_UPLOADED_ZIP = os.sep + "POSTupload.zip" @@ -48,12 +48,12 @@ def openFileSource(filename, cntlr=None, sourceZipStream=None, checkIfXmlIsEis=F filesource.select(selection) return filesource # not archived content - return FileSource(filename, cntlr, checkIfXmlIsEis) + return FileSource(filename, cntlr, checkIfXmlIsEis) def archiveFilenameParts(filename, checkIfXmlIsEis=False): # check if path has an archive file plus appended in-archive content reference for archiveSep in archivePathSeparators: - if (filename and + if (filename and archiveSep in filename and (not archiveSep.startswith(".xml") or checkIfXmlIsEis)): filenameParts = filename.partition(archiveSep) @@ -67,14 +67,14 @@ class FileNamedStringIO(io.StringIO): # provide string IO in memory but behave def __init__(self, fileName, *args, **kwargs): super(FileNamedStringIO, self).__init__(*args, **kwargs) self.fileName = fileName - + def close(self): del self.fileName super(FileNamedStringIO, self).close() def __str__(self): return self.fileName - + class FileNamedTextIOWrapper(io.TextIOWrapper): # provide string IO in memory but behave as a fileName string def __init__(self, fileName, *args, **kwargs): super(FileNamedTextIOWrapper, self).__init__(*args, **kwargs) @@ -82,19 +82,19 @@ def __init__(self, fileName, *args, **kwargs): def __str__(self): return self.fileName - + class FileNamedBytesIO(io.BytesIO): # provide Bytes IO in memory but behave as a fileName string def __init__(self, fileName, *args, **kwargs): super(FileNamedBytesIO, self).__init__(*args, **kwargs) self.fileName = fileName - + def close(self): del self.fileName super(FileNamedBytesIO, self).close() def __str__(self): return self.fileName - + class ArchiveFileIOError(IOError): def __init__(self, fileSource, errno, fileName): super(ArchiveFileIOError, self).__init__(errno, @@ -102,7 +102,7 @@ def __init__(self, fileSource, errno, fileName): fileName) self.fileName = fileName self.url = fileSource.url - + class FileSource: def __init__(self, url, cntlr=None, checkIfXmlIsEis=False): global pluginClassMethods @@ -128,7 +128,7 @@ def __init__(self, url, cntlr=None, checkIfXmlIsEis=False): self.referencedFileSources = {} # archive file name, fileSource object self.taxonomyPackage = None # taxonomy package self.mappedPaths = None # remappings of path segments may be loaded by taxonomyPackage manifest - + # for SEC xml files, check if it's an EIS anyway if (not (self.isZip or self.isEis or self.isXfd or self.isRss) and self.type == ".xml"): @@ -145,7 +145,7 @@ def __init__(self, url, cntlr=None, checkIfXmlIsEis=False): if self.cntlr: self.cntlr.addToLog(_("[{0}] {1}").format(type(err).__name__, err)) pass - + def logError(self, err): if self.cntlr: self.cntlr.addToLog(_("[{0}] {1}").format(type(err).__name__, err)) @@ -197,7 +197,7 @@ def open(self, reloadCache=False): #uncomment to save for debugging #with open("c:/temp/test.xml", "wb") as f: # f.write(buf) - + if buf.startswith(b"= 0: return xml[indexOfDeclarationEnd + 2:] return xml - + def saveFile(cntlr, filepath, contents, encoding=None, mode='wt'): if isHttpUrl(filepath): _cacheFilepath = cntlr.webCache.getfilename(filepath) @@ -718,7 +718,7 @@ def saveFile(cntlr, filepath, contents, encoding=None, mode='wt'): os.makedirs(_dirpath) with io.open(filepath, mode, encoding=(encoding or 'utf-8')) as f: f.write(contents) - + # GAE Blobcache gaeMemcache = None GAE_MEMCACHE_MAX_ITEM_SIZE = 900 * 1024 @@ -765,11 +765,11 @@ def gaeSet(key, bytesValue): # stores bytes, not string valye if gaeMemcache is None: from google.appengine.api import memcache as gaeMemcache compressedValue = zlib.compress(bytesValue) - + # delete previous entity with the given key # in order to conserve available memcache space. gaeDelete(key) - + valueSize = len(compressedValue) if valueSize < GAE_MEMCACHE_MAX_ITEM_SIZE: # only one segment return gaeMemcache.set(key, compressedValue, time=GAE_EXPIRE_WEEK) @@ -779,7 +779,7 @@ def gaeSet(key, bytesValue): # stores bytes, not string valye # TODO: use memcache.set_multi() for speedup, but don't forget # about batch operation size limit (32Mb currently). chunk = compressedValue[pos:pos + GAE_MEMCACHE_MAX_ITEM_SIZE] - + # the pos is used for reliable distinction between chunk keys. # the random suffix is used as a counter-measure for distinction # between different values, which can be simultaneously written diff --git a/arelle/FormulaConsisAsser.py b/arelle/FormulaConsisAsser.py index 0e180f5dbd..cf5ea0d92f 100644 --- a/arelle/FormulaConsisAsser.py +++ b/arelle/FormulaConsisAsser.py @@ -26,9 +26,9 @@ def evaluate(xpCtx, varSet, derivedFact): _("Consistency assertion %(id)s formula %(xlinkLabel)s fact %(derivedFact)s has zero precision and no radius is defined, skipping consistency assertion"), modelObject=consisAsser, id=consisAsser.id, xlinkLabel=varSet.xlinkLabel, derivedFact=derivedFact) continue - + # check xbrl validity of new fact - + # find source facts which match derived fact aspectMatchedInputFacts = [] isStrict = consisAsser.isStrict @@ -39,7 +39,7 @@ def evaluate(xpCtx, varSet, derivedFact): dimensionalAspectModel=(varSet.aspectModel == "dimensional")) and (not isNumeric or inputFact.unit.isEqualTo(derivedFact.unit))): aspectMatchedInputFacts.append( inputFact ) - + if len(aspectMatchedInputFacts) == 0: if isStrict: if derivedFact.isNil: @@ -56,7 +56,7 @@ def evaluate(xpCtx, varSet, derivedFact): isSatisfied = False else: isSatisfied = True - + paramQnamesAdded = [] for paramRel in consisAsser.orderedVariableRelationships: paramQname = paramRel.variableQname @@ -68,7 +68,7 @@ def evaluate(xpCtx, varSet, derivedFact): xpCtx.inScopeVars[paramQname] = paramValue acceptance = None for fact in aspectMatchedInputFacts: - if isSatisfied != True: + if isSatisfied != True: break if fact.isNil: if not derivedFact.isNil: diff --git a/arelle/FormulaEvaluator.py b/arelle/FormulaEvaluator.py index 47faecdff8..2cd299d3b2 100644 --- a/arelle/FormulaEvaluator.py +++ b/arelle/FormulaEvaluator.py @@ -38,7 +38,7 @@ def evaluate(xpCtx, varSet, variablesInScope=False, uncoveredAspectFacts=None): else: xpCtx.varBindings = {} uncoveredAspectFacts = {} - xpCtx.evaluations = [] # list of evaluations + xpCtx.evaluations = [] # list of evaluations xpCtx.evaluationHashDicts = {} # hash indexs of evaluations try: xpCtx.variableSet = varSet @@ -79,7 +79,7 @@ def evaluate(xpCtx, varSet, variablesInScope=False, uncoveredAspectFacts=None): messageCodes=("formula:assertionSatisfied", "formula:assertionUnsatisfied")) if xpCtx.formulaOptions.traceVariableSetExpressionResult: xpCtx.modelXbrl.info("formula:trace", - _("Existence Assertion %(xlinkLabel)s \nResult: %(result)s"), + _("Existence Assertion %(xlinkLabel)s \nResult: %(result)s"), modelObject=varSet, xlinkLabel=varSet.xlinkLabel, result=result) if msg is not None: xpCtx.inScopeVars[XbrlConst.qnEaTestExpression] = varSet.test @@ -92,9 +92,9 @@ def evaluate(xpCtx, varSet, variablesInScope=False, uncoveredAspectFacts=None): label=varSet.logLabel(), messageCodes=("message:{variableSetID|xlinkLabel}",)) xpCtx.inScopeVars.pop(XbrlConst.qnEaTestExpression) - if result: + if result: varSet.countSatisfied += 1 - else: + else: varSet.countNotSatisfied += 1 if unsatSeverity == "OK": varSet.countOkMessages += 1 elif unsatSeverity == "WARNING": varSet.countWarningMessages += 1 @@ -105,20 +105,20 @@ def evaluate(xpCtx, varSet, variablesInScope=False, uncoveredAspectFacts=None): modelObject=varSet, xlinkLabel=varSet.xlinkLabel) if xpCtx.formulaOptions.timeVariableSetEvaluation: xpCtx.modelXbrl.info("formula:time", - _("Variable set %(xlinkLabel)s time for %(count)s evaluations: %(time)s"), + _("Variable set %(xlinkLabel)s time for %(count)s evaluations: %(time)s"), modelObject=varSet, xlinkLabel=varSet.xlinkLabel, count=varSet.evaluationNumber, time=format_string(xpCtx.modelXbrl.modelManager.locale, "%.3f", time.time() - timeEvaluationsStarted)) xpCtx.variableSet = None except XPathContext.XPathException as err: xpCtx.modelXbrl.error(err.code, - _("Variable set %(label)s \nException: %(error)s"), + _("Variable set %(label)s \nException: %(error)s"), modelObject=varSet, label=varSet.logLabel(), error=err.message) xpCtx.variableSet = None if xpCtx.formulaOptions.traceVariableSetExpressionResult: xpCtx.modelXbrl.info("formula:trace", _("Variable set %(xlinkLabel)s evaluations: %(evaluations)s x %(variables)s"), modelObject=varSet, xlinkLabel=varSet.xlinkLabel, - evaluations=len(xpCtx.evaluations), + evaluations=len(xpCtx.evaluations), variables=max(len(e) for e in xpCtx.evaluations) if xpCtx.evaluations else 0) del xpCtx.evaluations[:] # dereference xpCtx.evaluationHashDicts.clear() @@ -129,8 +129,8 @@ def evaluate(xpCtx, varSet, variablesInScope=False, uncoveredAspectFacts=None): vb.close() # dereference xpCtx.varBindings.clear() # dereference uncoveredAspectFacts.clear() # dereference - pass - + pass + def evaluateVar(xpCtx, varSet, varIndex, cachedFilteredFacts, uncoveredAspectFacts): if varIndex == len(varSet.orderedVariableRelationships): # check if all fact vars are fallen back @@ -138,7 +138,7 @@ def evaluateVar(xpCtx, varSet, varIndex, cachedFilteredFacts, uncoveredAspectFac for vb in xpCtx.varBindings.values(): if vb.isFactVar: anyFactVar = True - if not vb.isFallback: anyBoundFactVar = True + if not vb.isFallback: anyBoundFactVar = True if xpCtx.varBindings and anyFactVar and not anyBoundFactVar: if xpCtx.formulaOptions.traceVariableSetExpressionResult: xpCtx.modelXbrl.info("formula:trace", @@ -159,7 +159,7 @@ def evaluateVar(xpCtx, varSet, varIndex, cachedFilteredFacts, uncoveredAspectFac if xpCtx.formulaOptions.timeVariableSetEvaluation: now = time.time() xpCtx.modelXbrl.info("formula:time", - _("Variable set %(xlinkLabel)s skipped evaluation %(count)s: %(time)s sec"), + _("Variable set %(xlinkLabel)s skipped evaluation %(count)s: %(time)s sec"), modelObject=varSet, xlinkLabel=varSet.xlinkLabel, count=varSet.evaluationNumber, time=format_string(xpCtx.modelXbrl.modelManager.locale, "%.3f", now - varSet.timeEvaluationStarted)) varSet.timeEvaluationStarted = now @@ -170,27 +170,27 @@ def evaluateVar(xpCtx, varSet, varIndex, cachedFilteredFacts, uncoveredAspectFac for vQn, vBoundFact in thisEvaluation.items(): # varQn, fact or tuple of facts bound to var # while i >= len(xpCtx.evaluationHashDicts): xpCtx.evaluationHashDicts.append(defaultdict(set)) if vQn not in xpCtx.evaluationHashDicts: xpCtx.evaluationHashDicts[vQn] = defaultdict(set) - xpCtx.evaluationHashDicts[vQn][hash(vBoundFact)].add(len(xpCtx.evaluations)) # hash and eval index + xpCtx.evaluationHashDicts[vQn][hash(vBoundFact)].add(len(xpCtx.evaluations)) # hash and eval index xpCtx.evaluations.append(thisEvaluation) # complete evaluations tuple # evaluate preconditions for precondition in varSet.preconditions: result = precondition.evalTest(xpCtx) if xpCtx.formulaOptions.traceVariableSetExpressionResult: xpCtx.modelXbrl.info("formula:trace", - _("Variable set %(xlinkLabel)s \nPrecondition %(precondition)s \nResult: %(result)s"), + _("Variable set %(xlinkLabel)s \nPrecondition %(precondition)s \nResult: %(result)s"), modelObject=varSet, xlinkLabel=varSet.xlinkLabel, precondition=precondition.xlinkLabel, result=result) if not result: # precondition blocks evaluation if xpCtx.formulaOptions.timeVariableSetEvaluation: varSet.evaluationNumber += 1 now = time.time() xpCtx.modelXbrl.info("formula:time", - _("Variable set %(xlinkLabel)s precondition blocked evaluation %(count)s: %(time)s sec"), + _("Variable set %(xlinkLabel)s precondition blocked evaluation %(count)s: %(time)s sec"), modelObject=varSet, xlinkLabel=varSet.xlinkLabel, count=varSet.evaluationNumber, time=format_string(xpCtx.modelXbrl.modelManager.locale, "%.3f", now - varSet.timeEvaluationStarted)) varSet.timeEvaluationStarted = now if xpCtx.isRunTimeExceeded: raise XPathContext.RunTimeExceededException() return - + # evaluate variable set if isinstance(varSet, ModelExistenceAssertion): varSet.evaluationsCount += 1 @@ -224,14 +224,14 @@ def evaluateVar(xpCtx, varSet, varIndex, cachedFilteredFacts, uncoveredAspectFac label=varSet.logLabel(), messageCodes=("message:{variableSetID|xlinkLabel}",)) xpCtx.inScopeVars.pop(XbrlConst.qnVaTestExpression) - if result: + if result: varSet.countSatisfied += 1 - else: + else: varSet.countNotSatisfied += 1 if unsatSeverity == "OK": varSet.countOkMessages += 1 elif unsatSeverity == "WARNING": varSet.countWarningMessages += 1 elif unsatSeverity == "ERROR": varSet.countErrorMessages += 1 - + if ((msg is None and not result and xpCtx.formulaOptions.traceUnmessagedUnsatisfiedAssertions and unsatSeverity in ("WARNING","ERROR")) or (xpCtx.formulaOptions.traceSatisfiedAssertions and result) or ((xpCtx.formulaOptions.traceUnsatisfiedAssertions or @@ -268,7 +268,7 @@ def evaluateVar(xpCtx, varSet, varIndex, cachedFilteredFacts, uncoveredAspectFac xpCtx.modelXbrl.info("formula:trace", _("%(variableSetType)s %(xlinkLabel)s{0} \nExpression: %(expression)s \nEvaluated: %(evaluatedExpression)s \nResult: %(result)s") .format(" \n%(label)s" if label else ""), - modelObject=varSet, variableSetType=traceOf, xlinkLabel=varSet.xlinkLabel, + modelObject=varSet, variableSetType=traceOf, xlinkLabel=varSet.xlinkLabel, label=label, result=result, expression=expression, evaluatedExpression=''.join(xpCtx.traceEffectiveVariableValue(varSet,expr) for grp in expressionVariablesPattern.findall(expression) @@ -280,17 +280,17 @@ def evaluateVar(xpCtx, varSet, varIndex, cachedFilteredFacts, uncoveredAspectFac if varSet.hasConsistencyAssertion: from arelle import FormulaConsisAsser FormulaConsisAsser.evaluate(xpCtx, varSet, newFact) - + if xpCtx.formulaOptions.timeVariableSetEvaluation: varSet.evaluationNumber += 1 now = time.time() xpCtx.modelXbrl.info("formula:time", - _("Variable set %(xlinkLabel)s completed evaluation %(count)s: %(time)s sec"), + _("Variable set %(xlinkLabel)s completed evaluation %(count)s: %(time)s sec"), modelObject=varSet, xlinkLabel=varSet.xlinkLabel, count=varSet.evaluationNumber, time=format_string(xpCtx.modelXbrl.modelManager.locale, "%.3f", now - varSet.timeEvaluationStarted)) varSet.timeEvaluationStarted = now if xpCtx.isRunTimeExceeded: raise XPathContext.RunTimeExceededException() - + # do dependent variable scope relationships for varScopeRel in xpCtx.modelXbrl.relationshipSet(XbrlConst.variablesScope).fromModelObject(varSet): try: @@ -301,7 +301,7 @@ def evaluateVar(xpCtx, varSet, varIndex, cachedFilteredFacts, uncoveredAspectFac vb = VariableBinding(xpCtx, varScopeRel) vb.yieldedEvaluation = result vb.yieldedFact = newFact - overriddenVarBinding = xpCtx.varBindings.get(resultQname) + overriddenVarBinding = xpCtx.varBindings.get(resultQname) xpCtx.varBindings[resultQname] = vb evaluate(xpCtx, varScopeRel.toModelObject, True, uncoveredAspectFacts) if resultQname: @@ -314,9 +314,9 @@ def evaluateVar(xpCtx, varSet, varIndex, cachedFilteredFacts, uncoveredAspectFac vb.close() # dereference except XPathContext.XPathException as err: xpCtx.modelXbrl.error(err.code, - _("Variable set chained in scope of variable set %(variableset)s \nException: \n%(error)s"), + _("Variable set chained in scope of variable set %(variableset)s \nException: \n%(error)s"), modelObject=(varSet, varScopeRel.toModelObject), variableSet=varSet.logLabel(), error=err.message) - + else: # produce variable bindings varRel = varSet.orderedVariableRelationships[varIndex] @@ -332,7 +332,7 @@ def evaluateVar(xpCtx, varSet, varIndex, cachedFilteredFacts, uncoveredAspectFac facts, vb.aspectsDefined, vb.aspectsCovered = cachedFilteredFacts[varQname] if xpCtx.formulaOptions.traceVariableFilterWinnowing: xpCtx.modelXbrl.info("formula:trace", - _("Fact Variable %(variable)s: start with %(factCount)s facts previously cached after explicit filters"), + _("Fact Variable %(variable)s: start with %(factCount)s facts previously cached after explicit filters"), modelObject=var, variable=varQname, factCount=len(facts)) else: if var.fromInstanceQnames: @@ -345,25 +345,25 @@ def evaluateVar(xpCtx, varSet, varIndex, cachedFilteredFacts, uncoveredAspectFac facts = cachedFilteredFacts[groupFilteredFactsKey] if xpCtx.formulaOptions.traceVariableFilterWinnowing: xpCtx.modelXbrl.info("formula:trace", - _("Fact Variable %(variable)s: start with %(factCount)s facts previously cached before variable filters"), + _("Fact Variable %(variable)s: start with %(factCount)s facts previously cached before variable filters"), modelObject=var, variable=varQname, factCount=len(facts)) else: facts = set.union(*[(inst.factsInInstance if varHasNilFacts else inst.nonNilFactsInInstance) for inst in vb.instances]) if xpCtx.formulaOptions.traceVariableFilterWinnowing: xpCtx.modelXbrl.info("formula:trace", - _("Fact Variable %(variable)s filtering: start with %(factCount)s facts"), + _("Fact Variable %(variable)s filtering: start with %(factCount)s facts"), modelObject=var, variable=varQname, factCount=len(facts)) - + checkVarSetFilterInfo(varSet) facts = trialFilterFacts(xpCtx, vb, facts, varSet.groupFilterRelationships, "group", varSet=varSet) - + vb.aspectsCovered.clear() # group boolean sub-filters may have covered aspects cachedFilteredFacts[groupFilteredFactsKey] = facts checkVarFilterInfo(var) facts = trialFilterFacts(xpCtx, vb, facts, var.filterRelationships, None, var=var) # also finds covered aspects (except aspect cover filter dims, not known until after this complete pass) - + # adding dim aspects must be done after explicit filterin for fact in facts: if fact.isItem and fact.context is not None: @@ -376,11 +376,11 @@ def evaluateVar(xpCtx, varSet, varIndex, cachedFilteredFacts, uncoveredAspectFac if any((_vb.isFactVar and not _vb.isFallback) for _vb in xpCtx.varBindings.values()): factCount = len(facts) facts = implicitFilter(xpCtx, vb, facts, uncoveredAspectFacts) - - if (considerFallback and varHasNoVariableDependencies and + + if (considerFallback and varHasNoVariableDependencies and factCount and - - len(facts) > 0 and + + len(facts) > 0 and len(xpCtx.varBindings) > 1): numVbAspectsdDefined = len(vb.aspectsDefined) trial_for_svc_311_1_like = True @@ -398,30 +398,30 @@ def evaluateVar(xpCtx, varSet, varIndex, cachedFilteredFacts, uncoveredAspectFac else: numNotCompletlyDefinedBindings += 1 break - if numNotCompletlyDefinedBindings == 0: + if numNotCompletlyDefinedBindings == 0: considerFallback = False else: if all(len(_vb.aspectsDefined) == numVbAspectsdDefined for _vb in xpCtx.varBindings.values()): considerFallback = False - + vb.facts = facts if xpCtx.formulaOptions.traceVariableFiltersResult: xpCtx.modelXbrl.info("formula:trace", - _("Fact Variable %(variable)s: filters result %(result)s"), + _("Fact Variable %(variable)s: filters result %(result)s"), modelObject=var, variable=varQname, result=str(vb.facts)) if considerFallback: vb.values = xpCtx.evaluate(var.fallbackValueProg) if xpCtx.formulaOptions.traceVariableExpressionResult: xpCtx.modelXbrl.info("formula:trace", - _("Fact Variable %(variable)s: fallbackValue result %(result)s"), + _("Fact Variable %(variable)s: fallbackValue result %(result)s"), modelObject=var, variable=varQname, result=str(vb.values)) elif vb.isGeneralVar: # general variable if var.fromInstanceQnames: - contextItem = [inst.modelDocument.targetXbrlRootElement - for qn in var.fromInstanceQnames + contextItem = [inst.modelDocument.targetXbrlRootElement + for qn in var.fromInstanceQnames for instSeq in (xpCtx.inScopeVars[qn],) - for inst in (instSeq if isinstance(instSeq,(list,tuple)) else (instSeq,)) - ] + for inst in (instSeq if isinstance(instSeq,(list,tuple)) else (instSeq,)) + ] else: contextItem = xpCtx.modelXbrl.modelDocument.targetXbrlRootElement # default is standard input instance vb.values = xpCtx.flattenSequence( xpCtx.evaluate(var.selectProg, contextItem=contextItem) ) @@ -432,7 +432,7 @@ def evaluateVar(xpCtx, varSet, varIndex, cachedFilteredFacts, uncoveredAspectFac elif vb.isParameter: vb.parameterValue = xpCtx.inScopeVars.get(var.parameterQname) # recurse partitions, preserve overlaid var bindings and inScopeVars - overriddenVarBinding = xpCtx.varBindings.get(varQname) + overriddenVarBinding = xpCtx.varBindings.get(varQname) xpCtx.varBindings[varQname] = vb for evaluationResult in vb.evaluationResults: overriddenInScopeVar = xpCtx.inScopeVars.get(varQname) @@ -446,7 +446,7 @@ def evaluateVar(xpCtx, varSet, varIndex, cachedFilteredFacts, uncoveredAspectFac uncoveredAspectFacts[aspect] = None if vb.hasAspectValueCovered(aspect) else vb.yieldedFact if xpCtx.formulaOptions.traceVariableFiltersResult: xpCtx.modelXbrl.info("formula:trace", - _("%(variableType)s %(variable)s: bound value %(result)s"), + _("%(variableType)s %(variable)s: bound value %(result)s"), modelObject=var, variableType=vb.resourceElementName, variable=varQname, result=str(evaluationResult)) if xpCtx.isRunTimeExceeded: raise XPathContext.RunTimeExceededException() evaluateVar(xpCtx, varSet, varIndex + 1, cachedFilteredFacts, uncoveredAspectFacts) @@ -494,7 +494,7 @@ def checkVarFilterInfo(var): if len(var.noComplHandledFilterRels) > 0 or len(var.complHandledFilterRels) > 0: var.filterInfo = True else: - var.filterInfo = False + var.filterInfo = False def checkVarSetFilterInfo(varSet): try: @@ -525,13 +525,13 @@ def checkVarSetFilterInfo(varSet): if len(varSet.noComplHandledFilterRels) > 0 or len(varSet.complHandledFilterRels) > 0: varSet.filterInfo = True else: - varSet.filterInfo = False - + varSet.filterInfo = False + def trialFilterFacts(xpCtx, vb, facts, filterRelationships, filterType, var=None, varSet=None): typeLbl = filterType + " " if filterType else "" orFilter = filterType == "or" groupFilter = filterType == "group" - if orFilter: + if orFilter: factSet = set() filterInfo = None if filterType is None and var is not None: @@ -562,7 +562,7 @@ def trialFilterFacts(xpCtx, vb, facts, filterRelationships, filterType, var=None facts = outFacts if len(facts) == 0: return facts - + for varFilterRel in filterRelationships: _filter = varFilterRel.toModelObject if isinstance(_filter,ModelFilter): # relationship not constrained to real filters @@ -570,25 +570,25 @@ def trialFilterFacts(xpCtx, vb, facts, filterRelationships, filterType, var=None pass # still continue to do the aspects covered thing else: result = _filter.filter(xpCtx, vb, facts, varFilterRel.isComplemented) - + if xpCtx.formulaOptions.traceVariableFilterWinnowing: allFacts = "" for fact in facts: - allFacts += str(fact) + allFacts += str(fact) xpCtx.modelXbrl.info("formula:trace", - _("Fact Variable %(variable)s %(filterType)s %(filter)s filter %(xlinkLabel)s passes %(factCount)s facts %(allFacts)s"), + _("Fact Variable %(variable)s %(filterType)s %(filter)s filter %(xlinkLabel)s passes %(factCount)s facts %(allFacts)s"), modelObject=vb.var, variable=vb.qname, filterType=typeLbl, filter=_filter.localName, xlinkLabel=_filter.xlinkLabel, factCount=len(result), allFacts=allFacts), - if orFilter: + if orFilter: factSet |= result - else: + else: facts = result if not groupFilter and varFilterRel.isCovered: # block boolean group filters that have cover in subnetworks vb.aspectsCovered |= _filter.aspectsCovered(vb) - if orFilter: + if orFilter: facts = factSet - - # handle now simple model type complement (at the end since it appears to reduce much less) + + # handle now simple model type complement (at the end since it appears to reduce much less) if filterInfo is not None: if filterInfo and len(complHandledFilterRels) > 0: for varFilterRel,dimQname in complHandledFilterRels: @@ -605,12 +605,12 @@ def trialFilterFacts(xpCtx, vb, facts, filterRelationships, filterType, var=None outFacts.add(fact) facts = outFacts return facts - + def filterFacts(xpCtx, vb, facts, filterRelationships, filterType): typeLbl = filterType + " " if filterType else "" orFilter = filterType == "or" groupFilter = filterType == "group" - if orFilter: + if orFilter: factSet = set() for varFilterRel in filterRelationships: _filter = varFilterRel.toModelObject @@ -618,20 +618,20 @@ def filterFacts(xpCtx, vb, facts, filterRelationships, filterType): result = _filter.filter(xpCtx, vb, facts, varFilterRel.isComplemented) if xpCtx.formulaOptions.traceVariableFilterWinnowing: xpCtx.modelXbrl.info("formula:trace", - _("Fact Variable %(variable)s %(filterType)s %(filter)s filter %(xlinkLabel)s passes %(factCount)s facts"), + _("Fact Variable %(variable)s %(filterType)s %(filter)s filter %(xlinkLabel)s passes %(factCount)s facts"), modelObject=vb.var, variable=vb.qname, filterType=typeLbl, filter=_filter.localName, xlinkLabel=_filter.xlinkLabel, factCount=len(result)), - if orFilter: + if orFilter: factSet |= result - else: + else: facts = result if not groupFilter and varFilterRel.isCovered: # block boolean group filters that have cover in subnetworks vb.aspectsCovered |= _filter.aspectsCovered(vb) - if orFilter: + if orFilter: return factSet - else: + else: return facts - + def coverAspectCoverFilterDims(xpCtx, vb, filterRelationships): for varFilterRel in filterRelationships: _filter = varFilterRel.toModelObject @@ -640,13 +640,13 @@ def coverAspectCoverFilterDims(xpCtx, vb, filterRelationships): vb.aspectsCovered |= _filter.dimAspectsCovered(vb) elif isinstance(_filter,ModelBooleanFilter) and varFilterRel.isCovered: coverAspectCoverFilterDims(xpCtx, vb, _filter.filterRelationships) - + def isVbTupleWithOnlyAnUncoveredDimension(xpCtx, vb, facts): # check for special cases (tuple with uncovered dimension matched to anything else) vbUncoveredAspects = (vb.aspectsDefined | xpCtx.dimensionsAspectUniverse) - vb.aspectsCovered - {Aspect.DIMENSIONS} # check if current vb is a tuple with an aspect universe dimension uncovered (46220 v14) - return (vbUncoveredAspects and - all(isinstance(a, QName) for a in vbUncoveredAspects) and + return (vbUncoveredAspects and + all(isinstance(a, QName) for a in vbUncoveredAspects) and all(f.isTuple for f in facts)) def implicitFilter(xpCtx, vb, facts, uncoveredAspectFacts): @@ -657,7 +657,7 @@ def implicitFilter(xpCtx, vb, facts, uncoveredAspectFacts): return [] # matching a tuple with an existing uncovered dimension to items # no matchable aspects return facts # nothing gets implicitly matched - + # check for prior vb a tuple with an aspect universe dimension uncovered (46220 v14) if len(xpCtx.varBindings) == 1: f = uncoveredAspectFacts.get(Aspect.DIMENSIONS) @@ -666,24 +666,24 @@ def implicitFilter(xpCtx, vb, facts, uncoveredAspectFacts): if isVbTupleWithOnlyAnUncoveredDimension(xpCtx, next(iter(xpCtx.varBindings.values())), (f,)): return [] # matching a tuple with an existing uncovered dimension to items return facts # no aspect to implicitly match on - - if xpCtx.formulaOptions.traceVariableFilterWinnowing: # trace shows by aspect by bound variable match - _facts = facts + + if xpCtx.formulaOptions.traceVariableFilterWinnowing: # trace shows by aspect by bound variable match + _facts = facts for aspect in aspects: if uncoveredAspectFacts.get(aspect, "none") is not None: - _facts = [fact - for fact in _facts + _facts = [fact + for fact in _facts if aspectMatches(xpCtx, uncoveredAspectFacts.get(aspect), fact, aspect)] a = str(aspect) if isinstance(aspect,QName) else Aspect.label[aspect] xpCtx.modelXbrl.info("formula:trace", - _("Fact Variable %(variable)s implicit filter %(aspect)s passes %(factCount)s facts"), + _("Fact Variable %(variable)s implicit filter %(aspect)s passes %(factCount)s facts"), modelObject=vb.var, variable=vb.qname, aspect=a, factCount=len(facts)) if len(_facts) == 0: break - else: - testableAspectFacts = [(aspect, uncoveredAspectFacts.get(aspect)) - for aspect in aspects + else: + testableAspectFacts = [(aspect, uncoveredAspectFacts.get(aspect)) + for aspect in aspects if uncoveredAspectFacts.get(aspect, "none") is not None] - #testableAspectFacts = [(aspect, fact) + #testableAspectFacts = [(aspect, fact) # for aspect, fact in uncoveredAspectFacts.items() # if not vb.hasAspectValueCovered(aspect)] if testableAspectFacts: @@ -694,9 +694,9 @@ def implicitFilter(xpCtx, vb, facts, uncoveredAspectFacts): for (aspect, uncoveredAspectFact) in testableAspectFacts)] else: _facts = facts - + return _facts - + def aspectsMatch(xpCtx, fact1, fact2, aspects): return all(aspectMatches(xpCtx, fact1, fact2, aspect) for aspect in aspects) @@ -731,9 +731,9 @@ def aspectMatches(xpCtx, fact1, fact2, aspect): if aspect == 3: # Aspect.ENTITY_IDENTIFIER: return c1.isEntityIdentifierEqualTo(c2) if aspect == 6: # Aspect.COMPLETE_SEGMENT: - return XbrlUtil.nodesCorrespond(fact1.modelXbrl, c1.segment, c2.segment, dts2=fact2.modelXbrl) + return XbrlUtil.nodesCorrespond(fact1.modelXbrl, c1.segment, c2.segment, dts2=fact2.modelXbrl) if aspect == 7: # Aspect.COMPLETE_SCENARIO: - return XbrlUtil.nodesCorrespond(fact1.modelXbrl, c1.scenario, c2.scenario, dts2=fact2.modelXbrl) + return XbrlUtil.nodesCorrespond(fact1.modelXbrl, c1.scenario, c2.scenario, dts2=fact2.modelXbrl) if aspect == 8 or aspect == 9: # aspect in (Aspect.NON_XDT_SEGMENT, Aspect.NON_XDT_SCENARIO): nXs1 = c1.nonDimValues(aspect) nXs2 = c2.nonDimValues(aspect) @@ -743,7 +743,7 @@ def aspectMatches(xpCtx, fact1, fact2, aspect): return False if lXs1 > 0: for i in range(lXs1): - if not XbrlUtil.nodesCorrespond(fact1.modelXbrl, nXs1[i], nXs2[i], dts2=fact2.modelXbrl): + if not XbrlUtil.nodesCorrespond(fact1.modelXbrl, nXs1[i], nXs2[i], dts2=fact2.modelXbrl): return False return True #if aspect == 10: # Aspect.DIMENSIONS: @@ -767,7 +767,7 @@ def aspectMatches(xpCtx, fact1, fact2, aspect): return False dimValue2 = c2.dimValue(aspect) if isinstance(dimValue1, ModelDimensionValue): - if dimValue1.isExplicit: + if dimValue1.isExplicit: if isinstance(dimValue2, QName): if dimValue1.memberQname != dimValue2: return False @@ -775,7 +775,7 @@ def aspectMatches(xpCtx, fact1, fact2, aspect): if dimValue2.isTyped: return False elif dimValue1.memberQname != dimValue2.memberQname: - return False + return False elif dimValue2 is None: return False elif dimValue1.isTyped: @@ -801,7 +801,7 @@ def aspectMatches(xpCtx, fact1, fact2, aspect): if dimValue2.isTyped: return False elif dimValue1 != dimValue2.memberQname: - return False + return False elif dimValue2 is None: # no dim aspect for fact 2 if fact1.modelXbrl == fact2.modelXbrl: # only allowed for multi-instance return False @@ -852,14 +852,14 @@ def evaluationIsUnnecessary(thisEval, xpCtx): vQn for vQn, vBoundFact in thisEval.items() if vBoundFact is not None and vQn in varBindings and - any(varBindings[varRefQn].isFallback and + any(varBindings[varRefQn].isFallback and any(m[varRefQn] is not None for m in matchingEvals) for varRefQn in varBindings[vQn].var.variableRefs() if varRefQn in varBindings)) # detects evaluations which are not different (duplicate) and extra fallback evaluations # vBoundFact may be single fact or tuple of facts - return any(all([vBoundFact == matchingEval[vQn] - for vQn, vBoundFact in thisEval.items() + return any(all([vBoundFact == matchingEval[vQn] + for vQn, vBoundFact in thisEval.items() if vBoundFact is not None and vQn not in vQnDependentOnOtherVarFallenBackButBoundInOtherEval]) for matchingEval in matchingEvals) @@ -875,24 +875,24 @@ def evaluationIsUnnecessary(thisEval, xpCtx): def produceOutputFact(xpCtx, formula, result): priorErrorCount = len(xpCtx.modelXbrl.errors) isTuple = isinstance(formula,ModelTuple) - + # assemble context conceptQname = formulaAspectValue(xpCtx, formula, Aspect.CONCEPT, "xbrlfe:missingConceptRule") if isinstance(conceptQname, VariableBindingError): xpCtx.modelXbrl.error(conceptQname.err, - _("Formula %(label)s concept: %(concept)s"), + _("Formula %(label)s concept: %(concept)s"), modelObject=formula, label=formula.logLabel(), concept=conceptQname.msg) modelConcept = None else: modelConcept = xpCtx.modelXbrl.qnameConcepts[conceptQname] if modelConcept is None or (not modelConcept.isTuple if isTuple else not modelConcept.isItem): xpCtx.modelXbrl.error("xbrlfe:missingConceptRule", - _("Formula %(label)s concept %(concept)s is not a %(element)s"), + _("Formula %(label)s concept %(concept)s is not a %(element)s"), modelObject=formula, label=formula.logLabel(), concept=conceptQname, element=formula.localName) - + outputLocation = formulaAspectValue(xpCtx, formula, Aspect.LOCATION_RULE, None) - if not isTuple: + if not isTuple: # entity entityIdentScheme = formulaAspectValue(xpCtx, formula, Aspect.SCHEME, "xbrlfe:missingEntityIdentifierRule") if isinstance(entityIdentScheme, VariableBindingError): @@ -904,9 +904,9 @@ def produceOutputFact(xpCtx, formula, result): entityIdentValue = formulaAspectValue(xpCtx, formula, Aspect.VALUE, "xbrlfe:missingEntityIdentifierRule") if isinstance(entityIdentValue, VariableBindingError): xpCtx.modelXbrl.error(str(entityIdentScheme), - _("Formula %(label)s entity identifier value: %(entityIdentifier)s"), + _("Formula %(label)s entity identifier value: %(entityIdentifier)s"), modelObject=formula, label=formula.logLabel(), entityIdentifier=entityIdentValue.msg) - + # period periodType = formulaAspectValue(xpCtx, formula, Aspect.PERIOD_TYPE, "xbrlfe:missingPeriodRule") periodStart = None @@ -919,20 +919,20 @@ def produceOutputFact(xpCtx, formula, result): periodEndInstant = formulaAspectValue(xpCtx, formula, Aspect.INSTANT, "xbrlfe:missingPeriodRule") if isinstance(periodEndInstant, VariableBindingError): xpCtx.modelXbrl.error(str(periodEndInstant), - _("Formula %(label)s period end: %(period)s"), + _("Formula %(label)s period end: %(period)s"), modelObject=formula, label=formula.logLabel(), period=periodEndInstant.msg) elif periodType == "duration": periodStart = formulaAspectValue(xpCtx, formula, Aspect.START, "xbrlfe:missingPeriodRule") if isinstance(periodStart, VariableBindingError): xpCtx.modelXbrl.error(str(periodStart), - _("Formula %(label)s period start: %(period)s"), + _("Formula %(label)s period start: %(period)s"), modelObject=formula, label=formula.logLabel(), period=periodStart.msg) periodEndInstant = formulaAspectValue(xpCtx, formula, Aspect.END, "xbrlfe:missingPeriodRule") if isinstance(periodEndInstant, VariableBindingError): xpCtx.modelXbrl.error(str(periodEndInstant), _("Formula %(label)s period end: %(period)s"), modelObject=formula, label=formula.logLabel(), period=periodEndInstant.msg) - + # unit if modelConcept is not None and modelConcept.isNumeric: unitSource = formulaAspectValue(xpCtx, formula, Aspect.UNIT_MEASURES, None) @@ -946,7 +946,7 @@ def produceOutputFact(xpCtx, formula, result): divMultBy = formulaAspectValue(xpCtx, formula, Aspect.DIVIDE_BY, "xbrlfe:missingUnitRule") if isinstance(divMultBy, VariableBindingError): xpCtx.modelXbrl.error(str(multDivBy) if isinstance(divMultBy, VariableBindingError) else "xbrlfe:missingUnitRule", - _("Formula %(label)s unit: %(unit)s"), + _("Formula %(label)s unit: %(unit)s"), modelObject=formula, label=formula.logLabel(), unit=divMultBy.msg) multiplyBy = (); divideBy = () # prevent errors later if bad else: @@ -966,11 +966,11 @@ def produceOutputFact(xpCtx, formula, result): if (Aspect.MULTIPLY_BY not in formula.aspectValues and Aspect.MULTIPLY_BY not in formula.aspectProgs and Aspect.DIVIDE_BY not in formula.aspectValues and Aspect.DIVIDE_BY not in formula.aspectProgs): xpCtx.modelXbrl.error("xbrlfe:missingUnitRule", - _("Formula %(label)s"), + _("Formula %(label)s"), modelObject=formula, label=formula.logLabel()) multiplyBy = (XbrlConst.qnXbrliPure,) - - + + # dimensions segOCCs = [] scenOCCs = [] @@ -985,14 +985,14 @@ def produceOutputFact(xpCtx, formula, result): if isinstance(dimValue, VariableBindingError): xpCtx.modelXbrl.error(dimErr, _("Formula %(label)s dimension %(dimension)s: %(value)s"), - modelObject=formula, label=formula.logLabel(), + modelObject=formula, label=formula.logLabel(), dimension=dimQname, value=dimValue.msg) elif dimConcept.isTypedDimension: if isinstance(dimValue, list): # result of flatten, always a list if len(dimValue) != 1 or not isinstance(dimValue[0], ModelObject): xpCtx.modelXbrl.error("xbrlfe:wrongXpathResultForTypedDimensionRule", _("Formula %(label)s dimension %(dimension)s value is not a node: %(value)s"), - modelObject=formula, label=formula.logLabel(), + modelObject=formula, label=formula.logLabel(), dimension=dimQname, value=dimValue) continue dimValue = dimValue[0] @@ -1005,21 +1005,21 @@ def produceOutputFact(xpCtx, formula, result): if isinstance(occElt, ModelObject) and occElt.namespaceURI == XbrlConst.xbrldi: xpCtx.modelXbrl.error("xbrlfe:badSubsequentOCCValue", _("Formula %(label)s OCC element %(occ)s covers a dimensional aspect"), - modelObject=(formula,occElt), label=formula.logLabel(), + modelObject=(formula,occElt), label=formula.logLabel(), occ=occElt.elementQname) else: dimAspects = None # non-dimensional segOCCs = formulaAspectValue(xpCtx, formula, Aspect.COMPLETE_SEGMENT, None) scenOCCs = formulaAspectValue(xpCtx, formula, Aspect.COMPLETE_SCENARIO, None) - + if priorErrorCount < len(xpCtx.modelXbrl.errors): return None # had errors, don't produce output fact - + # does context exist in out instance document outputInstanceQname = formula.outputInstanceQname outputXbrlInstance = xpCtx.inScopeVars[outputInstanceQname] xbrlElt = outputXbrlInstance.modelDocument.targetXbrlRootElement - + # in source instance document newFact = None if isTuple: @@ -1028,20 +1028,20 @@ def produceOutputFact(xpCtx, formula, result): else: # add context prevCntx = outputXbrlInstance.matchContext( - entityIdentScheme, entityIdentValue, periodType, periodStart, periodEndInstant, + entityIdentScheme, entityIdentValue, periodType, periodStart, periodEndInstant, dimAspects, segOCCs, scenOCCs) if prevCntx is not None: cntxId = prevCntx.id newCntxElt = prevCntx else: - newCntxElt = outputXbrlInstance.createContext(entityIdentScheme, entityIdentValue, + newCntxElt = outputXbrlInstance.createContext(entityIdentScheme, entityIdentValue, periodType, periodStart, periodEndInstant, conceptQname, dimAspects, segOCCs, scenOCCs, afterSibling=xpCtx.outputLastContext.get(outputInstanceQname), beforeSibling=xpCtx.outputFirstFact.get(outputInstanceQname)) cntxId = newCntxElt.id xpCtx.outputLastContext[outputInstanceQname] = newCntxElt # does unit exist - + # add unit if modelConcept.isNumeric: prevUnit = outputXbrlInstance.matchUnit(multiplyBy, divideBy) @@ -1049,12 +1049,12 @@ def produceOutputFact(xpCtx, formula, result): unitId = prevUnit.id newUnitElt = prevUnit else: - newUnitElt = outputXbrlInstance.createUnit(multiplyBy, divideBy, + newUnitElt = outputXbrlInstance.createUnit(multiplyBy, divideBy, afterSibling=xpCtx.outputLastUnit.get(outputInstanceQname), beforeSibling=xpCtx.outputFirstFact.get(outputInstanceQname)) unitId = newUnitElt.id xpCtx.outputLastUnit[outputInstanceQname] = newUnitElt - + # add fact attrs = [("contextRef", cntxId)] precision = None @@ -1066,8 +1066,8 @@ def produceOutputFact(xpCtx, formula, result): if valueSeqLen > 1: xpCtx.modelXbrl.error("xbrlfe:nonSingletonOutputValue", _("Formula %(label)s value is a sequence of length %(valueSequenceLength)s"), - modelObject=formula, label=formula.logLabel(), valueSequenceLength=valueSeqLen) - else: + modelObject=formula, label=formula.logLabel(), valueSequenceLength=valueSeqLen) + else: if valueSeqLen == 0: #xsi:nil if no value attrs.append((XbrlConst.qnXsiNil, "true")) v = None @@ -1083,11 +1083,11 @@ def produceOutputFact(xpCtx, formula, result): else: precision = 0 attrs.append(("precision", precision)) - + x = value[0] if isinstance(x,float): if (isnan(x) or - (precision and (isinf(precision) or precision == 0)) or + (precision and (isinf(precision) or precision == 0)) or (decimals and isinf(decimals))): v = xsString(xpCtx, None, x) elif decimals is not None: @@ -1100,7 +1100,7 @@ def produceOutputFact(xpCtx, formula, result): v = xsString(xpCtx, None, x) elif isinstance(x,Decimal): if (x.is_nan() or - (precision and (isinf(precision) or precision == 0)) or + (precision and (isinf(precision) or precision == 0)) or (decimals and isinf(decimals))): v = xsString(xpCtx, None, x) elif decimals is not None: @@ -1128,16 +1128,16 @@ def produceOutputFact(xpCtx, formula, result): def formulaAspectValue(xpCtx, formula, aspect, srcMissingErr): ruleValue = formula.evaluateRule(xpCtx, aspect) - + if ruleValue is not None: - if aspect in (Aspect.CONCEPT, + if aspect in (Aspect.CONCEPT, Aspect.VALUE, Aspect.SCHEME, Aspect.PERIOD_TYPE, Aspect.START, Aspect.END, Aspect.INSTANT, ): return ruleValue if isinstance(aspect,QName) and ruleValue != XbrlConst.qnFormulaDimensionSAV: return ruleValue - + sourceQname = formula.source(aspect) formulaUncovered = sourceQname == XbrlConst.qnFormulaUncovered if aspect == Aspect.LOCATION_RULE and sourceQname is None: @@ -1153,7 +1153,7 @@ def formulaAspectValue(xpCtx, formula, aspect, srcMissingErr): aspectSourceValue = xbrlfe_undefinedSAV # other then dimensions, absent is an error else: - aspectSourceValue = VariableBindingError(srcMissingErr, + aspectSourceValue = VariableBindingError(srcMissingErr, _("neither source {0}, nor an aspect rule, were found.") .format(sourceQname if sourceQname else '')) for vb in xpCtx.varBindings.values(): @@ -1167,17 +1167,17 @@ def formulaAspectValue(xpCtx, formula, aspect, srcMissingErr): if not vb.isBindAsSequence or vb.hasAspectValueUncovered(aspect): aspectSourceValue = vb.aspectValue(aspect) else: - aspectSourceValue = VariableBindingError("xbrlfe:sequenceSAVConflicts", + aspectSourceValue = VariableBindingError("xbrlfe:sequenceSAVConflicts", _("source, {0}, contains the QName of a fact variable that binds as a sequence where that fact's aspect rule covers this filtered aspect") .format(sourceQname)) break elif aspect == Aspect.LOCATION_RULE and sourceQname == vb.qname: aspectSourceValue = vb.aspectValue(aspect) break - - + + # modify by any specific rules - if aspect in (Aspect.CONCEPT, Aspect.LOCATION_RULE, + if aspect in (Aspect.CONCEPT, Aspect.LOCATION_RULE, Aspect.VALUE, Aspect.SCHEME, Aspect.PERIOD_TYPE, Aspect.START, Aspect.END, Aspect.INSTANT, ) or isinstance(aspect,QName): @@ -1212,7 +1212,7 @@ def formulaAspectValue(xpCtx, formula, aspect, srcMissingErr): if ruleValue: occFragments.extend(ruleValue[1 if occEmpty else 0:]) return occFragments - + return None def uncoveredAspectValue(xpCtx, aspect): @@ -1233,7 +1233,7 @@ def uncoveredVariableSetAspects(xpCtx): for vb in xpCtx.varBindings.values(): if vb.isFactVar and not vb.isFallback: aspectsCovered |= vb.aspectsCovered - aspectsDefined |= vb.aspectsDefined + aspectsDefined |= vb.aspectsDefined return (aspectsDefined - aspectsCovered) class VariableBindingError: @@ -1256,9 +1256,9 @@ def orderAspects(aspects): for key in sorted(d.keys()): result.append(d[key]) return result - + xbrlfe_undefinedSAV = VariableBindingError("xbrlfe:undefinedSAV") - + class VariableBinding: def __init__(self, xpCtx, varRel=None, boundFact=None): self.xpCtx = xpCtx @@ -1278,15 +1278,15 @@ def __init__(self, xpCtx, varRel=None, boundFact=None): self.yieldedFactResult = None self.isFallback = False self.instances = ([inst - for qn in self.var.fromInstanceQnames + for qn in self.var.fromInstanceQnames for inst in xpCtx.flattenSequence(xpCtx.inScopeVars[qn])] - if self.var is not None and self.var.fromInstanceQnames + if self.var is not None and self.var.fromInstanceQnames else [xpCtx.modelXbrl]) - + def close(self): self.__dict__.clear() # dereference pass - + @property def resourceElementName(self): if self.isFactVar: return _("Fact Variable") @@ -1296,7 +1296,7 @@ def resourceElementName(self): elif isinstance(self.var, ModelFormula): return _("Formula") elif isinstance(self.var, ModelValueAssertion): return _("ValueAssertion") elif isinstance(self.var, ModelExistenceAssertion): return _("ExistenceAssertion") - + def matchesSubPartitions(self, partition, aspects): if self.var.matches == "true": return [partition] @@ -1327,7 +1327,7 @@ def addSubpartition(l): subpartitions[j][i] = matchedFact addSubpartition(0) return subpartitions - + @property def evaluationResults(self): if self.isFactVar: @@ -1370,12 +1370,12 @@ def evaluationResults(self): self.yieldedEvaluation = None self.isFallback = False yield self.parameterValue - + def matchableBoundFact(self, fbVars): # return from this function has to be hashable - if (self.isFallback or self.isParameter + if (self.isFallback or self.isParameter # remove to allow different gen var evaluations: or self.isGeneralVar ##or (self.isGeneralVar and not fbVars.isdisjoint(self.var.variableRefs()))): - or self.isGeneralVar + or self.isGeneralVar # or not fbVars.isdisjoint(self.var.variableRefs()) ): return None @@ -1384,34 +1384,34 @@ def matchableBoundFact(self, fbVars): # return from this function has to be has if self.isFormulaResult: return self.yieldedFact return self.yieldedEvaluation - + def hasDimension(self, dimension): return dimension in self.definedDimensions - + def hasDimensionValueDefined(self, dimension): return dimension in self.definedDimensions - + def definedDimensions(self, dimension): return self.yieldedFact.context.dimAspects(self.xpCtx.defaultDimensionAspects) if self.yieldedFact.isItem and self.yieldedFact.context is not None else set() - + def isDimensionalValid(self, dimension): return False - + def hasAspectValueUncovered(self, aspect): if aspect in aspectModelAspect: aspect = aspectModelAspect[aspect] return aspect in self.aspectsDefined and aspect not in self.aspectsCovered - + def hasAspectValueCovered(self, aspect): if aspect in aspectModelAspect: aspect = aspectModelAspect[aspect] return aspect in self.aspectsCovered - + def aspectsNotCovered(self, aspects): return set(a for a in aspects if not self.hasAspectValueCovered(a)) - + def hasAspectValueDefined(self, aspect): if aspect in aspectModelAspect: aspect = aspectModelAspect[aspect] return aspect in self.aspectsDefined - + def aspectValue(self, aspect): fact = self.yieldedFact if fact is None: @@ -1461,5 +1461,5 @@ def aspectValue(self, aspect): return fact.unit.measures return None - - + + diff --git a/arelle/FunctionCustom.py b/arelle/FunctionCustom.py index 42ee75f9c3..cb455bb119 100644 --- a/arelle/FunctionCustom.py +++ b/arelle/FunctionCustom.py @@ -10,13 +10,13 @@ from arelle.ModelInstanceObject import ModelDimensionValue from arelle.PythonUtil import flattenSequence from decimal import Decimal - + class fnFunctionNotAvailable(Exception): def __init__(self): self.args = ("custom function not available",) def __repr__(self): return self.args[0] - + def call(xc, p, qname, contextItem, args): try: cfSig = xc.modelXbrl.modelCustomFunctionSignatures[qname, len(args)] @@ -29,7 +29,7 @@ def call(xc, p, qname, contextItem, args): raise XPathContext.FunctionNotAvailable("custom function:{0}".format(str(qname))) def callCfi(xc, p, qname, cfSig, contextItem, args): - if len(args) != len(cfSig.inputTypes): + if len(args) != len(cfSig.inputTypes): raise XPathContext.FunctionNumArgs() cfi = cfSig.customFunctionImplementation @@ -41,12 +41,12 @@ def callCfi(xc, p, qname, cfSig, contextItem, args): if argName in xc.inScopeVars: overriddenInScopeVars[argName] = xc.inScopeVars[argName] xc.inScopeVars[argName] = args[i] - + if traceEvaluation: xc.modelXbrl.info("formula:trace", _("%(cfi)s(%(arguments)s)"), modelObject=cfi, - cfi=qname, + cfi=qname, arguments=', '.join("{}={}".format(argName, args[i]) for i, argName in enumerate(inputNames))) @@ -95,7 +95,7 @@ def callCfi(xc, p, qname, cfSig, contextItem, args): raise XPathContext.FunctionArgType("output",cfSig.outputType,result) return result -# for test case 22015 v01 +# for test case 22015 v01 def my_fn_PDxEV(xc, p, contextItem, args): if len(args) != 2: raise XPathContext.FunctionNumArgs() PDseq = flattenSequence(args[0]) diff --git a/arelle/FunctionFn.py b/arelle/FunctionFn.py index bc0d6e9b31..e39e3c9e6f 100644 --- a/arelle/FunctionFn.py +++ b/arelle/FunctionFn.py @@ -16,25 +16,25 @@ from lxml import etree DECIMAL_5 = Decimal(.5) - + class fnFunctionNotAvailable(Exception): def __init__(self): self.args = ("fn function not available",) def __repr__(self): return self.args[0] - + def call(xc, p, localname, contextItem, args): try: if localname not in fnFunctions: raise fnFunctionNotAvailable return fnFunctions[localname](xc, p, contextItem, args) except fnFunctionNotAvailable: raise XPathContext.FunctionNotAvailable("fn:{0}".format(localname)) - + def node_name(xc, p, contextItem, args): node = nodeArg(xc, args, 0, "node()?", missingArgFallback=contextItem, emptyFallback=()) - if node != (): + if node != (): return qname(node) - return () + return () def nilled(xc, p, contextItem, args): node = nodeArg(xc, args, 0, "node()?", missingArgFallback=contextItem, emptyFallback=()) @@ -45,13 +45,13 @@ def nilled(xc, p, contextItem, args): def string(xc, p, contextItem, args): if len(args) > 1: raise XPathContext.FunctionNumArgs() item = anytypeArg(xc, args, 0, "item()?", missingArgFallback=contextItem) - if item == (): + if item == (): return '' if isinstance(item, ModelObject) and getattr(item,"xValid", 0) == VALID_NO_CONTENT: x = item.stringValue # represents inner text of this and all subelements else: x = xc.atomize(p, item) - return FunctionXs.xsString( xc, p, x ) + return FunctionXs.xsString( xc, p, x ) def data(xc, p, contextItem, args): if len(args) != 1: raise XPathContext.FunctionNumArgs() @@ -59,7 +59,7 @@ def data(xc, p, contextItem, args): def base_uri(xc, p, contextItem, args): item = anytypeArg(xc, args, 0, "node()?", missingArgFallback=contextItem) - if item == (): + if item == (): return '' if isinstance(item, (ModelObject, ModelDocument)): return UrlUtil.ensureUrl(item.modelDocument.uri) @@ -88,9 +88,9 @@ def fn_dateTime(xc, p, contextItem, args): def fn_abs(xc, p, contextItem, args): if len(args) != 1: raise XPathContext.FunctionNumArgs() x = numericArg(xc, p, args) - if math.isinf(x): + if math.isinf(x): x = float('inf') - elif not math.isnan(x): + elif not math.isnan(x): x = abs(x) return x @@ -105,7 +105,7 @@ def fn_floor(xc, p, contextItem, args): def fn_round(xc, p, contextItem, args): if len(args) != 1: raise XPathContext.FunctionNumArgs() x = numericArg(xc, p, args) - if math.isinf(x) or math.isnan(x): + if math.isinf(x) or math.isnan(x): return x return _INT(x + (DECIMAL_5 if isinstance(x,Decimal) else .5)) # round towards +inf @@ -175,7 +175,7 @@ def substring(xc, p, contextItem, args): if start < 0: length += start if length < 0: length = 0 - start = 0 + start = 0 return string[start:start + length] if start < 0: start = 0 return string[start:] @@ -273,7 +273,7 @@ def regexFlags(xc, p, args, n): else: raise XPathContext.XPathException(p, 'err:FORX0001', _('Regular expression interpretation flag unrecognized: {0}').format(flagsArg)) return f - + def matches(xc, p, contextItem, args): if not 2 <= len(args) <= 3: raise XPathContext.FunctionNumArgs() input = stringArg(xc, args, 0, "xs:string?", emptyFallback="") @@ -282,7 +282,7 @@ def matches(xc, p, contextItem, args): return bool(re.search(pattern,input,flags=regexFlags(xc, p, args, 2))) except sre_constants.error as err: raise XPathContext.XPathException(p, 'err:FORX0002', _('fn:matches regular expression pattern error: {0}').format(err)) - + def replace(xc, p, contextItem, args): if not 3 <= len(args) <= 4: raise XPathContext.FunctionNumArgs() @@ -291,7 +291,7 @@ def replace(xc, p, contextItem, args): fnReplacement = stringArg(xc, args, 2, "xs:string", emptyFallback="") if re.findall(r"(^|[^\\])[$]|[$][^0-9]", fnReplacement): raise XPathContext.XPathException(p, 'err:FORX0004', _('fn:replace pattern \'$\' error in: {0}').format(fnReplacement)) - reReplacement = re.sub(r"[\\][$]", "$", + reReplacement = re.sub(r"[\\][$]", "$", re.sub(r"(^|[^\\])[$]([1-9])", r"\\\2", fnReplacement)) try: return re.sub(pattern,reReplacement,input,flags=regexFlags(xc, p, args, 3)) @@ -322,7 +322,7 @@ def years_from_duration(xc, p, contextItem, args): if d == (): return d if isinstance(d, DayTimeDuration): return 0 if isinstance(d, YearMonthDuration): return d.years - raise XPathContext.FunctionArgType(1,"xs:duration") + raise XPathContext.FunctionArgType(1,"xs:duration") def months_from_duration(xc, p, contextItem, args): if len(args) != 1: raise XPathContext.FunctionNumArgs() @@ -330,7 +330,7 @@ def months_from_duration(xc, p, contextItem, args): if d == (): return d if isinstance(d, DayTimeDuration): return 0 if isinstance(d, YearMonthDuration): return d.months - raise XPathContext.FunctionArgType(1,"xs:duration") + raise XPathContext.FunctionArgType(1,"xs:duration") def days_from_duration(xc, p, contextItem, args): if len(args) != 1: raise XPathContext.FunctionNumArgs() @@ -338,7 +338,7 @@ def days_from_duration(xc, p, contextItem, args): if d == (): return d if isinstance(d, DayTimeDuration): return d.days if isinstance(d, YearMonthDuration): return d.dayHrsMinsSecs[0] - raise XPathContext.FunctionArgType(1,"xs:duration") + raise XPathContext.FunctionArgType(1,"xs:duration") def hours_from_duration(xc, p, contextItem, args): if len(args) != 1: raise XPathContext.FunctionNumArgs() @@ -346,7 +346,7 @@ def hours_from_duration(xc, p, contextItem, args): if d == (): return d if isinstance(d, DayTimeDuration): return 0 if isinstance(d, YearMonthDuration): return d.dayHrsMinsSecs[1] - raise XPathContext.FunctionArgType(1,"xs:duration") + raise XPathContext.FunctionArgType(1,"xs:duration") def minutes_from_duration(xc, p, contextItem, args): if len(args) != 1: raise XPathContext.FunctionNumArgs() @@ -354,7 +354,7 @@ def minutes_from_duration(xc, p, contextItem, args): if d == (): return d if isinstance(d, DayTimeDuration): return 0 if isinstance(d, YearMonthDuration): return d.dayHrsMinsSecs[2] - raise XPathContext.FunctionArgType(1,"xs:duration") + raise XPathContext.FunctionArgType(1,"xs:duration") def seconds_from_duration(xc, p, contextItem, args): if len(args) != 1: raise XPathContext.FunctionNumArgs() @@ -362,112 +362,112 @@ def seconds_from_duration(xc, p, contextItem, args): if d == (): return d if isinstance(d, DayTimeDuration): return 0 if isinstance(d, YearMonthDuration): return d.dayHrsMinsSecs[2] - raise XPathContext.FunctionArgType(1,"xs:duration") + raise XPathContext.FunctionArgType(1,"xs:duration") def year_from_dateTime(xc, p, contextItem, args): if len(args) != 1: raise XPathContext.FunctionNumArgs() d = anytypeArg(xc, args, 0, 'dateTime', missingArgFallback=()) if d == (): return d if isinstance(d, DateTime): return d.year - raise XPathContext.FunctionArgType(1,"xs:dateTime") + raise XPathContext.FunctionArgType(1,"xs:dateTime") def month_from_dateTime(xc, p, contextItem, args): if len(args) != 1: raise XPathContext.FunctionNumArgs() d = anytypeArg(xc, args, 0, 'dateTime', missingArgFallback=()) if d == (): return d if isinstance(d, DateTime): return d.month - raise XPathContext.FunctionArgType(1,"xs:dateTime") + raise XPathContext.FunctionArgType(1,"xs:dateTime") def day_from_dateTime(xc, p, contextItem, args): if len(args) != 1: raise XPathContext.FunctionNumArgs() d = anytypeArg(xc, args, 0, 'dateTime', missingArgFallback=()) if d == (): return d if isinstance(d, DateTime): return d.day - raise XPathContext.FunctionArgType(1,"xs:dateTime") + raise XPathContext.FunctionArgType(1,"xs:dateTime") def hours_from_dateTime(xc, p, contextItem, args): if len(args) != 1: raise XPathContext.FunctionNumArgs() d = anytypeArg(xc, args, 0, 'dateTime', missingArgFallback=()) if d == (): return d if isinstance(d, DateTime): return d.hour - raise XPathContext.FunctionArgType(1,"xs:dateTime") + raise XPathContext.FunctionArgType(1,"xs:dateTime") def minutes_from_dateTime(xc, p, contextItem, args): if len(args) != 1: raise XPathContext.FunctionNumArgs() d = anytypeArg(xc, args, 0, 'dateTime', missingArgFallback=()) if d == (): return d if isinstance(d, DateTime): return d.minute - raise XPathContext.FunctionArgType(1,"xs:dateTime") + raise XPathContext.FunctionArgType(1,"xs:dateTime") def seconds_from_dateTime(xc, p, contextItem, args): if len(args) != 1: raise XPathContext.FunctionNumArgs() d = anytypeArg(xc, args, 0, 'dateTime', missingArgFallback=()) if d == (): return d if isinstance(d, DateTime): return d.second - raise XPathContext.FunctionArgType(1,"xs:dateTime") + raise XPathContext.FunctionArgType(1,"xs:dateTime") def timezone_from_dateTime(xc, p, contextItem, args): if len(args) != 1: raise XPathContext.FunctionNumArgs() d = anytypeArg(xc, args, 0, 'dateTime', missingArgFallback=()) if d == (): return d if isinstance(d, DateTime): return d.tzinfo - raise XPathContext.FunctionArgType(1,"xs:dateTime") + raise XPathContext.FunctionArgType(1,"xs:dateTime") def year_from_date(xc, p, contextItem, args): if len(args) != 1: raise XPathContext.FunctionNumArgs() d = anytypeArg(xc, args, 0, 'dateTime', missingArgFallback=()) if d == (): return d if isinstance(d, DateTime): return d.year - raise XPathContext.FunctionArgType(1,"xs:dateTime") + raise XPathContext.FunctionArgType(1,"xs:dateTime") def month_from_date(xc, p, contextItem, args): if len(args) != 1: raise XPathContext.FunctionNumArgs() d = anytypeArg(xc, args, 0, 'dateTime', missingArgFallback=()) if d == (): return d if isinstance(d, DateTime): return d.month - raise XPathContext.FunctionArgType(1,"xs:dateTime") + raise XPathContext.FunctionArgType(1,"xs:dateTime") def day_from_date(xc, p, contextItem, args): if len(args) != 1: raise XPathContext.FunctionNumArgs() d = anytypeArg(xc, args, 0, 'dateTime', missingArgFallback=()) if d == (): return d if isinstance(d, DateTime): return d.day - raise XPathContext.FunctionArgType(1,"xs:dateTime") + raise XPathContext.FunctionArgType(1,"xs:dateTime") def timezone_from_date(xc, p, contextItem, args): if len(args) != 1: raise XPathContext.FunctionNumArgs() d = anytypeArg(xc, args, 0, 'dateTime', missingArgFallback=()) if d == (): return d if isinstance(d, DateTime): return d.tzinfo - raise XPathContext.FunctionArgType(1,"xs:dateTime") + raise XPathContext.FunctionArgType(1,"xs:dateTime") def hours_from_time(xc, p, contextItem, args): if len(args) != 1: raise XPathContext.FunctionNumArgs() d = anytypeArg(xc, args, 0, 'time', missingArgFallback=()) if d == (): return d if isinstance(d, Time): return d.hour - raise XPathContext.FunctionArgType(1,"xs:time") + raise XPathContext.FunctionArgType(1,"xs:time") def minutes_from_time(xc, p, contextItem, args): if len(args) != 1: raise XPathContext.FunctionNumArgs() d = anytypeArg(xc, args, 0, 'time', missingArgFallback=()) if d == (): return d if isinstance(d, Time): return d.minute - raise XPathContext.FunctionArgType(1,"xs:time") + raise XPathContext.FunctionArgType(1,"xs:time") def seconds_from_time(xc, p, contextItem, args): if len(args) != 1: raise XPathContext.FunctionNumArgs() d = anytypeArg(xc, args, 0, 'time', missingArgFallback=()) if d == (): return d if isinstance(d, Time): return d.second - raise XPathContext.FunctionArgType(1,"xs:time") + raise XPathContext.FunctionArgType(1,"xs:time") def timezone_from_time(xc, p, contextItem, args): if len(args) != 1: raise XPathContext.FunctionNumArgs() d = anytypeArg(xc, args, 0, 'time', missingArgFallback=()) if d == (): return d if isinstance(d, Time): return d.tzinfo - raise XPathContext.FunctionArgType(1,"xs:time") + raise XPathContext.FunctionArgType(1,"xs:time") def adjust_dateTime_to_timezone(xc, p, contextItem, args): raise fnFunctionNotAvailable() @@ -626,7 +626,7 @@ def subsequence(xc, p, contextItem, args): if start < 0: length += start if length < 0: length = 0 - start = 0 + start = 0 return sequence[start:start + length] if start < 0: start = 0 return sequence[start:] @@ -669,7 +669,7 @@ def avg(xc, p, contextItem, args): addends = xc.atomize( p, args[0] ) try: l = len(addends) - if l == 0: + if l == 0: return () # xpath allows empty sequence argument hasFloat = False hasDecimal = False @@ -691,7 +691,7 @@ def fn_max(xc, p, contextItem, args): if len(args) != 1: raise XPathContext.FunctionNumArgs() comparands = xc.atomize( p, args[0] ) try: - if len(comparands) == 0: + if len(comparands) == 0: return () # xpath allows empty sequence argument if any(isinstance(c, float) and math.isnan(c) for c in comparands): return NaN @@ -703,7 +703,7 @@ def fn_min(xc, p, contextItem, args): if len(args) != 1: raise XPathContext.FunctionNumArgs() comparands = xc.atomize( p, args[0] ) try: - if len(comparands) == 0: + if len(comparands) == 0: return () # xpath allows empty sequence argument if any(isinstance(c, float) and math.isnan(c) for c in comparands): return NaN @@ -715,7 +715,7 @@ def fn_sum(xc, p, contextItem, args): if len(args) != 1: raise XPathContext.FunctionNumArgs() addends = xc.atomize( p, args[0] ) try: - if len(addends) == 0: + if len(addends) == 0: return 0 # xpath allows empty sequence argument hasFloat = False hasDecimal = False @@ -749,7 +749,7 @@ def doc(xc, p, contextItem, args): if not UrlUtil.isValidUriReference(uri): raise XPathContext.XPathException(p, 'err:FODC0005', _('Function xf:doc $uri is not valid {0}').format(uri)) normalizedUri = xc.modelXbrl.modelManager.cntlr.webCache.normalizeUrl( - uri, + uri, xc.progHeader.element.modelDocument.baseForElement(xc.progHeader.element)) if normalizedUri in xc.modelXbrl.urlDocs: return xc.modelXbrl.urlDocs[normalizedUri].xmlDocument @@ -804,7 +804,7 @@ def format_number(xc, p, args): return format_picture(xc.modelXbrl.locale, value, picture) except ValueError as err: raise XPathContext.XPathException(p, 'err:FODF1310', str(err) ) - + fnFunctions = { 'node-name': node_name, 'nilled': nilled, diff --git a/arelle/FunctionIxt.py b/arelle/FunctionIxt.py index abd8801e7f..0426f5d0ed 100644 --- a/arelle/FunctionIxt.py +++ b/arelle/FunctionIxt.py @@ -20,7 +20,7 @@ def __init__(self): self.args = (_("ixt function not available"),) def __repr__(self): return self.args[0] - + def call(xc, p, qn, args): try: _ixtFunction = ixtNamespaceFunctions[qn.namespaceURI][qn.localName] @@ -37,7 +37,7 @@ def __init__(self, pattern, flags=0): self.pattern = pattern self.regex = None self.flags = flags - + def match(self, target): if self.regex is None: self.regex = re.compile(self.pattern, self.flags) @@ -184,10 +184,10 @@ def search(self, target): numCanonicalizationPattern = RePattern(r"^[ \t\n\r]*0*([1-9][0-9]*)?(([.]0*)[ \t\n\r]*$|([.][0-9]*[1-9])0*[ \t\n\r]*$|[ \t\n\r]*$)") monthnumber = {# english - "january":1, "february":2, "march":3, "april":4, "may":5, "june":6, - "july":7, "august":8, "september":9, "october":10, "november":11, "december":12, - "jan":1, "feb":2, "mar":3, "apr":4, "may":5, "jun":6, - "jul":7, "aug":8, "sep":9, "oct":10, "nov":11, "dec":12, + "january":1, "february":2, "march":3, "april":4, "may":5, "june":6, + "july":7, "august":8, "september":9, "october":10, "november":11, "december":12, + "jan":1, "feb":2, "mar":3, "apr":4, "may":5, "jun":6, + "jul":7, "aug":8, "sep":9, "oct":10, "nov":11, "dec":12, # bulgarian "ян":1, "фев":2, "мар":3, "апр":4, "май":5, "маи":5, "юни":6, "юли":7, "авг":8, "сеп":9, "окт":10, "ное":11, "дек":12, @@ -195,49 +195,49 @@ def search(self, target): "jan":1, "feb":2, "mar": 3, "apr":4, "maj":5, "jun":6, "jul":7, "aug":8, "sep":9, "okt":10, "nov":11, "dec":12, # de: german - "jan":1, "jän":1, "jaen":1, "feb":2, "mär":3, "maer":3, "mar":3,"apr":4, + "jan":1, "jän":1, "jaen":1, "feb":2, "mär":3, "maer":3, "mar":3,"apr":4, "mai":5, "jun":6, "jul":7, "aug":8, "sep":9, "okt":10, "nov":11, "dez":12, # el: greek - "ιαν":1, "ίαν":1, "iαν":1, "φεβ":2, "μάρ":3, "μαρ":3, - "απρ":4, "άπρ":4, "απρ":4, "aπρ":4, "αρίλ":4, "άρίλ":4, "αριλ":4, "άριλ":4, "άριλ":4, "αριλ":4, "aρίλ":4, "aριλ":4, - "μαΐ":5, "μαι":5, "μάι":5, "μαϊ":5, "μάϊ":5, "ΜΑΪ́".lower():5, # ΜΑΪ́ has combining diacritical marks not on lower case pattern - "ιούν":6, "ίούν":6, "ίουν":6, "ιουν":6, "ιουν":6, "ιουν":6, "iούν":6, "iουν":6, - "ιούλ":7, "ίούλ":7, "ίουλ":7, "ίουλ":7, "ιουλ":7, "iούλ":7, "iουλ":7, - "αύγ":8, "αυγ":8, + "ιαν":1, "ίαν":1, "iαν":1, "φεβ":2, "μάρ":3, "μαρ":3, + "απρ":4, "άπρ":4, "απρ":4, "aπρ":4, "αρίλ":4, "άρίλ":4, "αριλ":4, "άριλ":4, "άριλ":4, "αριλ":4, "aρίλ":4, "aριλ":4, + "μαΐ":5, "μαι":5, "μάι":5, "μαϊ":5, "μάϊ":5, "ΜΑΪ́".lower():5, # ΜΑΪ́ has combining diacritical marks not on lower case pattern + "ιούν":6, "ίούν":6, "ίουν":6, "ιουν":6, "ιουν":6, "ιουν":6, "iούν":6, "iουν":6, + "ιούλ":7, "ίούλ":7, "ίουλ":7, "ίουλ":7, "ιουλ":7, "iούλ":7, "iουλ":7, + "αύγ":8, "αυγ":8, "σεπ":9, "οκτ":10, "όκτ":10, "oκτ":10, "νοέ":11, "νοε":11, "δεκ":12, # es: spanish (differences) "ene":1, "abr":4, "ago":8, "dic":12, # et: estonian (differences) "jaan":1, "veebr":2, "märts":3, "marts":3, "mai":5, "juuni":6, "juuli":7, "sept":9, "okt":10, "dets":12, # fr: french (differences) - "janv":1, "févr":2, "fevr":2, "mars":3, "avr":4, "mai":5, "juin":6, "juil":7, "août":8, "aout":8, "déc":12, + "janv":1, "févr":2, "fevr":2, "mars":3, "avr":4, "mai":5, "juin":6, "juil":7, "août":8, "aout":8, "déc":12, # hu: hungary (differences) - "márc":3, "marc":3, "ápr":4, "máj":5, "maj":5, "jún":6, "jun":6, "júl":7, "jul":7, "szept":9, "okt":10, + "márc":3, "marc":3, "ápr":4, "máj":5, "maj":5, "jún":6, "jun":6, "júl":7, "jul":7, "szept":9, "okt":10, # it: italy (differences) - "gen":1, "mag":5, "giu":6, "lug":7, "ago":8, "set":9, "ott":10, "dic":12, + "gen":1, "mag":5, "giu":6, "lug":7, "ago":8, "set":9, "ott":10, "dic":12, # lv: latvian (differences) "janv":1, "febr":2, "marts":3, "maijs":5, "jūn":6, "jūl":7, "okt":10, # nl: dutch (differences) - "maa":3, "mrt":3, "mei":5, + "maa":3, "mrt":3, "mei":5, # no: norway - "mai":5, "des":12, + "mai":5, "des":12, # pt: portugese (differences) - "fev":2, "ago":8, "set":9, "out":10, "dez":12, + "fev":2, "ago":8, "set":9, "out":10, "dez":12, # ro: romanian (differences) "ian":1, "iun":6, "iul":7, "noi":11, # sk: skovak (differences) - "máj":5, "maj":5, "jún":6, "júl":7, + "máj":5, "maj":5, "jún":6, "júl":7, # sl: sloveniabn - "avg":8, + "avg":8, } -monthnumbercs = {"ledna":1, "leden":1, "lednu":1, "února":2, "unora":2, "únoru":2, "unoru":2, "únor":2, "unor":2, - "března":3, "brezna":3, "březen":3, "brezen":3, "březnu":3, "breznu":3, "dubna":4, "duben":4, "dubnu":4, +monthnumbercs = {"ledna":1, "leden":1, "lednu":1, "února":2, "unora":2, "únoru":2, "unoru":2, "únor":2, "unor":2, + "března":3, "brezna":3, "březen":3, "brezen":3, "březnu":3, "breznu":3, "dubna":4, "duben":4, "dubnu":4, "května":5, "kvetna":5, "květen":5, "kveten":5, "květnu":5, "kvetnu":5, - "června":6, "cervna":6, "červnu":6, "cervnu":6, "července":7, "cervence":7, + "června":6, "cervna":6, "červnu":6, "cervnu":6, "července":7, "cervence":7, "červen":6, "cerven":6, "červenec":7, "cervenec":7, "červenci":7, "cervenci":7, - "srpna":8, "srpen":8, "srpnu":8, "září":9, "zari":9, - "října":10, "rijna":10, "říjnu":10, "rijnu":10, "říjen":10, "rijen":10, "listopadu":11, "listopad":11, + "srpna":8, "srpen":8, "srpnu":8, "září":9, "zari":9, + "října":10, "rijna":10, "říjnu":10, "rijnu":10, "říjen":10, "rijen":10, "listopadu":11, "listopad":11, "prosince":12, "prosinec":12, "prosinci":12, "led":1, "úno":2, "uno":2, "bře":3, "bre":3, "dub":4, "kvě":5, "kve":5, "čvn":6, "cvn":6, "čvc":7, "cvc":7, "srp":8, "zář":9, "zar":9, @@ -264,12 +264,12 @@ def search(self, target): gregorianHindiMonthNumber = { "\u091C\u0928\u0935\u0930\u0940": "01", - "\u092B\u0930\u0935\u0930\u0940": "02", - "\u092E\u093E\u0930\u094D\u091A": "03", + "\u092B\u0930\u0935\u0930\u0940": "02", + "\u092E\u093E\u0930\u094D\u091A": "03", "\u0905\u092A\u094D\u0930\u0948\u0932": "04", - "\u092E\u0908": "05", + "\u092E\u0908": "05", "\u091C\u0942\u0928": "06", - "\u091C\u0941\u0932\u093E\u0908": "07", + "\u091C\u0941\u0932\u093E\u0908": "07", "\u0905\u0917\u0938\u094D\u0924": "08", "\u0938\u093F\u0924\u0902\u092C\u0930": "09", "\u0905\u0915\u094D\u0924\u0942\u092C\u0930": "10", @@ -284,8 +284,8 @@ def search(self, target): "Asadha":4, "Ashadha":4, "\u0100\u1E63\u0101\u1E0Dha":4, "\u0906\u0937\u093E\u0922":4, "\u0906\u0937\u093E\u0922\u093C":4, "Sravana":5, "Shravana":5, "\u015Ar\u0101va\u1E47a":5, "\u0936\u094D\u0930\u093E\u0935\u0923":5, "\u0938\u093E\u0935\u0928":5, "Bhadra":6, "Bhadrapad":6, "Bh\u0101drapada":6, "Bh\u0101dra":6, "Pro\u1E63\u1E6Dhapada":6, "\u092D\u093E\u0926\u094D\u0930\u092A\u0926":6, "\u092D\u093E\u0926\u094B":6, - "Aswina":7, "Ashwin":7, "Asvina":7, "\u0100\u015Bvina":7, "\u0906\u0936\u094D\u0935\u093F\u0928":7, - "Kartiak":8, "Kartik":8, "Kartika":8, "K\u0101rtika":8, "\u0915\u093E\u0930\u094D\u0924\u093F\u0915":8, + "Aswina":7, "Ashwin":7, "Asvina":7, "\u0100\u015Bvina":7, "\u0906\u0936\u094D\u0935\u093F\u0928":7, + "Kartiak":8, "Kartik":8, "Kartika":8, "K\u0101rtika":8, "\u0915\u093E\u0930\u094D\u0924\u093F\u0915":8, "Agrahayana":9,"Agrah\u0101ya\u1E47a":9,"Margashirsha":9, "M\u0101rga\u015B\u012Br\u1E63a":9, "\u092E\u093E\u0930\u094D\u0917\u0936\u0940\u0930\u094D\u0937":9, "\u0905\u0917\u0939\u0928":9, "Pausa":10, "Pausha":10, "Pau\u1E63a":10, "\u092A\u094C\u0937":10, "Magha":11, "Magh":11, "M\u0101gha":11, "\u092E\u093E\u0918":11, @@ -353,7 +353,7 @@ def sakaToGregorian(sYr, sMo, sDay): # replacement of plug-in sakaCalendar.py wh sMoLength = sakaMonthLength[sMo - 1] if sStartsInLeapYr and sMo == 1: sMoLength += 1 # Chaitra has 1 extra day when starting in gregorian leap years - if sDay < 1 or sDay > sMoLength: + if sDay < 1 or sDay > sMoLength: raise ValueError(_("Saka calendar day error: {0} {1} {2} "), sYr, sMo, sDay) gMo, gDayOffset, gYrOffset = sakaMonthOffset[sMo - 1] # offset Saka to Gregorian by Saka month if sStartsInLeapYr and sMo == 1: @@ -371,10 +371,10 @@ def sakaToGregorian(sYr, sMo, sDay): # replacement of plug-in sakaCalendar.py wh gYr += 1 return (gYr, gMo, gDay) -# see: http://www.i18nguy.com/l10n/emperor-date.html +# see: http://www.i18nguy.com/l10n/emperor-date.html eraStart = {'令和': 2018, '令': 2018, - '\u5E73\u6210': 1988, + '\u5E73\u6210': 1988, '\u5E73': 1988, '\u660E\u6CBB': 1867, '\u660E': 1867, @@ -393,11 +393,11 @@ def canonicalNumber(n): return (m.group(1) or "0") + (m.group(4) or "") return m -# transforms +# transforms def booleanfalse(arg): return 'false' - + def booleantrue(arg): return 'true' @@ -406,58 +406,58 @@ def dateslashus(arg): if m and m.lastindex == 3: return "{0}-{1}-{2}".format(yr4(m.group(3)), z2(m.group(1)), z2(m.group(2))) raise XPathContext.FunctionArgType(0,"xs:date") - + def dateslasheu(arg): m = dateslashPattern.match(arg) if m and m.lastindex == 3: return "{0}-{1}-{2}".format(yr4(m.group(3)), z2(m.group(2)), z2(m.group(1))) raise XPathContext.FunctionArgType(0,"xs:date") - + def datedotus(arg): m = datedotPattern.match(arg) if m and m.lastindex == 3: return "{0}-{1}-{2}".format(yr4(m.group(3)), z2(m.group(1)), z2(m.group(2))) raise XPathContext.FunctionArgType(0,"xs:date") - + def datedoteu(arg): m = datedotPattern.match(arg) if m and m.lastindex == 3: return "{0}-{1}-{2}".format(yr4(m.group(3)), z2(m.group(2)), z2(m.group(1))) raise XPathContext.FunctionArgType(0,"xs:date") - + def datelongusTR1(arg): return datedaymonthyear(arg, dateLongUsTR1Pattern, dy=2, mo=1, yr=3) - + def dateshortusTR1(arg): return datedaymonthyear(arg, dateShortUsTR1Pattern, dy=2, mo=1, yr=3) - + def datelongukTR1(arg): return datedaymonthyear(arg, dateLongUkTR1Pattern) def dateshortukTR1(arg): return datedaymonthyear(arg, dateShortUkTR1Pattern) - + def datelongeu(arg): return datedaymonthyear(arg, dateEuPattern) - + def datedaymonthTR2(arg): m = daymonthPattern.match(arg) if m and m.lastindex == 2: mo = z2(m.group(2)) day = z2(m.group(1)) - if "01" <= day <= maxDayInMo.get(mo, "00"): + if "01" <= day <= maxDayInMo.get(mo, "00"): return "--{0}-{1}".format(mo, day) raise XPathContext.FunctionArgType(0,"xs:gMonthDay") - + def datemonthday(arg): m = monthdayPattern.match(arg) if m and m.lastindex == 2: mo = z2(m.group(1)) day = z2(m.group(2)) - if "01" <= day <= maxDayInMo.get(mo, "00"): + if "01" <= day <= maxDayInMo.get(mo, "00"): return "--{0}-{1}".format(mo, day) raise XPathContext.FunctionArgType(0,"xs:gMonthDay") - + def datedaymonthSlashTR1(arg): m = daymonthslashPattern.match(arg) if m and m.lastindex == 2: @@ -465,7 +465,7 @@ def datedaymonthSlashTR1(arg): day = z2(m.group(1)) return "--{0}-{1}".format(mo, day) raise XPathContext.FunctionArgType(0,"xs:gMonthDay") - + def datemonthdaySlashTR1(arg): m = monthdayslashPattern.match(arg) if m and m.lastindex == 2: @@ -486,7 +486,7 @@ def datedaymonth(arg, pattern, moTbl=monthnumber, dy=1, mo=2, lastindex=2): except KeyError: pass raise XPathContext.FunctionArgType(0,"xs:gMonthDay") - + def datedaymonthbg(arg): return datedaymonth(arg, daymonthBgPattern) @@ -514,19 +514,19 @@ def datedaymonthdk(arg): "01" <= day <= maxDayInMo.get(mo, "00")): return "--{0:02}-{1}".format(mo, day) raise XPathContext.FunctionArgType(0,"xs:gMonthDay") - + def datedaymonthel(arg): return datedaymonth(arg, daymonthElPattern) def datedaymonthen(arg): return datedaymonth(arg, daymonthEnPattern) - + def datedaymonthShortEnTR1(arg): return datedaymonth(arg, daymonthShortEnTR1Pattern, dy=1, mo=2) - + def datedaymonthLongEnTR1(arg): return datedaymonth(arg, daymonthLongEnTR1Pattern, dy=1, mo=2) - + def datemonthdayen(arg): return datedaymonth(arg, monthdayEnPattern, dy=2, mo=1) @@ -592,13 +592,13 @@ def datedaymonthsl(arg): def datedaymonthyearTR2(arg): return datedaymonthyear(arg, daymonthyearPattern, None, dy=1, mo=2, yr=3) - + def datedaymonthyearTR4(arg): return datedaymonthyear(arg.translate(devanagariDigitsTrTable), daymonthyearPattern, None, dy=1, mo=2, yr=3) - -def datemonthdayyear(arg): + +def datemonthdayyear(arg): return datedaymonthyear(arg, monthdayyearPattern, None, dy=2, mo=1, yr=3) - + def datemonthyearTR3(arg): m = monthyearPattern.match(arg) # "(M)M*(Y)Y(YY)", with non-numeric separator, if m and m.lastindex == 2: @@ -606,7 +606,7 @@ def datemonthyearTR3(arg): if "01" <= _mo <= "12": return "{0}-{1:2}".format(yr4(m.group(2)), _mo) raise XPathContext.FunctionArgType(0,"xs:gYearMonth") - + def datemonthyearTR4(arg): m = monthyearPattern.match(arg.translate(devanagariDigitsTrTable)) # "(M)M*(Y)Y(YY)", with non-numeric separator, if m and m.lastindex == 2: @@ -614,7 +614,7 @@ def datemonthyearTR4(arg): if "01" <= _mo <= "12": return "{0}-{1:2}".format(yr4(m.group(2)), _mo) raise XPathContext.FunctionArgType(0,"xs:gYearMonth") - + def dateyearmonth(arg): m = yearmonthPattern.match(arg) # "(Y)Y(YY)*(M)M", with non-numeric separator, if m and m.lastindex == 2: @@ -625,7 +625,7 @@ def dateyearmonth(arg): def dateyearmonthTR4(arg): return dateyearmonth(arg.translate(jpDigitsTrTable)) - + def datemonthyear(arg, pattern, moTbl=monthnumber, mo=1, yr=2, lastindex=2): m = pattern.match(arg) try: @@ -634,7 +634,7 @@ def datemonthyear(arg, pattern, moTbl=monthnumber, mo=1, yr=2, lastindex=2): except KeyError: pass raise XPathContext.FunctionArgType(0,"xs:gYearMonth") - + def datemonthyearbg(arg): return datemonthyear(arg, monthyearBgPattern) @@ -647,7 +647,7 @@ def datemonthyearcy(arg): def datemonthyearde(arg): return datemonthyear(arg, monthyearDePattern) - + def datemonthyeardk(arg): m = monthyearDkPattern.match(arg) if m and m.lastindex == 4: @@ -659,22 +659,22 @@ def datemonthyeardk(arg): (monEnd and not monPer)): return "{0}-{1:02}".format(yr4(m.group(4)), monthnumber[mon3]) raise XPathContext.FunctionArgType(0,"xs:gYearMonth") - + def datemonthyearel(arg): return datemonthyear(arg, monthyearElPattern) def datemonthyearen(arg): return datemonthyear(arg, monthyearEnPattern, mo=1, yr=2) - + def datemonthyearShortEnTR1(arg): return datemonthyear(arg, monthyearShortEnTR1Pattern, mo=1, yr=2) - + def datemonthyearLongEnTR1(arg): return datemonthyear(arg, monthyearLongEnTR1Pattern, mo=1, yr=2) - + def datemonthyeares(arg): return datemonthyear(arg, monthyearEsPattern) - + def dateyearmonthen(arg): return datemonthyear(arg, yearmonthEnPattern, mo=2, yr=1) @@ -696,7 +696,7 @@ def datemonthyearhr(arg): def datemonthyearin(arg): m = monthyearInPattern.match(arg) try: - return "{0}-{1}".format(yr4(devanagariDigitsToNormal(m.group(2))), + return "{0}-{1}".format(yr4(devanagariDigitsToNormal(m.group(2))), gregorianHindiMonthNumber[m.group(1)]) except (AttributeError, IndexError, KeyError): pass @@ -794,7 +794,7 @@ def datedaymonthyearen(arg): def datemonthdayyearen(arg): return datedaymonthyear(arg, monthdayyearEnPattern, dy=2, mo=1, yr=3) - + def datedaymonthyeares(arg): return datedaymonthyear(arg, daymonthyearEsPattern) @@ -1087,7 +1087,7 @@ def numunitdecimalin(arg): } tr2Functions = { - + # 2011-07-31 functions 'booleanfalse': booleanfalse, 'booleantrue': booleantrue, @@ -1111,7 +1111,7 @@ def numunitdecimalin(arg): 'numdotdecimal': numdotdecimal, 'numunitdecimal': numunitdecimal } - + # transformation registry v-3 functions tr3Functions = tr2Functions.copy() # tr3 starts with tr2 and adds more functions tr3Functions.update ({ @@ -1130,7 +1130,7 @@ def numunitdecimalin(arg): # same as v2: 'dateerayearmonthjp': dateerayearmonthjp, # same as v2: 'datemonthday': datemonthday, # same as v2: 'datemonthdayen': datemonthdayen, - # same as v2: 'datemonthdayyear': datemonthdayyear, + # same as v2: 'datemonthdayyear': datemonthdayyear, # same as v2: 'datemonthdayyearen': datemonthdayyearen, 'datemonthyear': datemonthyearTR3, 'datemonthyeardk': datemonthyeardk, @@ -1204,8 +1204,8 @@ def numunitdecimalin(arg): 'date-monthname-day-lt': datemonthdaylt, 'date-monthname-day-year-en': datemonthdayyearen, 'date-month-day': datemonthday, - 'date-month-day-year': datemonthdayyear, - 'date-month-year': datemonthyearTR4, + 'date-month-day-year': datemonthdayyear, + 'date-month-year': datemonthyearTR4, 'date-monthname-year-bg': datemonthyearbg, 'date-monthname-year-cs': datemonthyearcs, 'date-monthname-year-da': datemonthyeardk, diff --git a/arelle/FunctionUtil.py b/arelle/FunctionUtil.py index 76c8a6d24b..59b8d44cfa 100644 --- a/arelle/FunctionUtil.py +++ b/arelle/FunctionUtil.py @@ -20,7 +20,7 @@ def anytypeArg(xc, args, i, type, missingArgFallback=None): if len(item) == 0: return () item = item[0] return item - + def atomicArg(xc, p, args, i, type, missingArgFallback=None, emptyFallback=()): item = anytypeArg(xc, args, i, type, missingArgFallback) if item == (): return emptyFallback @@ -37,7 +37,7 @@ def numericArg(xc, p, args, i=0, missingArgFallback=None, emptyFallback=0, conve item = anytypeArg(xc, args, i, "numeric?", missingArgFallback) if item == (): return emptyFallback numeric = xc.atomize(p, item) - if not isinstance(numeric,_NUM_TYPES): + if not isinstance(numeric,_NUM_TYPES): if convertFallback is None: raise FunctionArgType(i,"numeric?",numeric) try: @@ -50,7 +50,7 @@ def integerArg(xc, p, args, i=0, missingArgFallback=None, emptyFallback=0, conve item = anytypeArg(xc, args, i, "integer?", missingArgFallback) if item == (): return emptyFallback numeric = xc.atomize(p, item) - if not isinstance(numeric,_INT_TYPES): + if not isinstance(numeric,_INT_TYPES): if convertFallback is None: raise FunctionArgType(i,"integer?",numeric) try: diff --git a/arelle/FunctionXfi.py b/arelle/FunctionXfi.py index f0707ada9e..2d95b9df7c 100644 --- a/arelle/FunctionXfi.py +++ b/arelle/FunctionXfi.py @@ -26,7 +26,7 @@ def __init__(self): self.args = (_("xfi function not available"),) def __repr__(self): return self.args[0] - + def call(xc, p, localname, args): try: if localname not in xfiFunctions: raise xfiFunctionNotAvailable @@ -49,7 +49,7 @@ def instance(xc, p, args, i=0): def item(xc, args, i=0): if len(args[i]) != 1: raise XPathContext.FunctionArgType(i+1,"xbrl:item") modelItem = xc.modelItem(args[i][0]) - if modelItem is not None: + if modelItem is not None: return modelItem raise XPathContext.FunctionArgType(i,"xbrl:item") @@ -79,7 +79,7 @@ def unit(xc, p, args): if len(args) != 1: raise XPathContext.FunctionNumArgs() if len(args[0]) != 1: raise XPathContext.FunctionArgType(1,"xbrl:item") modelItem = xc.modelItem(args[0][0]) - if modelItem is not None: + if modelItem is not None: modelConcept = modelItem.concept if modelConcept.isNumeric and not modelConcept.isFraction: return modelItem.unit @@ -91,18 +91,18 @@ def unit_numerator(xc, p, args): if len(args[0]) != 1: raise XPathContext.FunctionArgType(1,"xbrl:unit") unit = args[0][0] if isinstance(unit,ModelObject) and \ - unit.localName == "unit" and unit.namespaceURI == XbrlConst.xbrli: + unit.localName == "unit" and unit.namespaceURI == XbrlConst.xbrli: measuresParent = XmlUtil.descendant(unit, XbrlConst.xbrli, "unitNumerator") if measuresParent is None: measuresParent = unit return XmlUtil.descendants(measuresParent, XbrlConst.xbrli, "measure") raise XPathContext.FunctionArgType(1,"xbrl:unit") - + def unit_denominator(xc, p, args): if len(args) != 1: raise XPathContext.FunctionNumArgs() if len(args[0]) != 1: raise XPathContext.FunctionArgType(1,"xbrl:unit") unit = args[0][0] if isinstance(unit,ModelObject) and \ - unit.localName == "unit" and unit.namespaceURI == XbrlConst.xbrli: + unit.localName == "unit" and unit.namespaceURI == XbrlConst.xbrli: measuresParent = XmlUtil.descendant(unit, XbrlConst.xbrli, "unitDenominator") if measuresParent is None: return [] return XmlUtil.descendants(measuresParent, XbrlConst.xbrli, "measure") @@ -114,7 +114,7 @@ def measure_name(xc, p, args): unit = args[0][0] if isinstance(unit,ModelObject) and \ unit.localName == "measure" and unit.namespaceURI == XbrlConst.xbrli: - return qname(unit, XmlUtil.text(unit)) + return qname(unit, XmlUtil.text(unit)) raise XPathContext.FunctionArgType(1,"xbrl:unit") def period(xc, p, args): @@ -175,7 +175,7 @@ def period_datetime(p, args, periodElement): if len(args) != 1: raise XPathContext.FunctionNumArgs() if len(args[0]) != 1: raise XPathContext.FunctionArgType(1,"xbrl:period") period = args[0][0] - if (isinstance(period,ModelObject) == 1 and + if (isinstance(period,ModelObject) == 1 and period.localName == "period" and period.namespaceURI == XbrlConst.xbrli): child = XmlUtil.child(period, XbrlConst.xbrli, periodElement) if child is not None: @@ -260,10 +260,10 @@ def infer_precision_decimals(xc, p, args, attrName): if len(args) != 1: raise XPathContext.FunctionNumArgs() if len(args[0]) != 1: raise XPathContext.FunctionArgType(1,"xbrl:item",args[0]) modelItem = xc.modelItem(args[0][0]) - if modelItem is None: + if modelItem is None: raise XPathContext.FunctionArgType(1,"xbrl:item") modelConcept = modelItem.concept - if modelConcept.isFraction: + if modelConcept.isFraction: return 'INF' if modelConcept.isNumeric: p = inferredPrecision(modelItem) if attrName == "precision" else inferredDecimals(modelItem) @@ -304,11 +304,11 @@ def checkXffFunctionUse(xc, p, functionName): while (modelResourceElt is not None and not isinstance(modelResourceElt, ModelFormulaResource)): modelResourceElt = modelResourceElt.getparent() xc.progHeader.element._modelResourceElt = modelResourceElt - + if (modelResourceElt is None or modelResourceElt.localName not in ("formula", "consistencyAssertion", "valueAssertion", "precondition", "message")): raise XPathContext.XPathException(p, 'xffe:invalidFunctionUse', _('Function xff:uncovered-aspect cannot be used on an XPath expression associated with a {0}').format(xc.progHeader.element.localName)) - + if xc.variableSet is not None and xc.variableSet.implicitFiltering == "false": raise XPathContext.XPathException(p, 'xffe:invalidFunctionUse', _('Function xff:uncovered-aspect cannot be used with implicitFiltering=false')) @@ -319,9 +319,9 @@ def uncovered_aspect(xc, p, args): aspect = aspectFromToken.get(stringArg(xc, args, 0, "xs:token").strip()) if aspect == Aspect.DIMENSIONS: qn = qnameArg(xc, p, args, 1, 'QName', emptyFallback=None) - + checkXffFunctionUse(xc, p, "uncovered-aspect") - + if aspect == Aspect.DIMENSIONS: if qn: modelConcept = xc.modelXbrl.qnameConcepts.get(qn) @@ -331,7 +331,7 @@ def uncovered_aspect(xc, p, args): return () # not a dimension dimValue = uncoveredAspectValue(xc, aspect) if isinstance(dimValue, ModelDimensionValue): - if dimValue.isExplicit: + if dimValue.isExplicit: return dimValue.memberQname elif dimValue.isTyped: return dimValue # return the typedMember element, not its contents @@ -347,9 +347,9 @@ def has_fallback_value(xc, p, args): from arelle.FormulaEvaluator import variableBindingIsFallback if len(args) != 1: raise XPathContext.FunctionNumArgs() variableQname = qnameArg(xc, p, args, 0, 'QName', emptyFallback=None) - + checkXffFunctionUse(xc, p, "has-fallback-value") - + return variableBindingIsFallback(xc, variableQname) def uncovered_non_dimensional_aspects(xc, p, args): @@ -362,14 +362,14 @@ def uncovered_aspects(xc, p, args, dimensionAspects=False): from arelle.ModelFormulaObject import aspectToToken, Aspect from arelle.FormulaEvaluator import uncoveredVariableSetAspects if len(args) != 0: raise XPathContext.FunctionNumArgs() - + # check function use after checking argument types if xc.progHeader is not None and xc.progHeader.element is not None: if xc.progHeader.element.localName not in ("formula", "consistencyAssertion", "valueAssertion", "message"): raise XPathContext.XPathException(p, 'xffe:invalidFunctionUse', _('Function xff:uncovered-aspect cannot be used on an XPath expression associated with a {0}').format(xc.progHeader.element.localName)) if xc.variableSet is not None and xc.variableSet.implicitFiltering == "false": raise XPathContext.XPathException(p, 'xffe:invalidFunctionUse', _('Function xff:uncovered-aspect cannot be used with implicitFiltering=false')) - + uncoveredAspects = uncoveredVariableSetAspects(xc) return [(a if dimensionAspects else aspectToToken.get(a)) for a in uncoveredAspects if a != Aspect.DIMENSIONS and isinstance(a,QName) == dimensionAspects ] @@ -381,14 +381,14 @@ def nodesEqual(xc, args, test, mustBeItems=False, nonItemErrCode=None): for i, node1 in enumerate(seq1): try: node2 = seq2[i] - if not isinstance(node1, (ModelObject,ModelAttribute)): + if not isinstance(node1, (ModelObject,ModelAttribute)): raise XPathContext.FunctionArgType(1,"node()*") - if not isinstance(node2, (ModelObject,ModelAttribute)): + if not isinstance(node2, (ModelObject,ModelAttribute)): raise XPathContext.FunctionArgType(2,"node()*") if mustBeItems: - if not isinstance(node1, (ModelFact, ModelInlineFact)) or not node1.isItem: + if not isinstance(node1, (ModelFact, ModelInlineFact)) or not node1.isItem: raise XPathContext.FunctionArgType(1,"xbrl:item*", errCode=nonItemErrCode) - if not isinstance(node2, (ModelFact, ModelInlineFact)) or not node2.isItem: + if not isinstance(node2, (ModelFact, ModelInlineFact)) or not node2.isItem: raise XPathContext.FunctionArgType(2,"xbrl:item*", errCode=nonItemErrCode) if not test(node1, node2): return False @@ -401,14 +401,14 @@ def setsEqual(xc, args, test, mustBeItems=False): seq1 = flattenSequence(args[0]) seq2 = flattenSequence(args[1]) for node1 in seq1: - if not isinstance(node1, ModelObject): + if not isinstance(node1, ModelObject): raise XPathContext.FunctionArgType(1,"node()*") - if mustBeItems and (not isinstance(node1, (ModelFact, ModelInlineFact)) or not node1.isItem): + if mustBeItems and (not isinstance(node1, (ModelFact, ModelInlineFact)) or not node1.isItem): raise XPathContext.FunctionArgType(1,"xbrl:item*", errCode="xfie:NodeIsNotXbrlItem") for node2 in seq2: - if not isinstance(node2, ModelObject): + if not isinstance(node2, ModelObject): raise XPathContext.FunctionArgType(2,"node()*") - if mustBeItems and (not isinstance(node2, (ModelFact, ModelInlineFact)) or not node2.isItem): + if mustBeItems and (not isinstance(node2, (ModelFact, ModelInlineFact)) or not node2.isItem): raise XPathContext.FunctionArgType(2,"xbrl:item*", errCode="xfie:NodeIsNotXbrlItem") if len(set(seq1)) != len(set(seq2)): # sequences can have nondistinct duplicates, just same set lengths needed return False @@ -430,7 +430,7 @@ def s_equal_test(node1, node2): if (isinstance(node1, (ModelFact, ModelInlineFact)) and node1.isItem and isinstance(node2, (ModelFact, ModelInlineFact)) and node2.isItem): return (c_equal_test(node1, node2) and u_equal_test(node1, node2) and - XbrlUtil.xEqual(node1, node2) and + XbrlUtil.xEqual(node1, node2) and # must be validated (by xEqual) before precision tests to assure xAttributes is set node1.xAttributes.get("precision") == node2.xAttributes.get("precision") and node1.xAttributes.get("decimals") == node2.xAttributes.get("decimals")) @@ -523,9 +523,9 @@ def p_equal(xc, p, args): return nodesEqual(xc, args, p_equal_test) def p_equal_test(node1, node2): - if not isinstance(node1, (ModelFact, ModelInlineFact)) or not (node1.isItem or node1.isTuple): + if not isinstance(node1, (ModelFact, ModelInlineFact)) or not (node1.isItem or node1.isTuple): raise XPathContext.FunctionArgType(1,"xbrli:item or xbrli:tuple", errCode="xfie:ElementIsNotXbrlConcept") - if not isinstance(node2, (ModelFact, ModelInlineFact)) or not (node1.isItem or node1.isTuple): + if not isinstance(node2, (ModelFact, ModelInlineFact)) or not (node1.isItem or node1.isTuple): raise XPathContext.FunctionArgType(2,"xbrli:item or xbrli:tuple", errCode="xfie:ElementIsNotXbrlConcept") return node1.parentElement == node2.parentElement @@ -555,17 +555,17 @@ def end_equal(xc, p, args): def taxonomy_refs(xc, p, args): return [ref.referringModelObject.xAttributes.get("{http://www.w3.org/1999/xlink}href").xValue # need typed value - for ref in sorted(xc.modelXbrl.modelDocument.referencesDocument.values(), + for ref in sorted(xc.modelXbrl.modelDocument.referencesDocument.values(), key=lambda r:r.referringModelObject.objectIndex) if ref.referringModelObject.localName == "schemaRef"] def date_equal_test(xc, p, args, instantEndDate): if len(args) != 2: raise XPathContext.FunctionNumArgs() date1 = atomicArg(xc, p, args, 0, "xbrldi:dateUnion", missingArgFallback=(), emptyFallback=None) - if not isinstance(date1, (DateTime,datetime.date)): + if not isinstance(date1, (DateTime,datetime.date)): raise XPathContext.FunctionArgType(1,"xbrldi:dateUnion") date2 = atomicArg(xc, p, args, 1, "xbrldi:dateUnion", missingArgFallback=(), emptyFallback=None) - if not isinstance(date1, (DateTime,datetime.date)): + if not isinstance(date1, (DateTime,datetime.date)): raise XPathContext.FunctionArgType(2,"xbrldi:dateUnion") return dateUnionEqual(date1, date2, instantEndDate) @@ -612,7 +612,7 @@ def non_nil_facts_in_instance(xc, p, args): def concept(xc, p, args): qnConcept = qnameArg(xc, p, args, 0, 'QName', emptyFallback=None) srcConcept = xc.modelXbrl.qnameConcepts.get(qnConcept) - if srcConcept is None or not (srcConcept.isItem or srcConcept.isTuple) or srcConcept.qname is None or srcConcept.qname.namespaceURI == XbrlConst.xbrli: + if srcConcept is None or not (srcConcept.isItem or srcConcept.isTuple) or srcConcept.qname is None or srcConcept.qname.namespaceURI == XbrlConst.xbrli: raise XPathContext.XPathException(p, 'xfie:invalidConceptQName', _('Argument 1 {0} is not a concept in the DTS.').format(qnConcept)) return srcConcept @@ -656,10 +656,10 @@ def concepts_from_local_name(xc, p, args): localName = stringArg(xc, args, 0, "xs:string") if len(args) == 2: nsPattern = re.compile(stringArg(xc, args, 1, "xs:string")) - return [c.qname for c in xc.modelXbrl.nameConcepts.get(localName,()) + return [c.qname for c in xc.modelXbrl.nameConcepts.get(localName,()) if (c.isItem or c.isTuple) and bool(nsPattern.search(c.qname.namespaceURI))] else: - return [c.qname for c in xc.modelXbrl.nameConcepts.get(localName,()) + return [c.qname for c in xc.modelXbrl.nameConcepts.get(localName,()) if c.isItem or c.isTuple] def concepts_from_local_name_pattern(xc, p, args): @@ -667,10 +667,10 @@ def concepts_from_local_name_pattern(xc, p, args): localNamePattern = re.compile(stringArg(xc, args, 0, "xs:string")) if len(args) == 2: nsPattern = re.compile(stringArg(xc, args, 1, "xs:string")) - return [c.qname for c in xc.modelXbrl.qnameConcepts.values() + return [c.qname for c in xc.modelXbrl.qnameConcepts.values() if (c.isItem or c.isTuple) and bool(localNamePattern.search(c.name)) and bool(nsPattern.search(c.qname.namespaceURI))] else: - return [c.qname for c in xc.modelXbrl.qnameConcepts.values() + return [c.qname for c in xc.modelXbrl.qnameConcepts.values() if (c.isItem or c.isTuple) and bool(localNamePattern.search(c.name))] def filter_member_network_selection(xc, p, args): @@ -703,7 +703,7 @@ def filter_member_network_selection(xc, p, args): filter_member_network_members(relationshipSet, fromRels, axis.startswith("descendant"), members=members) ''' removed 2011-03-10: if len(linkQnames) > 1 or len(arcQnames) > 1: - raise XPathContext.XPathException(p, 'xfie:ambiguousFilterMemberNetwork', + raise XPathContext.XPathException(p, 'xfie:ambiguousFilterMemberNetwork', _('Network of linkrole {0} and arcrole {1} dimension {2} from {3} is ambiguous because of multiple link elements, {4}, or arc elements {5}').format( linkroleURI, arcroleURI, qnDim, qnMem, linkQnames, arcQnames)) ''' @@ -729,7 +729,7 @@ def filter_member_network_members(relationshipSet, fromRels, recurse, members=No if relationships is not None: relationships.add(modelRel) if recurse: - filter_member_network_members(relationshipSet, relationshipSet.fromModelObject(toConcept), recurse, members, relationships, linkQnames, arcQnames) + filter_member_network_members(relationshipSet, relationshipSet.fromModelObject(toConcept), recurse, members, relationships, linkQnames, arcQnames) def filter_member_DRS_selection(xc, p, args): if len(args) != 5: raise XPathContext.FunctionNumArgs() @@ -761,12 +761,12 @@ def filter_member_DRS_selection(xc, p, args): if not linkroleURI or linkroleURI == hcELR: for hasHcRel in hcRels: hcConcept = hasHcRel.toModelObject - if hasHcRel.arcrole == XbrlConst.all: + if hasHcRel.arcrole == XbrlConst.all: dimELR = (hasHcRel.targetRole or hcELR) for hcDimRel in xc.modelXbrl.relationshipSet(XbrlConst.hypercubeDimension, dimELR).fromModelObject(hcConcept): if dimConcept == hcDimRel.toModelObject: filter_member_DRS_members(xc, - xc.modelXbrl.relationshipSet(XbrlConst.dimensionDomain, + xc.modelXbrl.relationshipSet(XbrlConst.dimensionDomain, (hcDimRel.targetRole or dimELR)) .fromModelObject(dimConcept), axis, @@ -787,7 +787,7 @@ def filter_member_DRS_members(xc, fromRels, axis, memConcept, inSelection, visit if toConceptQname not in visited and (not nestedSelection or axis == "DRS-descendant"): visited.add(toConcept) filter_member_DRS_members(xc, - xc.modelXbrl.relationshipSet(XbrlConst.domainMember, + xc.modelXbrl.relationshipSet(XbrlConst.domainMember, (rel.targetRole or rel.linkrole)) .fromModelObject(toConcept), axis, @@ -830,8 +830,8 @@ def fact_dim_value(xc, p, args, dimType): qnDim = qnameArg(xc, p, args, 1, 'QName', emptyFallback=None) dimConcept = xc.modelXbrl.qnameConcepts.get(qnDim) if dimConcept is None or not dimConcept.isDimensionItem: - raise XPathContext.XPathException(p, - 'xfie:invalid{0}DimensionQName'.format(dimType), + raise XPathContext.XPathException(p, + 'xfie:invalid{0}DimensionQName'.format(dimType), _('Argument 1 {0} is not a dimension concept QName.').format(qnDim)) if context is not None: return context.dimValue(qnDim) @@ -859,7 +859,7 @@ def fact_explicit_dimension_value_value(xc, p, args): dimValue = context.dimValue(qn) if isinstance(dimValue, ModelDimensionValue) and dimValue.isExplicit: return dimValue.memberQname # known to be valid given instance is valid - elif isinstance(dimValue, QName): #default, check if this is valid + elif isinstance(dimValue, QName): #default, check if this is valid ''' removed 2011-03-01 FWG clarification that default always applies #note that there's no way to check one dimension without full set of others for validity modelItem = xc.modelItem(args[0][0]) @@ -1062,7 +1062,7 @@ def concept_relationships(xc, p, args, nestResults=False): qnArc = qnameArg(xc, p, args, 6, 'QName', emptyFallback=None) else: qnArc = None - + removeSelf = axis == 'sibling' relationshipSet = inst.relationshipSet(arcroleURI, linkroleURI, qnLink, qnArc) if relationshipSet: @@ -1112,7 +1112,7 @@ def concept_relationships_step(xc, inst, relationshipSet, rels, axis, generation if axis == 'descendant': if relationshipSet.arcrole == "XBRL-dimensions": stepRelationshipSet = inst.relationshipSet("XBRL-dimensions", modelRel.consecutiveLinkrole) - else: + else: stepRelationshipSet = relationshipSet stepRels = stepRelationshipSet.fromModelObject(concept) else: @@ -1158,14 +1158,14 @@ def distinct_nonAbstract_parent_concepts(xc, p, args): linkroleURI = XbrlConst.defaultLinkRole arcroleURI = stringArg(xc, args, 1, "xs:string") # TBD allow instance as arg 2 - + result = set() relationshipSet = inst.relationshipSet(arcroleURI, linkroleURI) if relationshipSet: for rel in relationshipSet.modelRelationships: fromModelObject = rel.fromModelObject toModelObject = rel.toModelObject - if (isinstance(fromModelObject, ModelConcept) and + if (isinstance(fromModelObject, ModelConcept) and isinstance(toModelObject, ModelConcept) and not fromModelObject.isAbstract and not toModelObject.isAbstract): @@ -1201,7 +1201,7 @@ def element_attribute(element, attrQname): elif modelAttribute.xValid >= VALID: return modelAttribute.xValue return () - + def relationship_attribute(xc, p, args): return relationship_element_attribute(xc, p, args) @@ -1234,14 +1234,14 @@ def format_number(xc, p, args): except ValueError as err: raise XPathContext.XPathException(p, 'xfie:invalidPictureSyntax', str(err) ) -# note that this function was initially in plugin functionsXmlCreation when it was named xfxc:element +# note that this function was initially in plugin functionsXmlCreation when it was named xfxc:element def create_element(xc, p, args): if not 2 <= len(args) <= 4: raise XPathContext.FunctionNumArgs() qn = qnameArg(xc, p, args, 0, 'QName', emptyFallback=None) attrArg = flattenSequence(args[1]) # attributes have to be pairs if attrArg: - if (len(attrArg) & 1 or + if (len(attrArg) & 1 or any((not isinstance(arg, (QName, _STR_BASE))) or (isinstance(arg,_STR_BASE) and NCNamePattern.match(arg) is None) for i in range(0, len(attrArg),2) @@ -1252,17 +1252,17 @@ def create_element(xc, p, args): for i in range(0, len(attrArg),2)] else: attrParam = None - - value = atomicArg(xc, p, args, 2, "xs:anyAtomicType", emptyFallback='') + + value = atomicArg(xc, p, args, 2, "xs:anyAtomicType", emptyFallback='') if not value: # be sure '' is None so no text node is created - value = None + value = None if len(args) < 4: childElements = None else: childElements = xc.flattenSequence(args[3]) if value and childElements: raise XPathContext.FunctionArgType(1,str(value), errCode="xfie:MixedContentError") - + # scratchpad instance document emulates fn:doc( ) to hold XML nodes scratchpadXmlDocUrl = "http://www.xbrl.org/2012/function/creation/xml_scratchpad.xml" if scratchpadXmlDocUrl in xc.modelXbrl.urlDocs: @@ -1271,11 +1271,11 @@ def create_element(xc, p, args): # create scratchpad xml document # this will get the fake instance document in the list of modelXbrl docs so that it is garbage collected from arelle import ModelDocument - modelDocument = ModelDocument.create(xc.modelXbrl, - ModelDocument.Type.UnknownXML, + modelDocument = ModelDocument.create(xc.modelXbrl, + ModelDocument.Type.UnknownXML, scratchpadXmlDocUrl, initialXml="") - + newElement = XmlUtil.addChild(modelDocument.xmlRootElement, qn, attributes=attrParam, @@ -1284,10 +1284,10 @@ def create_element(xc, p, args): for element in childElements: if isinstance(element, etree.ElementBase): newElement.append(element) - + # node myst be validated for use in instance creation (typed dimension references) xmlValidate(xc.modelXbrl, newElement) - + return newElement def any_identifier(xc, p, args): @@ -1376,7 +1376,7 @@ def filingIndicatorValues(inst, filedValue): if fiValue: filingIndicators.add(fiValue) return filingIndicators - + def positive_filing_indicators(xc, p, args): if len(args) != 0: raise XPathContext.FunctionNumArgs() return sorted(filingIndicatorValues(xc.modelXbrl, "true")) @@ -1386,7 +1386,7 @@ def positive_filing_indicator(xc, p, args): ind = anytypeArg(xc, args, 0, "xs:string", None) if ind is None: raise XPathContext.FunctionArgType(1,"xs:string") return ind in filingIndicatorValues(xc.modelXbrl, "true") - + def negative_filing_indicators(xc, p, args): if len(args) != 0: raise XPathContext.FunctionNumArgs() return sorted(filingIndicatorValues(xc.modelXbrl, "false")) diff --git a/arelle/FunctionXs.py b/arelle/FunctionXs.py index b06d2e6aaa..7f7c0d90e3 100644 --- a/arelle/FunctionXs.py +++ b/arelle/FunctionXs.py @@ -11,7 +11,7 @@ from arelle.XPathParser import ProgHeader from math import isnan, fabs, isinf from decimal import Decimal, InvalidOperation - + class FORG0001(Exception): def __init__(self, message=None): self.message = message @@ -31,7 +31,7 @@ def __init__(self): self.args = (_("xs function not available"),) def __repr__(self): return self.args[0] - + def call(xc, p, localname, args): source = atomicArg(xc, p, args, 0, "value?", missingArgFallback=() ) if source == (): return source @@ -43,14 +43,14 @@ def call(xc, p, localname, args): exMsg = ", " + ex.message else: exMsg = "" - raise XPathContext.XPathException(p, 'err:FORG0001', + raise XPathContext.XPathException(p, 'err:FORG0001', _('invalid cast from {0} to xs:{1}{2}').format( type(source).__name__, localname, exMsg)) except xsFunctionNotAvailable: raise XPathContext.FunctionNotAvailable("xs:{0}".format(localname)) - + objtype = { #'untypedAtomic': untypedAtomic, 'dateTime': ModelValue.DateTime, @@ -102,20 +102,20 @@ def isXsType(localName): if localName[-1] in ('?', '+', '*'): return localName[:-1] in xsFunctions return localName in xsFunctions - + def untypedAtomic(xc, p, source): raise xsFunctionNotAvailable() - + def anyType(xc, p, source): raise xsFunctionNotAvailable() - + def anyAtomicType(xc, p, source): raise xsFunctionNotAvailable() - + def dateTime(xc, p, source): if isinstance(source,datetime.datetime): return source return ModelValue.dateTime(source, type=ModelValue.DATETIME, castException=FORG0001) - + def dateTimeInstantEnd(xc, p, source): if isinstance(source,datetime.datetime): return source # true for either datetime.date or datetime.datetime return ModelValue.dateTime(source, addOneDay=True, type=ModelValue.DATETIME, castException=FORG0001) @@ -123,46 +123,46 @@ def dateTimeInstantEnd(xc, p, source): def xbrliDateUnion(xc, p, source): if isinstance(source,datetime.date): return source # true for either datetime.date or datetime.datetime return ModelValue.dateTime(source, type=ModelValue.DATEUNION, castException=FORG0001) - + def date(xc, p, source): return ModelValue.dateTime(source, type=ModelValue.DATE, castException=FORG0001) - + def time(xc, p, source): return ModelValue.time(source, castException=FORG0001) - + def duration(xc, p, source): raise xsFunctionNotAvailable() - + def yearMonthDuration(xc, p, source): return ModelValue.yearMonthDuration(source) - + def dayTimeDuration(xc, p, source): return ModelValue.dayTimeDuration(source) - + def xs_float(xc, p, source): try: return float(source) except ValueError: raise FORG0001 - + def double(xc, p, source): try: return float(source) except ValueError: raise FORG0001 - + def decimal(xc, p, source): try: return Decimal(source) except InvalidOperation: raise FORG0001 - + def integer(xc, p, source): try: return _INT(source) except ValueError: raise FORG0001 - + def nonPositiveInteger(xc, p, source): try: i = _INT(source) @@ -170,7 +170,7 @@ def nonPositiveInteger(xc, p, source): except ValueError: pass raise FORG0001 - + def negativeInteger(xc, p, source): try: i = _INT(source) @@ -178,13 +178,13 @@ def negativeInteger(xc, p, source): except ValueError: pass raise FORG0001 - + def long(xc, p, source): try: return _INT(source) except ValueError: raise FORG0001 - + def xs_int(xc, p, source): try: i = _INT(source) @@ -192,7 +192,7 @@ def xs_int(xc, p, source): except ValueError: pass raise FORG0001 - + def short(xc, p, source): try: i = _INT(source) @@ -200,7 +200,7 @@ def short(xc, p, source): except ValueError: pass raise FORG0001 - + def byte(xc, p, source): try: i = _INT(source) @@ -208,7 +208,7 @@ def byte(xc, p, source): except ValueError: pass raise FORG0001 - + def nonNegativeInteger(xc, p, source): try: i = _INT(source) @@ -216,7 +216,7 @@ def nonNegativeInteger(xc, p, source): except ValueError: pass raise FORG0001 - + def unsignedLong(xc, p, source): try: i = _INT(source) @@ -224,7 +224,7 @@ def unsignedLong(xc, p, source): except ValueError: pass raise FORG0001 - + def unsignedInt(xc, p, source): try: i = _INT(source) @@ -232,7 +232,7 @@ def unsignedInt(xc, p, source): except ValueError: pass raise FORG0001 - + def unsignedShort(xc, p, source): try: i = _INT(source) @@ -240,7 +240,7 @@ def unsignedShort(xc, p, source): except ValueError: pass raise FORG0001 - + def unsignedByte(xc, p, source): try: i = _INT(source) @@ -248,7 +248,7 @@ def unsignedByte(xc, p, source): except ValueError: pass raise FORG0001 - + def positiveInteger(xc, p, source): try: i = _INT(source) @@ -256,7 +256,7 @@ def positiveInteger(xc, p, source): except ValueError: pass raise FORG0001 - + def gYearMonth(xc, p, source): try: match = lexicalPatterns['gYearMonth'].match(source) @@ -266,7 +266,7 @@ def gYearMonth(xc, p, source): except (ValueError, TypeError): pass raise FORG0001 - + def gYear(xc, p, source): try: match = lexicalPatterns['gYear'].match(source) @@ -276,7 +276,7 @@ def gYear(xc, p, source): except (ValueError, TypeError): pass raise FORG0001 - + def gMonthDay(xc, p, source): try: match = lexicalPatterns['gMonthDay'].match(source) @@ -287,7 +287,7 @@ def gMonthDay(xc, p, source): except (ValueError, TypeError): pass raise FORG0001 - + def gDay(xc, p, source): try: match = lexicalPatterns['gDay'].match(source) @@ -297,7 +297,7 @@ def gDay(xc, p, source): except (ValueError, TypeError): pass raise FORG0001 - + def gMonth(xc, p, source): try: match = lexicalPatterns['gMonth'].match(source) @@ -307,7 +307,7 @@ def gMonth(xc, p, source): except (ValueError, TypeError): pass raise FORG0001 - + def xsString(xc, p, source): if isinstance(source,bool): return 'true' if source else 'false' @@ -320,7 +320,7 @@ def xsString(xc, p, source): numMagnitude = fabs(source) if numMagnitude < 1000000 and numMagnitude > .000001: # don't want floating notation which python does for more than 4 decimal places - s = + s = ''' s = str(source) if s.endswith(".0"): @@ -335,40 +335,40 @@ def xsString(xc, p, source): elif isinstance(source,ModelValue.DateTime): return ('{0:%Y-%m-%d}' if source.dateOnly else '{0:%Y-%m-%dT%H:%M:%S}').format(source) return str(source) - + def normalizedString(xc, p, source): return str(source) - + tokenPattern = re.compile(r"^\s*([-\.:\w]+)\s*$") def token(xc, p, source): s = str(source) if tokenPattern.match(s): return s raise FORG0001 - + languagePattern = re.compile("[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*") def language(xc, p, source): s = str(source) if languagePattern.match(s): return s raise FORG0001 - + def NMTOKEN(xc, p, source): raise xsFunctionNotAvailable() - + def Name(xc, p, source): raise xsFunctionNotAvailable() - + def NCName(xc, p, source): raise xsFunctionNotAvailable() - + def ID(xc, p, source): raise xsFunctionNotAvailable() - + def IDREF(xc, p, source): raise xsFunctionNotAvailable() - + def ENTITY(xc, p, source): raise xsFunctionNotAvailable() - + def boolean(xc, p, source): if isinstance(source,bool): return source @@ -384,16 +384,16 @@ def boolean(xc, p, source): elif b in ('false','no'): return False raise FORG0001 - + def base64Binary(xc, p, source): raise xsFunctionNotAvailable() - + def hexBinary(xc, p, source): raise xsFunctionNotAvailable() - + def anyURI(xc, p, source): return ModelValue.anyURI(source) - + def QName(xc, p, source): if isinstance(p, ProgHeader): element = p.element @@ -402,7 +402,7 @@ def QName(xc, p, source): else: element = xc.sourceElement return ModelValue.qname(element, source, castException=FORG0001, prefixException=FONS0004) - + def NOTATION(xc, p, source): raise xsFunctionNotAvailable() diff --git a/arelle/HashUtil.py b/arelle/HashUtil.py index d148a10220..b4e79d5044 100644 --- a/arelle/HashUtil.py +++ b/arelle/HashUtil.py @@ -25,7 +25,7 @@ def toHex(self): if s.endswith('L'): return s[:-1] return s - + def __str__(self): return self.toHex() @@ -33,12 +33,12 @@ def __add__(self, other): if not isinstance(other, Md5Sum): other = Md5Sum(other) return Md5Sum(self.value + other.value) - + def __eq__(self, other): if not isinstance(other, Md5Sum): other = Md5Sum(other) return self.value == other.value - + def __ne__(self, other): return not (self.value == other.value) @@ -66,11 +66,11 @@ def md5hash(argList): _md5.update(_arg.encode('utf-8','replace')) elif isinstance(_arg, datetime): # always in isodate format _md5.update("{0.year:04}-{0.month:02}-{0.day:02}T{0.hour:02}:{0.minute:02}:{0.second:02}".format(_arg).encode('utf-8','replace')) - elif isinstance(_arg, date): + elif isinstance(_arg, date): _md5.update("{0.year:04}-{0.month:02}-{0.day:02}".format(_arg).encode('utf-8','replace')) elif isinstance(_arg, ModelObject): # use inner text list - _md5.update('\x1F'.join(text.strip() + _md5.update('\x1F'.join(text.strip() for text in XmlUtil.innerTextNodes(_arg, True, False, True, False)) .encode('utf-8','replace')) if firstMd5arg: diff --git a/arelle/HtmlUtil.py b/arelle/HtmlUtil.py index 3ebbb2877c..b79a059487 100644 --- a/arelle/HtmlUtil.py +++ b/arelle/HtmlUtil.py @@ -10,7 +10,7 @@ import re def attrValue(str, name): - # retrieves attribute in a string, such as xyz="abc" or xyz='abc' or xyz=abc; + # retrieves attribute in a string, such as xyz="abc" or xyz='abc' or xyz=abc; prestuff, matchedName, valuePart = str.lower().partition("charset") value = [] endSep = None diff --git a/arelle/InstanceAspectsEvaluator.py b/arelle/InstanceAspectsEvaluator.py index 1b420397f3..60df93f74c 100644 --- a/arelle/InstanceAspectsEvaluator.py +++ b/arelle/InstanceAspectsEvaluator.py @@ -12,9 +12,9 @@ def setup(view): relsSet = view.modelXbrl.relationshipSet(view.arcrole, view.linkrole, view.linkqname, view.arcqname) view.concepts = set(fact.concept for fact in view.modelXbrl.facts) view.linkroles = set( - rel.linkrole + rel.linkrole for c in view.concepts - for rels in (relsSet.fromModelObject(c), relsSet.toModelObject(c)) + for rels in (relsSet.fromModelObject(c), relsSet.toModelObject(c)) for rel in rels) def setupLinkrole(view, linkrole): @@ -23,7 +23,7 @@ def setupLinkrole(view, linkrole): concepts = set(c for c in view.concepts if relsSet.fromModelObject(c) or relsSet.toModelObject(c)) facts = set(f for f in view.modelXbrl.facts if f.concept in concepts) contexts = set(f.context for f in facts) - + view.periodContexts = defaultdict(set) contextStartDatetimes = {} view.dimensionMembers = defaultdict(set) @@ -41,7 +41,7 @@ def setupLinkrole(view, linkrole): for modelDimension in context.qnameDims.values(): if modelDimension.isExplicit: view.dimensionMembers[modelDimension.dimension] = modelDimension.member - + view.periodKeys = list(view.periodContexts.keys()) view.periodKeys.sort() - + diff --git a/arelle/LocalViewer.py b/arelle/LocalViewer.py index 73bc06b531..14e80d3744 100644 --- a/arelle/LocalViewer.py +++ b/arelle/LocalViewer.py @@ -14,18 +14,18 @@ class LocalViewer: noCacheHeaders = {'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0'} - + def __init__(self, title, staticReportsRoot): self.title = title self.port = None # viewer unique port self.reportsFolders = [staticReportsRoot] # first entry is root of common report files, rest are per-report root self.cntlr = None - + def init(self, cntlr, reportsFolder): try: if self.port is None: # already initialized self.cntlr = cntlr - + # find available port import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -33,21 +33,21 @@ def init(self, cntlr, reportsFolder): s.listen(1) self.port = s.getsockname()[1] s.close() - + # start server localserver = Bottle() - + localserver.route('/', 'GET', self.get) localserver.route('', 'GET', self.get) # start local server on the port on a separate thread - threading.Thread(target=localserver.run, - kwargs=dict(server='cheroot', host='localhost', port=self.port, quiet=True), + threading.Thread(target=localserver.run, + kwargs=dict(server='cheroot', host='localhost', port=self.port, quiet=True), daemon=True).start() time.sleep(2) # allow other thread to run and start up - + localhost = "http://localhost:{}/{}".format(self.port, len(self.reportsFolders)) self.reportsFolders.append(reportsFolder) - self.cntlr.addToLog(_("{}: http://localhost:{}").format(self.title, self.port), + self.cntlr.addToLog(_("{}: http://localhost:{}").format(self.title, self.port), messageCode="localViewer:listen",level=logging.DEBUG) #cntlr.addToLog("localhost={}".format(localhost), messageCode="localViewer:listen",level=logging.DEBUG) return localhost @@ -55,13 +55,13 @@ def init(self, cntlr, reportsFolder): self.cntlr.addToLog(_("{} exception: http://localhost:{} \nException: {} \nTraceback: {}").format( self.title, self.port, ex, traceback.format_tb(sys.exc_info()[2])), messageCode="localViewer:exception",level=logging.DEBUG) - + def get(self, file=None, relpath=None): self.cntlr.addToLog("http://localhost:{}/{}".format(self.port,file), messageCode="localViewer:get",level=logging.DEBUG) try: return self.getLocalFile(file, relpath, request) - except HTTPResponse: + except HTTPResponse: raise # re-raise, such as to support redirects except Exception as ex: self.cntlr.addToLog(_("{} exception: file: {} \nException: {} \nTraceback: {}").format( - self.title, file, ex, traceback.format_tb(sys.exc_info()[2])), messageCode="localViewer:exception",level=logging.DEBUG) + self.title, file, ex, traceback.format_tb(sys.exc_info()[2])), messageCode="localViewer:exception",level=logging.DEBUG) diff --git a/arelle/Locale.py b/arelle/Locale.py index da8327797b..29bb0d4daa 100644 --- a/arelle/Locale.py +++ b/arelle/Locale.py @@ -79,7 +79,7 @@ def getLanguageCode(): try: return locale.getdefaultlocale()[0].replace("_","-") except (AttributeError, ValueError): #language code and encoding may be None if their values cannot be determined. - return "en" + return "en" def getLanguageCodes(lang=None): if lang is None: @@ -144,7 +144,7 @@ def languageCodes(): # dynamically initialize after gettext is loaded global _languageCodes if _languageCodes is not None: return _languageCodes - else: + else: _languageCodes = { # language name (in English), code, and setlocale string which works in windows _("Afrikaans (South Africa)"): "af-ZA afrikaans", _("Albanian (Albania)"): "sq-AL albanian", @@ -260,7 +260,7 @@ def languageCodes(): # dynamically initialize after gettext is loaded def setDisableRTL(disableRTL): global _disableRTL _disableRTL = disableRTL - + def rtlString(source, lang): if lang and source and lang[0:2] in {"ar","he"} and not _disableRTL: line = [] @@ -521,25 +521,25 @@ def format_picture(conv, value, picture): value = Decimal(value) elif not isinstance(value, Decimal): raise ValueError(_('Picture requires a number convertable to decimal or float').format(picture)) - + if value.is_nan(): return 'NaN' - + isNegative = value.is_signed() - + pic, sep, negPic = picture.partition(';') if negPic and ';' in negPic: raise ValueError(_('Picture contains multiple picture sepearators {0}').format(picture)) if isNegative and negPic: pic = negPic - + if len([c for c in pic if c in (percent, per_mille) ]) > 1: raise ValueError(_('Picture contains multiple percent or per_mille charcters {0}').format(picture)) if percent in pic: value *= 100 elif per_mille in pic: value *= 1000 - + intPart, sep, fractPart = pic.partition(decimal_point) prefix = '' numPlaces = 0 @@ -550,7 +550,7 @@ def format_picture(conv, value, picture): if fractPart: if decimal_point in fractPart: raise ValueError(_('Sub-picture contains decimal point sepearators {0}').format(pic)) - + for c in fractPart: if c.isdecimal(): numPlaces += 1 @@ -560,7 +560,7 @@ def format_picture(conv, value, picture): else: suffix += c - intPosition = 0 + intPosition = 0 for c in reversed(intPart): if c.isdecimal() or c == '#' or c == thousands_sep: if prefix: @@ -583,8 +583,8 @@ def format_picture(conv, value, picture): raise ValueError(_('Sub-picture must contain at least one digit position or sign character {0}').format(pic)) if intPlaces == 0 and fractPlaces == 0: intPlaces = 1 - - return format_decimal(None, value, intPlaces=intPlaces, fractPlaces=fractPlaces, + + return format_decimal(None, value, intPlaces=intPlaces, fractPlaces=fractPlaces, sep=thousands_sep, dp=decimal_point, grouping=grouping, pos=prefix, neg=prefix if negPic else prefix + minus_sign, diff --git a/arelle/ModelFormulaObject.py b/arelle/ModelFormulaObject.py index 9e32987250..07a9e3bb24 100644 --- a/arelle/ModelFormulaObject.py +++ b/arelle/ModelFormulaObject.py @@ -52,11 +52,11 @@ def aspectStr(aspect): def isDimensionalAspect(aspect): return aspect in (Aspect.DIMENSIONS, Aspect.OMIT_DIMENSIONS) or isinstance(aspect, QName) - + aspectModelAspect = { # aspect of the model that corresponds to retrievable aspects Aspect.VALUE: Aspect.ENTITY_IDENTIFIER, Aspect.SCHEME:Aspect.ENTITY_IDENTIFIER, - Aspect.PERIOD_TYPE: Aspect.PERIOD, - Aspect.START: Aspect.PERIOD, Aspect.END: Aspect.PERIOD, + Aspect.PERIOD_TYPE: Aspect.PERIOD, + Aspect.START: Aspect.PERIOD, Aspect.END: Aspect.PERIOD, Aspect.INSTANT: Aspect.PERIOD, Aspect.INSTANT_END: Aspect.PERIOD, Aspect.UNIT_MEASURES: Aspect.UNIT, Aspect.MULTIPLY_BY: Aspect.UNIT, Aspect.DIVIDE_BY: Aspect.UNIT } @@ -78,24 +78,24 @@ def isDimensionalAspect(aspect): } aspectFromToken = { - "location": Aspect.LOCATION, "concept": Aspect.CONCEPT, - "entityIdentifier": Aspect.ENTITY_IDENTIFIER, "entity-identifier": Aspect.ENTITY_IDENTIFIER, + "location": Aspect.LOCATION, "concept": Aspect.CONCEPT, + "entityIdentifier": Aspect.ENTITY_IDENTIFIER, "entity-identifier": Aspect.ENTITY_IDENTIFIER, "period": Aspect.PERIOD, "unit": Aspect.UNIT, - "nonXDTSegment": Aspect.NON_XDT_SEGMENT, "non-XDT-segment": Aspect.NON_XDT_SEGMENT, + "nonXDTSegment": Aspect.NON_XDT_SEGMENT, "non-XDT-segment": Aspect.NON_XDT_SEGMENT, "nonXDTScenario": Aspect.NON_XDT_SCENARIO, "non-XDT-scenario": Aspect.NON_XDT_SCENARIO, "dimension": Aspect.DIMENSIONS, "dimensions": Aspect.DIMENSIONS, - "segment": Aspect.COMPLETE_SEGMENT, "complete-segment": Aspect.COMPLETE_SEGMENT, + "segment": Aspect.COMPLETE_SEGMENT, "complete-segment": Aspect.COMPLETE_SEGMENT, "scenario": Aspect.COMPLETE_SCENARIO, "complete-scenario": Aspect.COMPLETE_SCENARIO, } aspectToToken = { - Aspect.LOCATION: "location", Aspect.CONCEPT: "concept", - Aspect.ENTITY_IDENTIFIER: "entityIdentifier", Aspect.ENTITY_IDENTIFIER: "entity-identifier", + Aspect.LOCATION: "location", Aspect.CONCEPT: "concept", + Aspect.ENTITY_IDENTIFIER: "entityIdentifier", Aspect.ENTITY_IDENTIFIER: "entity-identifier", Aspect.PERIOD: "period", Aspect.UNIT:"unit", - Aspect.NON_XDT_SEGMENT: "non-XDT-segment", + Aspect.NON_XDT_SEGMENT: "non-XDT-segment", Aspect.NON_XDT_SCENARIO: "non-XDT-scenario", Aspect.DIMENSIONS: "dimension", Aspect.DIMENSIONS: "dimensions" , - Aspect.COMPLETE_SEGMENT: "complete-segment", + Aspect.COMPLETE_SEGMENT: "complete-segment", Aspect.COMPLETE_SCENARIO: "complete-scenario", } @@ -156,28 +156,28 @@ def __init__(self, savedValues=None): self.testcaseResultOptions = None if isinstance(savedValues, dict): self.__dict__.update(savedValues) - + def typedParameters(self, prefixedNamespaces=None): return dict((qname(paramName, prefixedNamespaces), paramValue) for paramName, paramValue in self.parameterValues.items()) - + # Note: if adding to this list keep DialogFormulaParameters in sync def traceSource(self, traceType): - if traceType in (Trace.VARIABLE_SET, Trace.FORMULA_RULES): + if traceType in (Trace.VARIABLE_SET, Trace.FORMULA_RULES): return self.traceVariableSetExpressionSource - elif traceType == Trace.VARIABLE: + elif traceType == Trace.VARIABLE: return self.traceVariableExpressionSource else: return False - + def traceEvaluation(self, traceType): - if traceType in (Trace.VARIABLE_SET, Trace.FORMULA_RULES): + if traceType in (Trace.VARIABLE_SET, Trace.FORMULA_RULES): return self.traceVariableSetExpressionEvaluation - elif traceType == Trace.VARIABLE: + elif traceType == Trace.VARIABLE: return self.traceVariableExpressionEvaluation else: return False - + class Trace(): PARAMETER = 1 VARIABLE_SET = 2 @@ -187,15 +187,15 @@ class Trace(): CUSTOM_FUNCTION = 6 CALL = 7 #such as testcase call or API formula call TEST = 8 #such as testcase test or API formula test - + class ModelFormulaResource(ModelResource): def init(self, modelDocument): super(ModelFormulaResource, self).init(modelDocument) - + @property def descendantArcroles(self): return () - + def compile(self): for arcrole in self.descendantArcroles: for modelRel in self.modelXbrl.relationshipSet(arcrole).fromModelObject(self): @@ -208,7 +208,7 @@ def compile(self): modelObject=self, element=toModelObject.elementQname) - + def variableRefs(self, progs=[], varRefSet=None): if varRefSet is None: varRefSet = set() if progs: @@ -226,8 +226,8 @@ def logLabel(self, preferredRole='*', lang=None): except AttributeError: self._logLabel = self.genLabel(role=preferredRole,strip=True) or self.id or self.xlinkLabel return self._logLabel - - + + class ModelAssertionSet(ModelFormulaResource): def init(self, modelDocument): super(ModelAssertionSet, self).init(modelDocument) @@ -236,12 +236,12 @@ def init(self, modelDocument): @property def descendantArcroles(self): return (XbrlConst.assertionSet,) - + @property def propertyView(self): return (("id", self.id), ("label", self.xlinkLabel)) - + def __repr__(self): return ("modelAssertionSet[{0}]{1})".format(self.objectId(),self.propertyView)) @@ -250,11 +250,11 @@ def init(self, modelDocument): super(ModelVariableSet, self).init(modelDocument) self.modelXbrl.modelVariableSets.add(self) self.modelXbrl.hasFormulae = True - + @property - def descendantArcroles(self): + def descendantArcroles(self): return (XbrlConst.variableSet, XbrlConst.variableSetFilter, XbrlConst.variableSetPrecondition) - + @property def aspectModel(self): return self.get("aspectModel") @@ -270,25 +270,25 @@ def groupFilterRelationships(self): except AttributeError: self._groupFilterRelationships = self.modelXbrl.relationshipSet(XbrlConst.variableSetFilter).fromModelObject(self) return self._groupFilterRelationships - + @property - def xmlElementView(self): + def xmlElementView(self): return XmlUtil.xmlstring(self, stripXmlns=True, prettyPrint=True) - + @property def propertyView(self): return (("id", self.id), ("label", self.xlinkLabel), ("aspectModel", self.aspectModel), ("implicitFiltering", self.implicitFiltering)) - + def __repr__(self): return ("modelVariableSet[{0}]{1})".format(self.objectId(),self.propertyView)) class ModelFormulaRules: def init(self, modelDocument): super(ModelFormulaRules, self).init(modelDocument) - + def clear(self): if hasattr(self, "valueProg"): XPathParser.clearProg(self.valueProg) @@ -299,7 +299,7 @@ def clear(self): self.aspectProgs.clear() self.typedDimProgAspects.clear() super(ModelFormulaRules, self).clear() - + def compile(self): if not hasattr(self, "valueProg"): self.valueProg = XPathParser.parse(self, self.value, self, "value", Trace.VARIABLE_SET) @@ -315,14 +315,14 @@ def compileRuleElement(ruleElt, exprs): if name == "qname": value = qname(ruleElt, XmlUtil.text(ruleElt)) if ruleElt.getparent().localName == "concept": - if Aspect.CONCEPT in self.aspectValues: + if Aspect.CONCEPT in self.aspectValues: self.modelXbrl.error("xbrlfe:conflictingAspectRules", _("Concept aspect has multiple rules in formula."), modelObject=self) self.aspectValues[Aspect.CONCEPT] = value elif ruleElt.getparent().getparent().get("dimension") is not None: qnDim = qname(ruleElt.getparent().getparent(), ruleElt.getparent().getparent().get("dimension")) if qnDim in self.aspectValues: - self.modelXbrl.error("xbrlfe:conflictingAspectRules", - _("Dimension %(dimension)s aspect has multiple rules in formula."), + self.modelXbrl.error("xbrlfe:conflictingAspectRules", + _("Dimension %(dimension)s aspect has multiple rules in formula."), modelObject=self, dimension=qnDim) self.aspectValues[qnDim] = value elif name == "qnameExpression": @@ -390,8 +390,8 @@ def compileRuleElement(ruleElt, exprs): self.aspectValues[Aspect.DIMENSIONS].append(qnDim) if not XmlUtil.hasChild(ruleElt, XbrlConst.formula, ("omit","member","value","xpath")): if qnDim in self.aspectValues: - self.modelXbrl.error("xbrlfe:conflictingAspectRules", - _("Dimension %(dimension)s aspect has multiple rules in formula."), + self.modelXbrl.error("xbrlfe:conflictingAspectRules", + _("Dimension %(dimension)s aspect has multiple rules in formula."), modelObject=self, dimension=qnDim) self.aspectValues[qnDim] = XbrlConst.qnFormulaDimensionSAV elif name == "precision": @@ -400,17 +400,17 @@ def compileRuleElement(ruleElt, exprs): elif name == "decimals": exprs = [(Aspect.DECIMALS, XmlUtil.text(ruleElt))] self.hasDecimals = True - + if len(exprs) > 0: for aspectExpr in exprs: aspect, expr = aspectExpr self.aspectProgs[aspect].append(XPathParser.parse(self, expr, ruleElt, ruleElt.localName, Trace.FORMULA_RULES)) del exprs[:] - + if name != "ruleSet": # don't descend ruleSets (table linkbase) for childElt in ruleElt.iterchildren(): compileRuleElement(childElt, exprs) - + exprs = [] for ruleElt in self.iterchildren(): compileRuleElement(ruleElt, exprs) @@ -457,7 +457,7 @@ def evaluateRule(self, xpCtx, aspect): else: # atomic results return xpCtx.evaluateAtomicValue(prog, type, xpCtx.inputXbrlInstance.xmlRootElement) return None - + def hasRule(self, aspect): return aspect in self.aspectValues or aspect in self.aspectProgs @@ -486,34 +486,34 @@ def source(self, aspect=None, ruleElement=None, acceptFormulaSource=True): if ruleElement == self: break ruleElement = ruleElement.getparent() return None - + def aspectRuleElements(self, aspect): if aspect in aspectElementNameAttrValue: eltName, ns, attrName, attrValue = aspectElementNameAttrValue[aspect] return XmlUtil.descendants(self, ns, eltName, attrName, attrValue) elif isinstance(aspect,QName): - return [d + return [d for d in XmlUtil.descendants(self, XbrlConst.formula, ("explicitDimension", "typedDimension")) if aspect == qname(d, d.get("dimension"))] return [] - + class ModelFormula(ModelFormulaRules, ModelVariableSet): def init(self, modelDocument): super(ModelFormula, self).init(modelDocument) - + @property def propertyView(self): return super(ModelFormula, self).propertyView + ( ("value", self.value), ("formula", XmlUtil.xmlstring(self, stripXmlns=True, prettyPrint=True))) - + def __repr__(self): return ("formula({0}, '{1}')".format(self.id if self.id else self.xlinkLabel, self.value)) @property def viewExpression(self): return self.value - + class ModelTuple(ModelFormula): def init(self, modelDocument): super(ModelTuple, self).init(modelDocument) @@ -521,11 +521,11 @@ def init(self, modelDocument): class ModelVariableSetAssertion(ModelVariableSet): def init(self, modelDocument): super(ModelVariableSetAssertion, self).init(modelDocument) - + def clear(self): XPathParser.clearNamedProg(self, "testProg") super(ModelVariableSetAssertion, self).clear() - + def compile(self): if not hasattr(self, "testProg"): self.testProg = XPathParser.parse(self, self.test, self, "test", Trace.VARIABLE_SET) @@ -547,7 +547,7 @@ def message(self,satisfied,preferredMessage='*',lang=None): if msgsRelationshipSet: return msgsRelationshipSet.label(self, preferredMessage, lang, returnText=False) return None - + def unsatisfiedSeverity(self, xpCtx, contextItem=None): msgsRelationshipSet = self.modelXbrl.relationshipSet(XbrlConst.assertionUnsatisfiedSeverities) if msgsRelationshipSet: @@ -555,43 +555,43 @@ def unsatisfiedSeverity(self, xpCtx, contextItem=None): if isinstance(rel.toModelObject, ModelAssertionSeverity): return rel.toModelObject.evaluate(xpCtx, contextItem=contextItem) return "ERROR" - + @property def propertyView(self): return super(ModelVariableSetAssertion, self).propertyView + (("test", self.test),) - + def __repr__(self): return ("{0}[{1}]{2})".format(self.__class__.__name__,self.objectId(),self.propertyView)) - + @property def viewExpression(self): return self.get("test") - + class ModelExistenceAssertion(ModelVariableSetAssertion): def init(self, modelDocument): self.evaluationsCount = 0 super(ModelExistenceAssertion, self).init(modelDocument) - + class ModelValueAssertion(ModelVariableSetAssertion): def init(self, modelDocument): super(ModelValueAssertion, self).init(modelDocument) - + def evaluate(self, xpCtx): try: - return xpCtx.evaluate(self.testProg) + return xpCtx.evaluate(self.testProg) except AttributeError: return None - + class ModelConsistencyAssertion(ModelFormulaResource): def init(self, modelDocument): super(ModelConsistencyAssertion, self).init(modelDocument) self.modelXbrl.modelConsistencyAssertions.add(self) self.modelXbrl.hasFormulae = True - + def clear(self): XPathParser.clearNamedProg(self, "radiusProg") super(ModelConsistencyAssertion, self).clear() - + def compile(self): if not hasattr(self, "radiusProg"): self.radiusProg = XPathParser.parse(self, self.radiusExpression, self, "radius", Trace.VARIABLE_SET) @@ -605,11 +605,11 @@ def evalRadius(self, xpCtx, factValue): @property def descendantArcroles(self): return (XbrlConst.consistencyAssertionFormula,) - + @property def hasProportionalAcceptanceRadius(self): return self.get("proportionalAcceptanceRadius") is not None - + @property def hasAbsoluteAcceptanceRadius(self): return self.get("absoluteAcceptanceRadius") is not None @@ -625,7 +625,7 @@ def message(self,satisfied,preferredMessage='*',lang=None): if msg is not None: return msg return None - + @property def radiusExpression(self): if self.get("proportionalAcceptanceRadius") is not None: @@ -641,9 +641,9 @@ def viewExpression(self): elif self.get("absoluteAcceptanceRadius") is not None: return "absoluteAcceptanceRadius=" + self.get("absoluteAcceptanceRadius") return "" - + @property - def xmlElementView(self): + def xmlElementView(self): return XmlUtil.xmlstring(self, stripXmlns=True, prettyPrint=True) @property @@ -653,10 +653,10 @@ def propertyView(self): ("proportional radius", self.get("proportionalAcceptanceRadius")) if self.get("proportionalAcceptanceRadius") else (), ("absolute radius", self.get("absoluteAcceptanceRadius")) if self.get("absoulteAcceptanceRadius") else () , ("strict", str(self.isStrict).lower())) - + def __repr__(self): return ("modelConsistencyAssertion[{0}]{1})".format(self.objectId(),self.propertyView)) - + class ModelParameter(ModelFormulaResource): def init(self, modelDocument): super(ModelParameter, self).init(modelDocument) @@ -666,29 +666,29 @@ def init(self, modelDocument): modelObject=self, name=self.parameterQname) else: self.modelXbrl.qnameParameters[self.parameterQname] = self - + def clear(self): XPathParser.clearNamedProg(self, "selectProg") super(ModelParameter, self).clear() - + def compile(self): if not hasattr(self, "selectProg"): self.selectProg = XPathParser.parse(self, self.select, self, "select", Trace.PARAMETER) super(ModelParameter, self).compile() - + def variableRefs(self, progs=[], varRefSet=None): return super(ModelParameter, self).variableRefs(self.selectProg, varRefSet) - + def evaluate(self, xpCtx, typeQname): try: return xpCtx.evaluateAtomicValue(self.selectProg, typeQname) except AttributeError: return None - + @property def name(self): return self.get("name") - + @property def parameterQname(self): # cannot overload with element's qname, needed for schema particle validation try: @@ -696,15 +696,15 @@ def parameterQname(self): # cannot overload with element's qname, needed for sch except AttributeError: self._parameterQname = self.prefixedNameQname(self.name) return self._parameterQname - + @property def select(self): return self.get("select") - + @property def isRequired(self): return self.get("required") == "true" - + @property def asType(self): try: @@ -712,7 +712,7 @@ def asType(self): except AttributeError: self._asType = self.prefixedNameQname(self.get("as")) return self._asType - + @property def propertyView(self): return (("id", self.id), @@ -721,11 +721,11 @@ def propertyView(self): ("select", self.select) if self.select else () , ("required", self.required) if self.isRequired else () , ("as", self.asType) if self.asType else () ) - - + + def __repr__(self): return ("{0}[{1}]{2})".format(self.__class__.__name__, self.objectId(),self.propertyView)) - + @property def viewExpression(self): return self.select @@ -733,7 +733,7 @@ def viewExpression(self): class ModelInstance(ModelParameter): def init(self, modelDocument): super(ModelInstance, self).init(modelDocument) - + @property def instanceQname(self): return self.parameterQname @@ -741,7 +741,7 @@ def instanceQname(self): class ModelVariable(ModelFormulaResource): def init(self, modelDocument): super(ModelVariable, self).init(modelDocument) - + def compile(self): super(ModelVariable, self).compile() @@ -752,40 +752,40 @@ def bindAsSequence(self): class ModelFactVariable(ModelVariable): def init(self, modelDocument): super(ModelFactVariable, self).init(modelDocument) - + def clear(self): XPathParser.clearNamedProg(self, "fallbackValueProg") del self._filterRelationships[:] super(ModelFactVariable, self).clear() - + def compile(self): if not hasattr(self, "fallbackValueProg"): self.fallbackValueProg = XPathParser.parse(self, self.fallbackValue, self, "fallbackValue", Trace.VARIABLE) super(ModelFactVariable, self).compile() - + def variableRefs(self, progs=[], varRefSet=None): try: return self._variableRefs except AttributeError: self._variableRefs = super(ModelFactVariable, self).variableRefs(self.fallbackValueProg, varRefSet) return self._variableRefs - + @property - def descendantArcroles(self): + def descendantArcroles(self): return (XbrlConst.variableFilter,) - + @property def nils(self): return self.get("nils") if self.get("nils") else "false" - + @property def matches(self): return self.get("matches") if self.get("matches") else "false" - + @property def fallbackValue(self): return self.get("fallbackValue") - + @property def filterRelationships(self): try: @@ -799,7 +799,7 @@ def filterRelationships(self): rels.append(rel) self._filterRelationships = rels return rels - + @property def propertyView(self): return (("label", self.xlinkLabel), @@ -810,7 +810,7 @@ def propertyView(self): def __repr__(self): return ("modelFactVariable[{0}]{1})".format(self.objectId(),self.propertyView)) - + @property def viewExpression(self): return ("fallbackValue =" + self.fallbackValue) if self.fallbackValue else "" @@ -818,29 +818,29 @@ def viewExpression(self): class ModelGeneralVariable(ModelVariable): def init(self, modelDocument): super(ModelGeneralVariable, self).init(modelDocument) - + def clear(self): XPathParser.clearNamedProg(self, "selectProg") super(ModelGeneralVariable, self).clear() - + def compile(self): if not hasattr(self, "selectProg"): self.selectProg = XPathParser.parse(self, self.select, self, "select", Trace.VARIABLE) super(ModelGeneralVariable, self).compile() - + def variableRefs(self, progs=[], varRefSet=None): return super(ModelGeneralVariable, self).variableRefs(self.selectProg, varRefSet) - + @property def select(self): return self.get("select") - + @property def propertyView(self): return (("label", self.xlinkLabel), ("select", self.select) if self.select else () , ("bindAsSequence", self.bindAsSequence) ) - + def __repr__(self): return ("modelGeneralVariable[{0}]{1})".format(self.objectId(),self.propertyView)) @@ -851,29 +851,29 @@ def viewExpression(self): class ModelPrecondition(ModelFormulaResource): def init(self, modelDocument): super(ModelPrecondition, self).init(modelDocument) - + def clear(self): XPathParser.clearNamedProg(self, "testProg") super(ModelPrecondition, self).clear() - + def compile(self): if not hasattr(self, "testProg"): self.testProg = XPathParser.parse(self, self.test, self, "test", Trace.VARIABLE) super(ModelPrecondition, self).compile() - + def variableRefs(self, progs=[], varRefSet=None): return super(ModelPrecondition, self).variableRefs(self.testProg, varRefSet) - + @property def test(self): return self.get("test") - + def evalTest(self, xpCtx): try: return xpCtx.evaluateBooleanValue(self.testProg) except AttributeError: return True # true if no test attribute because predicate has no filtering action - + @property def propertyView(self): return (("label", self.xlinkLabel), @@ -881,7 +881,7 @@ def propertyView(self): def __repr__(self): return ("modelPrecondition[{0}]{1})".format(self.objectId(),self.propertyView)) - + @property def viewExpression(self): return self.test @@ -889,10 +889,10 @@ def viewExpression(self): class ModelFilter(ModelFormulaResource): def init(self, modelDocument): super(ModelFilter, self).init(modelDocument) - + def aspectsCovered(self, varBinding): return set() #enpty set - + def filter(self, xpCtx, varBinding, facts, cmplmt): return facts @@ -902,7 +902,7 @@ def hasNoFilterVariableDependencies(self, xpCtx): except AttributeError: self._hasNoVariableDependencies = len(self.variableRefs() - xpCtx.parameterQnames) == 0 return self._hasNoVariableDependencies - + @property def isFilterShared(self): try: @@ -910,11 +910,11 @@ def isFilterShared(self): except AttributeError: self._isFilterShared = len(self.modelXbrl.relationshipSet("XBRL-formulae").toModelObject(self)) > 1 return self._isFilterShared - + @property def propertyView(self): return (("label", self.xlinkLabel),) - + def __repr__(self): return ("{0}[{1}]{2})".format(self.__class__.__name__, self.objectId(),self.propertyView)) @@ -925,33 +925,33 @@ def init(self, modelDocument): def clear(self): XPathParser.clearNamedProg(self, "testProg") super(ModelTestFilter, self).clear() - + def compile(self): if not hasattr(self, "testProg"): self.testProg = XPathParser.parse(self, self.test, self, "test", Trace.VARIABLE) super(ModelTestFilter, self).compile() - + def variableRefs(self, progs=[], varRefSet=None): # called from subclasses possibly with progs return super(ModelTestFilter, self).variableRefs((progs or []) + (self.testProg or []), varRefSet) - + @property def test(self): return self.get("test") - + def evalTest(self, xpCtx, fact): try: return xpCtx.evaluateBooleanValue(self.testProg, fact) except AttributeError: return True # true if no test attribute because predicate has no filtering action - + @property def propertyView(self): return (("label", self.xlinkLabel), ("test", self.test) ) - + def __repr__(self): return ("{0}[{1}]{2})".format(self.__class__.__name__, self.objectId(),self.propertyView)) - + @property def viewExpression(self): return self.test @@ -963,7 +963,7 @@ def init(self, modelDocument): @property def pattern(self): return self.get("pattern") - + @property def rePattern(self): try: @@ -971,15 +971,15 @@ def rePattern(self): except AttributeError: self._rePattern = re.compile(self.pattern) return self._rePattern - + @property def propertyView(self): return (("label", self.xlinkLabel), ("pattern", self.pattern) ) - + def __repr__(self): return ("{0}[{1}]{2})".format(self.__class__.__name__, self.objectId(),self.propertyView)) - + @property def viewExpression(self): return self.pattern @@ -992,7 +992,7 @@ def clear(self): XPathParser.clearNamedProgs(self, "excludedDimQnameProgs") XPathParser.clearNamedProgs(self, "includedDimQnameProgs") super(ModelAspectCover, self).clear() - + def aspectsCovered(self, varBinding, xpCtx=None): try: return self._aspectsCovered @@ -1008,7 +1008,7 @@ def aspectsCovered(self, varBinding, xpCtx=None): self.allDimensions = True self._aspectsCovered |= { Aspect.LOCATION, Aspect.CONCEPT, Aspect.ENTITY_IDENTIFIER, Aspect.PERIOD, Aspect.UNIT, - Aspect.NON_XDT_SEGMENT, Aspect.NON_XDT_SCENARIO, + Aspect.NON_XDT_SEGMENT, Aspect.NON_XDT_SCENARIO, Aspect.COMPLETE_SEGMENT, Aspect.COMPLETE_SCENARIO} elif aspect == "dimensions": self.allDimensions = True @@ -1029,7 +1029,7 @@ def aspectsCovered(self, varBinding, xpCtx=None): else: self._aspectsCovered.add(dimAspect) return self._aspectsCovered - + def dimAspectsCovered(self, varBinding): # if DIMENSIONS are provided then return all varBinding's dimensions less excluded dimensions self.aspectsCovered # must have aspectsCovered initialized before the rest of this method @@ -1042,7 +1042,7 @@ def dimAspectsCovered(self, varBinding): if aspectUniverseDimension not in self._dimsExcluded: dimsCovered.add(aspectUniverseDimension) return dimsCovered - + def compile(self): if not hasattr(self, "includedDimQnameProgs"): self.includedDimQnameProgs = [] @@ -1060,7 +1060,7 @@ def compile(self): def variableRefs(self, progs=[], varRefSet=None): return super(ModelAspectCover, self).variableRefs(self.includedDimQnameProgs + self.excludedDimQnameProgs, varRefSet) - + @property def viewExpression(self): return XmlUtil.innerTextList(self) @@ -1068,15 +1068,15 @@ def viewExpression(self): class ModelBooleanFilter(ModelFilter): def init(self, modelDocument): super(ModelBooleanFilter, self).init(modelDocument) - + @property - def descendantArcroles(self): + def descendantArcroles(self): return (XbrlConst.booleanFilter,) @property def filterRelationships(self): return self.modelXbrl.relationshipSet(XbrlConst.booleanFilter).fromModelObject(self) - + def aspectsCovered(self, varBinding): aspectsCovered = set() for rel in self.filterRelationships: @@ -1085,7 +1085,7 @@ def aspectsCovered(self, varBinding): if isinstance(_filter, ModelFilter): aspectsCovered |= _filter.aspectsCovered(varBinding) return aspectsCovered - + class ModelAndFilter(ModelBooleanFilter): def init(self, modelDocument): super(ModelAndFilter, self).init(modelDocument) @@ -1096,7 +1096,7 @@ def filter(self, xpCtx, varBinding, facts, cmplmt): else: andedFacts = set() return (facts - andedFacts) if cmplmt else andedFacts - + class ModelOrFilter(ModelBooleanFilter): def init(self, modelDocument): super(ModelOrFilter, self).init(modelDocument) @@ -1115,10 +1115,10 @@ def init(self, modelDocument): def clear(self): XPathParser.clearNamedProgs(self, "qnameExpressionProgs") super(ModelConceptName, self).clear() - + def aspectsCovered(self, varBinding): return {Aspect.CONCEPT} - + def compile(self): if not hasattr(self, "qnameExpressionProgs"): self.qnameExpressionProgs = [] @@ -1128,7 +1128,7 @@ def compile(self): self.qnameExpressionProgs.append( XPathParser.parse( self, XmlUtil.text(qnameExpression), qnameExpression, qNE, Trace.VARIABLE ) ) i += 1 super(ModelConceptName, self).compile() - + def variableRefs(self, progs=[], varRefSet=None): return super(ModelConceptName, self).variableRefs(self.qnameExpressionProgs, varRefSet) @@ -1141,35 +1141,35 @@ def conceptQnames(self): for qnameElt in XmlUtil.descendants(self, XbrlConst.cf, "qname"): self._conceptQnames.add( qname( qnameElt, XmlUtil.text(qnameElt) ) ) return self._conceptQnames - + @property def qnameExpressions(self): return [XmlUtil.text(qnameExpression) for qnameExpression in XmlUtil.descendants(self, XbrlConst.cf, "qnameExpression")] - + def evalQnames(self, xpCtx, fact): try: return set(xpCtx.evaluateAtomicValue(exprProg, 'xs:QName', fact) for exprProg in self.qnameExpressionProgs) except AttributeError: return set() - + def filter(self, xpCtx, varBinding, facts, cmplmt): if not self.qnameExpressionProgs: # optimize if simple qnamedFacts = set.union(*[inst.factsByQname[qn] for inst in varBinding.instances for qn in self.conceptQnames]) - return (facts - qnamedFacts) if cmplmt else (facts & qnamedFacts) - return set(fact for fact in facts - if cmplmt ^ (fact.qname in self.conceptQnames | self.evalQnames(xpCtx,fact))) - + return (facts - qnamedFacts) if cmplmt else (facts & qnamedFacts) + return set(fact for fact in facts + if cmplmt ^ (fact.qname in self.conceptQnames | self.evalQnames(xpCtx,fact))) + @property def propertyView(self): return (("label", self.xlinkLabel), ("names", self.viewExpression) ) - + def __repr__(self): return ("{0}[{1}]{2})".format(self.__class__.__name__, self.objectId(),self.propertyView)) - + @property def viewExpression(self): return XmlUtil.innerTextList(self) @@ -1180,24 +1180,24 @@ def init(self, modelDocument): def aspectsCovered(self, varBinding): return {Aspect.CONCEPT} - + @property def periodType(self): return self.get("periodType") - + def filter(self, xpCtx, varBinding, facts, cmplmt): factsOfPeriodType = set.union(*[inst.factsByPeriodType(self.periodType) for inst in varBinding.instances]) return (facts - factsOfPeriodType) if cmplmt else (facts & factsOfPeriodType) - + @property def propertyView(self): return (("label", self.xlinkLabel), ("periodType", self.periodType) ) - + def __repr__(self): return ("{0}[{1}]{2})".format(self.__class__.__name__, self.objectId(),self.propertyView)) - + @property def viewExpression(self): return self.periodType @@ -1208,23 +1208,23 @@ def init(self, modelDocument): def aspectsCovered(self, varBinding): return {Aspect.CONCEPT} - + @property def balance(self): return self.get("balance") - + def filter(self, xpCtx, varBinding, facts, cmplmt): - return set(fact for fact in facts - if cmplmt ^ ((fact.concept.balance == self.balance) if fact.concept.balance else (self.balance == "none"))) - + return set(fact for fact in facts + if cmplmt ^ ((fact.concept.balance == self.balance) if fact.concept.balance else (self.balance == "none"))) + @property def propertyView(self): return (("label", self.xlinkLabel), ("balance", self.balance) ) - + def __repr__(self): return ("{0}[{1}]{2})".format(self.__class__.__name__, self.objectId(),self.propertyView)) - + @property def viewExpression(self): return self.balance @@ -1236,22 +1236,22 @@ def init(self, modelDocument): def clear(self): XPathParser.clearNamedProgs(self, "qnameExpressionProgs") super(ModelConceptFilterWithQnameExpression, self).clear() - + def aspectsCovered(self, varBinding): return {Aspect.CONCEPT} - + @property def filterQname(self): qnameElt = XmlUtil.descendant(self, XbrlConst.cf, "qname") if qnameElt is not None: return qname( qnameElt, XmlUtil.text(qnameElt) ) return None - + @property def qnameExpression(self): qnExprElt = XmlUtil.descendant(self, XbrlConst.cf, "qnameExpression") return XmlUtil.text(qnExprElt) if qnExprElt is not None else None - + def compile(self): if not hasattr(self, "qnameExpressionProg"): qnExprElt = XmlUtil.descendant(self, XbrlConst.cf, "qnameExpression") @@ -1261,7 +1261,7 @@ def compile(self): def variableRefs(self, progs=[], varRefSet=None): # subclass may contribute progs return super(ModelConceptFilterWithQnameExpression, self).variableRefs((progs or []) + (self.qnameExpressionProg or []), varRefSet) - + def evalQname(self, xpCtx, fact): if self.filterQname: return self.filterQname @@ -1274,7 +1274,7 @@ def init(self, modelDocument): def clear(self): XPathParser.clearNamedProg(self, "valueProg") super(ModelConceptCustomAttribute, self).clear() - + @property def value(self): return self.get("value") @@ -1286,7 +1286,7 @@ def compile(self): def variableRefs(self, progs=[], varRefSet=None): return super(ModelConceptCustomAttribute, self).variableRefs(self.valueProg, varRefSet) - + def evalValue(self, xpCtx, fact): if not self.value: return None @@ -1294,15 +1294,15 @@ def evalValue(self, xpCtx, fact): return xpCtx.evaluateAtomicValue(self.valueProg, None, fact) except: return None - + def filter(self, xpCtx, varBinding, facts, cmplmt): - return set(fact for fact in facts + return set(fact for fact in facts for qn in (self.evalQname(xpCtx,fact),) for v in (self.evalValue(xpCtx,fact),) for c in (fact.concept,) if cmplmt ^ (qn is not None and c.get(qn.clarkNotation) is not None and - (v is None or v == typedValue(xpCtx.modelXbrl, c, attrQname=qn)))) + (v is None or v == typedValue(xpCtx.modelXbrl, c, attrQname=qn)))) @property def propertyView(self): @@ -1310,13 +1310,13 @@ def propertyView(self): ("value", self.value) if self.value else () , ("qname", self.filterQname) if self.filterQname else () , ("qnameExpr", self.qnameExpression) if self.qnameExpression else () ) - + def __repr__(self): return ("{0}[{1}]{2})".format(self.__class__.__name__, self.objectId(),self.propertyView)) - + @property def viewExpression(self): - return (XmlUtil.innerTextList(self) + + return (XmlUtil.innerTextList(self) + (" = " + self.value) if self.value else "") class ModelConceptDataType(ModelConceptFilterWithQnameExpression): @@ -1326,14 +1326,14 @@ def init(self, modelDocument): @property def strict(self): return self.get("strict") - + def filter(self, xpCtx, varBinding, facts, cmplmt): notStrict = self.strict != "true" if self.filterQname: # optimize if simple without a formula factsOfType = set.union(*[inst.factsByDatatype(notStrict, self.filterQname) for inst in varBinding.instances]) - return (facts - factsOfType) if cmplmt else (facts & factsOfType) - return set(fact for fact in facts + return (facts - factsOfType) if cmplmt else (facts & factsOfType) + return set(fact for fact in facts for qn in (self.evalQname(xpCtx,fact),) for c in (fact.concept,) if cmplmt ^ (c.typeQname == qn or (notStrict and c.type.isDerivedFrom(qn)))) @@ -1344,10 +1344,10 @@ def propertyView(self): ("strict", self.strict), ("type", self.filterQname) if self.filterQname else () , ("typeExpr", self.qnameExpression) if self.qnameExpression else () ) - + def __repr__(self): return ("{0}[{1}]{2})".format(self.__class__.__name__, self.objectId(),self.propertyView)) - + @property def viewExpression(self): return XmlUtil.innerTextList(self) + " \n(strict={0})".format(self.strict) @@ -1360,24 +1360,24 @@ def init(self, modelDocument): @property def strict(self): return self.get("strict") - + def filter(self, xpCtx, varBinding, facts, cmplmt): if self.strict == "true": - return set(fact for fact in facts + return set(fact for fact in facts if cmplmt ^ (fact.concept.substitutionGroupQname == self.evalQname(xpCtx,fact))) - return set(fact for fact in facts + return set(fact for fact in facts if cmplmt ^ fact.concept.substitutesForQname(self.evalQname(xpCtx,fact))) - + @property def propertyView(self): return (("label", self.xlinkLabel), ("strict", self.strict), ("subsGrp", self.filterQname) if self.filterQname else () , ("subsGrpExpr", self.qnameExpression) if self.qnameExpression else () ) - + def __repr__(self): return ("{0}[{1}]{2})".format(self.__class__.__name__, self.objectId(),self.propertyView)) - + @property def viewExpression(self): return XmlUtil.innerTextList(self) + " \n(strict={0})".format(self.strict) @@ -1394,24 +1394,24 @@ def clear(self): XPathParser.clearNamedProg(self, "arcnameExpressionProg") XPathParser.clearNamedProg(self, "testExpressionProg") super(ModelConceptRelation, self).clear() - + def aspectsCovered(self, varBinding): return {Aspect.CONCEPT} - + @property def variable(self): variableElt = XmlUtil.child(self, XbrlConst.crf, "variable") if variableElt is not None: return qname( variableElt, XmlUtil.text(variableElt), noPrefixIsNoNamespace=True ) return None - + @property def sourceQname(self): sourceQname = XmlUtil.child(self, XbrlConst.crf, "qname") if sourceQname is not None: return qname( sourceQname, XmlUtil.text(sourceQname) ) return None - + @property def linkrole(self): return XmlUtil.childText(self, XbrlConst.crf, "linkrole") @@ -1438,10 +1438,10 @@ def generations(self): try: return _INT( XmlUtil.childText(self, XbrlConst.crf, "generations") ) except (TypeError, ValueError): - if self.axis in ('sibling', 'child', 'parent'): + if self.axis in ('sibling', 'child', 'parent'): return 1 return 0 - + @property def test(self): return self.get("test") @@ -1482,7 +1482,7 @@ def compile(self): self.arcnameExpressionProg = XPathParser.parse(self, self.arcnameExpression, self, "arcnameQnameExpressionProg", Trace.VARIABLE) self.testExpressionProg = XPathParser.parse(self, self.test, self, "testExpressionProg", Trace.VARIABLE) super(ModelConceptRelation, self).compile() - + def variableRefs(self, progs=[], varRefSet=None): if self.variable and self.variable != XbrlConst.qnXfiRoot: if varRefSet is None: varRefSet = set() @@ -1500,7 +1500,7 @@ def evalSourceQname(self, xpCtx, fact): return xpCtx.evaluateAtomicValue(self.sourceQnameExpressionProg, 'xs:QName', fact) except: return None - + def evalLinkrole(self, xpCtx, fact): try: if self.linkrole: @@ -1508,7 +1508,7 @@ def evalLinkrole(self, xpCtx, fact): return xpCtx.evaluateAtomicValue(self.linkroleExpressionProg, 'xs:anyURI', fact) except: return None - + def evalLinkQname(self, xpCtx, fact): try: if self.linkQname: @@ -1516,7 +1516,7 @@ def evalLinkQname(self, xpCtx, fact): return xpCtx.evaluateAtomicValue(self.linkQnameExpressionProg, 'xs:QName', fact) except: return None - + def evalArcrole(self, xpCtx, fact): try: if self.arcrole: @@ -1524,7 +1524,7 @@ def evalArcrole(self, xpCtx, fact): return xpCtx.evaluateAtomicValue(self.arcroleExpressionProg, 'xs:anyURI', fact) except: return None - + def evalArcQname(self, xpCtx, fact): try: if self.arcQname: @@ -1532,13 +1532,13 @@ def evalArcQname(self, xpCtx, fact): return xpCtx.evaluateAtomicValue(self.arcQnameExpressionProg, 'xs:QName', fact) except: return None - + def evalTest(self, xpCtx, fact): try: return xpCtx.evaluateBooleanValue(self.testExpressionProg, fact) except: return None - + def filter(self, xpCtx, varBinding, facts, cmplmt): if self.variable: otherFact = xpCtx.inScopeVars.get(self.variable) @@ -1573,7 +1573,7 @@ def filter(self, xpCtx, varBinding, facts, cmplmt): break if (not factOk and axis.startswith('sibling') and - factQname != sourceQname and + factQname != sourceQname and not concept_relationships(xpCtx, None, (sourceQname, linkrole, arcrole, 'parent', 1, linkQname, arcQname)) and not concept_relationships(xpCtx, None, (factQname, linkrole, arcrole, 'parent', 1, linkQname, arcQname)) and concept_relationships(xpCtx, None, (factQname, linkrole, arcrole, 'child', 1, linkQname, arcQname))): @@ -1589,14 +1589,14 @@ def filter(self, xpCtx, varBinding, facts, cmplmt): factOk = True if cmplmt ^ (factOk): outFacts.add(fact) - return outFacts - + return outFacts + @property def viewExpression(self): return ' \n'.join("{}: {}".format(e.localName, e.text) for e in XmlUtil.children(self, "*", "*")) - + @property - def xmlElementView(self): + def xmlElementView(self): return XmlUtil.xmlstring(self, stripXmlns=True, prettyPrint=True) class ModelEntityIdentifier(ModelTestFilter): @@ -1604,14 +1604,14 @@ def init(self, modelDocument): super(ModelEntityIdentifier, self).init(modelDocument) def filter(self, xpCtx, varBinding, facts, cmplmt): - return set(fact for fact in facts + return set(fact for fact in facts if cmplmt ^ (fact.isItem and - self.evalTest(xpCtx, + self.evalTest(xpCtx, fact.context.entityIdentifierElement))) - + def aspectsCovered(self, varBinding): return {Aspect.ENTITY_IDENTIFIER} - + class ModelEntitySpecificIdentifier(ModelFilter): def init(self, modelDocument): super(ModelEntitySpecificIdentifier, self).init(modelDocument) @@ -1620,18 +1620,18 @@ def clear(self): XPathParser.clearNamedProg(self, "schemeProg") XPathParser.clearNamedProg(self, "valueProg") super(ModelEntitySpecificIdentifier, self).clear() - + def aspectsCovered(self, varBinding): return {Aspect.ENTITY_IDENTIFIER} - + @property def scheme(self): return self.get("scheme") - + @property def value(self): return self.get("value") - + def compile(self): if not hasattr(self, "schemeProg"): self.schemeProg = XPathParser.parse(self, self.scheme, self, "scheme", Trace.VARIABLE) @@ -1640,11 +1640,11 @@ def compile(self): def variableRefs(self, progs=[], varRefSet=None): return super(ModelEntitySpecificIdentifier, self).variableRefs((self.schemeProg or []) + (self.valueProg or []), varRefSet) - + def filter(self, xpCtx, varBinding, facts, cmplmt): - return set(fact for fact in facts - if cmplmt ^ (fact.isItem and ( - fact.context.entityIdentifier[0] == xpCtx.evaluateAtomicValue(self.schemeProg, 'xs:string', fact) and + return set(fact for fact in facts + if cmplmt ^ (fact.isItem and ( + fact.context.entityIdentifier[0] == xpCtx.evaluateAtomicValue(self.schemeProg, 'xs:string', fact) and fact.context.entityIdentifier[1] == xpCtx.evaluateAtomicValue(self.valueProg, 'xs:string', fact)))) @property @@ -1652,10 +1652,10 @@ def propertyView(self): return (("label", self.xlinkLabel), ("scheme", self.scheme), ("value", self.value) ) - + def __repr__(self): return ("{0}[{1}]{2})".format(self.__class__.__name__, self.objectId(),self.propertyView)) - + @property def viewExpression(self): return "{0} {1}".format(self.scheme, self.value) @@ -1667,35 +1667,35 @@ def init(self, modelDocument): def clear(self): XPathParser.clearNamedProg(self, "schemeProg") super(ModelEntityScheme, self).clear() - + def aspectsCovered(self, varBinding): return {Aspect.ENTITY_IDENTIFIER} - + @property def scheme(self): return self.get("scheme") - + def compile(self): if not hasattr(self, "schemeProg"): self.schemeProg = XPathParser.parse(self, self.scheme, self, "scheme", Trace.VARIABLE) super(ModelEntityScheme, self).compile() - + def variableRefs(self, progs=[], varRefSet=None): return super(ModelEntityScheme, self).variableRefs(self.schemeProg, varRefSet) - + def filter(self, xpCtx, varBinding, facts, cmplmt): - return set(fact for fact in facts - if cmplmt ^ (fact.isItem and - fact.context.entityIdentifier[0] == xpCtx.evaluateAtomicValue(self.schemeProg, 'xs:string', fact))) + return set(fact for fact in facts + if cmplmt ^ (fact.isItem and + fact.context.entityIdentifier[0] == xpCtx.evaluateAtomicValue(self.schemeProg, 'xs:string', fact))) @property def propertyView(self): return (("label", self.xlinkLabel), ("scheme", self.scheme) ) - + def __repr__(self): return ("{0}[{1}]{2})".format(self.__class__.__name__, self.objectId(),self.propertyView)) - + @property def viewExpression(self): return self.scheme @@ -1706,11 +1706,11 @@ def init(self, modelDocument): def aspectsCovered(self, varBinding): return {Aspect.ENTITY_IDENTIFIER} - + def filter(self, xpCtx, varBinding, facts, cmplmt): - return set(fact for fact in facts - if cmplmt ^ (fact.isItem and - self.rePattern.search(fact.context.entityIdentifierElement.xValue) is not None)) + return set(fact for fact in facts + if cmplmt ^ (fact.isItem and + self.rePattern.search(fact.context.entityIdentifierElement.xValue) is not None)) class ModelEntityRegexpScheme(ModelPatternFilter): def init(self, modelDocument): @@ -1718,11 +1718,11 @@ def init(self, modelDocument): def aspectsCovered(self, varBinding): return {Aspect.ENTITY_IDENTIFIER} - + def filter(self, xpCtx, varBinding, facts, cmplmt): - return set(fact for fact in facts - if cmplmt ^ (fact.isItem and - self.rePattern.search(fact.context.entityIdentifier[0]) is not None)) + return set(fact for fact in facts + if cmplmt ^ (fact.isItem and + self.rePattern.search(fact.context.entityIdentifier[0]) is not None)) class ModelGeneral(ModelTestFilter): def init(self, modelDocument): @@ -1733,15 +1733,15 @@ def filter(self, xpCtx, varBinding, facts, cmplmt): if self in xpCtx.cachedFilterResults: qualifyingFacts = xpCtx.cachedFilterResults[self] else: - xpCtx.cachedFilterResults[self] = qualifyingFacts = set(fact - for inst in varBinding.instances + xpCtx.cachedFilterResults[self] = qualifyingFacts = set(fact + for inst in varBinding.instances for fact in inst.factsInInstance if self.evalTest(xpCtx, fact)) - return (facts - qualifyingFacts) if cmplmt else (facts & qualifyingFacts) - return set(fact for fact in facts - if cmplmt ^ (self.evalTest(xpCtx, fact))) - - + return (facts - qualifyingFacts) if cmplmt else (facts & qualifyingFacts) + return set(fact for fact in facts + if cmplmt ^ (self.evalTest(xpCtx, fact))) + + class ModelMatchFilter(ModelFilter): def init(self, modelDocument): super(ModelMatchFilter, self).init(modelDocument) @@ -1753,11 +1753,11 @@ def aspectName(self): except AttributeError: self._aspectName = self.localName[5].lower() + self.localName[6:] return self._aspectName - + @property def dimension(self): return qname( self, self.get("dimension")) if self.get("dimension") else None - + @property def matchAny(self): try: @@ -1765,8 +1765,8 @@ def matchAny(self): except AttributeError: self._matchAny = self.get("matchAny") in ("true", "1") return self._matchAny - - + + @property def aspect(self): try: @@ -1776,16 +1776,16 @@ def aspect(self): if self._aspect == Aspect.DIMENSIONS: self._aspect = self.dimension return self._aspect - + def aspectsCovered(self, varBinding): return {self.aspect} - + @property def variable(self): return qname( self, self.get("variable"), noPrefixIsNoNamespace=True ) if self.get("variable") else None - + def variableRefs(self, progs=[], varRefSet=None): - if self.variable: + if self.variable: if varRefSet is None: varRefSet = set() varRefSet.add(self.variable) return super(ModelMatchFilter, self).variableRefs(None, varRefSet) @@ -1805,7 +1805,7 @@ def filter(self, xpCtx, varBinding, facts, cmplmt): firstFact = fact elif matchAll and not aspectMatches(xpCtx, fact, firstFact, aspect): ### error - raise XPathContext.XPathException(xpCtx, 'xbrlmfe:inconsistentMatchedVariableSequence', + raise XPathContext.XPathException(xpCtx, 'xbrlmfe:inconsistentMatchedVariableSequence', _('Matched variable sequence includes fact {0} inconsistent in aspect {1}').format( str(fact), aspectStr(aspect))) return cmplmt @@ -1816,12 +1816,12 @@ def filter(self, xpCtx, varBinding, facts, cmplmt): if not isinstance(otherFact,(ModelFact,tuple,list)): return set() if matchAll: - return set(fact for fact in facts - if cmplmt ^ (aspectMatches(xpCtx, fact, otherFact, aspect))) + return set(fact for fact in facts + if cmplmt ^ (aspectMatches(xpCtx, fact, otherFact, aspect))) else: # each otherFact may be different from the other, any one of which makes the match succeed - return set(fact for fact in facts + return set(fact for fact in facts if cmplmt ^ (any(aspectMatches(xpCtx, fact, anotherFact, aspect) - for anotherFact in otherFact))) + for anotherFact in otherFact))) @property def propertyView(self): @@ -1831,10 +1831,10 @@ def propertyView(self): ("matchAny", self.matchAny.lower()), ("variable", self.variable), ) - + def __repr__(self): return ("{0}[{1}]{2})".format(self.__class__.__name__, self.objectId(),self.propertyView)) - + @property def viewExpression(self): return self.dimension @@ -1845,12 +1845,12 @@ def init(self, modelDocument): def aspectsCovered(self, varBinding): return {Aspect.PERIOD} - + def filter(self, xpCtx, varBinding, facts, cmplmt): - return set(fact for fact in facts - if cmplmt ^ (fact.isItem and - self.evalTest(xpCtx, fact.context.period))) - + return set(fact for fact in facts + if cmplmt ^ (fact.isItem and + self.evalTest(xpCtx, fact.context.period))) + class ModelDateTimeFilter(ModelFilter): def init(self, modelDocument): super(ModelDateTimeFilter, self).init(modelDocument) @@ -1859,18 +1859,18 @@ def clear(self): XPathParser.clearNamedProg(self, "dateProg") XPathParser.clearNamedProg(self, "timeProg") super(ModelDateTimeFilter, self).clear() - + def aspectsCovered(self, varBinding): return {Aspect.PERIOD} - + @property def date(self): return self.get("date") - + @property def time(self): return self.get("time") - + def compile(self): if not hasattr(self, "dateProg"): self.dateProg = XPathParser.parse(self, self.date, self, "date", Trace.VARIABLE) @@ -1880,7 +1880,7 @@ def compile(self): def variableRefs(self, progs=[], varRefSet=None): # no subclasses super to this return super(ModelDateTimeFilter, self).variableRefs((self.dateProg or []) + (getattr(self, "timeProg", None) or []), varRefSet) - + def evalDatetime(self, xpCtx, fact, addOneDay=False): date = xpCtx.evaluateAtomicValue(self.dateProg, 'xs:date', fact) if hasattr(self,"timeProg"): @@ -1889,72 +1889,72 @@ def evalDatetime(self, xpCtx, fact, addOneDay=False): if addOneDay: return date + datetime.timedelta(1) return date - + @property def propertyView(self): return (("label", self.xlinkLabel), ("date", self.date), ("time", self.time) if self.time else () ) - + def __repr__(self): return ("{0}[{1}]{2})".format(self.__class__.__name__, self.objectId(),self.propertyView)) - + @property def viewExpression(self): return self.date + (" " + self.time) if self.time else "" - + class ModelPeriodStart(ModelDateTimeFilter): def init(self, modelDocument): super(ModelPeriodStart, self).init(modelDocument) def filter(self, xpCtx, varBinding, facts, cmplmt): - return set(fact for fact in facts - if cmplmt ^ (fact.isItem and - fact.context.startDatetime == self.evalDatetime(xpCtx, fact, addOneDay=False))) + return set(fact for fact in facts + if cmplmt ^ (fact.isItem and + fact.context.startDatetime == self.evalDatetime(xpCtx, fact, addOneDay=False))) class ModelPeriodEnd(ModelDateTimeFilter): def init(self, modelDocument): super(ModelPeriodEnd, self).init(modelDocument) def filter(self, xpCtx, varBinding, facts, cmplmt): - return set(fact for fact in facts - if cmplmt ^ (fact.isItem and (fact.context.isStartEndPeriod - and fact.context.endDatetime == self.evalDatetime(xpCtx, fact, addOneDay=True)))) + return set(fact for fact in facts + if cmplmt ^ (fact.isItem and (fact.context.isStartEndPeriod + and fact.context.endDatetime == self.evalDatetime(xpCtx, fact, addOneDay=True)))) class ModelPeriodInstant(ModelDateTimeFilter): def init(self, modelDocument): super(ModelPeriodInstant, self).init(modelDocument) def filter(self, xpCtx, varBinding, facts, cmplmt): - return set(fact for fact in facts - if cmplmt ^ (fact.isItem and - fact.context.instantDatetime == self.evalDatetime(xpCtx, fact, addOneDay=True))) - + return set(fact for fact in facts + if cmplmt ^ (fact.isItem and + fact.context.instantDatetime == self.evalDatetime(xpCtx, fact, addOneDay=True))) + class ModelForever(ModelFilter): def init(self, modelDocument): super(ModelForever, self).init(modelDocument) def filter(self, xpCtx, varBinding, facts, cmplmt): - return set(fact for fact in facts - if cmplmt ^ (fact.isItem and fact.context.isForeverPeriod)) + return set(fact for fact in facts + if cmplmt ^ (fact.isItem and fact.context.isForeverPeriod)) def aspectsCovered(self, varBinding): return {Aspect.PERIOD} - + class ModelInstantDuration(ModelFilter): def init(self, modelDocument): super(ModelInstantDuration, self).init(modelDocument) def aspectsCovered(self, varBinding): return {Aspect.PERIOD} - + @property def variable(self): return qname( self, self.get("variable"), noPrefixIsNoNamespace=True ) if self.get("variable") else None - + def variableRefs(self, progs=[], varRefSet=None): - if self.variable: + if self.variable: if varRefSet is None: varRefSet = set() varRefSet.add(self.variable) return super(ModelInstantDuration, self).variableRefs(None, varRefSet) @@ -1962,19 +1962,19 @@ def variableRefs(self, progs=[], varRefSet=None): @property def boundary(self): return self.get("boundary") - + def filter(self, xpCtx, varBinding, facts, cmplmt): otherFact = xpCtx.inScopeVars.get(self.variable) - if (otherFact is not None and isinstance(otherFact,ModelFact) and otherFact.isItem and + if (otherFact is not None and isinstance(otherFact,ModelFact) and otherFact.isItem and otherFact.context is not None and otherFact.context.isStartEndPeriod): if self.boundary == 'start': otherDatetime = otherFact.context.startDatetime else: otherDatetime = otherFact.context.endDatetime - return set(fact for fact in facts + return set(fact for fact in facts if cmplmt ^ (fact.isItem and (fact.context is not None and fact.context.isInstantPeriod and - fact.context.instantDatetime == otherDatetime))) + fact.context.instantDatetime == otherDatetime))) return facts # couldn't filter @property @@ -1982,10 +1982,10 @@ def propertyView(self): return (("label", self.xlinkLabel), ("variable", self.variable), ("boundary", self.boundary) ) - + def __repr__(self): return ("{0}[{1}]{2})".format(self.__class__.__name__, self.objectId(),self.propertyView)) - + @property def viewExpression(self): return "${0} ({1})".format(self.variable, self.boundary) @@ -1999,7 +1999,7 @@ def __init__(self, qname, qnameExprProg, variable, linkrole, arcrole, axis): self.arcrole = arcrole self.axis = axis self.isMemberStatic = qname and not qnameExprProg and not variable and not axis - + class ModelExplicitDimension(ModelFilter): def init(self, modelDocument): super(ModelExplicitDimension, self).init(modelDocument) @@ -2011,10 +2011,10 @@ def clear(self): XPathParser.clearProg(memberModel.qnameExprProg) memberModel.__dict__.clear() # dereference super(ModelExplicitDimension, self).clear() - + def aspectsCovered(self, varBinding): return {self.dimQname} - + @property def dimQname(self): try: @@ -2023,13 +2023,13 @@ def dimQname(self): dQn = XmlUtil.child(XmlUtil.child(self,XbrlConst.df,"dimension"), XbrlConst.df, "qname") self._dimQname = qname( dQn, XmlUtil.text(dQn) ) if dQn is not None else None return self._dimQname - + @property def dimQnameExpression(self): qnameExpression = XmlUtil.descendant(XmlUtil.child(self,XbrlConst.df,"dimension"), XbrlConst.df, "qnameExpression") if qnameExpression is not None: return XmlUtil.text(qnameExpression) - return None + return None def compile(self): if not hasattr(self, "dimQnameExpressionProg"): @@ -2037,7 +2037,7 @@ def compile(self): self.memberProgs = [] for memberElt in XmlUtil.children(self, XbrlConst.df, "member"): qnameElt = XmlUtil.child(memberElt, XbrlConst.df, "qname") - qnameExpr = XmlUtil.child(memberElt, XbrlConst.df, "qnameExpression") + qnameExpr = XmlUtil.child(memberElt, XbrlConst.df, "qnameExpression") variableElt = XmlUtil.child(memberElt, XbrlConst.df, "variable") linkrole = XmlUtil.child(memberElt, XbrlConst.df, "linkrole") arcrole = XmlUtil.child(memberElt, XbrlConst.df, "arcrole") @@ -2051,7 +2051,7 @@ def compile(self): XmlUtil.text(axis) if axis is not None else None) self.memberProgs.append(memberModel) super(ModelExplicitDimension, self).compile() - self.isFilterStatic = (self.dimQname and not self.dimQnameExpressionProg and + self.isFilterStatic = (self.dimQname and not self.dimQnameExpressionProg and all(mp.isMemberStatic for mp in self.memberProgs)) if self.isFilterStatic: self.staticMemberQnames = set(mp.qname for mp in self.memberProgs) @@ -2060,7 +2060,7 @@ def compile(self): self.modelXbrl.error("xfie:invalidExplicitDimensionQName", _("%(dimension)s is not an explicit dimension concept QName."), modelObject=self, dimension=self.dimQname) - + def variableRefs(self, progs=[], varRefSet=None): if varRefSet is None: varRefSet = set() memberModelMemberProgs = [] @@ -2078,7 +2078,7 @@ def evalDimQname(self, xpCtx, fact): return xpCtx.evaluateAtomicValue(self.dimQnameExpressionProg, 'xs:QName', fact) except: return None - + def filter(self, xpCtx, varBinding, facts, cmplmt): if not facts: # if an empty winnowing fact set return it return facts @@ -2092,7 +2092,7 @@ def filter(self, xpCtx, varBinding, facts, cmplmt): else: dimedFacts = set.union(*[inst.factsByDimMemQname(dimQname) for inst in varBinding.instances]) - return (facts - dimedFacts) if cmplmt else (facts & dimedFacts) + return (facts - dimedFacts) if cmplmt else (facts & dimedFacts) else: outFacts = set() @@ -2124,7 +2124,7 @@ def filter(self, xpCtx, varBinding, facts, cmplmt): matchMemQname = xpCtx.evaluateAtomicValue(memberModel.qnameExprProg, 'xs:QName', fact) memConcept = xpCtx.modelXbrl.qnameConcepts.get(matchMemQname) if memConcept is None: - #self.modelXbrl.error(_("{0} is not a domain item concept.").format(matchMemQname), + #self.modelXbrl.error(_("{0} is not a domain item concept.").format(matchMemQname), # "err", "xbrldfe:invalidDomainMember") return set() if (not memberModel.axis or memberModel.axis.endswith('-self')) and \ @@ -2159,14 +2159,14 @@ def filter(self, xpCtx, varBinding, facts, cmplmt): else: # check dynamic mem qname for validity from arelle.ValidateXbrlDimensions import checkPriItemDimValueValidity if not checkPriItemDimValueValidity(self, fact.concept, dimConcept, memConcept): - self.modelXbrl.error(_("{0} is not a valid domain member for dimension {1} of primary item {2}.").format(matchMemQname, dimConcept.qname, fact.qname), + self.modelXbrl.error(_("{0} is not a valid domain member for dimension {1} of primary item {2}.").format(matchMemQname, dimConcept.qname, fact.qname), "err", "xbrldfe:invalidDomainMember") return set() ''' ''' if not factOk: if not domainMembersExist and memberModel.axis: - self.modelXbrl.error(_("No member found in the network of explicit dimension concept {0}").format(dimQname), + self.modelXbrl.error(_("No member found in the network of explicit dimension concept {0}").format(dimQname), "err", "xbrldfe:invalidDomainMember") return set() ''' @@ -2176,7 +2176,7 @@ def filter(self, xpCtx, varBinding, facts, cmplmt): factOk = True # don't filter facts which are tuples if cmplmt ^ (factOk): outFacts.add(fact) - return outFacts + return outFacts @property def propertyView(self): @@ -2198,7 +2198,7 @@ def propertyView(self): ("dimExpr", self.dimQnameExpression) if self.dimQnameExpression else () , ("members", "({0})".format(len(members)), tuple(members)) if members else (), ) - + @property def viewExpression(self): lines = [] @@ -2220,21 +2220,21 @@ def init(self, modelDocument): def aspectsCovered(self, varBinding): return {self.dimQname} - + @property def dimQname(self): dimQname = XmlUtil.descendant(self, XbrlConst.df, "qname") if dimQname is not None: return qname( dimQname, XmlUtil.text(dimQname) ) return None - + @property def dimQnameExpression(self): qnameExpression = XmlUtil.descendant(self, XbrlConst.df, "qnameExpression") if qnameExpression is not None: return XmlUtil.text(qnameExpression) - return None - + return None + def compile(self): if not hasattr(self, "dimQnameExpressionProg"): self.dimQnameExpressionProg = XPathParser.parse(self, self.dimQnameExpression, self, "dimQnameExpressionProg", Trace.VARIABLE) @@ -2242,7 +2242,7 @@ def compile(self): def variableRefs(self, progs=[], varRefSet=None): return super(ModelTypedDimension, self).variableRefs(self.dimQnameExpression, varRefSet) - + def evalDimQname(self, xpCtx, fact): try: if self.dimQname: @@ -2250,7 +2250,7 @@ def evalDimQname(self, xpCtx, fact): return xpCtx.evaluateAtomicValue(self.dimQnameExpressionProg, 'xs:QName', fact) except: return None - + def filter(self, xpCtx, varBinding, facts, cmplmt): outFacts = set() for fact in facts: @@ -2261,8 +2261,8 @@ def filter(self, xpCtx, varBinding, facts, cmplmt): # typed dimension test item is the element, not its contents, e.g. dim self.evalTest(xpCtx, dim))): outFacts.add(fact) - return outFacts - + return outFacts + @property def viewExpression(self): return XmlUtil.innerTextList(self) @@ -2274,9 +2274,9 @@ def init(self, modelDocument): @property def variable(self): return qname(self, self.get("variable"), noPrefixIsNoNamespace=True) if self.get("variable") else None - + def variableRefs(self, progs=[], varRefSet=None): - if self.variable: + if self.variable: if varRefSet is None: varRefSet = set() varRefSet.add(self.variable) return super(ModelRelativeFilter, self).variableRefs(None, varRefSet) @@ -2289,7 +2289,7 @@ def filter(self, xpCtx, varBinding, facts, cmplmt): otherVarBinding = xpCtx.varBindings.get(self.variable) hasOtherFactVar = otherVarBinding and otherVarBinding.isFactVar and not otherVarBinding.isFallback otherFact = otherVarBinding.yieldedFact if hasOtherFactVar else None - return set(fact for fact in facts + return set(fact for fact in facts if cmplmt ^ (hasOtherFactVar and aspectsMatch(xpCtx, otherFact, fact, aspectsUncovered) and (fact.isTuple or @@ -2298,15 +2298,15 @@ def filter(self, xpCtx, varBinding, facts, cmplmt): if (not varBinding.hasAspectValueCovered(dimAspect) and not otherVarBinding.hasAspectValueCovered(dimAspect)))) )) - + @property def propertyView(self): return (("label", self.xlinkLabel), ("variable", self.variable) ) - + def __repr__(self): return ("{0}[{1}]{2})".format(self.__class__.__name__, self.objectId(),self.propertyView)) - + @property def viewExpression(self): return "${0}".format(self.variable) @@ -2317,50 +2317,50 @@ def init(self, modelDocument): def aspectsCovered(self, varBinding): return {Aspect.COMPLETE_SEGMENT} - + def filter(self, xpCtx, varBinding, facts, cmplmt): - return set(fact for fact in facts - if cmplmt ^ (fact.isItem and + return set(fact for fact in facts + if cmplmt ^ (fact.isItem and (fact.context.hasSegment and self.evalTest(xpCtx, fact.context.segment)))) - + class ModelScenarioFilter(ModelTestFilter): def init(self, modelDocument): super(ModelScenarioFilter, self).init(modelDocument) def aspectsCovered(self, varBinding): return {Aspect.COMPLETE_SCENARIO} - + def filter(self, xpCtx, varBinding, facts, cmplmt): - return set(fact for fact in facts + return set(fact for fact in facts if cmplmt ^ (fact.isItem and (fact.context.hasScenario and self.evalTest(xpCtx, fact.context.scenario)))) - + class ModelAncestorFilter(ModelFilter): def init(self, modelDocument): super(ModelAncestorFilter, self).init(modelDocument) def aspectsCovered(self, varBinding): return {Aspect.LOCATION} - + @property def ancestorQname(self): ancestorQname = XmlUtil.descendant(self, XbrlConst.tf, "qname") if ancestorQname is not None: return qname( ancestorQname, XmlUtil.text(ancestorQname) ) return None - + @property def qnameExpression(self): qnameExpression = XmlUtil.descendant(self, XbrlConst.tf, "qnameExpression") if qnameExpression: return XmlUtil.text(qnameExpression) - return None - + return None + def compile(self): if not hasattr(self, "qnameExpressionProg"): self.qnameExpressionProg = XPathParser.parse(self, self.qnameExpression, self, "qnameExpressionProg", Trace.VARIABLE) super(ModelAncestorFilter, self).compile() - + def variableRefs(self, progs=[], varRefSet=None): return super(ModelAncestorFilter, self).variableRefs(self.qnameExpressionProg, varRefSet) @@ -2372,19 +2372,19 @@ def evalQname(self, xpCtx, fact): return xpCtx.evaluateAtomicValue(self.qnameExpressionProg, 'xs:QName', fact) except: return None - + def filter(self, xpCtx, varBinding, facts, cmplmt): return set(fact for fact in facts if cmplmt ^ ( self.evalQname(xpCtx,fact) in fact.ancestorQnames )) - + @property def propertyView(self): return (("label", self.xlinkLabel), ("ancestor", self.ancestorQname) if self.ancestorQname else () , ("ancestorExpr", self.qnameExpression) if self.qnameExpression else () ) - + def __repr__(self): return ("{0}[{1}]{2})".format(self.__class__.__name__, self.objectId(),self.propertyView)) - + @property def viewExpression(self): return self.ancestorQname if self.ancestorQname else self.qnameExpression @@ -2396,29 +2396,29 @@ def init(self, modelDocument): def clear(self): XPathParser.clearNamedProg(self, "qnameExpressionProg") super(ModelParentFilter, self).clear() - + def aspectsCovered(self, varBinding): return {Aspect.LOCATION} - + @property def parentQname(self): parentQname = XmlUtil.descendant(self, XbrlConst.tf, "qname") if parentQname is not None: return qname( parentQname, XmlUtil.text(parentQname) ) return None - + @property def qnameExpression(self): qnameExpression = XmlUtil.descendant(self, XbrlConst.tf, "qnameExpression") if qnameExpression: return XmlUtil.text(qnameExpression) return None - + def compile(self): if not hasattr(self, "qnameExpressionProg"): self.qnameExpressionProg = XPathParser.parse(self, self.qnameExpression, self, "qnameExpressionProg", Trace.VARIABLE) super(ModelParentFilter, self).compile() - + def variableRefs(self, progs=[], varRefSet=None): return super(ModelParentFilter, self).variableRefs(self.qnameExpressionProg, varRefSet) @@ -2430,20 +2430,20 @@ def evalQname(self, xpCtx, fact): return xpCtx.evaluateAtomicValue(self.qnameExpressionProg, 'xs:QName', fact) except: return None - + def filter(self, xpCtx, varBinding, facts, cmplmt): return set(fact for fact in facts if cmplmt ^ ( self.evalQname(xpCtx,fact) == fact.parentQname )) - - + + @property def propertyView(self): return (("label", self.xlinkLabel), ("parent", self.parentQname) if self.parentQname else () , ("parentExpr", self.qnameExpression) if self.qnameExpression else () ) - + def __repr__(self): return ("{0}[{1}]{2})".format(self.__class__.__name__, self.objectId(),self.propertyView)) - + @property def viewExpression(self): return self.parentQname if self.parentQname else self.qnameExpression @@ -2455,25 +2455,25 @@ def init(self, modelDocument): def clear(self): XPathParser.clearNamedProg(self, "locationProg") super(ModelLocationFilter, self).clear() - + def aspectsCovered(self, varBinding): return {Aspect.LOCATION} - + @property def location(self): return self.get("location") - + @property def variable(self): return qname(self, self.get("variable"), noPrefixIsNoNamespace=True) if self.get("variable") else None - + def compile(self): if not hasattr(self, "locationProg"): self.locationProg = XPathParser.parse(self, self.location, self, "locationProg", Trace.VARIABLE) super(ModelLocationFilter, self).compile() - + def variableRefs(self, progs=[], varRefSet=None): - if self.variable: + if self.variable: if varRefSet is None: varRefSet = set() varRefSet.add(self.variable) return super(ModelLocationFilter, self).variableRefs(progs, varRefSet) @@ -2483,25 +2483,25 @@ def evalLocation(self, xpCtx, fact): return set(xpCtx.flattenSequence(xpCtx.evaluate(self.locationProg, fact))) except: return set() - + def filter(self, xpCtx, varBinding, facts, cmplmt): varFacts = xpCtx.inScopeVars.get(self.variable,[]) if isinstance(varFacts,ModelFact): candidateElts = {varFacts} elif isinstance(varFacts,(list,tuple)): - candidateElts = set(f for f in varFacts if isinstance(f,ModelFact)) - return set(fact for fact in facts + candidateElts = set(f for f in varFacts if isinstance(f,ModelFact)) + return set(fact for fact in facts if cmplmt ^ ( len(candidateElts & self.evalLocation(xpCtx,fact) ) > 0 )) - + @property def propertyView(self): return (("label", self.xlinkLabel), ("location", self.location), ("variable", self.variable) ) - + def __repr__(self): return ("{0}[{1}]{2})".format(self.__class__.__name__, self.objectId(),self.propertyView)) - + @property def viewExpression(self): return "{0} ${1}".format(self.location, self.variable) @@ -2512,13 +2512,13 @@ def init(self, modelDocument): def aspectsCovered(self, varBinding): return {Aspect.LOCATION} - + @property def variable(self): return qname(self, self.get("variable"), noPrefixIsNoNamespace=True) if self.get("variable") else None - + def variableRefs(self, progs=[], varRefSet=None): - if self.variable: + if self.variable: if varRefSet is None: varRefSet = set() varRefSet.add(self.variable) return super(ModelSiblingFilter, self).variableRefs(progs, varRefSet) @@ -2531,17 +2531,17 @@ def filter(self, xpCtx, varBinding, facts, cmplmt): otherFactParent = otherFact.parentElement else: otherFactParent = None - return set(fact for fact in facts + return set(fact for fact in facts if cmplmt ^ (fact.parentElement == otherFactParent)) @property def propertyView(self): return (("label", self.xlinkLabel), ("variable", self.variable) ) - + def __repr__(self): return ("{0}[{1}]{2})".format(self.__class__.__name__, self.objectId(),self.propertyView)) - + @property def viewExpression(self): return "${0}".format(self.variable) @@ -2552,12 +2552,12 @@ def init(self, modelDocument): def aspectsCovered(self, varBinding): return {Aspect.UNIT} - + def filter(self, xpCtx, varBinding, facts, cmplmt): - return set(fact for fact in facts - if cmplmt ^ (fact.isItem and + return set(fact for fact in facts + if cmplmt ^ (fact.isItem and (fact.isNumeric and self.evalTest(xpCtx, fact.unit)))) - + class ModelSingleMeasure(ModelFilter): def init(self, modelDocument): super(ModelSingleMeasure, self).init(modelDocument) @@ -2565,29 +2565,29 @@ def init(self, modelDocument): def clear(self): XPathParser.clearNamedProg(self, "qnameExpressionProg") super(ModelSingleMeasure, self).clear() - + def aspectsCovered(self, varBinding): return {Aspect.UNIT} - + @property def measureQname(self): measureQname = XmlUtil.descendant(self, XbrlConst.uf, "qname") if measureQname is not None: return qname( measureQname, XmlUtil.text(measureQname) ) return None - + @property def qnameExpression(self): qnameExpression = XmlUtil.descendant(self, XbrlConst.uf, "qnameExpression") if qnameExpression is not None: return XmlUtil.text(qnameExpression) return None - + def compile(self): if not hasattr(self, "qnameExpressionProg"): self.qnameExpressionProg = XPathParser.parse(self, self.qnameExpression, self, "qnameExpressionProg", Trace.VARIABLE) super(ModelSingleMeasure, self).compile() - + def variableRefs(self, progs=[], varRefSet=None): return super(ModelSingleMeasure, self).variableRefs(self.qnameExpressionProg, varRefSet) @@ -2600,22 +2600,22 @@ def evalQname(self, xpCtx, fact): except Exception as ex: print ("filter exception {}".format(ex)) return None - + def filter(self, xpCtx, varBinding, facts, cmplmt): - return set(fact for fact in facts - if cmplmt ^ (fact.isNumeric and + return set(fact for fact in facts + if cmplmt ^ (fact.isNumeric and fact.unit.isSingleMeasure and (fact.unit.measures[0][0] == self.evalQname(xpCtx,fact)))) - + @property def propertyView(self): return (("label", self.xlinkLabel), ("measure", self.qname) if self.qname else () , ("measureExpr", self.qnameExpression) if self.qnameExpression else () ) - + def __repr__(self): return ("{0}[{1}]{2})".format(self.__class__.__name__, self.objectId(),self.propertyView)) - + @property def viewExpression(self): return self.qname if self.qname else self.qnameExpression @@ -2625,9 +2625,9 @@ def init(self, modelDocument): super(ModelNilFilter, self).init(modelDocument) def filter(self, xpCtx, varBinding, facts, cmplmt): - return set(fact for fact in facts + return set(fact for fact in facts if cmplmt ^ fact.isNil) - + class ModelPrecisionFilter(ModelFilter): def init(self, modelDocument): super(ModelPrecisionFilter, self).init(modelDocument) @@ -2635,24 +2635,24 @@ def init(self, modelDocument): @property def minimum(self): return self.get("minimum") - + def filter(self, xpCtx, varBinding, facts, cmplmt): minimum = self.minimum numMinimum = float('INF') if minimum == 'INF' else _INT(minimum) - return set(fact for fact in facts + return set(fact for fact in facts if cmplmt ^ (self.minimum != 'INF' and not fact.isNil and fact.isNumeric and not fact.isFraction and - inferredPrecision(fact) >= numMinimum)) + inferredPrecision(fact) >= numMinimum)) @property def propertyView(self): return (("label", self.xlinkLabel), ("minimum", self.minimum) ) - + def __repr__(self): return ("{0}[{1}]{2})".format(self.__class__.__name__, self.objectId(),self.propertyView)) - + @property def viewExpression(self): return self.minimum @@ -2660,7 +2660,7 @@ def viewExpression(self): class ModelEqualityDefinition(ModelTestFilter): def init(self, modelDocument): super(ModelEqualityDefinition, self).init(modelDocument) - + def evalTest(self, xpCtx, facta, factb): xpCtx.inScopeVars[XbrlConst.qnEqualityTestA] = facta xpCtx.inScopeVars[XbrlConst.qnEqualityTestB] = factb @@ -2681,11 +2681,11 @@ def clear(self): def xmlLang(self): """(str) -- xml:lang attribute""" return XmlUtil.ancestorOrSelfAttr(self, "{http://www.w3.org/XML/1998/namespace}lang") - + @property def separator(self): return self.get("separator") - + def compile(self): if not hasattr(self, "expressionProgs") and hasattr(self, "expressions"): self.expressionProgs = [] @@ -2695,7 +2695,7 @@ def compile(self): self.expressionProgs.append( XPathParser.parse( self, expression, self, name, Trace.MESSAGE ) ) i += 1 super(ModelMessage, self).compile() - + def variableRefs(self, progs=[], varRefSet=None): # no subclass calls this try: return super(ModelMessage, self).variableRefs(self.expressionProgs, varRefSet) @@ -2703,7 +2703,7 @@ def variableRefs(self, progs=[], varRefSet=None): # no subclass calls this return set() # no expressions def evaluate(self, xpCtx, contextItem=None): - return self.formatString.format([xpCtx.evaluateAtomicValue(p, 'xs:string', contextItem=contextItem) + return self.formatString.format([xpCtx.evaluateAtomicValue(p, 'xs:string', contextItem=contextItem) for p in self.expressionProgs]) @property @@ -2711,14 +2711,14 @@ def propertyView(self): return (("label", self.xlinkLabel), ("separator", self.separator) if self.separator else (), ("text", self.text) ) - + def __repr__(self): return ("{0}[{1}]{2})".format(self.__class__.__name__, self.objectId(),self.propertyView)) - + @property def viewExpression(self): return XmlUtil.text(self) - + class ModelAssertionSeverity(ModelFormulaResource): # both Assertion Severity 1.0 & 2.0 def init(self, modelDocument): super(ModelAssertionSeverity, self).init(modelDocument) @@ -2727,12 +2727,12 @@ def clear(self): if not self.isStatic: XPathParser.clearNamedProg(self, "severityProg") super(ModelResource, self).clear() - + def compile(self): if not self.isStatic and not hasattr(self, "severityProg"): self.severityProg = XPathParser.parse(self, self.severity, self, "severity", Trace.VARIABLE_SET) super(ModelAssertionSeverity, self).compile() - + def evaluate(self, xpCtx, contextItem=None): if self.isStatic: return self.id # OK, WARNING or ERROR @@ -2743,20 +2743,20 @@ def evaluate(self, xpCtx, contextItem=None): _("Invalid assertion severity id %(id)s expression result %(result)s"), modelObject=self, id=self.id, result=result) return "ERROR" - + def variableRefs(self, progs=[], varRefSet=None): if self.isStatic: - return [] + return [] return super(ModelAssertionSeverity, self).variableRefs((self.severityProg or []), varRefSet) - + @property def isStatic(self): return self.localName != "expression" - + @property def level(self): return self.localName - + @property def severity(self): return self.get("severity") @@ -2767,14 +2767,14 @@ def propertyView(self): ("level", self.level) ) + ( () if self.isStatic else (("severity", self.severity),) ) - + def __repr__(self): return ("{0}[{1}]{2})".format(self.__class__.__name__, self.objectId(),self.propertyView)) - + @property def viewExpression(self): return self.level if self.isStatic else self.severity - + class ModelCustomFunctionSignature(ModelFormulaResource): @@ -2787,7 +2787,7 @@ def init(self, modelDocument): @property def descendantArcroles(self): return (XbrlConst.functionImplementation,) - + @property def name(self): try: @@ -2795,7 +2795,7 @@ def name(self): except AttributeError: self._name = self.get("name") return self._name - + @property def functionQname(self): # cannot overload qname, needed for element and particle validation try: @@ -2803,7 +2803,7 @@ def functionQname(self): # cannot overload qname, needed for element and particl except AttributeError: self._functionQname = self.prefixedNameQname(self.name) return self._functionQname - + @property def outputType(self): try: @@ -2811,7 +2811,7 @@ def outputType(self): except AttributeError: self._outputType = self.prefixedNameQname(self.get("output")) return self._outputType - + @property def inputTypes(self): try: @@ -2820,17 +2820,17 @@ def inputTypes(self): self._inputTypes = [elt.prefixedNameQname(elt.get("type")) for elt in XmlUtil.children(self, XbrlConst.variable, "input")] return self._inputTypes - + @property def propertyView(self): return (("label", self.xlinkLabel), ("name", self.name), ("output type", self.outputType) ) + \ tuple((_("input {0}").format(i+1), type) for i,type in enumerate(self.inputTypes)) - + def __repr__(self): return ("{0}[{1}]{2}").format(self.__class__.__name__, self.objectId(),self.propertyView) - + @property def viewExpression(self): return _("{0}({1}) as {2}").format(self.name, ", ".join(str(t) for t in self.inputTypes), self.outputType) @@ -2845,7 +2845,7 @@ def clear(self): XPathParser.clearNamedProg(self, "outputProg") XPathParser.clearNamedProgs(self, "stepProgs") super(ModelCustomFunctionImplementation, self).clear() - + @property def inputNames(self): try: @@ -2854,7 +2854,7 @@ def inputNames(self): self._inputNames = [qname(elt, elt.get("name")) for elt in XmlUtil.children(self, XbrlConst.cfi, "input")] return self._inputNames - + @property def stepExpressions(self): try: @@ -2863,7 +2863,7 @@ def stepExpressions(self): self._stepExpressions = [(qname(elt, elt.get("name")), elt.text) for elt in XmlUtil.children(self, XbrlConst.cfi, "step")] return self._stepExpressions - + @property def outputExpression(self): try: @@ -2872,7 +2872,7 @@ def outputExpression(self): outputElt = XmlUtil.child(self, XbrlConst.cfi, "output") self._outputExpression = XmlUtil.text(outputElt) if outputElt is not None else None return self._outputExpression - + def compile(self): if not hasattr(self, "outputProg"): elt = XmlUtil.child(self, XbrlConst.cfi, "output") @@ -2882,7 +2882,7 @@ def compile(self): name = "qnameExpression_{0}".format(qname(elt, elt.get("name"))) self.stepProgs.append( XPathParser.parse( self, elt.text, elt, name, Trace.CUSTOM_FUNCTION ) ) super(ModelCustomFunctionImplementation, self).compile() - + def variableRefs(self, progs=[], varRefSet=None): return super(ModelCustomFunctionImplementation, self).variableRefs((self.outputProg or []) + self.stepProgs, varRefSet) @@ -2892,7 +2892,7 @@ def propertyView(self): tuple((_("input {0}").format(i+1), str(name)) for i,name in enumerate(self.inputNames)) + \ tuple((_("step {0}").format(str(qname)), expr) for qname,expr in enumerate(self.stepExpressions)) + \ (("output", self.outputExpression),)) - + def __repr__(self): return ("{0}[{1}]{2})".format(self.__class__.__name__, self.objectId(),self.propertyView)) @@ -2900,7 +2900,7 @@ def __repr__(self): def viewExpression(self): return ' \n'.join([_("step ${0}: \n{1}").format(str(qname),expr) for qname,expr in self.stepExpressions] + [_("output: \n{0}").format(self.outputExpression)]) - + from arelle.ModelObjectFactory import elementSubstitutionModelClass elementSubstitutionModelClass.update(( diff --git a/arelle/ModelInstanceObject.py b/arelle/ModelInstanceObject.py index 9803c359b0..c2ab689879 100644 --- a/arelle/ModelInstanceObject.py +++ b/arelle/ModelInstanceObject.py @@ -5,30 +5,30 @@ .. module:: arelle.ModelInstanceObject :copyright: Copyright 2010-2012 Mark V Systems Limited, All rights reserved. :license: Apache-2. - :synopsis: This module contains Instance-specialized ModelObject classes: ModelFact (xbrli:item - and xbrli:tuple elements of an instance document), ModelInlineFact specializes ModelFact when - in an inline XBRL document, ModelContext (xblrli:context element), ModelDimensionValue - (xbrldi:explicitMember and xbrli:typedMember elements), and ModelUnit (xbrli:unit elements). - - Model facts represent XBRL instance facts (that are elements in the instance document). - Model inline facts represent facts in a source xhtml document, but may accumulate text - across multiple mixed-content elements in the instance document, according to the rendering - transform in effect. All inline facts are lxml proxy objects for the inline fact and have a - cached value representing the transformed value content. PSVI values for the inline fact's - value and attributes are on the model inline fact object (not necessarily the element that + :synopsis: This module contains Instance-specialized ModelObject classes: ModelFact (xbrli:item + and xbrli:tuple elements of an instance document), ModelInlineFact specializes ModelFact when + in an inline XBRL document, ModelContext (xblrli:context element), ModelDimensionValue + (xbrldi:explicitMember and xbrli:typedMember elements), and ModelUnit (xbrli:unit elements). + + Model facts represent XBRL instance facts (that are elements in the instance document). + Model inline facts represent facts in a source xhtml document, but may accumulate text + across multiple mixed-content elements in the instance document, according to the rendering + transform in effect. All inline facts are lxml proxy objects for the inline fact and have a + cached value representing the transformed value content. PSVI values for the inline fact's + value and attributes are on the model inline fact object (not necessarily the element that held the mixed-content text). - Model context objects are the lxml proxy object of the context XML element, but cache and - interface context semantics that may either be internal to the context, or inferred from - the DTS (such as default dimension values). PSVI values for elements internal to the context, - including segment and scenario elements, are on the individual model object lxml custom proxy - elements. For fast comparison of dimensions and segment/scenario, hash values are retained + Model context objects are the lxml proxy object of the context XML element, but cache and + interface context semantics that may either be internal to the context, or inferred from + the DTS (such as default dimension values). PSVI values for elements internal to the context, + including segment and scenario elements, are on the individual model object lxml custom proxy + elements. For fast comparison of dimensions and segment/scenario, hash values are retained for each comparable item. - Model dimension objects not only represent proxy objects for the XML elements, but have resolved + Model dimension objects not only represent proxy objects for the XML elements, but have resolved model DTS concepts of the dimension and member, and access to the typed member contents. - Model unit objects represent algebraically usable set objects for the numerator and denominator + Model unit objects represent algebraically usable set objects for the numerator and denominator measure sets. """ from collections import defaultdict @@ -54,19 +54,19 @@ class NewFactItemOptions(): """ .. class:: NewFactItemOptions(savedOptions=None, xbrlInstance=None) - + NewFactItemOptions persists contextual parameters for interactive creation of new facts, such as when entering into empty table linkbase rendering pane cells. - + If savedOptions is provided (from configuration saved json file), then persisted last used values of item contextual options are used. If no saved options, then the first fact in an existing instance (xbrlInstance) is used to glean prototype contextual parameters. - + Note that all attributes of this class must be compatible with json conversion, e.g., datetime must be persisted in string, not datetime object, form. - + Properties of this class (all str): - + - entityIdentScheme - entityIdentValue - startDate @@ -74,7 +74,7 @@ class NewFactItemOptions(): - monetaryUnit (str prefix:localName, e.g, iso4217:JPY) - monetaryDecimals (decimals attribute for numeric monetary facts) - nonMonetaryDecimals (decimals attribute for numeric non-monetary facts, e.g., shares) - + :param savedOptions: prior persisted dict of this class's attributes :param xbrlInstance: an open instance document from which to glean prototpye contextual parameters. """ @@ -108,8 +108,8 @@ def __init__(self, savedOptions=None, xbrlInstance=None): elif not self.nonMonetaryDecimals: self.nonMonetaryDecimals = fact.decimals if self.entityIdentScheme and self.startDate and self.monetaryUnit and self.monetaryDecimals and self.nonMonetaryDecimals: - break - + break + @property def startDateDate(self): """(datetime) -- date-typed date value of startDate (which is persisted in str form)""" @@ -119,30 +119,30 @@ def startDateDate(self): def endDateDate(self): # return a date-typed date """(datetime) -- date-typed date value of endDate (which is persisted in str form)""" return XmlUtil.datetimeValue(self.endDate, addOneDay=True) - - + + class ModelFact(ModelObject): """ .. class:: ModelFact(modelDocument) - + Model fact (both instance document facts and inline XBRL facts) - + :param modelDocument: owner document :type modelDocument: ModelDocument .. attribute:: modelTupleFacts - + ([ModelFact]) - List of child facts in source document order """ def init(self, modelDocument): super(ModelFact, self).init(modelDocument) self.modelTupleFacts = [] - + @property def concept(self): """(ModelConcept) -- concept of the fact.""" return self.elementDeclaration() # logical (fact) declaration in own modelXbrl, not physical element (if inline) - + @property def contextID(self): """(str) -- contextRef attribute""" @@ -157,12 +157,12 @@ def context(self): if not self.modelXbrl.contexts: return None # don't attempt before contexts are loaded self._context = self.modelXbrl.contexts.get(self.contextID) return self._context - + @property def unit(self): """(ModelUnit) -- unit of the fact if any else None (e.g., non-numeric or tuple)""" return self.modelXbrl.units.get(self.unitID) - + @property def unitID(self): """(str) -- unitRef attribute""" @@ -172,14 +172,14 @@ def unitID(self): def unitID(self, value): """(str) -- unitRef attribute""" self.set("unitRef", value) - + @property def utrEntries(self): """(set(UtrEntry)) -- set of UtrEntry objects that match this fact and unit""" if self.unit is not None and self.concept is not None: return self.unit.utrEntries(self.concept.type) return None - + def unitSymbol(self): """(str) -- utr symbol for this fact and unit""" if self.unit is not None and self.concept is not None: @@ -188,14 +188,14 @@ def unitSymbol(self): @property def conceptContextUnitHash(self): - """(int) -- Hash value of fact's concept QName, dimensions-aware + """(int) -- Hash value of fact's concept QName, dimensions-aware context hash, unit hash, useful for fast comparison of facts for EFM 6.5.12""" try: return self._conceptContextUnitHash except AttributeError: context = self.context unit = self.unit - self._conceptContextUnitHash = hash( + self._conceptContextUnitHash = hash( (self.qname, context.contextDimAwareHash if context is not None else None, unit.hash if unit is not None else None) ) @@ -256,7 +256,7 @@ def isFraction(self): concept = self.concept self._isFraction = (concept is not None) and concept.isFraction return self._isFraction - + @property def parentElement(self): """(ModelObject) -- parent element (tuple or xbrli:xbrl)""" @@ -286,14 +286,14 @@ def decimals(self): type = concept.type self._decimals = type.fixedOrDefaultAttrValue("decimals") if type is not None else None else: - self._decimals = None + self._decimals = None return self._decimals @decimals.setter def decimals(self, value): self._decimals = value self.set("decimals", value) - + @property def precision(self): """(str) -- Value of precision attribute, or fixed or default value for precision on concept type declaration""" @@ -309,7 +309,7 @@ def precision(self): type = self.concept.type self._precision = type.fixedOrDefaultAttrValue("precision") if type is not None else None else: - self._precision = None + self._precision = None return self._precision @property @@ -331,17 +331,17 @@ def xmlLang(self): if concept is not None and not concept.isNumeric: lang = self.modelXbrl.modelManager.disclosureSystem.defaultXmlLang return lang - + @property def xsiNil(self): """(str) -- value of xsi:nil or 'false' if absent""" return self.get("{http://www.w3.org/2001/XMLSchema-instance}nil", "false") - + @property def isNil(self): """(bool) -- True if xsi:nil is 'true'""" return self.xsiNil in ("true","1") - + @isNil.setter def isNil(self, value): """:param value: if true, set xsi:nil to 'true', if false, remove xsi:nil attribute """ @@ -354,7 +354,7 @@ def isNil(self, value): del self._precision else: # also remove decimals and precision, if they were there self.attrib.pop("{http://www.w3.org/2001/XMLSchema-instance}nil", "false") - + @property def value(self): """(str) -- Text value of fact or default or fixed if any, otherwise None""" @@ -365,7 +365,7 @@ def value(self): elif self.concept.fixed is not None: v = self.concept.fixed return v - + @property def fractionValue(self): """( (str,str) ) -- (text value of numerator, text value of denominator)""" @@ -375,10 +375,10 @@ def fractionValue(self): self._fractionValue = (XmlUtil.text(XmlUtil.child(self, None, "numerator")), XmlUtil.text(XmlUtil.child(self, None, "denominator"))) return self._fractionValue - + @property def effectiveValue(self): - """(str) -- Effective value for views, (nil) if isNil, None if no value, + """(str) -- Effective value for views, (nil) if isNil, None if no value, locale-formatted string of decimal value (if decimals specified) , otherwise string value""" concept = self.concept if concept is None or concept.isTuple: @@ -410,7 +410,7 @@ def effectiveValue(self): if dec < 0: dec = 0 # {} formatting doesn't accept negative dec return Locale.format(self.modelXbrl.locale, "{:.{}f}", (num,dec), True) - except ValueError: + except ValueError: return "(error)" if len(val) == 0: # zero length string for HMRC fixed fact return "(reported)" @@ -424,10 +424,10 @@ def vEqValue(self): if self.concept.isNumeric: return float(self.value) return self.value - + def isVEqualTo(self, other, deemP0Equal=False, deemP0inf=False, normalizeSpace=True, numericIntervalConsistency=False): """(bool) -- v-equality of two facts - + Note that facts may be in different instances """ if self.isTuple or other.isTuple: @@ -444,12 +444,12 @@ def isVEqualTo(self, other, deemP0Equal=False, deemP0inf=False, normalizeSpace=T if other.concept.isNumeric: if self.unit is None or not self.unit.isEqualTo(other.unit): return False - + if numericIntervalConsistency: # values consistent with being rounded from same number (a1,b1) = rangeValue(self.value, inferredDecimals(self)) (a2,b2) = rangeValue(other.value, inferredDecimals(other)) return not (b1 < a2 or b2 < a1) - + if self.modelXbrl.modelManager.validateInferDecimals: d = min((inferredDecimals(self), inferredDecimals(other))); p = None if isnan(d): @@ -469,7 +469,7 @@ def isVEqualTo(self, other, deemP0Equal=False, deemP0inf=False, normalizeSpace=T return False elif self.concept.isFraction: return (other.concept.isFraction and - self.unit is not None and self.unit.isEqualTo(other.unit) and + self.unit is not None and self.unit.isEqualTo(other.unit) and self.xValue == other.xValue) selfValue = self.value otherValue = other.value @@ -477,18 +477,18 @@ def isVEqualTo(self, other, deemP0Equal=False, deemP0inf=False, normalizeSpace=T return ' '.join(selfValue.split()) == ' '.join(otherValue.split()) else: return selfValue == otherValue - - def isDuplicateOf(self, other, topLevel=True, deemP0Equal=False, unmatchedFactsStack=None): + + def isDuplicateOf(self, other, topLevel=True, deemP0Equal=False, unmatchedFactsStack=None): """(bool) -- fact is duplicate of other fact - + Note that facts may be in different instances - + :param topLevel: fact parent is xbrli:instance, otherwise nested in a tuple :type topLevel: bool :param deemPOEqual: True to deem any precision=0 facts equal ignoring value :type deepPOEqual: bool """ - if unmatchedFactsStack is not None: + if unmatchedFactsStack is not None: if topLevel: del unmatchedFactsStack[0:] entryDepth = len(unmatchedFactsStack) unmatchedFactsStack.append(self) @@ -515,15 +515,15 @@ def isDuplicateOf(self, other, topLevel=True, deemP0Equal=False, unmatchedFactsS if not any(child1.isVEqualTo(child2, deemP0Equal) for child2 in other.modelTupleFacts if child1.qname == child2.qname): return False elif child1.isTuple: - if not any(child1.isDuplicateOf( child2, False, deemP0Equal, unmatchedFactsStack) + if not any(child1.isDuplicateOf( child2, False, deemP0Equal, unmatchedFactsStack) for child2 in other.modelTupleFacts): return False else: return False - if unmatchedFactsStack is not None: + if unmatchedFactsStack is not None: del unmatchedFactsStack[entryDepth:] return True - + @property def md5sum(self): # note this must work in --skipDTS and streaming modes _toHash = [self.qname] @@ -542,7 +542,7 @@ def md5sum(self): # note this must work in --skipDTS and streaming modes if self.unit is not None: _toHash.append(self.unit.md5sum) return md5hash(_toHash) - + @property def propertyView(self): try: @@ -553,12 +553,12 @@ def propertyView(self): if self.isNumeric and self.unit is not None: unitValue = self.unitID unitSymbol = self.unitSymbol() - if unitSymbol: + if unitSymbol: unitValue += " (" + unitSymbol + ")" return lbl + ( (("namespace", self.qname.namespaceURI), ("name", self.qname.localName), - ("QName", self.qname)) + + ("QName", self.qname)) + (((("contextRef", self.contextID, self.context.propertyView) if self.context is not None else ()), (("unitRef", unitValue, self.unit.propertyView) if self.isNumeric and self.unit is not None else ()), ("decimals", self.decimals), @@ -566,26 +566,26 @@ def propertyView(self): ("xsi:nil", self.xsiNil), ("value", self.effectiveValue.strip())) if self.isItem else () )) - + def __repr__(self): return ("modelFact[{0}, qname: {1}, contextRef: {2}, unitRef: {3}, value: {4}, {5}, line {6}]" .format(self.objectIndex, self.qname, self.get("contextRef"), self.get("unitRef"), self.effectiveValue.strip() if self.isItem else '(tuple)', self.modelDocument.basename, self.sourceline)) - + @property def viewConcept(self): return self.concept - + class ModelInlineValueObject: def init(self, modelDocument): super(ModelInlineValueObject, self).init(modelDocument) - + @property def sign(self): """(str) -- sign attribute of inline element""" return self.get("sign") - + @property def format(self): """(QName) -- format attribute of inline element""" @@ -595,7 +595,7 @@ def format(self): def scale(self): """(str) -- scale attribute of inline element""" return self.getStripped("scale") - + @property def scaleInt(self): """(int) -- scale attribute of inline element""" @@ -606,15 +606,15 @@ def scaleInt(self): return int(self.get("scale")) except ValueError: return None # should have rasied a validation error in XhtmlValidate.py - + def setInvalid(self): self._ixValue = ModelValue.INVALIDixVALUE self.xValid = INVALID self.xValue = None - + @property def value(self): - """(str) -- Overrides and corresponds to value property of ModelFact, + """(str) -- Overrides and corresponds to value property of ModelFact, for relevant inner text nodes aggregated and transformed as needed.""" try: return self._ixValue @@ -623,9 +623,9 @@ def value(self): self.xValue = None f = self.format ixEscape = self.get("escape") in ("true","1") - v = XmlUtil.innerText(self, - ixExclude="tuple" if self.elementQname == XbrlConst.qnIXbrl11Tuple else "html", - ixEscape=ixEscape, + v = XmlUtil.innerText(self, + ixExclude="tuple" if self.elementQname == XbrlConst.qnIXbrl11Tuple else "html", + ixEscape=ixEscape, ixContinuation=(self.elementQname == XbrlConst.qnIXbrl11NonNumeric), ixResolveUris=ixEscape, strip=(f is not None)) # transforms are whitespace-collapse, otherwise it is preserved. @@ -690,27 +690,27 @@ def textValue(self): will raise any value errors if transforming string or numeric has an error """ return self.value - + @property def stringValue(self): """(str) -- override xml-level stringValue for transformed value descendants text will raise any value errors if transforming string or numeric has an error """ return self.value - - + + class ModelInlineFact(ModelInlineValueObject, ModelFact): """ .. class:: ModelInlineFact(modelDocument) - + Model inline fact (inline XBRL facts) - + :param modelDocument: owner document :type modelDocument: ModelDocument """ def init(self, modelDocument): super(ModelInlineFact, self).init(modelDocument) - + @property def qname(self): """(QName) -- QName of concept from the name attribute, overrides and corresponds to the qname property of a ModelFact (inherited from ModelObject)""" @@ -728,7 +728,7 @@ def tupleID(self): except AttributeError: self._tupleId = self.get("tupleID") return self._tupleId - + @property def tupleRef(self): """(str) -- tupleRef attribute of inline element""" @@ -756,7 +756,7 @@ def parentElement(self): """(ModelObject) -- parent element (tuple or xbrli:xbrl) of the inline target instance document for inline root element, the xbrli:xbrl element is substituted for by the inline root element""" return getattr(self, "_ixFactParent") # set by ModelDocument locateFactInTuple for the inline target's root element - + def ixIter(self, childOnly=False): """(ModelObject) -- child elements (tuple facts) of the inline target instance document""" for fact in self.modelTupleFacts: @@ -769,7 +769,7 @@ def fractionValue(self): """( (str,str) ) -- (text value of numerator, text value of denominator)""" return (XmlUtil.text(XmlUtil.descendant(self, self.namespaceURI, "numerator")), XmlUtil.text(XmlUtil.descendant(self, self.namespaceURI, "denominator"))) - + @property def footnoteRefs(self): """([str]) -- list of footnoteRefs attribute contents of inline 1.0 element""" @@ -784,7 +784,7 @@ def __iter__(self): yield d for tupleFact in self.modelTupleFacts: yield tupleFact - + @property def propertyView(self): if self.localName == "nonFraction" or self.localName == "fraction": @@ -797,14 +797,14 @@ def propertyView(self): ("line", self.sourceline)) + \ super(ModelInlineFact,self).propertyView + \ numProperties - + def __repr__(self): return ("modelInlineFact[{0}]{1})".format(self.objectId(),self.propertyView)) - + class ModelInlineFraction(ModelInlineFact): def init(self, modelDocument): super(ModelInlineFraction, self).init(modelDocument) - + @property def textValue(self): return "" # no text value for fraction @@ -814,7 +814,7 @@ def init(self, modelDocument): super(ModelInlineFractionTerm, self).init(modelDocument) self.isNil = False # required for inherited value property self.modelTupleFacts = [] # required for inherited XmlValudate of fraction term - + @property def qname(self): if self.localName == "numerator": @@ -822,50 +822,50 @@ def qname(self): elif self.localName == "denominator": return XbrlConst.qnXbrliDenominator return self.elementQname - + @property def concept(self): return self.modelXbrl.qnameConcepts.get(self.qname) # for fraction term type determination - + @property def isInteger(self): return False # fraction terms are xs:decimal - + def __iter__(self): if False: yield None # generator with nothing to generate - - + + class ModelContext(ModelObject): """ .. class:: ModelContext(modelDocument) - + Model context - + :param modelDocument: owner document :type modelDocument: ModelDocument .. attribute:: segDimValues - + (dict) - Dict by dimension ModelConcept of segment dimension ModelDimensionValues .. attribute:: scenDimValues - + (dict) - Dict by dimension ModelConcept of scenario dimension ModelDimensionValues .. attribute:: qnameDims - + (dict) - Dict by dimension concept QName of ModelDimensionValues (independent of whether segment or scenario) .. attribute:: errorDimValues - + (list) - List of ModelDimensionValues whose dimension concept could not be determined or which were duplicates .. attribute:: segNonDimValues - + (list) - List of segment child non-dimension ModelObjects .. attribute:: scenNonDimValues - + (list) - List of scenario child non-dimension ModelObjects """ def init(self, modelDocument): @@ -877,11 +877,11 @@ def init(self, modelDocument): self.segNonDimValues = [] self.scenNonDimValues = [] self._isEqualTo = {} - + def clearCachedProperties(self): for key in [k for k in vars(self).keys() if k.startswith("_")]: delattr(self, key) - + @property def isStartEndPeriod(self): """(bool) -- True for startDate/endDate period""" @@ -890,7 +890,7 @@ def isStartEndPeriod(self): except AttributeError: self._isStartEndPeriod = XmlUtil.hasChild(self.period, XbrlConst.xbrli, ("startDate","endDate")) return self._isStartEndPeriod - + @property def isInstantPeriod(self): """(bool) -- True for instant period""" @@ -917,7 +917,7 @@ def startDatetime(self): except AttributeError: self._startDatetime = XmlUtil.datetimeValue(XmlUtil.child(self.period, XbrlConst.xbrli, "startDate")) return self._startDatetime - + @startDatetime.setter def startDatetime(self, value): self.clearCachedProperties() @@ -934,7 +934,7 @@ def endDatetime(self): except AttributeError: self._endDatetime = XmlUtil.datetimeValue(XmlUtil.child(self.period, XbrlConst.xbrli, ("endDate","instant")), addOneDay=True) return self._endDatetime - + @endDatetime.setter def endDatetime(self, value): self.clearCachedProperties() @@ -942,7 +942,7 @@ def endDatetime(self, value): if elt is not None: elt.text = XmlUtil.dateunionValue(value, subtractOneDay=True) xmlValidate(self.modelXbrl, elt) - + @property def instantDatetime(self): """(datetime) -- instant attribute, with adjustment to end-of-day midnight as needed""" @@ -951,7 +951,7 @@ def instantDatetime(self): except AttributeError: self._instantDatetime = XmlUtil.datetimeValue(XmlUtil.child(self.period, XbrlConst.xbrli, "instant"), addOneDay=True) return self._instantDatetime - + @instantDatetime.setter def instantDatetime(self, value): self.clearCachedProperties() @@ -959,7 +959,7 @@ def instantDatetime(self, value): if elt is not None: elt.text = XmlUtil.dateunionValue(value, subtractOneDay=True) xmlValidate(self.modelXbrl, elt) - + @property def period(self): """(ModelObject) -- period element""" @@ -1032,15 +1032,15 @@ def segment(self): def hasScenario(self): """(bool) -- True if a xbrli:scenario element is present""" return XmlUtil.hasChild(self, XbrlConst.xbrli, "scenario") - + @property def scenario(self): """(ModelObject) -- xbrli:scenario element""" return XmlUtil.child(self, XbrlConst.xbrli, "scenario") - + def dimValues(self, contextElement): """(dict) -- Indicated context element's dimension dict (indexed by ModelConcepts) - + :param contextElement: 'segment' or 'scenario' :returns: dict of ModelDimension objects indexed by ModelConcept dimension object, or empty dict """ @@ -1049,11 +1049,11 @@ def dimValues(self, contextElement): elif contextElement == "scenario": return self.scenDimValues return {} - + def hasDimension(self, dimQname): """(bool) -- True if dimension concept qname is reported by context (in either context element), not including defaulted dimensions.""" return dimQname in self.qnameDims - + # returns ModelDimensionValue for instance dimensions, else QName for defaults def dimValue(self, dimQname): """(ModelDimension or QName) -- ModelDimension object if dimension is reported (in either context element), or QName of dimension default if there is a default, otherwise None""" @@ -1064,7 +1064,7 @@ def dimValue(self, dimQname): return self.modelXbrl.qnameDimensionDefaults[dimQname] except KeyError: return None - + def dimMemberQname(self, dimQname, includeDefaults=False): """(QName) -- QName of explicit dimension if reported (or defaulted if includeDefaults is True), else None""" dimValue = self.dimValue(dimQname) @@ -1075,13 +1075,13 @@ def dimMemberQname(self, dimQname, includeDefaults=False): if dimValue is None and includeDefaults and dimQname in self.modelXbrl.qnameDimensionDefaults: return self.modelXbrl.qnameDimensionDefaults[dimQname] return None - + def dimAspects(self, defaultDimensionAspects=None): """(set) -- For formula and instance aspects processing, set of all dimensions reported or defaulted.""" if defaultDimensionAspects: return _DICT_SET(self.qnameDims.keys()) | defaultDimensionAspects return _DICT_SET(self.qnameDims.keys()) - + @property def dimsHash(self): """(int) -- A hash of the set of reported dimension values.""" @@ -1090,13 +1090,13 @@ def dimsHash(self): except AttributeError: self._dimsHash = hash( frozenset(self.qnameDims.values()) ) return self._dimsHash - + def nonDimValues(self, contextElement): """([ModelObject]) -- ContextElement is either string or Aspect code for segment or scenario, returns nonXDT ModelObject children of context element. - + :param contextElement: one of 'segment', 'scenario', Aspect.NON_XDT_SEGMENT, Aspect.NON_XDT_SCENARIO, Aspect.COMPLETE_SEGMENT, Aspect.COMPLETE_SCENARIO - :type contextElement: str or Aspect type - :returns: list of ModelObjects + :type contextElement: str or Aspect type + :returns: list of ModelObjects """ if contextElement in ("segment", Aspect.NON_XDT_SEGMENT): return self.segNonDimValues @@ -1107,17 +1107,17 @@ def nonDimValues(self, contextElement): elif contextElement == Aspect.COMPLETE_SCENARIO and self.hasScenario: return XmlUtil.children(self.scenario, None, "*") return [] - + @property def segmentHash(self): """(int) -- Hash of the segment, based on s-equality values""" return XbrlUtil.equalityHash( self.segment ) # self-caching - + @property def scenarioHash(self): """(int) -- Hash of the scenario, based on s-equality values""" return XbrlUtil.equalityHash( self.scenario ) # self-caching - + @property def nonDimSegmentHash(self): """(int) -- Hash, of s-equality values, of non-XDT segment objects""" @@ -1126,7 +1126,7 @@ def nonDimSegmentHash(self): except AttributeError: self._nonDimSegmentHash = XbrlUtil.equalityHash(self.nonDimValues("segment")) return self._nonDimSegmentHash - + @property def nonDimScenarioHash(self): """(int) -- Hash, of s-equality values, of non-XDT scenario objects""" @@ -1135,16 +1135,16 @@ def nonDimScenarioHash(self): except AttributeError: self._nonDimScenarioHash = XbrlUtil.equalityHash(self.nonDimValues("scenario")) return self._nonDimScenarioHash - + @property def nonDimHash(self): """(int) -- Hash, of s-equality values, of non-XDT segment and scenario objects""" try: return self._nonDimsHash except AttributeError: - self._nonDimsHash = hash( (self.nonDimSegmentHash, self.nonDimScenarioHash) ) + self._nonDimsHash = hash( (self.nonDimSegmentHash, self.nonDimScenarioHash) ) return self._nonDimsHash - + @property def contextDimAwareHash(self): """(int) -- Hash of period, entityIdentifier, dim, and nonDims""" @@ -1153,7 +1153,7 @@ def contextDimAwareHash(self): except AttributeError: self._contextDimAwareHash = hash( (self.periodHash, self.entityIdentifierHash, self.dimsHash, self.nonDimHash) ) return self._contextDimAwareHash - + @property def contextNonDimAwareHash(self): """(int) -- Hash of period, entityIdentifier, segment, and scenario (s-equal based)""" @@ -1162,7 +1162,7 @@ def contextNonDimAwareHash(self): except AttributeError: self._contextNonDimAwareHash = hash( (self.periodHash, self.entityIdentifierHash, self.segmentHash, self.scenarioHash) ) return self._contextNonDimAwareHash - + @property def md5sum(self): try: @@ -1180,7 +1180,7 @@ def md5sum(self): _toHash.extend([dim.md5sum for dim in self.qnameDims.values()]) self._md5sum = md5hash(_toHash) return self._md5sum - + def isPeriodEqualTo(self, cntx2): """(bool) -- True if periods are datetime equal (based on 2.1 date offsets)""" if self.isForeverPeriod: @@ -1195,11 +1195,11 @@ def isPeriodEqualTo(self, cntx2): return self.instantDatetime == cntx2.instantDatetime else: return False - + def isEntityIdentifierEqualTo(self, cntx2): """(bool) -- True if entityIdentifier values are equal (scheme and text value)""" return self.entityIdentifierHash == cntx2.entityIdentifierHash - + def isEqualTo(self, cntx2, dimensionalAspectModel=None): if dimensionalAspectModel is None: dimensionalAspectModel = self.modelXbrl.hasXDT try: @@ -1208,10 +1208,10 @@ def isEqualTo(self, cntx2, dimensionalAspectModel=None): result = self.isEqualTo_(cntx2, dimensionalAspectModel) self._isEqualTo[(cntx2,dimensionalAspectModel)] = result return result - + def isEqualTo_(self, cntx2, dimensionalAspectModel): - """(bool) -- If dimensionalAspectModel is absent, True is assumed. - False means comparing based on s-equality of segment, scenario, while + """(bool) -- If dimensionalAspectModel is absent, True is assumed. + False means comparing based on s-equality of segment, scenario, while True means based on dimensional values and nonDimensional values separately.""" if cntx2 is None: return False @@ -1219,7 +1219,7 @@ def isEqualTo_(self, cntx2, dimensionalAspectModel): return True if (self.periodHash != cntx2.periodHash or self.entityIdentifierHash != cntx2.entityIdentifierHash): - return False + return False if dimensionalAspectModel: if (self.dimsHash != cntx2.dimsHash or self.nonDimHash != cntx2.nonDimHash): @@ -1242,7 +1242,7 @@ def isEqualTo_(self, cntx2, dimensionalAspectModel): return False for i, nonDimVal1 in enumerate(nonDimVals1): if not XbrlUtil.sEqual(self.modelXbrl, nonDimVal1, nonDimVals2[i]): - return False + return False else: if self.hasSegment: if not cntx2.hasSegment: @@ -1251,7 +1251,7 @@ def isEqualTo_(self, cntx2, dimensionalAspectModel): return False elif cntx2.hasSegment: return False - + if self.hasScenario: if not cntx2.hasScenario: return False @@ -1259,7 +1259,7 @@ def isEqualTo_(self, cntx2, dimensionalAspectModel): return False elif cntx2.hasScenario: return False - + return True @property @@ -1288,15 +1288,15 @@ def __repr__(self): class ModelDimensionValue(ModelObject): """ .. class:: ModelDimensionValue(modelDocument) - + Model dimension value (both explicit and typed, non-default values) - + :param modelDocument: owner document :type modelDocument: ModelDocument """ def init(self, modelDocument): super(ModelDimensionValue, self).init(modelDocument) - + def __hash__(self): if self.isExplicit: return hash( (self.dimensionQname, self.memberQname) ) @@ -1309,7 +1309,7 @@ def md5sum(self): return md5hash([self.dimensionQname, self.memberQname]) else: return md5hash([self.dimensionQname, self.typedMember]) - + @property def dimensionQname(self): """(QName) -- QName of the dimension concept""" @@ -1318,7 +1318,7 @@ def dimensionQname(self): return dimAttr.xValue return None #return self.prefixedNameQname(self.get("dimension")) - + @property def dimension(self): """(ModelConcept) -- Dimension concept""" @@ -1327,16 +1327,16 @@ def dimension(self): except AttributeError: self._dimension = self.modelXbrl.qnameConcepts.get(self.dimensionQname) return self._dimension - + @property def isExplicit(self): """(bool) -- True if explicitMember element""" return self.localName == "explicitMember" - + @property def typedMember(self): """(ModelConcept) -- Child ModelObject that is the dimension member element - + (To get element use 'self'). """ for child in self.iterchildren(): @@ -1361,7 +1361,7 @@ def memberQname(self): self._memberQname = None #self._memberQname = self.prefixedNameQname(self.textValue) if self.isExplicit else None return self._memberQname - + @property def member(self): """(ModelConcept) -- Concept of an explicit dimension member""" @@ -1370,10 +1370,10 @@ def member(self): except AttributeError: self._member = self.modelXbrl.qnameConcepts.get(self.memberQname) return self._member - + def isEqualTo(self, other, equalMode=XbrlUtil.XPATH_EQ): """(bool) -- True if explicit member QNames equal or typed member nodes correspond, given equalMode (s-equal, s-equal2, or xpath-equal for formula) - + :param equalMode: XbrlUtil.S_EQUAL (ordinary S-equality from 2.1 spec), XbrlUtil.S_EQUAL2 (XDT definition of equality, adding QName comparisions), or XbrlUtil.XPATH_EQ (XPath EQ on all types) """ if other is None: @@ -1381,30 +1381,30 @@ def isEqualTo(self, other, equalMode=XbrlUtil.XPATH_EQ): if self.isExplicit: # other is either ModelDimensionValue or the QName value of explicit dimension return self.memberQname == (other.memberQname if isinstance(other, (ModelDimensionValue,DimValuePrototype)) else other) else: # typed dimension compared to another ModelDimensionValue or other is the value nodes - return XbrlUtil.nodesCorrespond(self.modelXbrl, self.typedMember, - other.typedMember if isinstance(other, (ModelDimensionValue,DimValuePrototype)) else other, + return XbrlUtil.nodesCorrespond(self.modelXbrl, self.typedMember, + other.typedMember if isinstance(other, (ModelDimensionValue,DimValuePrototype)) else other, equalMode=equalMode, excludeIDs=XbrlUtil.NO_IDs_EXCLUDED) - + @property def contextElement(self): """(str) -- 'segment' or 'scenario'""" return self.getparent().localName - + @property def propertyView(self): if self.isExplicit: return (str(self.dimensionQname),str(self.memberQname)) else: return (str(self.dimensionQname), XmlUtil.xmlstring( XmlUtil.child(self), stripXmlns=True, prettyPrint=True ) ) - + def measuresOf(parent): if parent.xValid >= VALID: # has DTS and is validated - return tuple(sorted([m.xValue - for m in parent.iterchildren(tag="{http://www.xbrl.org/2003/instance}measure") + return tuple(sorted([m.xValue + for m in parent.iterchildren(tag="{http://www.xbrl.org/2003/instance}measure") if isinstance(m, ModelObject) and m.xValue])) else: # probably skipDTS return tuple(sorted([m.prefixedNameQname(m.textValue) or XbrlConst.qnInvalidMeasure - for m in parent.iterchildren(tag="{http://www.xbrl.org/2003/instance}measure") + for m in parent.iterchildren(tag="{http://www.xbrl.org/2003/instance}measure") if isinstance(m, ModelObject)])) def measuresStr(m): @@ -1413,18 +1413,18 @@ def measuresStr(m): class ModelUnit(ModelObject): """ .. class:: ModelUnit(modelDocument) - + Model unit - + :param modelDocument: owner document :type modelDocument: ModelDocument """ def init(self, modelDocument): super(ModelUnit, self).init(modelDocument) - + @property def measures(self): - """([QName],[Qname]) -- Returns a tuple of multiply measures list and divide members list + """([QName],[Qname]) -- Returns a tuple of multiply measures list and divide members list (empty if not a divide element). Each list of QNames is in prefixed-name order.""" try: return self._measures @@ -1463,40 +1463,40 @@ def md5hash(self): # should this use frozenSet of each measures element? self._md5hash = md5hash.hexdigest() return self._md5hash - + @property def md5sum(self): try: return self._md5sum except AttributeError: if self.isDivide: # hash of mult and div hex strings of hashes of measures - self._md5sum = md5hash([md5hash([md5hash(m) for m in md]).toHex() + self._md5sum = md5hash([md5hash([md5hash(m) for m in md]).toHex() for md in self.measures]) else: # sum of hash sums self._md5sum = md5hash([md5hash(m) for m in self.measures[0]]) return self._md5sum - + @property def isDivide(self): """(bool) -- True if unit has a divide element""" return XmlUtil.hasChild(self, XbrlConst.xbrli, "divide") - + @property def isSingleMeasure(self): """(bool) -- True for a single multiply and no divide measures""" measures = self.measures return len(measures[0]) == 1 and len(measures[1]) == 0 - + def isEqualTo(self, unit2): """(bool) -- True if measures are equal""" - if unit2 is None or unit2.hash != self.hash: + if unit2 is None or unit2.hash != self.hash: return False return unit2 is self or self.measures == unit2.measures - + @property def value(self): - """(str) -- String value for view purposes, space separated list of string qnames - of multiply measures, and if any divide, a '/' character and list of string qnames + """(str) -- String value for view purposes, space separated list of string qnames + of multiply measures, and if any divide, a '/' character and list of string qnames of divide measure qnames.""" mul, div = self.measures return ' '.join([measuresStr(m) for m in mul] + (['/'] + [measuresStr(d) for d in div] if div else [])) @@ -1513,7 +1513,7 @@ def utrEntries(self, modelType): from arelle.ValidateUtr import utrEntries self._utrEntries[modelType] = utrEntries(modelType, self) return self._utrEntries[modelType] - + def utrSymbol(self, modelType): try: return self._utrSymbols[modelType] @@ -1526,14 +1526,14 @@ def utrSymbol(self, modelType): from arelle.ValidateUtr import utrSymbol self._utrSymbols[modelType] = utrSymbol(modelType, self.measures) return self._utrSymbols[modelType] - - + + @property def propertyView(self): measures = self.measures if measures[1]: return tuple(('mul',m) for m in measures[0]) + \ - tuple(('div',d) for d in measures[1]) + tuple(('div',d) for d in measures[1]) else: return tuple(('measure',m) for m in measures[0]) @@ -1541,20 +1541,20 @@ def propertyView(self): class ModelInlineFootnote(ModelResource): """ .. class:: ModelInlineFootnote(modelDocument) - + Model inline footnote (inline XBRL facts) - + :param modelDocument: owner document :type modelDocument: ModelDocument """ def init(self, modelDocument): super(ModelInlineFootnote, self).init(modelDocument) - + @property def qname(self): """(QName) -- QName of generated object""" return XbrlConst.qnLinkFootnote - + @property def footnoteID(self): if self.namespaceURI == XbrlConst.ixbrl: @@ -1564,25 +1564,25 @@ def footnoteID(self): @property def value(self): - """(str) -- Overrides and corresponds to value property of ModelFact, + """(str) -- Overrides and corresponds to value property of ModelFact, for relevant inner text nodes aggregated and transformed as needed.""" try: return self._ixValue except AttributeError: - self._ixValue = XmlUtil.innerText(self, - ixExclude=True, - ixEscape="html", + self._ixValue = XmlUtil.innerText(self, + ixExclude=True, + ixEscape="html", ixContinuation=(self.namespaceURI != XbrlConst.ixbrl), ixResolveUris=True, strip=True) # include HTML constructs return self._ixValue - + @property def textValue(self): """(str) -- override xml-level stringValue for transformed value descendants text""" return self.value - + @property def stringValue(self): """(str) -- override xml-level stringValue for transformed value descendants text""" @@ -1591,12 +1591,12 @@ def stringValue(self): @property def htmlValue(self): return XmlUtil.innerText(self, ixExclude=True, ixContinuation=True, strip=False) - + @property def role(self): """(str) -- xlink:role attribute""" return self.get("footnoteRole") or XbrlConst.footnote - + @property def xlinkLabel(self): """(str) -- xlink:label attribute""" @@ -1606,7 +1606,7 @@ def xlinkLabel(self): def xmlLang(self): """(str) -- xml:lang attribute""" return XmlUtil.ancestorOrSelfAttr(self, "{http://www.w3.org/XML/1998/namespace}lang") - + @property def attributes(self): # for output of derived instance, includes all output-applicable attributes @@ -1622,39 +1622,39 @@ def attributes(self): def viewText(self, labelrole=None, lang=None): return self.value - + @property def propertyView(self): return (("file", self.modelDocument.basename), ("line", self.sourceline)) + \ super(ModelInlineFootnote,self).propertyView + \ (("html value", self.htmlValue),) - + def __repr__(self): return ("modelInlineFootnote[{0}]{1})".format(self.objectId(),self.propertyView)) class ModelInlineXbrliXbrl(ModelObject): """ .. class:: ModelInlineXbrliXbrl(modelDocument) - + Model inline xbrli:xbrl element for root of derived/extracted instance - + :param modelDocument: owner document :type modelDocument: ModelDocument """ def init(self, modelDocument): super(ModelInlineXbrliXbrl, self).init(modelDocument) - + @property def qname(self): """(QName) -- QName of generated object""" return XbrlConst.qnXbrliXbrl - + @property def parentElement(self): """(ModelObject) -- inline root element has no parent element""" return None - + def ixIter(self, childOnly=False): """(ModelObject) -- generator of child elements of the inline target instance document""" global Type @@ -1664,7 +1664,7 @@ def ixIter(self, childOnly=False): # roleRef and arcroleRef (of each inline document) for sourceRefs in (modelXbrl.targetRoleRefs, modelXbrl.targetArcroleRefs): for roleRefElt in sourceRefs.values(): - yield roleRefElt + yield roleRefElt # contexts if not hasattr(self, "_orderedContenxts"): # contexts may come from multiple IXDS files self._orderedContenxts = sorted(modelXbrl.contexts.values(), key=lambda c: c.objectIndex) @@ -1701,10 +1701,10 @@ def ixIter(self, childOnly=False): if not childOnly: for e in linkPrototype.iterdescendants(): yield e - + from arelle.ModelFormulaObject import Aspect from arelle import FunctionIxt - + from arelle.ModelObjectFactory import elementSubstitutionModelClass elementSubstitutionModelClass.update(( (XbrlConst.qnXbrliItem, ModelFact), diff --git a/arelle/ModelManager.py b/arelle/ModelManager.py index 9db933656b..61b6182579 100644 --- a/arelle/ModelManager.py +++ b/arelle/ModelManager.py @@ -12,44 +12,44 @@ def initialize(cntlr): modelManager = ModelManager(cntlr) modelManager.modelXbrl = None return modelManager - + class ModelManager: - """ModelManager provides an interface between modelXbrl's and the controller. Model manager is a + """ModelManager provides an interface between modelXbrl's and the controller. Model manager is a singleton object, one is created in initialization of a controller. - - The ModelManager coordinates ModelXbrl instances for the Controller, and is the interface to utility - functions (such as the Python web cache), and application specific formalisms (such as the SEC + + The ModelManager coordinates ModelXbrl instances for the Controller, and is the interface to utility + functions (such as the Python web cache), and application specific formalisms (such as the SEC restrictions on referencable base taxonomies). .. attribute:: validateDisclosureSystem - + True if disclosure system is to be validated (e.g., EFM) - + .. attribute:: disclosureSystem - + Disclosure system object. To select the disclosure system, e.g., 'gfm', moduleManager.disclosureSystem.select('gfm'). - + .. attribute:: validateCalcLB - + True for calculation linkbase validation. - + .. attribute:: validateInferDecimals - + True for calculation linkbase validation to infer decimals (instead of precision) - + .. attribute:: validateDedupCalcs - + True for calculation linkbase validation de-duplicate calculations - + .. attribute:: validateUTR - + True for validation of unit type registry - + .. attribute:: defaultLang - + The default language code for labels selection and views (e.g. 'en-US'), set from the operating system defaults on startup. """ - + def __init__(self, cntlr): self.cntlr = cntlr self.validateDisclosureSystem = False @@ -71,10 +71,10 @@ def __init__(self, cntlr): def shutdown(self): self.status = "shutdown" - + def addToLog(self, message, messageCode="", file="", refs=[], level=logging.INFO): """Add a simple info message to the default logger - + :param message: Text of message to add to log. :type message: str :param messageCode: Message code (e.g., a prefix:id of a standard error) @@ -84,49 +84,49 @@ def addToLog(self, message, messageCode="", file="", refs=[], level=logging.INFO :type file: str """ self.cntlr.addToLog(message, messageCode=messageCode, file=file, refs=refs, level=level) - + def showStatus(self, message, clearAfter=None): """Provide user feedback on status line of GUI or web page according to type of controller. - + :param message: Message to display on status widget. :type message: str :param clearAfter: Time, in ms., after which to clear the message (e.g., 5000 for 5 sec.) :type clearAfter: int """ self.cntlr.showStatus(message, clearAfter) - + def viewModelObject(self, modelXbrl, objectId): """Notify any active views to show and highlight selected object. Generally used to scroll list control to object and highlight it, or if tree control, to find the object and open tree branches as needed for visibility, scroll to and highlight the object. - + :param modelXbrl: ModelXbrl (DTS) whose views are to be notified :type modelXbrl: ModelXbrl :param objectId: Selected object id (string format corresponding to ModelObject.objectId() ) :type objectId: str """ self.cntlr.viewModelObject(modelXbrl, objectId) - + def reloadViews(self, modelXbrl): """Notify all active views to reload and redisplay their entire contents. May be used when loaded model is changed significantly, or when individual object change notifications (by viewModelObject) would be difficult to identify or too numerous. - + :param modelXbrl: ModelXbrl (DTS) whose views are to be reloaded :type modelXbrl: ModelXbrl """ self.cntlr.reloadViews(modelXbrl) - + def load(self, filesource, nextaction=None, taxonomyPackages=None, **kwargs): - """Load an entry point modelDocument object(s), which in turn load documents they discover - (for the case of instance, taxonomies, and versioning reports), but defer loading instances - for test case and RSS feeds. - - The modelXbrl that is loaded is 'stacked', by this class, so that any modelXbrl operations such as validate, - and close, operate on the most recently loaded modelXbrl, and compareDTSes operates on the two + """Load an entry point modelDocument object(s), which in turn load documents they discover + (for the case of instance, taxonomies, and versioning reports), but defer loading instances + for test case and RSS feeds. + + The modelXbrl that is loaded is 'stacked', by this class, so that any modelXbrl operations such as validate, + and close, operate on the most recently loaded modelXbrl, and compareDTSes operates on the two most recently loaded modelXbrl's. - - :param filesource: may be a FileSource object, with the entry point selected, or string file name (or web URL). + + :param filesource: may be a FileSource object, with the entry point selected, or string file name (or web URL). :type filesource: FileSource or str :param nextAction: status line text string, if any, to show upon completion :type nextAction: str @@ -158,23 +158,23 @@ def load(self, filesource, nextaction=None, taxonomyPackages=None, **kwargs): self.modelXbrl = modelXbrl self.loadedModelXbrls.append(self.modelXbrl) return self.modelXbrl - + def saveDTSpackage(self, allDTSes=False): if allDTSes: for modelXbrl in self.loadedModelXbrls: modelXbrl.saveDTSpackage() elif self.modelXbrl is not None: self.modelXbrl.saveDTSpackage() - + def create(self, newDocumentType=None, url=None, schemaRefs=None, createModelDocument=True, isEntry=False, errorCaptureLevel=None, initialXml=None, base=None): - self.modelXbrl = ModelXbrl.create(self, newDocumentType=newDocumentType, url=url, schemaRefs=schemaRefs, createModelDocument=createModelDocument, + self.modelXbrl = ModelXbrl.create(self, newDocumentType=newDocumentType, url=url, schemaRefs=schemaRefs, createModelDocument=createModelDocument, isEntry=isEntry, errorCaptureLevel=errorCaptureLevel, initialXml=initialXml, base=base) self.loadedModelXbrls.append(self.modelXbrl) return self.modelXbrl - + def validate(self): """Validates the most recently loaded modelXbrl (according to validation properties). - + Results of validation will be in log. """ try: @@ -184,10 +184,10 @@ def validate(self): self.addToLog(_("[exception] Validation exception: {0} at {1}").format( err, traceback.format_tb(sys.exc_info()[2]))) - + def compareDTSes(self, versReportFile, writeReportFile=True): """Compare two most recently loaded DTSes, saving versioning report in to the file name provided. - + :param versReportFile: file name in which to save XBRL Versioning Report :type versReportFile: str :param writeReportFile: False to prevent writing XBRL Versioning Report file @@ -204,10 +204,10 @@ def compareDTSes(self, versReportFile, writeReportFile=True): ModelVersReport(modelVersReport).diffDTSes(versReportFile, fromDTS, toDTS) return modelVersReport return None - + def close(self, modelXbrl=None): """Closes the specified or most recently loaded modelXbrl - + :param modelXbrl: Specific ModelXbrl to be closed (defaults to last opened ModelXbrl) :type modelXbrl: ModelXbrl """ @@ -228,4 +228,4 @@ def loadCustomTransforms(self): self.customTransforms = {} for pluginMethod in pluginClassMethods("ModelManager.LoadCustomTransforms"): pluginMethod(self.customTransforms) - + diff --git a/arelle/ModelObject.py b/arelle/ModelObject.py index bb8a039ad1..fdff5cd3f0 100644 --- a/arelle/ModelObject.py +++ b/arelle/ModelObject.py @@ -18,76 +18,76 @@ def init(): # init globals if XmlUtil is None: from arelle import XmlUtil from arelle.XmlValidate import VALID_NO_CONTENT - + class ModelObject(etree.ElementBase): - """ModelObjects represent the XML elements within a document, and are implemented as custom - lxml proxy objects. Each modelDocument has a parser with the parser objects in ModelObjectFactory.py, - to determine the type of model object to correspond to a proxied lxml XML element. - Both static assignment of class, by namespace and local name, and dynamic assignment, by dynamic - resolution of element namespace and local name according to the dynamically loaded schemas, are + """ModelObjects represent the XML elements within a document, and are implemented as custom + lxml proxy objects. Each modelDocument has a parser with the parser objects in ModelObjectFactory.py, + to determine the type of model object to correspond to a proxied lxml XML element. + Both static assignment of class, by namespace and local name, and dynamic assignment, by dynamic + resolution of element namespace and local name according to the dynamically loaded schemas, are used in the ModelObjectFactory. - - ModelObjects are grouped into Python modules to ensure minimal inter-package references - (which causes a performance impact). ModelDtsObjects collects DTS objects (schema and linkbase), - ModelInstanceObjects collects instance objects (facts, contexts, dimensions, and units), - ModelTestcaseObject collects testcase and variation objects, ModelVersioningObject has specialized - objects representing versioning report contents, and ModelRssItem represents the item objects in an - RSS feed. - - The ModelObject custom lxml proxy object is implemented as a specialization of etree.ElementBase, - and used as the superclass of discovered and created objects in XML-based objects in Arelle. - ModelObject is also used as a phantom proxy object, for non-XML objects that are resolved - from modelDocument objects, such as the ModelRelationship object. ModelObjects persistent + + ModelObjects are grouped into Python modules to ensure minimal inter-package references + (which causes a performance impact). ModelDtsObjects collects DTS objects (schema and linkbase), + ModelInstanceObjects collects instance objects (facts, contexts, dimensions, and units), + ModelTestcaseObject collects testcase and variation objects, ModelVersioningObject has specialized + objects representing versioning report contents, and ModelRssItem represents the item objects in an + RSS feed. + + The ModelObject custom lxml proxy object is implemented as a specialization of etree.ElementBase, + and used as the superclass of discovered and created objects in XML-based objects in Arelle. + ModelObject is also used as a phantom proxy object, for non-XML objects that are resolved + from modelDocument objects, such as the ModelRelationship object. ModelObjects persistent with their owning ModelDocument, due to reference by modelObject list in modelDocument object. - + (The attributes and methods for ModelObject are in addition to those for lxml base class, _ElementBase.) - .. attribute:: modelDocument + .. attribute:: modelDocument Owning ModelDocument object - + .. attribute:: modelXbrl modelDocument's owning ModelXbrl object - + .. attribute:: localName W3C DOM localName - + .. attribute:: prefixedName Prefix by ancestor xmlns and localName of element - + .. attribute:: namespaceURI W3C DOM namespaceURI (overridden for schema elements) - + .. attribute:: elementNamespaceURI W3C DOM namespaceURI (not overridden by subclasses) - + .. attribute:: qname QName of element (overridden for schema elements) - + .. attribute:: elementQname QName of element (not overridden by subclasses) - + .. attribute:: parentQname QName of parent element - + .. attribute:: id Id attribute or None - + .. attribute:: elementAttributesTuple Python tuple of (tag, value) of specified attributes of element, where tag is in Clark notation - + .. attribute:: elementAttributesStr String of tag=value[,tag=value...] of specified attributes of element - + .. attribute:: xValid XmlValidation.py validation state enumeration - + .. attribute:: xValue PSVI value (for formula processing) - + .. attribute:: sValue s-equals value (for s-equality) - + .. attribute:: xAttributes Dict by attrTag of ModelAttribute objects (see below) of specified and default attributes of this element. """ @@ -96,11 +96,11 @@ def _init(self): parent = self.getparent() if parent is not None and hasattr(parent, "modelDocument"): self.init(parent.modelDocument) - + def clear(self): self.__dict__.clear() # delete local attributes super(ModelObject, self).clear() # delete children - + def init(self, modelDocument): self.modelDocument = modelDocument self.objectIndex = len(modelDocument.modelXbrl.modelObjects) @@ -108,25 +108,25 @@ def init(self, modelDocument): id = self.get("id") if id: modelDocument.idObjects[id] = self - + def objectId(self,refId=""): - """Returns a string surrogate representing the object index of the model document, + """Returns a string surrogate representing the object index of the model document, prepended by the refId string. :param refId: A string to prefix the refId for uniqueless (such as to use in tags for tkinter) :type refId: str """ return "_{0}_{1}".format(refId, self.objectIndex) - + @property def modelXbrl(self): try: return self.modelDocument.modelXbrl except AttributeError: return None - + def attr(self, attrname): return self.get(attrname) - + @property def slottedAttributesNames(self): return emptySet @@ -141,7 +141,7 @@ def setNamespaceLocalName(self): self._prefixedName = self.prefix + ":" + self.localName else: self._prefixedName = self.localName - + def getStripped(self, attrName): attrValue = self.get(attrName) if attrValue is not None: @@ -155,7 +155,7 @@ def localName(self): except AttributeError: self.setNamespaceLocalName() return self._localName - + @property def prefixedName(self): try: @@ -163,7 +163,7 @@ def prefixedName(self): except AttributeError: self.setNamespaceLocalName() return self._prefixedName - + @property def namespaceURI(self): try: @@ -171,7 +171,7 @@ def namespaceURI(self): except AttributeError: self.setNamespaceLocalName() return self._namespaceURI - + @property def elementNamespaceURI(self): # works also for concept elements try: @@ -179,7 +179,7 @@ def elementNamespaceURI(self): # works also for concept elements except AttributeError: self.setNamespaceLocalName() return self._namespaceURI - + # qname of concept of fact or element for all but concept element, type, attr, param, override to the name parameter @property def qname(self): @@ -188,7 +188,7 @@ def qname(self): except AttributeError: self._elementQname = QName(self.prefix, self.namespaceURI, self.localName) return self._elementQname - + # qname is overridden for concept, type, attribute, and formula parameter, elementQname is unambiguous @property def elementQname(self): @@ -197,23 +197,23 @@ def elementQname(self): except AttributeError: self._elementQname = qname(self) return self._elementQname - + def vQname(self, validationModelXbrl=None): if validationModelXbrl is not None and validationModelXbrl != self.modelXbrl: # use physical element declaration in specified modelXbrl return self.elementQname # use logical qname (inline element's fact qname, or concept's qname) return self.qname - - + + def elementDeclaration(self, validationModelXbrl=None): elementModelXbrl = self.modelXbrl - if validationModelXbrl is not None and validationModelXbrl != elementModelXbrl: + if validationModelXbrl is not None and validationModelXbrl != elementModelXbrl: # use physical element declaration in specified modelXbrl return validationModelXbrl.qnameConcepts.get(self.elementQname) # use logical element declaration in element's own modelXbrl return elementModelXbrl.qnameConcepts.get(self.qname) - + @property def elementSequence(self): # ordinal position among siblings, 1 is first position @@ -222,7 +222,7 @@ def elementSequence(self): except AttributeError: self._elementSequence = 1 + sum(isinstance(s, etree.ElementBase) for s in self.itersiblings(preceding=True)) return self._elementSequence - + @property def parentQname(self): try: @@ -232,19 +232,19 @@ def parentQname(self): self._parentQname = parentObj.elementQname if parentObj is not None else None return self._parentQname - + @property def id(self): return self.get("id") - + @property def stringValue(self): # "string value" of node, text of all Element descendants return ''.join(self._textNodes(recurse=True)) # return text of Element descendants - + @property def textValue(self): # xml axis text() differs from string value, no descendant element text return ''.join(self._textNodes()) # no text nodes returns '' - + def _textNodes(self, recurse=False): if self.text and getattr(self,"xValid", 0) != VALID_NO_CONTENT: # skip tuple whitespaces yield self.text @@ -252,16 +252,16 @@ def _textNodes(self, recurse=False): if recurse and isinstance(c, ModelObject): for nestedText in c._textNodes(recurse): yield nestedText - if c.tail and getattr(self,"xValid", 0) != VALID_NO_CONTENT: # skip tuple whitespaces + if c.tail and getattr(self,"xValid", 0) != VALID_NO_CONTENT: # skip tuple whitespaces yield c.tail # get tail of nested element, comment or processor nodes @property def document(self): return self.modelDocument - + def prefixedNameQname(self, prefixedName): """Returns ModelValue.QName of prefixedName using this element and its ancestors' xmlns. - + :param prefixedName: A prefixed name string :type prefixedName: str :returns: QName -- the resolved prefixed name, or None if no prefixed name was provided @@ -270,11 +270,11 @@ def prefixedNameQname(self, prefixedName): return qnameEltPfxName(self, prefixedName) else: return None - + @property def elementAttributesTuple(self): return tuple((name,value) for name,value in self.items()) - + @property def elementAttributesStr(self): return ', '.join(["{0}='{1}'".format(name,value) for name,value in self.items()]) @@ -282,7 +282,7 @@ def elementAttributesStr(self): def resolveUri(self, hrefObject=None, uri=None, dtsModelXbrl=None): """Returns the modelObject within modelDocment that resolves a URI based on arguments relative to this element - + :param hrefObject: an optional tuple of (hrefElement, modelDocument, id), or :param uri: An (element scheme pointer), and dtsModelXbrl (both required together if for a multi-instance href) :type uri: str @@ -302,7 +302,7 @@ def resolveUri(self, hrefObject=None, uri=None, dtsModelXbrl=None): doc = self.modelDocument else: normalizedUrl = self.modelXbrl.modelManager.cntlr.webCache.normalizeUrl( - url, + url, self.modelDocument.baseForElement(self)) doc = dtsModelXbrl.urlDocs.get(normalizedUrl) from arelle import ModelDocument @@ -340,18 +340,18 @@ def genLabel(self,role=None,fallbackToQname=False,fallbackToXlinkLabel=False,lan def viewText(self, labelrole=None, lang=None): return self.stringValue - + @property def propertyView(self): return (("QName", self.elementQname),) + tuple( (XmlUtil.clarkNotationToPrefixedName(self, _tag, isAttribute=True), _value) for _tag, _value in self.items()) - + def __repr__(self): return ("{0}[{1}, {2} line {3})".format(type(self).__name__, self.objectIndex, self.modelDocument.basename, self.sourceline)) from arelle.ModelValue import qname, qnameEltPfxName, QName - + class ModelComment(etree.CommentBase): """ModelConcept is a custom proxy objects for etree. """ @@ -363,7 +363,7 @@ def _init(self): def init(self, modelDocument): self.modelDocument = modelDocument - + class ModelProcessingInstruction(etree.PIBase): """ModelProcessingInstruction is a custom proxy object for etree. """ @@ -373,10 +373,10 @@ def _init(self): class ModelAttribute: """ .. class:: ModelAttribute(modelElement, attrTag, xValid, xValue, sValue, text) - + ModelAttribute is a class of slot-based instances to store PSVI attribute values for each ModelObject that has been validated. It does not correspond to, or proxy, any lxml object. - + :param modelElement: owner element of attribute node :type modelElement: ModelObject :param attrTag: Clark notation attribute tag (from lxml) @@ -399,12 +399,12 @@ class ObjectPropertyViewWrapper: # extraProperties = ( (p1, v1), (p2, v2), ... def __init__(self, modelObject, extraProperties=()): self.modelObject = modelObject self.extraProperties = extraProperties - + @property def propertyView(self): return self.modelObject.propertyView + self.extraProperties - + def __repr__(self): return "objectPropertyViewWrapper({}, extraProperties={})".format(self.modelObject, self.extraProperties) - + diff --git a/arelle/ModelObjectFactory.py b/arelle/ModelObjectFactory.py index 45621a4f45..304eb701a7 100644 --- a/arelle/ModelObjectFactory.py +++ b/arelle/ModelObjectFactory.py @@ -12,7 +12,7 @@ from lxml import etree from arelle import XbrlConst, XmlUtil from arelle.ModelValue import qnameNsLocalName -from arelle.ModelDtsObject import (ModelConcept, ModelAttribute, ModelAttributeGroup, ModelType, +from arelle.ModelDtsObject import (ModelConcept, ModelAttribute, ModelAttributeGroup, ModelType, ModelGroupDefinition, ModelAll, ModelChoice, ModelSequence, ModelAny, ModelAnyAttribute, ModelEnumeration, ModelRoleType, ModelLocator, ModelLink, ModelResource) @@ -107,7 +107,7 @@ def lookup(self, node_type, document, ns, ln): return ModelRssItem else: return ModelObject - + # match specific element types or substitution groups for types return self.modelXbrl.matchSubstitutionGroup( qnameNsLocalName(ns, ln), @@ -132,7 +132,7 @@ def __init__(self, modelXbrl, baseUrl, fallback=None): from arelle import ModelDocument if self.streamingOrSkipDTS and ModelFact is None: from arelle.ModelInstanceObject import ModelFact - + def lookup(self, document, proxyElement): # check if proxyElement's namespace is not known ns, sep, ln = proxyElement.tag.partition("}") @@ -141,8 +141,8 @@ def lookup(self, document, proxyElement): else: ln = ns ns = None - if (ns and - ns not in self.discoveryAttempts and + if (ns and + ns not in self.discoveryAttempts and ns not in self.modelXbrl.namespaceDocs): # is schema loadable? requires a schemaLocation relativeUrl = XmlUtil.schemaLocation(proxyElement, ns) @@ -153,10 +153,10 @@ def lookup(self, document, proxyElement): modelObjectClass = self.modelXbrl.matchSubstitutionGroup( qnameNsLocalName(ns, ln), elementSubstitutionModelClass) - + if modelObjectClass is not None: return modelObjectClass - elif (self.streamingOrSkipDTS and + elif (self.streamingOrSkipDTS and ns not in (XbrlConst.xbrli, XbrlConst.link)): # self.makeelementParentModelObject is set in streamingExtensions.py and ModelXbrl.createFact ancestor = proxyElement.getparent() or getattr(self.modelXbrl, "makeelementParentModelObject", None) @@ -168,10 +168,10 @@ def lookup(self, document, proxyElement): else: break # cannot be a fact ancestor = ancestor.getparent() - + xlinkType = proxyElement.get("{http://www.w3.org/1999/xlink}type") if xlinkType == "extended": return ModelLink elif xlinkType == "locator": return ModelLocator elif xlinkType == "resource": return ModelResource - + return ModelObject diff --git a/arelle/ModelRenderingObject.py b/arelle/ModelRenderingObject.py index 1010a7c847..be7523bb6a 100644 --- a/arelle/ModelRenderingObject.py +++ b/arelle/ModelRenderingObject.py @@ -13,12 +13,12 @@ from arelle.ModelFormulaObject import (Trace, ModelFormulaResource, ModelFormulaRules, ModelConceptName, ModelParameter, Aspect, aspectStr, aspectRuleAspects) from arelle.ModelInstanceObject import ModelFact -from arelle.FormulaEvaluator import (filterFacts as formulaEvaluatorFilterFacts, +from arelle.FormulaEvaluator import (filterFacts as formulaEvaluatorFilterFacts, aspectsMatch, factsPartitions, VariableBinding) from arelle.PrototypeInstanceObject import FactPrototype ROLLUP_NOT_ANALYZED = 0 -CHILD_ROLLUP_FIRST = 1 +CHILD_ROLLUP_FIRST = 1 CHILD_ROLLUP_LAST = 2 CHILDREN_BUT_NO_ROLLUP = 3 @@ -57,11 +57,11 @@ def __init__(self, parentStructuralNode, breakdownNode, definitionNode, zInherit self.breakdownNode = breakdownNode # CR definition node self.tagSelector = definitionNode.tagSelector self.isLabeled = True - + @property def modelXbrl(self): return self.definitionNode.modelXbrl - + @property def choiceStructuralNodes(self): if hasattr(self, "_choiceStructuralNodes"): @@ -70,7 +70,7 @@ def choiceStructuralNodes(self): return self.parentStructuralNode.choiceStructuralNodes # choiceStrNodes are on the breakdown node (if any) return None - + @property def isAbstract(self): if self.subtreeRollUp: @@ -82,7 +82,7 @@ def isAbstract(self): return self.definitionNode.isAbstract except AttributeError: # axis may never be abstract return False - + def isSummary(self): if self.childStructuralNodes is not None and not self.isAbstract: return True @@ -92,17 +92,17 @@ def isSummary(self): @property def isRollUp(self): return self.definitionNode.isRollUp - + @property def cardinalityAndDepth(self): return self.definitionNode.cardinalityAndDepth(self) - + @property def structuralDepth(self): if self.parentStructuralNode is not None: return self.parentStructuralNode.structuralDepth + 1 return 0 - + ''' def breakdownNode(self, tableELR): definitionNode = self.definitionNode @@ -117,7 +117,7 @@ def breakdownNode(self, tableELR): break # recurse to move to this node's parent breakdown node return definitionNode # give up here ''' - + def constraintSet(self, tagSelectors=None): definitionNode = self.definitionNode if tagSelectors: @@ -125,20 +125,20 @@ def constraintSet(self, tagSelectors=None): if tag in definitionNode.constraintSets: return definitionNode.constraintSets[tag] return definitionNode.constraintSets.get(None) # returns None if no default constraint set - + def aspectsCovered(self, inherit=False): aspectsCovered = _DICT_SET(self.aspects.keys()) | self.definitionNode.aspectsCovered() if inherit and self.parentStructuralNode is not None: aspectsCovered.update(self.parentStructuralNode.aspectsCovered(inherit=inherit)) return aspectsCovered - + def hasAspect(self, aspect, inherit=True): - return (aspect in self.aspects or - self.definitionNode.hasAspect(self, aspect) or + return (aspect in self.aspects or + self.definitionNode.hasAspect(self, aspect) or (inherit and - self.parentStructuralNode is not None and + self.parentStructuralNode is not None and self.parentStructuralNode.hasAspect(aspect, inherit))) - + def aspectValue(self, aspect, inherit=True, dims=None, depth=0, tagSelectors=None): xc = self._rendrCntx if False: # TEST: self.choiceStructuralNodes: # use aspects from choice structural node @@ -188,28 +188,28 @@ def aspectValue(self, aspect, inherit=True, dims=None, depth=0, tagSelectors=Non return None ''' - @property + @property def primaryItemQname(self): # for compatibility with viewRelationsihps if Aspect.CONCEPT in self.aspects: return self.aspects[Aspect.CONCEPT] return self.definitionNode.primaryItemQname - + @property def explicitDims(self): return self.definitionNode.explicitDims ''' - + def objectId(self, refId=""): return self.definitionNode.objectId(refId) - + def header(self, role=None, lang=None, evaluate=True, returnGenLabel=True, returnMsgFormatString=False, recurseParent=True, returnStdLabel=True): # if ord is a nested selectionAxis selection, use selection-message or text contents instead of axis headers isZSelection = isinstance(self.definitionNode, ModelSelectionDefinitionNode) and hasattr(self, "zSelection") if role is None: # check for message before checking for genLabel msgsRelationshipSet = self.definitionNode.modelXbrl.relationshipSet( - (XbrlConst.tableDefinitionNodeSelectionMessage201301, XbrlConst.tableAxisSelectionMessage2011) - if isZSelection else + (XbrlConst.tableDefinitionNodeSelectionMessage201301, XbrlConst.tableAxisSelectionMessage2011) + if isZSelection else (XbrlConst.tableDefinitionNodeMessage201301, XbrlConst.tableAxisMessage2011)) if msgsRelationshipSet: msg = msgsRelationshipSet.label(self.definitionNode, XbrlConst.standardMessage, lang, returnText=False) @@ -250,7 +250,7 @@ def header(self, role=None, lang=None, evaluate=True, returnGenLabel=True, retur elif isinstance(aspectValue, ModelObject): text = XmlUtil.innerTextList(aspectValue) if not text and XmlUtil.hasChild(aspectValue, aspectValue.namespaceURI, "forever"): - text = "forever" + text = "forever" return text if concept is not None: label = concept.label(lang=lang) @@ -260,7 +260,7 @@ def header(self, role=None, lang=None, evaluate=True, returnGenLabel=True, retur if role and recurseParent and self.parentStructuralNode is not None: return self.parentStructuralNode.header(role, lang, evaluate, returnGenLabel, returnMsgFormatString, recurseParent) return None - + def evaluate(self, evalObject, evalMethod, otherAxisStructuralNode=None, evalArgs=(), handleXPathException=True, **kwargs): xc = self._rendrCntx if self.contextItemBinding and not isinstance(xc.contextItem, ModelFact): @@ -292,8 +292,8 @@ def evaluate(self, evalObject, evalMethod, otherAxisStructuralNode=None, evalArg if not handleXPathException: raise xc.modelXbrl.error(err.code, - _("%(element)s set %(xlinkLabel)s \nException: %(error)s"), - modelObject=evalObject, element=evalObject.localName, + _("%(element)s set %(xlinkLabel)s \nException: %(error)s"), + modelObject=evalObject, element=evalObject.localName, xlinkLabel=evalObject.xlinkLabel, error=err.message) result = '' for qn in removeVarQnames: @@ -301,22 +301,22 @@ def evaluate(self, evalObject, evalMethod, otherAxisStructuralNode=None, evalArg if previousContextItem is not None: xc.contextItem = previousContextItem # xbrli.xbrl return result - + def hasValueExpression(self, otherAxisStructuralNode=None): - return (self.definitionNode.hasValueExpression or + return (self.definitionNode.hasValueExpression or (otherAxisStructuralNode is not None and otherAxisStructuralNode.definitionNode.hasValueExpression)) - + def evalValueExpression(self, fact, otherAxisStructuralNode=None): for structuralNode in (self, otherAxisStructuralNode): if structuralNode is not None and structuralNode.definitionNode.hasValueExpression: return self.evaluate(self.definitionNode, structuralNode.definitionNode.evalValueExpression, otherAxisStructuralNode=otherAxisStructuralNode, evalArgs=(fact,)) return None - + @property def isEntryAspect(self): # true if open node and bound to a fact prototype return self.contextItemBinding is not None and isinstance(self.contextItemBinding.yieldedFact, FactPrototype) - + def isEntryPrototype(self, default=False): # true if all axis open nodes before this one are entry prototypes (or not open axes) if self.contextItemBinding is not None: @@ -325,14 +325,14 @@ def isEntryPrototype(self, default=False): if self.parentStructuralNode is not None: return self.parentStructuralNode.isEntryPrototype(default) return default # nothing open to be bound to a fact - + @property def tableDefinitionNode(self): if self.parentStructuralNode is None: return self.tableNode else: return self.parentStructuralNode.tableDefinitionNode - + @property def tagSelectors(self): try: @@ -345,7 +345,7 @@ def tagSelectors(self): if self.tagSelector: self._tagSelectors.add(self.tagSelector) return self._tagSelectors - + @property def leafNodeCount(self): childLeafCount = 0 @@ -357,7 +357,7 @@ def leafNodeCount(self): if not self.isAbstract and isinstance(self.definitionNode, (ModelClosedDefinitionNode, ModelEuAxisCoord)): childLeafCount += 1 # has a roll up return childLeafCount - + def setHasOpenNode(self): if self.parentStructuralNode is not None: self.parentStructuralNode.setHasOpenNode() @@ -366,7 +366,7 @@ def setHasOpenNode(self): def inheritedPrimaryItemQname(self, view): return (self.primaryItemQname or self.inheritedPrimaryItemQname(self.parentStructuralNode, view)) - + def inheritedExplicitDims(self, view, dims=None, nested=False): if dims is None: dims = {} if self.parentOrdinateContext: @@ -377,7 +377,7 @@ def inheritedExplicitDims(self, view, dims=None, nested=False): return {(dim,mem) for dim,mem in dims.items() if mem != 'omit'} def inheritedAspectValue(self, otherAxisStructuralNode, - view, aspect, tagSelectors, + view, aspect, tagSelectors, xAspectStructuralNodes, yAspectStructuralNodes, zAspectStructuralNodes): aspectStructuralNodes = xAspectStructuralNodes.get(aspect, EMPTY_SET) | yAspectStructuralNodes.get(aspect, EMPTY_SET) | zAspectStructuralNodes.get(aspect, EMPTY_SET) structuralNode = None @@ -391,11 +391,11 @@ def inheritedAspectValue(self, otherAxisStructuralNode, if structuralNode: hasClash = True else: - structuralNode = _aspectStructuralNode + structuralNode = _aspectStructuralNode else: # take closest structural node hasClash = True - + ''' reported in static analysis by RenderingEvaluator.py if hasClash: from arelle.ModelFormulaObject import aspectStr @@ -406,23 +406,23 @@ def inheritedAspectValue(self, otherAxisStructuralNode, if structuralNode: definitionNodeConstraintSet = structuralNode.constraintSet(tagSelectors) if definitionNodeConstraintSet is not None and definitionNodeConstraintSet.aspectValueDependsOnVars(aspect): - return self.evaluate(definitionNodeConstraintSet, + return self.evaluate(definitionNodeConstraintSet, definitionNodeConstraintSet.aspectValue, # this passes a method otherAxisStructuralNode=otherAxisStructuralNode, evalArgs=(aspect,)) return structuralNode.aspectValue(aspect, tagSelectors=tagSelectors) - return None + return None + - def __repr__(self): return ("structuralNode[{0}]{1})".format(self.objectId(),self.definitionNode)) - + # Root class for rendering is formula, to allow linked and nested compiled expressions def definitionModelLabelsView(mdlObj): return tuple(sorted([("{} {} {} {}".format(label.localName, str(rel.order).rstrip("0").rstrip("."), os.path.basename(label.role or ""), - label.xmlLang), + label.xmlLang), label.stringValue) for rel in mdlObj.modelXbrl.relationshipSet((XbrlConst.elementLabel,XbrlConst.elementReference)).fromModelObject(mdlObj) for label in (rel.toModelObject,)] + @@ -433,63 +433,63 @@ class ModelEuTable(ModelResource): def init(self, modelDocument): super(ModelEuTable, self).init(modelDocument) self.aspectsInTaggedConstraintSets = set() - + @property def aspectModel(self): return "dimensional" - + @property def propertyView(self): return ((("id", self.id),) + self.definitionLabelsView) - - ''' now only accessed from structural node + + ''' now only accessed from structural node def header(self, role=None, lang=None, strip=False, evaluate=True): return self.genLabel(role=role, lang=lang, strip=strip) ''' - + @property def parameters(self): return {} - + @property def definitionLabelsView(self): return definitionModelLabelsView(self) - + def filteredFacts(self, xpCtx, facts): return facts - + @property def xpathContext(self): return None - + def __repr__(self): return ("table[{0}]{1})".format(self.objectId(),self.propertyView)) class ModelEuAxisCoord(ModelResource): def init(self, modelDocument): super(ModelEuAxisCoord, self).init(modelDocument) - + @property def abstract(self): return self.get("abstract") or 'false' - + @property def isAbstract(self): return self.abstract == "true" - + @property def isMerged(self): return False - + @property def parentChildOrder(self): return self.get("parentChildOrder") - + @property def isRollUp(self): return False - + @property def parentDefinitionNode(self): try: @@ -511,15 +511,15 @@ def aspectsCovered(self): for e in XmlUtil.children(self, XbrlConst.euRend, "explicitDimCoord"): aspectsCovered.add(self.prefixedNameQname(e.get("dimension"))) return aspectsCovered - + @property def constraintSets(self): return {None: self} - + @property def tagSelector(self): # default constraint set for ruleNode has name None return None - + def hasAspect(self, structuralNode, aspect): if aspect == Aspect.CONCEPT: return XmlUtil.hasChild(self, XbrlConst.euRend, "primaryItem") @@ -532,10 +532,10 @@ def hasAspect(self, structuralNode, aspect): if self.prefixedNameQname(e.get("dimension")) == aspect: return True return False - + def aspectValueDependsOnVars(self, aspect): return False - + def aspectValue(self, xpCtx, aspect, inherit=False): if aspect == Aspect.DIMENSIONS: dims = set(self.prefixedNameQname(e.get("dimension")) @@ -556,7 +556,7 @@ def aspectValue(self, xpCtx, aspect, inherit=False): if XmlUtil.hasChild(self, XbrlConst.euRend, "timeReference"): return "instant" elif aspect == Aspect.INSTANT: - return XmlUtil.datetimeValue(XmlUtil.childAttr(self, XbrlConst.euRend, "timeReference", "instant"), + return XmlUtil.datetimeValue(XmlUtil.childAttr(self, XbrlConst.euRend, "timeReference", "instant"), addOneDay=True) elif isinstance(aspect, QName): for e in XmlUtil.children(self, XbrlConst.euRend, "explicitDimCoord"): @@ -571,47 +571,47 @@ def primaryItemQname(self): if priItem is not None: return self.prefixedNameQname(priItem) return None - + @property def explicitDims(self): return {(self.prefixedNameQname(e.get("dimension")), self.prefixedNameQname(e.get("value"))) for e in XmlUtil.children(self, XbrlConst.euRend, "explicitDimCoord")} - + @property def instant(self): - return XmlUtil.datetimeValue(XmlUtil.childAttr(self, XbrlConst.euRend, "timeReference", "instant"), + return XmlUtil.datetimeValue(XmlUtil.childAttr(self, XbrlConst.euRend, "timeReference", "instant"), addOneDay=True) ''' def cardinalityAndDepth(self, structuralNode, **kwargs): return (1, 1) - - ''' now only accessed from structural node + + ''' now only accessed from structural node def header(self, role=None, lang=None, strip=False, evaluate=True): return self.genLabel(role=role, lang=lang, strip=strip) ''' - + @property def hasValueExpression(self): return False - + @property def definitionLabelsView(self): return definitionModelLabelsView(self) - + @property def propertyView(self): explicitDims = self.aspectValue(None, Aspect.DIMENSIONS, inherit=True) return ((("id", self.id), ("primary item", self.aspectValue(None, Aspect.CONCEPT, inherit=True)), ("dimensions", "({0})".format(len(explicitDims)), - tuple((str(dim),str(self.aspectValue(None, dim, inherit=True))) + tuple((str(dim),str(self.aspectValue(None, dim, inherit=True))) for dim in sorted(explicitDims))) if explicitDims else (), ("abstract", self.abstract)) + self.definitionLabelsView) - + def __repr__(self): return ("axisCoord[{0}]{1})".format(self.objectId(),self.propertyView)) @@ -622,22 +622,22 @@ def init(self, modelDocument): self.modelXbrl.modelRenderingTables.add(self) self.modelXbrl.hasRenderingTables = True self.aspectsInTaggedConstraintSets = set() - + def clear(self): if getattr(self, "_rendrCntx"): self._rendrCntx.close() super(ModelTable, self).clear() # delete children - + @property def aspectModel(self): return self.get("aspectModel", "dimensional") # attribute removed 2013-06, always dimensional @property - def descendantArcroles(self): - return (XbrlConst.tableFilter, XbrlConst.tableFilterMMDD, XbrlConst.tableFilter201305, XbrlConst.tableFilter201301, XbrlConst.tableFilter2011, + def descendantArcroles(self): + return (XbrlConst.tableFilter, XbrlConst.tableFilterMMDD, XbrlConst.tableFilter201305, XbrlConst.tableFilter201301, XbrlConst.tableFilter2011, XbrlConst.tableBreakdown, XbrlConst.tableBreakdownMMDD, XbrlConst.tableBreakdown201305, XbrlConst.tableBreakdown201301, XbrlConst.tableAxis2011, XbrlConst.tableParameter, XbrlConst.tableParameterMMDD) - + @property def filterRelationships(self): try: @@ -651,20 +651,20 @@ def filterRelationships(self): rels.append(rel) self._filterRelationships = rels return rels - - ''' now only accessed from structural node + + ''' now only accessed from structural node def header(self, role=None, lang=None, strip=False, evaluate=True): return self.genLabel(role=role, lang=lang, strip=strip) ''' - + @property def definitionLabelsView(self): return definitionModelLabelsView(self) - + def filteredFacts(self, xpCtx, facts): - return formulaEvaluatorFilterFacts(xpCtx, VariableBinding(xpCtx), + return formulaEvaluatorFilterFacts(xpCtx, VariableBinding(xpCtx), facts, self.filterRelationships, None) - + @property def renderingXPathContext(self): try: @@ -681,35 +681,35 @@ def renderingXPathContext(self): else: self._rendrCntx = None return self._rendrCntx - + @property def propertyView(self): return ((("id", self.id),) + self.definitionLabelsView) - + def __repr__(self): return ("modlTable[{0}]{1})".format(self.objectId(),self.propertyView)) class ModelDefinitionNode(ModelFormulaResource): def init(self, modelDocument): super(ModelDefinitionNode, self).init(modelDocument) - + @property def parentDefinitionNode(self): return None - + @property - def descendantArcroles(self): + def descendantArcroles(self): return (XbrlConst.tableDefinitionNodeMessage201301, XbrlConst.tableAxisMessage2011, XbrlConst.tableDefinitionNodeSubtree201305, XbrlConst.tableDefinitionNodeSubtree, XbrlConst.tableDefinitionNodeSubtreeMMDD) - + def hasAspect(self, structuralNode, aspect): return False def aspectValueDependsOnVars(self, aspect): return False - + @property def variablename(self): """(str) -- name attribute""" @@ -725,64 +725,64 @@ def aspectValue(self, xpCtx, aspect, inherit=True): if aspect == Aspect.DIMENSIONS: return [] return None - + def aspectsCovered(self): return set() @property def constraintSets(self): return {None: self} - + @property def tagSelector(self): return self.get("tagSelector") - + @property def valueExpression(self): - return self.get("value") + return self.get("value") @property def hasValueExpression(self): return bool(self.valueProg) # non empty program - + def compile(self): if not hasattr(self, "valueProg"): value = self.valueExpression self.valueProg = XPathParser.parse(self, value, self, "value", Trace.VARIABLE) # duplicates formula resource for RuleAxis but not for other subclasses super(ModelDefinitionNode, self).compile() - + def evalValueExpression(self, xpCtx, fact): # compiled by FormulaResource compile() return xpCtx.evaluateAtomicValue(self.valueProg, 'xs:string', fact) - + ''' - @property + @property def primaryItemQname(self): # for compatibility with viewRelationsihps return None - + @property def explicitDims(self): return set() ''' - + @property def isAbstract(self): return False - + @property def isMerged(self): return False - + @property def isRollUp(self): return self.get("rollUp") == 'true' - + def cardinalityAndDepth(self, structuralNode, **kwargs): - return (1, + return (1, 1 if (structuralNode.header(evaluate=False) is not None) else 0) - - ''' now only accessed from structural node (mulst have table context for evaluate) + + ''' now only accessed from structural node (mulst have table context for evaluate) def header(self, role=None, lang=None, strip=False, evaluate=True): if role is None: # check for message before checking for genLabel @@ -801,26 +801,26 @@ def header(self, role=None, lang=None, strip=False, evaluate=True): ''' @property - def definitionNodeView(self): + def definitionNodeView(self): return XmlUtil.xmlstring(self, stripXmlns=True, prettyPrint=True) - + @property def definitionLabelsView(self): return definitionModelLabelsView(self) - + class ModelBreakdown(ModelDefinitionNode): def init(self, modelDocument): super(ModelBreakdown, self).init(modelDocument) - + @property def parentChildOrder(self): return self.get("parentChildOrder") @property - def descendantArcroles(self): + def descendantArcroles(self): return (XbrlConst.tableBreakdownTree, XbrlConst.tableBreakdownTreeMMDD, XbrlConst.tableBreakdownTree201305) - + @property def propertyView(self): return ((("id", self.id), @@ -831,23 +831,23 @@ def propertyView(self): class ModelClosedDefinitionNode(ModelDefinitionNode): def init(self, modelDocument): super(ModelClosedDefinitionNode, self).init(modelDocument) - + @property def abstract(self): return self.get("abstract") - + @property def isAbstract(self): return self.abstract == 'true' - + @property def parentChildOrder(self): return self.get("parentChildOrder") @property - def descendantArcroles(self): + def descendantArcroles(self): return (XbrlConst.tableDefinitionNodeSubtree, XbrlConst.tableDefinitionNodeSubtreeMMDD, XbrlConst.tableDefinitionNodeSubtree201305, XbrlConst.tableDefinitionNodeSubtree201301, XbrlConst.tableAxisSubtree2011, XbrlConst.tableDefinitionNodeMessage201301, XbrlConst.tableAxisMessage2011) - + def filteredFacts(self, xpCtx, facts): aspects = self.aspectsCovered() axisAspectValues = dict((aspect, self.aspectValue(xpCtx, aspect)) @@ -856,7 +856,7 @@ def filteredFacts(self, xpCtx, facts): return set(fact for fact in facts if aspectsMatch(xpCtx, fact, fp, aspects)) - + class ModelConstraintSet(ModelFormulaRules): def init(self, modelDocument): super(ModelConstraintSet, self).init(modelDocument) @@ -865,17 +865,17 @@ def init(self, modelDocument): self.aspectValues = {} # only needed if error blocks compiling this node, replaced by compile() self.aspectProgs = {} # ditto if self._locationSourceVar: self._locationAspectCovered.add(Aspect.LOCATION) # location is parent (tuple), not sibling - + def hasAspect(self, structuralNode, aspect, inherit=None): return self._hasAspect(structuralNode, aspect, inherit) - + def _hasAspect(self, structuralNode, aspect, inherit=None): # opaque from ModelRuleDefinitionNode if aspect == Aspect.LOCATION and self._locationSourceVar: return True elif aspect in aspectRuleAspects: return any(self.hasRule(a) for a in aspectRuleAspects[aspect]) return self.hasRule(aspect) - + def aspectValue(self, xpCtx, aspect, inherit=None): try: # if xpCtx is None: xpCtx = self.modelXbrl.rendrCntx (must have xpCtx of callint table) @@ -884,13 +884,13 @@ def aspectValue(self, xpCtx, aspect, inherit=None): return self.evaluateRule(xpCtx, aspect) except AttributeError: return '(unavailable)' # table defective or not initialized - + def aspectValueDependsOnVars(self, aspect): return aspect in _DICT_SET(self.aspectProgs.keys()) or aspect in self._locationAspectCovered - + def aspectsCovered(self): return _DICT_SET(self.aspectValues.keys()) | _DICT_SET(self.aspectProgs.keys()) | self._locationAspectCovered - + # provide model table's aspect model to compile() method of ModelFormulaRules @property def aspectModel(self): @@ -899,9 +899,9 @@ def aspectModel(self): if isinstance(obj,ModelTable): return obj.aspectModel return None - + ''' - @property + @property def primaryItemQname(self): return self.evaluateRule(self.modelXbrl.rendrCntx, Aspect.CONCEPT) @@ -915,17 +915,17 @@ def explicitDims(self): if mem: # may be none if dimension was omitted dimMemSet.add( (dim, mem) ) return dimMemSet - + @property def instant(self): periodType = self.evaluateRule(self.modelXbrl.rendrCntx, Aspect.PERIOD_TYPE) if periodType == "forever": return None - return self.evaluateRule(self.modelXbrl.rendrCntx, + return self.evaluateRule(self.modelXbrl.rendrCntx, {"instant": Aspect.INSTANT, "duration": Aspect.END}[periodType]) ''' - + def cardinalityAndDepth(self, structuralNode, **kwargs): if self.aspectValues or self.aspectProgs or structuralNode.header(evaluate=False) is not None: return (1, 1) @@ -935,7 +935,7 @@ def cardinalityAndDepth(self, structuralNode, **kwargs): class ModelRuleSet(ModelConstraintSet, ModelFormulaResource): def init(self, modelDocument): super(ModelRuleSet, self).init(modelDocument) - + @property def tagName(self): # can't call it tag because that would hide ElementBase.tag return self.get("tag") @@ -943,15 +943,15 @@ def tagName(self): # can't call it tag because that would hide ElementBase.tag class ModelRuleDefinitionNode(ModelConstraintSet, ModelClosedDefinitionNode): def init(self, modelDocument): super(ModelRuleDefinitionNode, self).init(modelDocument) - + @property def merge(self): return self.get("merge") - + @property def isMerged(self): return self.merge == "true" - + @property def constraintSets(self): try: @@ -960,13 +960,13 @@ def constraintSets(self): self._constraintSets = dict((ruleSet.tagName, ruleSet) for ruleSet in XmlUtil.children(self, self.namespaceURI, "ruleSet")) if self.aspectsCovered(): # any local rule? - self._constraintSets[None] = self + self._constraintSets[None] = self return self._constraintSets - + def hasAspect(self, structuralNode, aspect): return any(constraintSet._hasAspect(structuralNode, aspect) for constraintSet in self.constraintSets.values()) - + @property def aspectsInTaggedConstraintSet(self): try: @@ -979,13 +979,13 @@ def aspectsInTaggedConstraintSet(self): if aspect != Aspect.DIMENSIONS: self._aspectsInTaggedConstraintSet.add(aspect) return self._aspectsInTaggedConstraintSet - + def compile(self): super(ModelRuleDefinitionNode, self).compile() for constraintSet in self.constraintSets.values(): if constraintSet != self: # compile nested constraint sets constraintSet.compile() - + @property def propertyView(self): return ((("id", self.id), @@ -993,7 +993,7 @@ def propertyView(self): ("merge", self.merge), ("definition", self.definitionNodeView)) + self.definitionLabelsView) - + def __repr__(self): return ("modelRuleDefinitionNode[{0}]{1})".format(self.objectId(),self.propertyView)) @@ -1001,27 +1001,27 @@ def __repr__(self): class ModelTupleDefinitionNode(ModelRuleDefinitionNode): def init(self, modelDocument): super(ModelTupleDefinitionNode, self).init(modelDocument) - + @property - def descendantArcroles(self): + def descendantArcroles(self): return (XbrlConst.tableTupleContent201301, XbrlConst.tableTupleContent2011, XbrlConst.tableDefinitionNodeMessage201301, XbrlConst.tableAxisMessage2011) - + @property def contentRelationships(self): return self.modelXbrl.relationshipSet((XbrlConst.tableTupleContent201301, XbrlConst.tableTupleContent2011)).fromModelObject(self) - + def hasAspect(self, structuralNode, aspect, inherit=None): return aspect == Aspect.LOCATION # non-location aspects aren't leaked to ordinate for Tuple or self.hasRule(aspect) - + def aspectValue(self, xpCtx, aspect, inherit=None): return self.evaluateRule(xpCtx, aspect) - + def aspectsCovered(self): return {Aspect.LOCATION} # tuple's aspects don't leak to ordinates - + def tupleAspectsCovered(self): return _DICT_SET(self.aspectValues.keys()) | _DICT_SET(self.aspectProgs.keys()) | {Aspect.LOCATION} - + def filteredFacts(self, xpCtx, facts): aspects = self.aspectsCovered() axisAspectValues = dict((aspect, self.tupleAspectsCovered(aspect)) @@ -1035,7 +1035,7 @@ def filteredFacts(self, xpCtx, facts): class ModelCompositionDefinitionNode(ModelClosedDefinitionNode): def init(self, modelDocument): super(ModelCompositionDefinitionNode, self).init(modelDocument) - + @property def abstract(self): # always abstract, no filters, no data return 'true' @@ -1043,7 +1043,7 @@ def abstract(self): # always abstract, no filters, no data class ModelRelationshipDefinitionNode(ModelClosedDefinitionNode): def init(self, modelDocument): super(ModelRelationshipDefinitionNode, self).init(modelDocument) - + def aspectsCovered(self): return {Aspect.CONCEPT} @@ -1051,14 +1051,14 @@ def aspectsCovered(self): def conceptQname(self): name = self.getStripped("conceptname") return qname(self, name, noPrefixIsNoNamespace=True) if name else None - + @property def relationshipSourceQname(self): sourceQname = XmlUtil.child(self, (XbrlConst.table, XbrlConst.tableMMDD, XbrlConst.table201305, XbrlConst.table201301, XbrlConst.table2011), "relationshipSource") if sourceQname is not None: return qname( sourceQname, XmlUtil.text(sourceQname) ) return None - + @property def linkrole(self): return XmlUtil.childText(self, (XbrlConst.table, XbrlConst.tableMMDD, XbrlConst.table201305, XbrlConst.table201301, XbrlConst.table2011), "linkrole") @@ -1068,7 +1068,7 @@ def axis(self): a = XmlUtil.childText(self, (XbrlConst.table, XbrlConst.tableMMDD, XbrlConst.table201305, XbrlConst.table201301, XbrlConst.table2011), ("axis", "formulaAxis")) if not a: a = 'descendant' # would be an XML error return a - + @property def isOrSelfAxis(self): return self.axis.endswith('-or-self') @@ -1078,7 +1078,7 @@ def generations(self): try: return _INT( XmlUtil.childText(self, (XbrlConst.table, XbrlConst.tableMMDD, XbrlConst.table201305, XbrlConst.table201301, XbrlConst.table2011), "generations") ) except (TypeError, ValueError): - if self.axis in ('sibling', 'child', 'parent'): + if self.axis in ('sibling', 'child', 'parent'): return 1 return 0 @@ -1105,7 +1105,7 @@ def compile(self): self.axisExpressionProg = XPathParser.parse(self, self.axisExpression, self, "axisExpressionProg", Trace.VARIABLE) self.generationsExpressionProg = XPathParser.parse(self, self.generationsExpression, self, "generationsExpressionProg", Trace.VARIABLE) super(ModelRelationshipDefinitionNode, self).compile() - + def variableRefs(self, progs=[], varRefSet=None): if self.relationshipSourceQname and self.relationshipSourceQname != XbrlConst.qnXfiRoot: if varRefSet is None: varRefSet = set() @@ -1120,26 +1120,26 @@ def evalRrelationshipSourceQname(self, xpCtx, fact=None): if self.relationshipSourceQname: return self.relationshipSourceQname return xpCtx.evaluateAtomicValue(self.relationshipSourceQnameExpressionProg, 'xs:QName', fact) - + def evalLinkrole(self, xpCtx, fact=None): if self.linkrole: return self.linkrole return xpCtx.evaluateAtomicValue(self.linkroleExpressionProg, 'xs:anyURI', fact) - + def evalAxis(self, xpCtx, fact=None): if self.axis: return self.axis return xpCtx.evaluateAtomicValue(self.axisExpressionProg, 'xs:token', fact) - + def evalGenerations(self, xpCtx, fact=None): if self.generations: return self.generations return xpCtx.evaluateAtomicValue(self.generationsExpressionProg, 'xs:integer', fact) def cardinalityAndDepth(self, structuralNode, **kwargs): - return self.lenDepth(self.relationships(structuralNode, **kwargs), + return self.lenDepth(self.relationships(structuralNode, **kwargs), self.axis.endswith('-or-self')) - + def lenDepth(self, nestedRelationships, includeSelf): l = 0 d = 1 @@ -1157,21 +1157,21 @@ def lenDepth(self, nestedRelationships, includeSelf): if includeSelf: d += 1 return (l, d) - + @property def propertyView(self): return ((("id", self.id), ("abstract", self.abstract), ("definition", self.definitionNodeView)) + self.definitionLabelsView) - + def __repr__(self): return ("modelRelationshipDefinitionNode[{0}]{1})".format(self.objectId(),self.propertyView)) - + class ModelConceptRelationshipDefinitionNode(ModelRelationshipDefinitionNode): def init(self, modelDocument): super(ModelConceptRelationshipDefinitionNode, self).init(modelDocument) - + def hasAspect(self, structuralNode, aspect): return aspect == Aspect.CONCEPT @@ -1192,7 +1192,7 @@ def linkQname(self): if linknameElt is not None: return qname( linknameElt, XmlUtil.text(linknameElt) ) return None - + def compile(self): if not hasattr(self, "arcroleExpressionProg"): @@ -1200,7 +1200,7 @@ def compile(self): self.linkQnameExpressionProg = XPathParser.parse(self, self.linkQnameExpression, self, "linkQnameExpressionProg", Trace.VARIABLE) self.arcQnameExpressionProg = XPathParser.parse(self, self.arcQnameExpression, self, "arcQnameExpressionProg", Trace.VARIABLE) super(ModelConceptRelationshipDefinitionNode, self).compile() - + def variableRefs(self, progs=[], varRefSet=None): return super(ModelConceptRelationshipDefinitionNode, self).variableRefs( [p for p in (self.arcroleExpressionProg, @@ -1211,12 +1211,12 @@ def evalArcrole(self, xpCtx, fact=None): if self.arcrole: return self.arcrole return xpCtx.evaluateAtomicValue(self.arcroleExpressionProg, 'xs:anyURI', fact) - + def evalLinkQname(self, xpCtx, fact=None): if self.linkQname: return self.linkQname return xpCtx.evaluateAtomicValue(self.linkQnameExpressionProg, 'xs:QName', fact) - + def evalArcQname(self, xpCtx, fact=None): if self.arcQname: return self.arcQname @@ -1233,7 +1233,7 @@ def linkQnameExpression(self): @property def arcQnameExpression(self): return XmlUtil.childText(self, (XbrlConst.table, XbrlConst.tableMMDD, XbrlConst.table201305, XbrlConst.table201301, XbrlConst.table2011), "arcnameExpression") - + def coveredAspect(self, ordCntx=None): return Aspect.CONCEPT @@ -1247,8 +1247,8 @@ def relationships(self, structuralNode, **kwargs): arcQname = (structuralNode.evaluate(self, self.evalArcQname) or () ) self._axis = (structuralNode.evaluate(self, self.evalAxis) or () ) self._generations = (structuralNode.evaluate(self, self.evalGenerations) or () ) - return concept_relationships(self.modelXbrl.rendrCntx, - None, + return concept_relationships(self.modelXbrl.rendrCntx, + None, (self._sourceQname, linkrole, arcrole, @@ -1257,19 +1257,19 @@ def relationships(self, structuralNode, **kwargs): linkQname, arcQname), True) # return nested lists representing concept tree nesting - + class ModelDimensionRelationshipDefinitionNode(ModelRelationshipDefinitionNode): def init(self, modelDocument): super(ModelDimensionRelationshipDefinitionNode, self).init(modelDocument) - + def hasAspect(self, structuralNode, aspect): return aspect == self.coveredAspect(structuralNode) or aspect == Aspect.DIMENSIONS - + def aspectValue(self, xpCtx, aspect, inherit=None): if aspect == Aspect.DIMENSIONS: return (self.coveredAspect(xpCtx), ) return None - + def aspectsCovered(self): return {self.dimensionQname} @@ -1288,7 +1288,7 @@ def compile(self): if not hasattr(self, "dimensionQnameExpressionProg"): self.dimensionQnameExpressionProg = XPathParser.parse(self, self.dimensionQnameExpression, self, "dimensionQnameExpressionProg", Trace.VARIABLE) super(ModelDimensionRelationshipDefinitionNode, self).compile() - + def variableRefs(self, progs=[], varRefSet=None): return super(ModelDimensionRelationshipDefinitionNode, self).variableRefs(self.dimensionQnameExpressionProg, varRefSet) @@ -1296,17 +1296,17 @@ def evalDimensionQname(self, xpCtx, fact=None): if self.dimensionQname: return self.dimensionQname return xpCtx.evaluateAtomicValue(self.dimensionQnameExpressionProg, 'xs:QName', fact) - + def coveredAspect(self, structuralNode=None): try: return self._coveredAspect except AttributeError: self._coveredAspect = self.dimRelationships(structuralNode, getDimQname=True) return self._coveredAspect - + def relationships(self, structuralNode, **kwargs): return self.dimRelationships(structuralNode, getMembers=True) - + def dimRelationships(self, structuralNode, getMembers=False, getDimQname=False): self._dimensionQname = structuralNode.evaluate(self, self.evalDimensionQname) self._sourceQname = structuralNode.evaluate(self, self.evalRrelationshipSourceQname) or XbrlConst.qnXfiRoot @@ -1327,8 +1327,8 @@ def dimRelationships(self, structuralNode, getMembers=False, getDimQname=False): if sourceConcept is None: sourceConcept = dimConcept if getMembers: - return concept_relationships(self.modelXbrl.rendrCntx, - None, + return concept_relationships(self.modelXbrl.rendrCntx, + None, (self._sourceQname, linkrole, "XBRL-dimensions", # all dimensions arcroles @@ -1340,7 +1340,7 @@ def dimRelationships(self, structuralNode, getMembers=False, getDimQname=False): # look back from member to a dimension return self.stepDimRel(sourceConcept, linkrole) return None - + def stepDimRel(self, stepConcept, linkrole): if stepConcept.isDimensionItem: return stepConcept.qname @@ -1350,11 +1350,11 @@ def stepDimRel(self, stepConcept, linkrole): if dim: return dim return None - -coveredAspectToken = {"concept": Aspect.CONCEPT, - "entity-identifier": Aspect.VALUE, - "period-start": Aspect.START, "period-end": Aspect.END, - "period-instant": Aspect.INSTANT, "period-instant-end": Aspect.INSTANT_END, + +coveredAspectToken = {"concept": Aspect.CONCEPT, + "entity-identifier": Aspect.VALUE, + "period-start": Aspect.START, "period-end": Aspect.END, + "period-instant": Aspect.INSTANT, "period-instant-end": Aspect.INSTANT_END, "unit": Aspect.UNIT} class ModelOpenDefinitionNode(ModelDefinitionNode): @@ -1365,15 +1365,15 @@ def init(self, modelDocument): class ModelSelectionDefinitionNode(ModelOpenDefinitionNode): def init(self, modelDocument): super(ModelSelectionDefinitionNode, self).init(modelDocument) - + @property - def descendantArcroles(self): + def descendantArcroles(self): return (XbrlConst.tableDefinitionNodeMessage201301, XbrlConst.tableAxisMessage2011, XbrlConst.tableDefinitionNodeSelectionMessage201301, XbrlConst.tableAxisSelectionMessage2011) - + def clear(self): XPathParser.clearNamedProg(self, "selectProg") super(ModelSelectionDefinitionNode, self).clear() - + def coveredAspect(self, structuralNode=None): try: return self._coveredAspect @@ -1384,31 +1384,31 @@ def coveredAspect(self, structuralNode=None): else: # must be a qname self._coveredAspect = qname(self, coveredAspect) return self._coveredAspect - + def aspectsCovered(self): return {self.coveredAspect} def hasAspect(self, structuralNode, aspect): return aspect == self.coveredAspect() or (isinstance(self._coveredAspect,QName) and aspect == Aspect.DIMENSIONS) - + @property def select(self): return self.get("select") - + def compile(self): if not hasattr(self, "selectProg"): self.selectProg = XPathParser.parse(self, self.select, self, "select", Trace.PARAMETER) super(ModelSelectionDefinitionNode, self).compile() - + def variableRefs(self, progs=[], varRefSet=None): return super(ModelSelectionDefinitionNode, self).variableRefs(self.selectProg, varRefSet) - + def evaluate(self, xpCtx, typeQname=None): if typeQname: return xpCtx.evaluateAtomicValue(self.selectProg, typeQname) else: return xpCtx.flattenSequence(xpCtx.evaluate(self.selectProg, None)) - + aspectNodeAspectCovered = {"conceptAspect": Aspect.CONCEPT, "unitAspect": Aspect.UNIT, "entityIdentifierAspect": Aspect.ENTITY_IDENTIFIER, @@ -1417,13 +1417,13 @@ def evaluate(self, xpCtx, typeQname=None): class ModelFilterDefinitionNode(ModelOpenDefinitionNode): def init(self, modelDocument): super(ModelFilterDefinitionNode, self).init(modelDocument) - + @property - def descendantArcroles(self): + def descendantArcroles(self): return (XbrlConst.tableAspectNodeFilter, XbrlConst.tableAspectNodeFilterMMDD, XbrlConst.tableAspectNodeFilter201305, XbrlConst.tableFilterNodeFilter2011, XbrlConst.tableAxisFilter2011,XbrlConst.tableAxisFilter201205, XbrlConst.tableDefinitionNodeMessage201301, XbrlConst.tableAxisMessage2011, XbrlConst.tableDefinitionNodeSubtree, XbrlConst.tableDefinitionNodeSubtreeMMDD, XbrlConst.tableDefinitionNodeSubtree201305, XbrlConst.tableDefinitionNodeSubtree201301, XbrlConst.tableAxisSubtree2011, XbrlConst.tableDefinitionNodeMessage201301, XbrlConst.tableAxisMessage2011) - - + + @property def filterRelationships(self): try: @@ -1437,10 +1437,10 @@ def filterRelationships(self): rels.append(rel) self._filterRelationships = rels return rels - + def hasAspect(self, structuralNode, aspect): return aspect in self.aspectsCovered() - + def aspectsCovered(self, varBinding=None): try: return self._aspectsCovered @@ -1458,7 +1458,7 @@ def aspectsCovered(self, varBinding=None): self._dimensionsCovered.add(dimQname) self.includeUnreportedValue = aspectElt.get("includeUnreportedValue") in ("true", "1") else: - self._aspectsCovered.add(aspectNodeAspectCovered[aspectElt.localName]) + self._aspectsCovered.add(aspectNodeAspectCovered[aspectElt.localName]) else: # filter node (prior to 2013-05-17) for rel in self.filterRelationships: @@ -1475,9 +1475,9 @@ def aspectValue(self, xpCtx, aspect, inherit=None): return self._dimensionsCovered # does not apply to filter, value can only come from a bound fact return None - + def filteredFactsPartitions(self, xpCtx, facts): - filteredFacts = formulaEvaluatorFilterFacts(xpCtx, VariableBinding(xpCtx), + filteredFacts = formulaEvaluatorFilterFacts(xpCtx, VariableBinding(xpCtx), facts, self.filterRelationships, None) if not self.includeUnreportedValue: # remove unreported falue @@ -1490,7 +1490,7 @@ def filteredFactsPartitions(self, xpCtx, facts): else: reportedAspectFacts = filteredFacts return factsPartitions(xpCtx, reportedAspectFacts, self.aspectsCovered()) - + @property def propertyView(self): return ((("id", self.id), @@ -1499,8 +1499,8 @@ def propertyView(self): if aspect != Aspect.DIMENSIONS)), ("definition", self.definitionNodeView)) + self.definitionLabelsView) - - + + from arelle.ModelObjectFactory import elementSubstitutionModelClass elementSubstitutionModelClass.update(( diff --git a/arelle/ModelRssItem.py b/arelle/ModelRssItem.py index 4c170c1c02..40b990bb6e 100644 --- a/arelle/ModelRssItem.py +++ b/arelle/ModelRssItem.py @@ -24,13 +24,13 @@ "alertValiditionError": False, "latestPubDate": None, } - + # Note: if adding to this list keep DialogRssWatch in sync class ModelRssItem(ModelObject): def init(self, modelDocument): super(ModelRssItem, self).init(modelDocument) try: - if (self.modelXbrl.modelManager.rssWatchOptions.latestPubDate and + if (self.modelXbrl.modelManager.rssWatchOptions.latestPubDate and self.pubDate <= self.modelXbrl.modelManager.rssWatchOptions.latestPubDate): self.status = _("tested") else: @@ -55,27 +55,27 @@ def init(self, modelDocument): self.edgrType = edgrPrefix + "type" self.edgrUrl = edgrPrefix + "url" - + @property def cikNumber(self): return XmlUtil.text(XmlUtil.descendant(self, self.edgr, "cikNumber")) - + @property def accessionNumber(self): return XmlUtil.text(XmlUtil.descendant(self, self.edgr, "accessionNumber")) - + @property def fileNumber(self): return XmlUtil.text(XmlUtil.descendant(self, self.edgr, "fileNumber")) - + @property def companyName(self): return XmlUtil.text(XmlUtil.descendant(self, self.edgr, "companyName")) - + @property def formType(self): return XmlUtil.text(XmlUtil.descendant(self, self.edgr, "formType")) - + @property def pubDate(self): try: @@ -92,22 +92,22 @@ def filingDate(self): import datetime self._filingDate = None date = XmlUtil.text(XmlUtil.descendant(self, self.edgr, "filingDate")) - d = date.split("/") + d = date.split("/") if d and len(d) == 3: self._filingDate = datetime.date(_INT(d[2]),_INT(d[0]),_INT(d[1])) return self._filingDate - + @property def period(self): per = XmlUtil.text(XmlUtil.descendant(self, self.edgr, "period")) if per and len(per) == 8: return "{0}-{1}-{2}".format(per[0:4],per[4:6],per[6:8]) return None - + @property def assignedSic(self): return XmlUtil.text(XmlUtil.descendant(self, self.edgr, "assignedSic")) - + @property def acceptanceDatetime(self): try: @@ -119,14 +119,14 @@ def acceptanceDatetime(self): if date and len(date) == 14: self._acceptanceDatetime = datetime.datetime(_INT(date[0:4]),_INT(date[4:6]),_INT(date[6:8]),_INT(date[8:10]),_INT(date[10:12]),_INT(date[12:14])) return self._acceptanceDatetime - + @property def fiscalYearEnd(self): yrEnd = XmlUtil.text(XmlUtil.descendant(self, self.edgr, "fiscalYearEnd")) if yrEnd and len(yrEnd) == 4: return "{0}-{1}".format(yrEnd[0:2],yrEnd[2:4]) return None - + @property def htmlUrl(self): # main filing document htmlDocElt = XmlUtil.descendant(self, self.edgr, "xbrlFile", attrName=self.edgrSequence, attrValue="1") @@ -145,11 +145,11 @@ def url(self): self._url = instDocElt.get(self.edgrUrl) break return self._url - + @property def enclosureUrl(self): return XmlUtil.childAttr(self, None, "enclosure", "url") - + @property def zippedUrl(self): enclosure = XmlUtil.childAttr(self, None, "enclosure", "url") @@ -160,8 +160,8 @@ def zippedUrl(self): return enclosure + sep + file else: # no zipped enclosure, just use unzipped file return self.url - - + + @property def htmURLs(self): try: @@ -172,7 +172,7 @@ def htmURLs(self): for instDocElt in XmlUtil.descendants(self, self.edgr, "xbrlFile") if instDocElt.get(self.edgrFile).endswith(".htm")] return self._htmURLs - + @property def primaryDocumentURL(self): try: @@ -185,7 +185,7 @@ def primaryDocumentURL(self): self._primaryDocumentURL = instDocElt.get(self.edgrUrl) break return self._primaryDocumentURL - + def setResults(self, modelXbrl): self.results = [] self.assertionUnsuccessful = False @@ -202,7 +202,7 @@ def setResults(self, modelXbrl): self.results.append(error) self.status = "fail" # error code self.results.sort() - + @property def propertyView(self): return (("CIK", self.cikNumber), diff --git a/arelle/ModelRssObject.py b/arelle/ModelRssObject.py index 54c8d41b00..cd6cf6cf70 100644 --- a/arelle/ModelRssObject.py +++ b/arelle/ModelRssObject.py @@ -11,17 +11,17 @@ class ModelRssObject(ModelDocument): """ .. class:: ModelRssObject(type=ModelDocument.Type.RSSFEED, uri=None, filepath=None, xmlDocument=None) - + ModelRssObject is a specialization of ModelDocument for RSS Feeds. - + (for parameters and inherited attributes, please see ModelDocument) """ - def __init__(self, modelXbrl, - type=Type.RSSFEED, + def __init__(self, modelXbrl, + type=Type.RSSFEED, uri=None, filepath=None, xmlDocument=None): super(ModelRssObject, self).__init__(modelXbrl, type, uri, filepath, xmlDocument) self.rssItems = [] - + def rssFeedDiscover(self, rootElement): """Initiates discovery of RSS feed """ @@ -29,4 +29,4 @@ def rssFeedDiscover(self, rootElement): self.xmlRootElement = rootElement for itemElt in XmlUtil.descendants(rootElement, None, "item"): self.rssItems.append(itemElt) - + diff --git a/arelle/ModelTestcaseObject.py b/arelle/ModelTestcaseObject.py index 04f7750d3e..d51106b012 100644 --- a/arelle/ModelTestcaseObject.py +++ b/arelle/ModelTestcaseObject.py @@ -18,7 +18,7 @@ def testcaseVariationsByTarget(testcaseVariations): for modelTestcaseVariation in testcaseVariations: modelTestcaseVariation.errors = None # Errors accumulate over multiple ixdsTargets for same variation ixdsTargets = [instElt.get("target") - for resultElt in modelTestcaseVariation.iterdescendants("{*}result") + for resultElt in modelTestcaseVariation.iterdescendants("{*}result") for instElt in resultElt.iterdescendants("{*}instance")] if ixdsTargets: # track status and actual (error codes, counts) across all targets @@ -43,7 +43,7 @@ def init(self, modelDocument): self.actual = [] self.assertions = None self.ixdsTarget = None - + @property def id(self): # if there is a real ID, use it @@ -96,7 +96,7 @@ def reference(self): if functRegistryRefElt is not None: # function registry return functRegistryRefElt.get("{http://www.w3.org/1999/xlink}href") return None - + @property def readMeFirstUris(self): try: @@ -110,15 +110,15 @@ def readMeFirstUris(self): if self.localName == "testGroup": #w3c testcase instanceTestElement = XmlUtil.descendant(self, None, "instanceTest") if instanceTestElement is not None: # take instance first - self._readMeFirstUris.append(XmlUtil.descendantAttr(instanceTestElement, None, - "instanceDocument", + self._readMeFirstUris.append(XmlUtil.descendantAttr(instanceTestElement, None, + "instanceDocument", "{http://www.w3.org/1999/xlink}href")) self.readMeFirstElements.append(instanceTestElement) else: schemaTestElement = XmlUtil.descendant(self, None, "schemaTest") if schemaTestElement is not None: - self._readMeFirstUris.append(XmlUtil.descendantAttr(schemaTestElement, None, - "schemaDocument", + self._readMeFirstUris.append(XmlUtil.descendantAttr(schemaTestElement, None, + "schemaDocument", "{http://www.w3.org/1999/xlink}href")) self.readMeFirstElements.append(schemaTestElement) elif self.localName == "test-case": #xpath testcase @@ -150,7 +150,7 @@ def readMeFirstUris(self): self._readMeFirstUris.append(os.path.join(self.modelXbrl.modelManager.cntlr.configDir, "empty-instance.xml")) self.readMeFirstElements.append(None) return self._readMeFirstUris - + @property def dataUris(self): try: @@ -161,22 +161,22 @@ def dataUris(self): for elt in XmlUtil.descendants(dataElement, None, ("xsd", "schema", "linkbase", "instance")): self._dataUris["schema" if elt.localName == "xsd" else elt.localName].append(elt.textValue.strip()) return self._dataUris - + @property def parameters(self): try: return self._parameters except AttributeError: self._parameters = dict([ - (ModelValue.qname(paramElt, paramElt.get("name")), # prefix-less parameter names take default namespace of element - (ModelValue.qname(paramElt, paramElt.get("datatype")),paramElt.get("value"))) + (ModelValue.qname(paramElt, paramElt.get("name")), # prefix-less parameter names take default namespace of element + (ModelValue.qname(paramElt, paramElt.get("datatype")),paramElt.get("value"))) for paramElt in XmlUtil.descendants(self, self.namespaceURI, "parameter")]) return self._parameters - + @property def resultIsVersioningReport(self): return XmlUtil.descendant(XmlUtil.descendant(self, None, "result"), None, "versioningReport") is not None - + @property def versioningReportUri(self): return XmlUtil.text(XmlUtil.descendant(self, None, "versioningReport")) @@ -184,20 +184,20 @@ def versioningReportUri(self): @property def resultIsXbrlInstance(self): return XmlUtil.descendant(XmlUtil.descendant(self, None, "result"), None, "instance") is not None - + @property def resultXbrlInstanceUri(self): for pluginXbrlMethod in pluginClassMethods("ModelTestcaseVariation.ResultXbrlInstanceUri"): resultInstanceUri = pluginXbrlMethod(self) if resultInstanceUri is not None: return resultInstanceUri or None # (empty string returns None) - + for resultElt in self.iterdescendants("{*}result"): for instElt in resultElt.iterdescendants("{*}instance"): if (instElt.get("target") or "") == (self.ixdsTarget or ""): # match null and emptyString return XmlUtil.text(instElt) return None - + @property def resultIsInfoset(self): if self.modelDocument.outpath: @@ -205,15 +205,15 @@ def resultIsInfoset(self): if result is not None: return XmlUtil.child(result, None, "file") is not None or XmlUtil.text(result).endswith(".xml") return False - + @property def resultInfosetUri(self): result = XmlUtil.descendant(self, None, "result") if result is not None: child = XmlUtil.child(result, None, "file") return os.path.join(self.modelDocument.outpath, XmlUtil.text(child if child is not None else result)) - return None - + return None + @property def resultIsTable(self): result = XmlUtil.descendant(self, None, "result") @@ -222,7 +222,7 @@ def resultIsTable(self): if child is not None and XmlUtil.text(child).endswith(".xml"): return True return False - + @property def resultTableUri(self): result = XmlUtil.descendant(self, None, "result") @@ -230,12 +230,12 @@ def resultTableUri(self): child = XmlUtil.child(result, None, "table") if child is not None: return os.path.join(self.modelDocument.outpath, XmlUtil.text(child)) - return None - + return None + @property def resultIsTaxonomyPackage(self): return any(e.localName for e in XmlUtil.descendants(self,None,TXMY_PKG_SRC_ELTS)) - + @property def variationDiscoversDTS(self): return any(e.localName != "taxonomyPackage" # find any nonTP element (instance, schema, linkbase, etc) @@ -251,7 +251,7 @@ def cfcnCall(self): self._cfcnCall = None if self.localName == "test-case": #xpath testcase queryElement = XmlUtil.descendant(self, None, "query") - if queryElement is not None: + if queryElement is not None: filepath = (self.modelDocument.filepathdir + "/" + "Queries/XQuery/" + self.get("FilePath") + queryElement.get("name") + '.xq') if os.sep != "/": filepath = filepath.replace("/", os.sep) @@ -267,7 +267,7 @@ def cfcnCall(self): if name and input: self._cfcnCall = ("{0}('{1}')".format(name, input.replace("'","''")), self) return self._cfcnCall - + @property def cfcnTest(self): # tuple of (expression, element holding the expression) @@ -277,7 +277,7 @@ def cfcnTest(self): self._cfcnTest = None if self.localName == "test-case": #xpath testcase outputFileElement = XmlUtil.descendant(self, None, "output-file") - if outputFileElement is not None and outputFileElement.get("compare") == "Text": + if outputFileElement is not None and outputFileElement.get("compare") == "Text": filepath = (self.modelDocument.filepathdir + "/" + "ExpectedTestResults/" + self.get("FilePath") + outputFileElement.text) if os.sep != "/": filepath = filepath.replace("/", os.sep) @@ -292,7 +292,7 @@ def cfcnTest(self): if output: self._cfcnTest = ("$result eq '{0}'".format(output.replace("'","''")), self) return self._cfcnTest - + @property def expected(self): for pluginXbrlMethod in pluginClassMethods("ModelTestcaseVariation.ExpectedResult"): @@ -348,9 +348,9 @@ def expected(self): return asserTests elif self.get("result"): return self.get("result") - + return None - + @property def expectedCount(self): for pluginXbrlMethod in pluginClassMethods("ModelTestcaseVariation.ExpectedCount"): @@ -358,8 +358,8 @@ def expectedCount(self): if _count is not None: # ignore plug in if not a plug-in-recognized test case return _count return None - - + + @property def severityLevel(self): for pluginXbrlMethod in pluginClassMethods("ModelTestcaseVariation.ExpectedSeverity"): @@ -381,7 +381,7 @@ def blockedMessageCodes(self): if ignoredCodes: blockedCodesRegex = "|".join(".*" + c.stringValue for c in ignoredCodes) return blockedCodesRegex - + @property def expectedVersioningReport(self): XmlUtil.text(XmlUtil.text(XmlUtil.descendant(XmlUtil.descendant(self, None, "result"), None, "versioningReport"))) @@ -417,6 +417,6 @@ def propertyView(self): ("expected", self.expected) if self.expected else (), ("actual", " ".join(str(i) for i in self.actual) if len(self.actual) > 0 else ())] + \ assertions - + def __repr__(self): return ("modelTestcaseVariation[{0}]{1})".format(self.objectId(),self.propertyView)) diff --git a/arelle/ModelValue.py b/arelle/ModelValue.py index facc72c699..5c992affb9 100644 --- a/arelle/ModelValue.py +++ b/arelle/ModelValue.py @@ -83,7 +83,7 @@ def qname(value, name=None, noPrefixIsNoNamespace=False, castException=None, pre if not namespaceURI and prefix == 'xml': namespaceURI = "http://www.w3.org/XML/1998/namespace" if not namespaceURI: - if prefix: + if prefix: if prefixException: raise prefixException return None # error, prefix not found namespaceURI = None # cancel namespace if it is a zero length string @@ -159,7 +159,7 @@ def expandedName(self) -> str: return '{0}#{1}'.format(self.namespaceURI or "", self.localName) def __repr__(self): - return self.__str__() + return self.__str__() def __str__(self): if self.prefix: @@ -183,7 +183,7 @@ def __bool__(self): from arelle.ModelObject import ModelObject - + def anyURI(value): return AnyURI(value) @@ -207,7 +207,7 @@ def tzinfo(tz): return datetime.timezone(datetime.timedelta(0)) else: return datetime.timezone(datetime.timedelta(hours=int(tz[0:3]), minutes=int(tz[0]+tz[4:6]))) - + def tzinfoStr(dt): tz = str(dt.tzinfo or "") if tz.startswith("UTC"): @@ -224,14 +224,14 @@ def dateTime(value, time=None, addOneDay=None, type=None, castException=None): elif isinstance(value, DateTime) and not addOneDay and (value.dateOnly == (type == DATE)): return value # no change needed for cast or conversion elif isinstance(value, datetime.datetime): - if type == DATE: + if type == DATE: dateOnly = True - elif type == DATETIME: + elif type == DATETIME: dateOnly = False - else: + else: dateOnly = isinstance(value, DateTime) and value.dateOnly if addOneDay and not dateOnly: - addOneDay = False + addOneDay = False return DateTime(value.year, value.month, value.day, value.hour, value.minute, value.second, value.microsecond, tzinfo=value.tzinfo, dateOnly=dateOnly, addOneDay=addOneDay) elif isinstance(value, datetime.date): return DateTime(value.year, value.month, value.day,dateOnly=True,addOneDay=addOneDay) @@ -245,7 +245,7 @@ def dateTime(value, time=None, addOneDay=None, type=None, castException=None): raise castException("lexical pattern mismatch") return None if 6 <= match.lastindex <= 8: - if type == DATE: + if type == DATE: if castException: raise castException("date-only object has too many fields or contains time") return None @@ -255,11 +255,11 @@ def dateTime(value, time=None, addOneDay=None, type=None, castException=None): ms = int(fracSec[1:7].ljust(6,'0')) result = DateTime(int(match.group(1)),int(match.group(2)),int(match.group(3)),int(match.group(4)),int(match.group(5)),int(match.group(6)),ms,tzinfo(match.group(8)), dateOnly=False) else: - if type == DATE or type == DATEUNION: + if type == DATE or type == DATEUNION: dateOnly = True - elif type == DATETIME: + elif type == DATETIME: dateOnly = False - else: + else: dateOnly = False result = DateTime(int(match.group(9)),int(match.group(10)),int(match.group(11)),tzinfo=tzinfo(match.group(12)),dateOnly=dateOnly,addOneDay=addOneDay) return result @@ -283,7 +283,7 @@ def __new__(cls, y, m, d, hr=0, min=0, sec=0, microsec=0, tzinfo=None, dateOnly= if min != 0 or sec != 0 or microsec != 0: raise ValueError("hour 24 must have 0 mins and secs.") hr = 0 d += 1 - if addOneDay: + if addOneDay: d += 1 if d > lastDay: d -= lastDay; m += 1 if m > 12: m = 1; y += 1 @@ -325,7 +325,7 @@ def __sub__(self, other): else: if isinstance(other, Time): other = dayTimeDuration(other) return DateTime(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond, dt.tzinfo, self.dateOnly) - + def dateUnionEqual(dateUnion1, dateUnion2, instantEndDate=False): if isinstance(dateUnion1,DateTime): if instantEndDate and dateUnion1.dateOnly: @@ -338,7 +338,7 @@ def dateUnionEqual(dateUnion1, dateUnion2, instantEndDate=False): elif isinstance(dateUnion2,datetime.date): dateUnion2 = dateTime(dateUnion2, addOneDay=instantEndDate) return dateUnion1 == dateUnion2 - + def dateunionDate(datetimeValue, subtractOneDay=False): isDate = (hasattr(datetimeValue,'dateOnly') and datetimeValue.dateOnly) or not hasattr(datetimeValue, 'hour') d = datetimeValue @@ -351,17 +351,17 @@ def yearMonthDuration(value): if hasDay or hasHr or hasMin or hasSec: raise ValueError sign = -1 if minus else 1 return YearMonthDuration(sign * int(yrs if yrs else 0), sign * int(mos if mos else 0)) - + class YearMonthDuration(): def __init__(self, years, months): self.years = years self.months = months def __repr__(self): - return self.__str__() + return self.__str__() def __str__(self): return "P{0}Y{1}M".format(self.years, self.months) - + def dayTimeDuration(value): if isinstance(value,Time): return DayTimeDuration(1 if value.hour24 else 0, value.hour, value.minute, value.second) @@ -371,7 +371,7 @@ def dayTimeDuration(value): if hasYr or hasMo: raise ValueError sign = -1 if minus else 1 return DayTimeDuration(sign * int(days if days else 0), sign * int(hrs if hrs else 0), sign * int(mins if mins else 0), sign * int(secs if secs else 0)) - + class DayTimeDuration(datetime.timedelta): def __new__(cls, days, hours, minutes, seconds): dyTm = datetime.timedelta.__new__(cls,days,hours,minutes,seconds) @@ -401,11 +401,11 @@ def dayHrsMinsSecs(self): seconds -= minutes * 60 return (days, hours, minutes, seconds) def __repr__(self): - return self.__str__() + return self.__str__() def __str__(self): x = self.dayHrsMinsSecs() return "P{0}DT{1}H{2}M{3}S".format(x[0], x[1], x[2], x[3]) - + def yearMonthDayTimeDuration(value, value2=None): if isinstance(value, datetime.datetime) and isinstance(value, datetime.datetime): years = value2.year - value.year @@ -435,7 +435,7 @@ def yearMonthDayTimeDuration(value, value2=None): sign = -1 if minus else 1 # TBD implement return YearMonthDayTimeDuration(sign * int(yrs if yrs else 0), sign * int(mos if mos else 0)) - + class YearMonthDayTimeDuration(): def __init__(self, years, months, days, hours, minutes, seconds): self.years = years @@ -446,7 +446,7 @@ def __init__(self, years, months, days, hours, minutes, seconds): self.seconds = seconds def __repr__(self): - return self.__str__() + return self.__str__() def __str__(self): per = [] if self.years: per.append("{}Y".format(self.years)) @@ -459,7 +459,7 @@ def __str__(self): if not per: return "PT0S" return "P" + ''.join(per) - + def time(value, castException=None): if value == "MinTime": return Time(time.min) @@ -491,13 +491,13 @@ def __new__(cls, hour=0, minute=0, second=0, microsecond=0, tzinfo=None): time = datetime.time.__new__(cls, hour, minute, second, microsecond, tzinfo) time.hour24 = hour24 return time - + class gYearMonth(): def __init__(self, year, month): self.year = int(year) # may be negative self.month = int(month) def __repr__(self): - return self.__str__() + return self.__str__() def __str__(self): return "{0:0{2}}-{1:02}".format(self.year, self.month, 5 if self.year < 0 else 4) # may be negative def __eq__(self,other): @@ -514,14 +514,14 @@ def __ge__(self,other): return type(other) == gYearMonth and ((self.year >= other.year) or (self.year == other.year and self.month >= other.month)) def __bool__(self): return self.year != 0 or self.month != 0 - - + + class gMonthDay(): def __init__(self, month, day): self.month = int(month) self.day = int(day) def __repr__(self): - return self.__str__() + return self.__str__() def __str__(self): return "--{0:02}-{1:02}".format(self.month, self.day) def __eq__(self,other): @@ -538,12 +538,12 @@ def __ge__(self,other): return type(other) == gMonthDay and ((self.month >= other.month) or (self.month == other.month and self.day >= other.day)) def __bool__(self): return self.month != 0 or self.day != 0 - + class gYear(): def __init__(self, year): self.year = int(year) # may be negative def __repr__(self): - return self.__str__() + return self.__str__() def __str__(self): return "{0:0{1}}".format(self.year, 5 if self.year < 0 else 4) # may be negative def __eq__(self,other): @@ -560,12 +560,12 @@ def __ge__(self,other): return type(other) == gYear and self.year >= other.year def __bool__(self): return self.year != 0 != 0 - + class gMonth(): def __init__(self, month): self.month = int(month) def __repr__(self): - return self.__str__() + return self.__str__() def __str__(self): return "--{0:02}".format(self.month) def __eq__(self,other): @@ -582,12 +582,12 @@ def __ge__(self,other): return type(other) == gMonth and self.month >= other.month def __bool__(self): return self.month != 0 - + class gDay(): def __init__(self, day): self.day = int(day) def __repr__(self): - return self.__str__() + return self.__str__() def __str__(self): return "---{0:02}".format(self.day) def __eq__(self,other): @@ -604,7 +604,7 @@ def __ge__(self,other): return type(other) == gDay and self.day >= other.day def __bool__(self): return self.day != 0 - + isoDurationPattern = re.compile( r"^(?P[+-])?" r"P(?!\b)" @@ -617,7 +617,7 @@ def __bool__(self): r"(?P[0-9]+([,.][0-9]+)?S)?)?$") def isoDuration(value): - """(str) -- Text of contained (inner) text nodes except for any whose localName + """(str) -- Text of contained (inner) text nodes except for any whose localName starts with URI, for label and reference parts displaying purposes. (Footnotes, which return serialized html content of footnote.) """ @@ -644,11 +644,11 @@ def isoDuration(value): weeks=groups["weeks"], negate=(groups["sign"]=='-'), sourceValue=value) # preserve source lexical value for str() value - + DAYSPERMONTH = Decimal("30.4375") # see: https://www.ibm.com/support/knowledgecenter/SSLVMB_20.0.0/com.ibm.spss.statistics.help/alg_adp_date-time_handling.htm """ - XPath 2.0 does not define arithmetic operations on xs:duration + XPath 2.0 does not define arithmetic operations on xs:duration for arithmetic one must use xs:yearMonthDuration or xs:dayTimeDuration instead Arelle provides value comparisons for xs:duration even though they are not "totally ordered" per XPath 1.0 @@ -657,10 +657,10 @@ def isoDuration(value): class IsoDuration(isodate.Duration): """ .. class:: IsoDuration(modelDocument) - + Implements custom class for xs:duration to work for typed dimensions Uses DAYSPERMONTH approximation of ordering days/months (only for typed dimensions, not XPath). - + For formula purposes this object is not used because xpath 1.0 requires use of xs:yearMonthDuration or xs:dayTimeDuration which are totally ordered instead. """ @@ -713,11 +713,11 @@ def viewText(self, labelrole=None, lang=None): return super(IsoDuration, self).__str__() # textual words form of duration def __str__(self): return self.sourceValue - + class InvalidValue(str): def __new__(cls, value): return str.__new__(cls, value) INVALIDixVALUE = InvalidValue("(ixTransformValueError)") - \ No newline at end of file + diff --git a/arelle/ModelVersObject.py b/arelle/ModelVersObject.py index 89958222f1..b0b8af729c 100644 --- a/arelle/ModelVersObject.py +++ b/arelle/ModelVersObject.py @@ -23,7 +23,7 @@ def relateConceptMdlObjs(modelDocument, fromConceptMdlObjs, toConceptMdlObjs): class ModelVersObject(ModelObject): def init(self, modelDocument): super(ModelVersObject, self).init(modelDocument) - + @property def name(self): return self.localName @@ -35,7 +35,7 @@ class ModelAssignment(ModelVersObject): def init(self, modelDocument): super(ModelAssignment, self).init(modelDocument) self.modelDocument.assignments[self.id] = self - + @property def categoryqname(self): for child in self.iterchildren(): @@ -61,11 +61,11 @@ def init(self, modelDocument): actionKey = self.id if self.id else "action{0:05}".format(len(self.modelDocument.actions) + 1) self.modelDocument.actions[actionKey] = self self.events = [] - + @property def assignmentRefs(self): return XmlUtil.childrenAttrs(self, XbrlConst.ver, "assignmentRef", "ref") - + @property def propertyView(self): return (("id", self.id), @@ -75,11 +75,11 @@ def propertyView(self): class ModelUriMapped(ModelVersObject): def init(self, modelDocument): super(ModelUriMapped, self).init(modelDocument) - + @property def fromURI(self): return XmlUtil.childAttr(self, XbrlConst.ver, "fromURI", "value") - + @property def toURI(self): return XmlUtil.childAttr(self, XbrlConst.ver, "toURI", "value") @@ -88,10 +88,10 @@ def toURI(self): def propertyView(self): return (("fromURI", self.fromURI), ("toURI", self.toURI)) - + def viewText(self, labelrole=None, lang=None): return "{0} -> {1}".format(self.fromURI, self.toURI) - + class ModelNamespaceRename(ModelUriMapped): def init(self, modelDocument): super(ModelNamespaceRename, self).init(modelDocument) @@ -99,7 +99,7 @@ def init(self, modelDocument): self.modelDocument.namespaceRenameFromURI[self.fromURI] = self.toURI self.modelDocument.namespaceRenameTo[self.toURI] = self self.modelDocument.namespaceRenameToURI[self.toURI] = self.fromURI - + class ModelRoleChange(ModelUriMapped): def init(self, modelDocument): super(ModelRoleChange, self).init(modelDocument) @@ -108,19 +108,19 @@ def init(self, modelDocument): class ModelConceptChange(ModelVersObject): def init(self, modelDocument): super(ModelConceptChange, self).init(modelDocument) - + @property def actionId(self): return XmlUtil.parentId(self, XbrlConst.ver, "action") - + @property def physical(self): return self.get("physical") or "true" # default="true" - + @property def isPhysical(self): return self.physical == "true" - + @property def fromConceptQname(self): fromConcept = XmlUtil.child(self, None, "fromConcept") # can be vercu or vercb, schema validation will assure right elements @@ -128,7 +128,7 @@ def fromConceptQname(self): return qname(fromConcept, fromConcept.get("name")) else: return None - + @property def toConceptQname(self): toConcept = XmlUtil.child(self, None, "toConcept") @@ -136,17 +136,17 @@ def toConceptQname(self): return qname(toConcept, toConcept.get("name")) else: return None - + @property def fromConcept(self): # for href: return self.resolveUri(uri=self.fromConceptValue, dtsModelXbrl=self.modelDocument.fromDTS) return self.modelDocument.fromDTS.qnameConcepts.get(self.fromConceptQname) - + @property def toConcept(self): # return self.resolveUri(uri=self.toConceptValue, dtsModelXbrl=self.modelDocument.toDTS) return self.modelDocument.toDTS.qnameConcepts.get(self.toConceptQname) - + def setConceptEquivalence(self): if self.fromConcept is not None and self.toConcept is not None: self.modelDocument.equivalentConcepts[self.fromConcept.qname] = self.toConcept.qname @@ -190,49 +190,49 @@ def viewText(self, labelrole=XbrlConst.conceptNameLabelRole, lang=None): return str(toConceptQname) else: return "(invalidConceptReference)" - + class ModelConceptUseChange(ModelConceptChange): def init(self, modelDocument): super(ModelConceptUseChange, self).init(modelDocument) self.modelDocument.conceptUseChanges.append(self) - - + + class ModelConceptDetailsChange(ModelConceptChange): def init(self, modelDocument): super(ModelConceptDetailsChange, self).init(modelDocument) self.modelDocument.conceptDetailsChanges.append(self) - + def customAttributeQname(self, eventName): custAttrElt = XmlUtil.child(self, None, eventName) # will be vercd or verce if custAttrElt is not None and custAttrElt.get("name"): return qname(custAttrElt, custAttrElt.get("name")) return None - + @property def fromCustomAttributeQname(self): return self.customAttributeQname("fromCustomAttribute") - + @property def toCustomAttributeQname(self): return self.customAttributeQname("toCustomAttribute") - + @property def fromResourceValue(self): return XmlUtil.childAttr(self, None, "fromResource", "value") - + @property def toResourceValue(self): return XmlUtil.childAttr(self, None, "toResource", "value") - + @property def fromResource(self): return self.resolveUri(uri=self.fromResourceValue, dtsModelXbrl=self.modelDocument.fromDTS) - + @property def toResource(self): return self.resolveUri(uri=self.toResourceValue, dtsModelXbrl=self.modelDocument.toDTS) - + @property def propertyView(self): fromConcept = self.fromConcept @@ -254,7 +254,7 @@ def init(self, modelDocument): self.modelDocument.relationshipSetChanges.append(self) self.fromRelationshipSet = None self.toRelationshipSet = None - + @property def propertyView(self): return (("event", self.localName), @@ -264,15 +264,15 @@ class ModelRelationshipSet(ModelVersObject): def init(self, modelDocument): super(ModelRelationshipSet, self).init(modelDocument) self.relationships = [] - + @property def isFromDTS(self): return self.localName == "fromRelationshipSet" - + @property def dts(self): return self.modelDocument.fromDTS if self.isFromDTS else self.modelDocument.toDTS - + @property def relationshipSetElement(self): return XmlUtil.child(self, XbrlConst.verrels, "relationshipSet") @@ -283,28 +283,28 @@ def link(self): return self.prefixedNameQname(self.relationshipSetElement.get("link")) else: return None - + @property def linkrole(self): if self.relationshipSetElement.get("linkrole"): return self.relationshipSetElement.get("linkrole") else: return None - + @property def arc(self): if self.relationshipSetElement.get("arc"): return self.prefixedNameQname(self.relationshipSetElement.get("arc")) else: return None - + @property def arcrole(self): if self.relationshipSetElement.get("arcrole"): return self.relationshipSetElement.get("arcrole") else: return None - + @property def propertyView(self): return self.modelRelationshipSetEvent.propertyView + \ @@ -318,39 +318,39 @@ def propertyView(self): class ModelRelationships(ModelVersObject): def init(self, modelDocument): super(ModelRelationships, self).init(modelDocument) - + @property def fromName(self): if self.get("fromName"): return self.prefixedNameQname(self.get("fromName")) else: return None - + @property def toName(self): return self.prefixedNameQname(self.get("toName")) if self.get("toName") else None - + @property def fromConcept(self): # for href: return self.resolveUri(uri=self.fromConceptValue, dtsModelXbrl=self.modelDocument.fromDTS) return self.modelRelationshipSet.dts.qnameConcepts.get(self.fromName) if self.fromName else None - + @property def toConcept(self): # return self.resolveUri(uri=self.toConceptValue, dtsModelXbrl=self.modelDocument.toDTS) return self.modelRelationshipSet.dts.qnameConcepts.get(self.toName) if self.toName else None - + @property def axis(self): if self.get("axis"): return self.get("axis") else: return None - + @property def isFromDTS(self): return self.modelRelationshipSet.isFromDTS - + @property def fromRelationships(self): mdlRel = self.modelRelationshipSet @@ -358,7 +358,7 @@ def fromRelationships(self): if relSet: return relSet.fromModelObject(self.fromConcept) return None - + @property def fromRelationship(self): fromRelationships = self.fromRelationships @@ -372,7 +372,7 @@ def fromRelationship(self): return None else: # return first (any) relationship return fromRelationships[0] - + @property def propertyView(self): return self.modelRelationshipSet.propertyView + \ @@ -387,7 +387,7 @@ def init(self, modelDocument): self.modelDocument.instanceAspectChanges.append(self) self.fromAspects = None self.toAspects = None - + @property def propertyView(self): return (("event", self.localName), @@ -397,19 +397,19 @@ class ModelInstanceAspects(ModelVersObject): def init(self, modelDocument): super(ModelInstanceAspects, self).init(modelDocument) self.aspects = [] - + @property def isFromDTS(self): return self.localName == "fromAspects" - + @property def dts(self): return self.modelDocument.fromDTS if self.isFromDTS else self.modelDocument.toDTS - + @property def excluded(self): return self.get("excluded") if self.get("excluded") else None - + @property def propertyView(self): return self.aspectModelEvent.propertyView + \ @@ -424,13 +424,13 @@ def init(self, modelDocument): @property def isFromDTS(self): return self.modelAspects.isFromDTS - + @property def propertyView(self): return self.modelAspects.propertyView + \ (("aspect", self.localName), ) + self.elementAttributesTuple - + class ModelConceptsDimsAspect(ModelInstanceAspect): def init(self, modelDocument): super(ModelConceptsDimsAspect, self).init(modelDocument) @@ -439,12 +439,12 @@ def init(self, modelDocument): @property def conceptName(self): return self.prefixedNameQname(self.get("name")) if self.get("name") else None - + @property def concept(self): # for href: return self.resolveUri(uri=self.fromConceptValue, dtsModelXbrl=self.modelDocument.fromDTS) return self.modelAspects.dts.qnameConcepts.get(self.conceptName) if self.conceptName else None - + @property def sourceDtsObject(self): if self.localName == "explicitDimension": @@ -467,16 +467,16 @@ def init(self, modelDocument): class ModelRelatedConcept(ModelVersObject): def init(self, modelDocument): super(ModelRelatedConcept, self).init(modelDocument) - + @property def conceptName(self): return self.prefixedNameQname(self.get("name")) if self.get("name") else None - + @property def concept(self): # for href: return self.resolveUri(uri=self.fromConceptValue, dtsModelXbrl=self.modelDocument.fromDTS) return self.modelAspect.modelAspects.dts.qnameConcepts.get(self.conceptName) if self.conceptName else None - + @property def sourceDtsObject(self): return self.concept @@ -484,33 +484,33 @@ def sourceDtsObject(self): @property def isFromDTS(self): return self.modelAspect.modelAspects.isFromDTS - + @property def hasNetwork(self): return XmlUtil.hasChild(self, XbrlConst.verdim, "network") - + @property def hasDrsNetwork(self): return XmlUtil.hasChild(self, XbrlConst.verdim, "drsNetwork") - + @property def arcrole(self): return XmlUtil.childAttr(self, XbrlConst.verdim, ("network","drsNetwork"), "arcrole") - + @property def linkrole(self): return XmlUtil.childAttr(self, XbrlConst.verdim, ("network","drsNetwork"), "linkrole") - + @property def arc(self): arc = XmlUtil.childAttr(self, XbrlConst.verdim, ("network","drsNetwork"), "arc") return self.prefixedNameQname(arc) if arc else None - + @property def link(self): link = XmlUtil.childAttr(self, XbrlConst.verdim, ("network","drsNetwork"), "link") return self.prefixedNameQname(link) if link else None - + @property def propertyView(self): return self.modelAspect.propertyView + \ @@ -521,7 +521,7 @@ def propertyView(self): class ModelAspectProperty(ModelVersObject): def init(self, modelDocument): super(ModelRelatedConcept, self).init(modelDocument) - + @property def propertyView(self): return self.modelAspect.propertyView + \ diff --git a/arelle/ModelVersReport.py b/arelle/ModelVersReport.py index a0e58b4b04..bd30966cff 100644 --- a/arelle/ModelVersReport.py +++ b/arelle/ModelVersReport.py @@ -14,7 +14,7 @@ from arelle.FileSource import FileNamedStringIO def create(modelXbrlFromDTS, modelXbrlToDTS): - """Returns a new modelXbrl representing a Version Report object, by creation of its modelXbrl, its ModelVersReport (modelDocument), and diffing the from and to DTSes + """Returns a new modelXbrl representing a Version Report object, by creation of its modelXbrl, its ModelVersReport (modelDocument), and diffing the from and to DTSes :param modelXbrlFromDTS: fromDTS DTS object :type modelXbrlFromDTS: ModelXbrl @@ -34,83 +34,83 @@ def create(modelXbrlFromDTS, modelXbrlToDTS): "{http://www.w3.org/1999/xlink}actuate", "{http://www.w3.org/1999/xlink}show", "{http://www.w3.org/1999/xlink}title", - "{http://www.w3.org/XML/1998/namespace}lang", + "{http://www.w3.org/XML/1998/namespace}lang", "{http://www.w3.org/XML/1998/namespace}space", "id", "use","priority","order" } - + authoritiesEquivalence = { "http://xbrl.iasb.org": "IFRS", "http://xbrl.ifrs.org": "IFRS", "http://xbrl.us": "XBRL-US", "http://fasb.org": "XBRL-US", "http://xbrl.sec.gov": "XBRL-US", } - + dateRemovalPattern = re.compile(r"[/]?(draft-)?(19|20)[0-9][0-9](-[01][0-9](-[0-3][0-9])?)?") numberRemovalPattern = re.compile(r"[/]?[0-9][0-9\.]*") class ModelVersReport(ModelDocument.ModelDocument): """ .. class:: ModelVersReport(type=ModelDocument.Type.VERSIONINGREPORT, uri=None, filepath=None, xmlDocument=None) - + ModelVersReport is a specialization of ModelDocument for Versioning Reports. - + (for parameters and inherited attributes, please see ModelDocument) - + .. attribute:: fromDTS From DTS (modelXbrl object) - + .. attribute:: toDTS - + To DTS (modelXbrl object) - + .. attribute:: assignments Dict by id of ModelAssignment objects - + .. attribute:: actions - + Dict by id of ModelAction objects - + .. attribute:: namespaceRenameFrom - + Dict by fromURI of ModelNamespaceRename objects - + .. attribute:: namespaceRenameTo - + Dict by toURI of ModelNamespaceRename objects - + .. attribute:: roleChanges - + Dict by uri of ModelRoleChange objects - + .. attribute:: conceptUseChanges - + List of ModelConceptUseChange objects - + .. attribute:: conceptDetailsChanges - + List of ModelConceptDetailsChange objects - + .. attribute:: equivalentConcepts - + Dict by qname of equivalent qname - + .. attribute:: relatedConceptsDefaultDict by qname of list of related concept qnames - - + + .. attribute:: relationshipSetChanges List of ModelRelationshipSet objects - + .. attribute:: instanceAspectChanges - + List of ModelInstanceAspectChange objects - + .. attribute:: typedDomainsCorrespond - + Dict by (fromDimConcept,toDimConcept) of bool that is True if corresponding """ - - def __init__(self, modelXbrl, - type=ModelDocument.Type.VERSIONINGREPORT, + + def __init__(self, modelXbrl, + type=ModelDocument.Type.VERSIONINGREPORT, uri=None, filepath=None, xmlDocument=None): super(ModelVersReport, self).__init__(modelXbrl, type, uri, filepath, xmlDocument) self.fromDTS = None @@ -129,21 +129,21 @@ def __init__(self, modelXbrl, self.relationshipSetChanges = [] self.instanceAspectChanges = [] self.typedDomainsCorrespond = {} - + def close(self, *args, **kwargs): - """Closes any views, formula output instances, modelDocument(s), and dereferences all memory used + """Closes any views, formula output instances, modelDocument(s), and dereferences all memory used """ super(ModelVersReport, self).close(*args, **kwargs) - + def versioningReportDiscover(self, rootElement): """Initiates discovery of versioning report - + :param rootElement: lxml root element of versioning report :type rootElement: xml element node """ - + XmlValidate.validate(self.modelXbrl, rootElement) # schema validate - + actionRelatedFromMdlObjs = [] actionRelatedToMdlObjs = [] modelAction = None @@ -166,10 +166,10 @@ def versioningReportDiscover(self, rootElement): if len(schemaRefElts) == 1 and schemaRefElts[0].get("{http://www.w3.org/1999/xlink}href") is not None: DTSmodelXbrl = ModelXbrl.load(self.modelXbrl.modelManager, schemaRefElts[0].get("{http://www.w3.org/1999/xlink}href"), - "loading validation report", + "loading validation report", base=self.baseForElement(schemaRefElts[0])) else: # need multi-schemaRefs DTS - DTSmodelXbrl = ModelXbrl.create(self.modelXbrl.modelManager, + DTSmodelXbrl = ModelXbrl.create(self.modelXbrl.modelManager, newDocumentType=ModelDocument.Type.DTSENTRIES, url=self.uri[:-4] + "-" + ln + ".dts", isEntry=True) DTSdoc = DTSmodelXbrl.modelDocument @@ -183,7 +183,7 @@ def versioningReportDiscover(self, rootElement): doc.inDTS = True if DTSmodelXbrl is not None: setattr(self, ln, DTSmodelXbrl) - elif ln in ("namespaceRename", "roleChange"): + elif ln in ("namespaceRename", "roleChange"): if modelAction is not None: modelAction.events.append(modelObject) elif self.fromDTS is None or self.toDTS is None: @@ -199,11 +199,11 @@ def versioningReportDiscover(self, rootElement): elif ln == "conceptAdd": actionRelatedToMdlObjs.append(modelObject) elif ns in (XbrlConst.vercd, XbrlConst.verce): - if ln in {"conceptIDChange", "conceptTypeChange", "conceptSubstitutionGroupChange", + if ln in {"conceptIDChange", "conceptTypeChange", "conceptSubstitutionGroupChange", "conceptDefaultChange", "conceptNillableChange", "conceptAbstractChange", "conceptBlockChange", "conceptFixedChange", "conceptFinalChange", "conceptPeriodTypeChange", "conceptBalanceChange", - "conceptAttributeAdd", "conceptAttributeDelete", "conceptAttributeChange", + "conceptAttributeAdd", "conceptAttributeDelete", "conceptAttributeChange", "tupleContentModelChange", "conceptLabelAdd", "conceptLabelDelete", "conceptLabelChange", "conceptReferenceAdd", "conceptReferenceDelete", "conceptReferenceChange"}: @@ -226,7 +226,7 @@ def versioningReportDiscover(self, rootElement): if modelRelationshipSet is not None: modelRelationshipSet.relationships.append(modelObject) modelObject.modelRelationshipSet = modelRelationshipSet - + elif ns in (XbrlConst.verdim, XbrlConst.veria): if ln in ("aspectModelChange", "aspectModelAdd", "aspectModelDelete"): if modelAction is not None: @@ -241,7 +241,7 @@ def versioningReportDiscover(self, rootElement): else: aspectModelEvent.toAspects = modelObject modelObject.aspectModelEvent = aspectModelEvent - elif ln in ("concepts", "explicitDimension", "typedDimension", "segment", "scenario", + elif ln in ("concepts", "explicitDimension", "typedDimension", "segment", "scenario", "entityIdentifier", "period", "location", "unit"): modelAspect = modelObject if modelAspects is not None: @@ -276,7 +276,7 @@ def versioningReportDiscover(self, rootElement): self.modelXbrl.modelManager.addToLog("discovery: {0} error {1}".format( os.path.basename(self.uri), err)) - + def entryURIs(self, DTS): if DTS.modelDocument: if DTS.modelDocument.type == ModelDocument.Type.DTSENTRIES: @@ -284,11 +284,11 @@ def entryURIs(self, DTS): else: return [DTS.uri] return [] - + def diffDTSes(self, reportOutput, fromDTS, toDTS, assignment="technical", schemaDir=None): - """Initiates diffing of fromDTS and toDTS, populating the ModelVersReport object, and saving the + """Initiates diffing of fromDTS and toDTS, populating the ModelVersReport object, and saving the versioning report file). - + :param versReporFile: file name to save the versioning report :type versReporFile: str :param fromDTS: first modelXbrl's (DTSes) to be diffed @@ -314,27 +314,27 @@ def diffDTSes(self, reportOutput, fromDTS, toDTS, assignment="technical", schema import io file = io.StringIO( #'' - '' # for lxml expandable namespace purposes - '' - '' # for lxml expandable namespace purposes + '' + '' - '' - '{0}' - '{1}' - '<{2}/>' + '>' + '' + '{0}' + '{1}' + '<{2}/>' ''.format( ''.join([''.format(self.relativeUri(uri)) for uri in self.entryURIs(fromDTS)]), @@ -353,7 +353,7 @@ def diffDTSes(self, reportOutput, fromDTS, toDTS, assignment="technical", schema for self.reportElement in self.xmlDocument.iter(tag="{http://xbrl.org/2013/versioning-base}report"): self.xmlRootElement = self.reportElement self.actionNum = 1 - + self.modelXbrl.modelManager.showStatus(_("Comparing namespaces")) self.diffNamespaces() self.modelXbrl.modelManager.showStatus(_("Comparing roles")) @@ -363,13 +363,13 @@ def diffDTSes(self, reportOutput, fromDTS, toDTS, assignment="technical", schema for arcroleUri in (XbrlConst.parentChild, XbrlConst.summationItem, XbrlConst.essenceAlias, XbrlConst.requiresElement, XbrlConst.generalSpecial): self.modelXbrl.modelManager.showStatus(_("Comparing {0} relationships").format(os.path.basename(arcroleUri))) self.diffRelationshipSet(arcroleUri) - + self.modelXbrl.modelManager.showStatus(_("Comparing dimension defaults")) self.diffDimensionDefaults() - + self.modelXbrl.modelManager.showStatus(_("Comparing explicit dimensions")) self.diffDimensions() - + # determine namespaces schemaLocations = [] if schemaDir is not None: @@ -393,9 +393,9 @@ def diffDTSes(self, reportOutput, fromDTS, toDTS, assignment="technical", schema elif prefix == XbrlConst.veria: schemaLocations.append(XbrlConst.veria) schemaLocations.append(schemasRelPath + "versioning-instance-aspects.xsd") - self.reportElement.set("{http://www.w3.org/2001/XMLSchema-instance}schemaLocation", + self.reportElement.set("{http://www.w3.org/2001/XMLSchema-instance}schemaLocation", " ".join(schemaLocations)) - + self.modelXbrl.modelManager.showStatus(_("Checking report file")) self.modelXbrl.modelDocument = self # model document is now established self.versioningReportDiscover(self.reportElement) @@ -410,7 +410,7 @@ def diffDTSes(self, reportOutput, fromDTS, toDTS, assignment="technical", schema self.filepath = versReportFile self.modelXbrl.modelManager.showStatus(_("C report file")) self.modelXbrl.modelManager.showStatus(_("ready"), 2000) - + def diffNamespaces(self): # build fomr and to lists based on namespaces self.namespaceRenameFromURI = {} @@ -423,20 +423,20 @@ def diffNamespaces(self): for toModelDoc in self.toDTS.urlDocs.values(): if toModelDoc.type == ModelDocument.Type.SCHEMA: toNSes.add(toModelDoc.targetNamespace) - self.diffURIs( fromNSes, toNSes, + self.diffURIs( fromNSes, toNSes, "namespaceRename", (self.rolePathlessDatelessMatchPattern, self.roleNumlessMatchPattern, self.roleNoFromToMatchPattern), self.namespaceRenameFromURI, self.namespaceRenameToURI) - + def diffRoles(self): self.roleChangeFromURI = {} self.roleChangeToURI = {} self.diffURIs( set( self.fromDTS.roleTypes.keys() ), set( self.toDTS.roleTypes.keys() ), - "roleChange", + "roleChange", (self.rolePathlessDatelessMatchPattern, self.roleNumlessMatchPattern, self.roleNoFromToMatchPattern), self.roleChangeFromURI, self.roleChangeToURI ) - + def diffURIs(self, fromURIs, toURIs, eventName, matchers, changeFrom, changeTo): # remove common roles from each commonRoles = fromURIs & toURIs @@ -459,7 +459,7 @@ def diffURIs(self, fromURIs, toURIs, eventName, matchers, changeFrom, changeTo): # removed from consideration by next pass on final path segment fromURIs.discard(fromURI) toURIs.discard(toURI) - + def uriNumlessMatchPattern(self, uri): # remove date and numbers from uri (for now, more sophisticated later) return ''.join((c if str.isalpha(c) or c == '/' else '') for c in uri) @@ -495,7 +495,7 @@ def diffConcepts(self): vercu = XbrlConst.vercu vercd = XbrlConst.vercd for fromConceptQname, fromConcept in self.fromDTS.qnameConcepts.items(): - if not fromConcept.isItem and not fromConcept.isTuple: + if not fromConcept.isItem and not fromConcept.isTuple: continue toConceptQname = self.toDTSqname(fromConceptQname) if toConceptQname in self.toDTS.qnameConcepts: @@ -531,7 +531,7 @@ def diffConcepts(self): fromType = fromConcept.type # it is null for xsd:anyType toType = toConcept.type # TBD change to xml comparison with namespaceURI mappings, prefixes ignored - if (fromType is not None and toType is not None and + if (fromType is not None and toType is not None and not XbrlUtil.nodesCorrespond(self.fromDTS, fromType, toType, self.toDTS)): action = self.createConceptEvent(vercd, "vercd:tupleContentModelChange", fromConcept, toConcept, action) # custom attributes in from Concept @@ -541,8 +541,8 @@ def diffConcepts(self): for attrName, attrValue in concept.items(): attrQname = qname(attrName) if (attrName not in ("abstract","block","default","final","fixed","form","id","maxOccurs", - "minOccurs","name","nillable","ref","substitutionGroup","type") and - attrQname.namespaceURI != XbrlConst.xbrli and + "minOccurs","name","nillable","ref","substitutionGroup","type") and + attrQname.namespaceURI != XbrlConst.xbrli and attrQname.namespaceURI != XbrlConst.xbrldt): attrs[concept.prefixedNameQname(attrQname)] = attrValue for attr in fromCustAttrs.keys(): @@ -553,7 +553,7 @@ def diffConcepts(self): for attr in toCustAttrs.keys(): if attr not in fromCustAttrs: action = self.createConceptEvent(vercd, "vercd:conceptAttributeAdd", None, toConcept, action, toCustomAttribute=attr, toValue=toCustAttrs[attr]) - + # labels, references from each concept for event, arcroles in (("vercd:conceptLabel", (XbrlConst.conceptLabel, XbrlConst.elementLabel)), ("vercd:conceptReference", (XbrlConst.conceptReference, XbrlConst.elementReference))): @@ -587,21 +587,21 @@ def diffConcepts(self): if key not in fromResources: action = self.createConceptEvent(vercd, event + "Add", None, toConcept, action, toResource=label, toResourceText=toText) - - # + + # else: self.createConceptEvent(vercu, "vercu:conceptDelete", fromConcept=fromConcept) for toConceptQname, toConcept in self.toDTS.qnameConcepts.items(): if ((toConcept.isItem or toConcept.isTuple) and toConceptQname not in toConceptsMatched): self.createConceptEvent(vercu, "vercu:conceptAdd", toConcept=toConcept) - + def diffRelationshipSet(self, arcrole): # compare ELRs for new/removed fromLinkRoleUris = set() toLinkRoleUris = set() - for dts, linkRoleUris in ((self.fromDTS, fromLinkRoleUris), + for dts, linkRoleUris in ((self.fromDTS, fromLinkRoleUris), (self.toDTS, toLinkRoleUris) ): for linkroleUri in dts.relationshipSet(arcrole).linkRoleUris: linkRoleUris.add(linkroleUri) @@ -697,12 +697,12 @@ def diffRelationships(self, fromConcept, toConcept, fromRelationshipSet, toRelat else: comment = _('fromDTS does not have a corresponding relationship at position {0}').format(i+1) self.createRelationshipSetEvent("relationships", eventParent=self.relSetAddedEvent, fromConcept=toConcept, toConcept=toTgtConcept, comment=comment) - + def diffDimensionDefaults(self): # dimension-defaults are global fromDimDefaults = {} toDimDefaults = {} - for dts, dimDefaults in ((self.fromDTS, fromDimDefaults), + for dts, dimDefaults in ((self.fromDTS, fromDimDefaults), (self.toDTS, toDimDefaults) ): for rel in dts.relationshipSet(XbrlConst.dimensionDefault).modelRelationships: dimDefaults[rel.fromModelObject.qname] = rel.toModelObject.qname @@ -722,7 +722,7 @@ def diffDimensionDefaults(self): explDim = self.createInstanceAspectsEvent("explicitDimension", (('name',fromDimQname),), eventParent=aspectEvent) # removed isDefault per Vers WG e-mail from Richard Ashby 2012-07-11 # self.createInstanceAspectsEvent("member", (('name',fromDefaultQname),('isDefaultMember','true')), eventParent=explDim, comment="dimension default") - + def diffDimensions(self): # DRS rels by (primary item,linkrole) of the has-hypercube relationship fromDRSrels = defaultdict(list) @@ -753,14 +753,14 @@ def diffDimensions(self): relatedConcepts = self.createInstanceAspectsEvent("concepts", eventParent=aspectEvent) priItem = self.createInstanceAspectsEvent("concept", (('name',priItemQname),), eventParent=relatedConcepts) if priItemInheritRels: - priItemNetwork = self.createInstanceAspectsEvent("drsNetwork", + priItemNetwork = self.createInstanceAspectsEvent("drsNetwork", (('linkrole',linkrole), ('arcrole',XbrlConst.domainMember), ('axis', 'descendant-or-self')), eventParent=priItem) for dimRel, isNotAll, isClosed in self.DRSdimRels(dts, priItemDRSrels): dimConcept = dimRel.toModelObject - explDim = self.createInstanceAspectsEvent("typedDimension" if dimConcept.isTypedDimension else "explicitDimension", + explDim = self.createInstanceAspectsEvent("typedDimension" if dimConcept.isTypedDimension else "explicitDimension", (('name',dimConcept.qname),) + \ ((('excluded','true'),) if isNotAll else ()), comment=self.typedDomainElementComment(dimConcept), @@ -838,14 +838,14 @@ def diffDimensions(self): priItemInheritRels = dts.relationshipSet(XbrlConst.domainMember, linkrole).fromModelObject(priItemConcept) relatedConcepts = self.createInstanceAspectsEvent("concepts", eventParent=aspectEvent) priItem = self.createInstanceAspectsEvent("concept", (('name',priQn),), eventParent=relatedConcepts) - if priItemInheritRels: + if priItemInheritRels: self.createInstanceAspectsEvent("drsNetwork", (('linkrole',linkrole), ('arcrole',XbrlConst.domainMember), ('axis', 'descendant-or-self')), eventParent=priItem) dimConcept = dimRel.toModelObject - explDim = self.createInstanceAspectsEvent("typedDimension" if dimConcept.isTypedDimension else "explicitDimension", + explDim = self.createInstanceAspectsEvent("typedDimension" if dimConcept.isTypedDimension else "explicitDimension", (('name',dimConcept.qname),) + \ ((('excluded','true'),) if isNotAll else ()), comment=self.typedDomainElementComment(dimConcept), @@ -871,16 +871,16 @@ def diffDimensions(self): ('arcrole',XbrlConst.domainMember), ('axis', 'DRS-descendant-or-self')) if domHasMemRels else ()), eventParent=explDim) - - + + def DRSdimRels(self, dts, priItemDRSrels): return [(dimRel, hcRel.arcrole == XbrlConst.notAll, hcRel.isClosed) for hcRel in priItemDRSrels for dimRel in dts.relationshipSet(XbrlConst.hypercubeDimension, hcRel.consecutiveLinkrole).fromModelObject(hcRel.toModelObject)] - + def DRSdomRels(self, dts, dimRel): return dts.relationshipSet(XbrlConst.dimensionDomain, dimRel.consecutiveLinkrole).fromModelObject(dimRel.toModelObject) - + def DRSdiff(self, fromConcept, fromLinkrole, toConcept, toLinkrole, arcrole, diffs=None): if diffs is None: diffs = [] fromRels = self.fromDTS.relationshipSet(arcrole, fromLinkrole).fromModelObject(fromConcept) @@ -912,7 +912,7 @@ def DRSdiff(self, fromConcept, fromLinkrole, toConcept, toLinkrole, arcrole, dif if fromRel is None or not isinstance(fromRel.toModelObject, ModelConcept) or fromRel.toModelObject.qname != fromTgtQname: diffs.append((None, toRel, set(), set())) return diffs - + def DRShcDiff(self, fromDTS, fromPriItemDRSrels, toDTS, toPriItemDRSrels): fromHcRels = {} toHcRels = {} @@ -937,7 +937,7 @@ def DRShcDiff(self, fromDTS, fromPriItemDRSrels, toDTS, toPriItemDRSrels): except KeyError: pass # not tracking addition or removal of hypercubes return diffs - + def typedDomainIsDifferent(self, fromDimConcept, toDimConcept): try: return self.typedDomainsCorrespond[fromDimConcept, toDimConcept] @@ -945,7 +945,7 @@ def typedDomainIsDifferent(self, fromDimConcept, toDimConcept): fromTypedDomain = fromDimConcept.typedDomainElement toTypedDomain = toDimConcept.typedDomainElement isCorresponding = (fromTypedDomain is not None and toTypedDomain is not None and - XbrlUtil.sEqual(self.fromDTS, fromTypedDomain, toTypedDomain, + XbrlUtil.sEqual(self.fromDTS, fromTypedDomain, toTypedDomain, excludeIDs=XbrlUtil.ALL_IDs_EXCLUDED, dts2=self.toDTS, ns2ns1Tbl=self.namespaceRenameToURI)) self.typedDomainsCorrespond[fromDimConcept, toDimConcept] = isCorresponding return isCorresponding @@ -977,7 +977,7 @@ def DRSdimsDiff(self, fromDTS, fromPriItemDRSrels, toDTS, toPriItemDRSrels): toDimConcept, toDimRel.consecutiveLinkrole, XbrlConst.dimensionDomain) dimsCorrespond = True - if fromDimConcept.isTypedDimension: + if fromDimConcept.isTypedDimension: if toDimConcept.isExplicitDimension or self.typedDomainIsDifferent(fromDimConcept, toDimConcept): dimsCorrespond = False elif toDimConcept.isTypedDimension: @@ -997,27 +997,27 @@ def DRSdimsDiff(self, fromDTS, fromPriItemDRSrels, toDTS, toPriItemDRSrels): def toDTSqname(self, fromDTSqname): if fromDTSqname is not None and fromDTSqname.namespaceURI in self.namespaceRenameFromURI: - return qname(self.namespaceRenameFromURI[fromDTSqname.namespaceURI], + return qname(self.namespaceRenameFromURI[fromDTSqname.namespaceURI], fromDTSqname.localName) return fromDTSqname - + def fromDTSqname(self, toDTSqname): if toDTSqname and toDTSqname.namespaceURI in self.namespaceRenameToURI: - return qname(self.namespaceRenameToURI[toDTSqname.namespaceURI], + return qname(self.namespaceRenameToURI[toDTSqname.namespaceURI], toDTSqname.localName) return toDTSqname - + def createAction(self): action = XmlUtil.addChild(self.reportElement, XbrlConst.ver, "ver:action", (("id","action{0:05}".format(self.actionNum) ),)) self.actionNum += 1 assignmentRef = XmlUtil.addChild(action, XbrlConst.ver, "ver:assignmentRef", (("ref","versioningTask"),) ) return action - + def createBaseEvent(self, eventName, fromURI, toURI): event = XmlUtil.addChild(self.createAction(), XbrlConst.ver, eventName) XmlUtil.addChild(event, XbrlConst.ver, "ver:fromURI", ("value",fromURI)) XmlUtil.addChild(event, XbrlConst.ver, "ver:toURI", ("value",toURI)) - + def createConceptEvent(self, eventNS, eventName, fromConcept=None, toConcept=None, action=None, fromCustomAttribute=None, toCustomAttribute=None, fromResource=None, toResource=None, fromValue=None, toValue=None, fromResourceText=None, toResourceText=None): if action is None: action = self.createAction() @@ -1056,12 +1056,12 @@ def createConceptEvent(self, eventNS, eventName, fromConcept=None, toConcept=Non XmlUtil.addChild(event, XbrlConst.vercd, "vercd:toCustomAttribute", (("name",attQname),) ) else: # no namespace XmlUtil.addChild(event, XbrlConst.vercd, "vercd:toCustomAttribute", (("name",toCustomAttribute.localName),) ) - + return action - + def conceptHref(self, concept): - return self.relativeUri(concept.modelDocument.uri) + "#" + XmlUtil.elementFragmentIdentifier(concept) - + return self.relativeUri(concept.modelDocument.uri) + "#" + XmlUtil.elementFragmentIdentifier(concept) + def createRelationshipSetEvent(self, eventName, linkrole=None, arcrole=None, fromConcept=None, toConcept=None, axis=None, attrValues=None, comment=None, eventParent=None): if eventParent is None: eventParent = self.createAction() @@ -1082,11 +1082,11 @@ def createRelationshipSetEvent(self, eventName, linkrole=None, arcrole=None, fro if attrValues: XmlUtil.addComment(eventParent, ' ' + ', '.join("{0[0]}='{0[1]}'".format(a) for a in sorted(attrValues)) + ' ') return eventElement - + def createInstanceAspectsEvent(self, eventName, eventAttributes=None, comment=None, eventParent=None): if eventParent is None: eventParent = self.createAction() - eventElement = XmlUtil.addChild(eventParent, XbrlConst.verdim, "verdim:" + eventName, + eventElement = XmlUtil.addChild(eventParent, XbrlConst.verdim, "verdim:" + eventName, attributes=tuple((name, (XmlUtil.addQnameValue(self.reportElement, val) if isinstance(val,QName) else val) ) for name, val in eventAttributes) if eventAttributes else None) diff --git a/arelle/PluginManager.py b/arelle/PluginManager.py index 179d18b3f8..ba2e60cec3 100644 --- a/arelle/PluginManager.py +++ b/arelle/PluginManager.py @@ -532,4 +532,4 @@ def _removePluginModule(moduleInfo): global pluginConfigChanged pluginConfigChanged = True return True - return False # unable to remove \ No newline at end of file + return False # unable to remove diff --git a/arelle/PrototypeDtsObject.py b/arelle/PrototypeDtsObject.py index 740dfb84ce..68f283ad96 100644 --- a/arelle/PrototypeDtsObject.py +++ b/arelle/PrototypeDtsObject.py @@ -10,22 +10,22 @@ def __init__(self, modelDocument, sourceElement=None): self.modelDocument = modelDocument self.sourceElement = sourceElement self.attributes = {} - + @property def sourceline(self): return self.sourceElement.sourceline if self.sourceElement is not None else None def get(self, key, default=None): return self.attributes.get(key, default) - + def itersiblings(self, **kwargs): """Method proxy for itersiblings() of lxml arc element""" return self.sourceElement.itersiblings(**kwargs) if self.sourceElement is not None else () - + def getparent(self): """(_ElementBase) -- Method proxy for getparent() of lxml arc element""" return self.sourceElement.getparent() if self.sourceElement is not None else None - + def iterchildren(self): yield from () # no children @@ -34,7 +34,7 @@ def iterdescendants(self): yield elt for e in elt.iterdescendants(): yield e - + class LinkPrototype(PrototypeObject): # behaves like a ModelLink for relationship prototyping def __init__(self, modelDocument, parent, qname, role, sourceElement=None): super(LinkPrototype, self).__init__(modelDocument, sourceElement) @@ -49,24 +49,24 @@ def __init__(self, modelDocument, parent, qname, role, sourceElement=None): self.text = self.textValue = None self.attributes = {"{http://www.w3.org/1999/xlink}type":"extended"} if role: - self.attributes["{http://www.w3.org/1999/xlink}role"] = role + self.attributes["{http://www.w3.org/1999/xlink}role"] = role self.labeledResources = defaultdict(list) - + def clear(self): self.__dict__.clear() # dereference here, not an lxml object, don't use superclass clear() - + def __iter__(self): return iter(self.childElements) - + def getparent(self): return self._parent - + def iterchildren(self): return iter(self.childElements) - + def __getitem(self, key): return self.attributes[key] - + class LocPrototype(PrototypeObject): def __init__(self, modelDocument, parent, label, locObject, role=None, sourceElement=None): super(LocPrototype, self).__init__(modelDocument, sourceElement) @@ -83,12 +83,12 @@ def __init__(self, modelDocument, parent, label, locObject, role=None, sourceEle if isinstance(locObject,_STR_BASE): # it is an id self.attributes["{http://www.w3.org/1999/xlink}href"] = "#" + locObject if role: - self.attributes["{http://www.w3.org/1999/xlink}role"] = role + self.attributes["{http://www.w3.org/1999/xlink}role"] = role self.locObject = locObject - + def clear(self): self.__dict__.clear() # dereference here, not an lxml object, don't use superclass clear() - + @property def xlinkLabel(self): return self.attributes.get("{http://www.w3.org/1999/xlink}label") @@ -98,16 +98,16 @@ def dereference(self): return self.modelDocument.idObjects.get(self.locObject,None) # id may not exist else: # it's an object pointer return self.locObject - + def getparent(self): return self._parent - + def get(self, key, default=None): return self.attributes.get(key, default) - + def __getitem(self, key): return self.attributes[key] - + class ArcPrototype(PrototypeObject): def __init__(self, modelDocument, parent, qname, fromLabel, toLabel, linkrole, arcrole, order="1", sourceElement=None): super(ArcPrototype, self).__init__(modelDocument, sourceElement) @@ -129,7 +129,7 @@ def __init__(self, modelDocument, parent, qname, fromLabel, toLabel, linkrole, a self.xValid = VALID self.xValue = self.sValue = None self.xAttributes = {} - + @property def orderDecimal(self): return decimal.Decimal(self.order) @@ -140,13 +140,13 @@ def clear(self): @property def arcElement(self): return self.sourceElement if self.sourceElement is not None else None - + def getparent(self): return self._parent - + def get(self, key, default=None): return self.attributes.get(key, default) - + def items(self): return self.attributes.items() @@ -181,23 +181,23 @@ def __init__(self, modelXbrl, uri, base=None, referringElement=None, isEntry=Fal self.referencedNamespaces = set() self.inDTS = False self.xmlRootElement = None - - + + def clear(self): self.__dict__.clear() # dereference here, not an lxml object, don't use superclass clear() - + class PrototypeElementTree(): # equivalent to _ElementTree for parenting root element in non-lxml situations def __init__(self, rootElement): self.rootElement = rootElement - + def getroot(self): return self.rootElement - + def iter(self): yield self.rootElement for e in self.rootElement.iterdescendants(): yield e - + def ixIter(self, childOnly=False): yield self.rootElement if not childOnly: diff --git a/arelle/PrototypeInstanceObject.py b/arelle/PrototypeInstanceObject.py index 1cfae2a674..5428c7e010 100644 --- a/arelle/PrototypeInstanceObject.py +++ b/arelle/PrototypeInstanceObject.py @@ -44,13 +44,13 @@ def clear(self): if self.context is not None: self.context.clear() self.__dict__.clear() # delete local attributes - + def objectId(self): return "_factPrototype_" + str(self.qname) - + def getparent(self): return self.parent - + @property def propertyView(self): dims = self.context.qnameDims @@ -74,7 +74,7 @@ def __init__(self, v, aspectValues): self.qnameDims = {} self.entityIdentifierHash = self.entityIdentifier = None self.isStartEndPeriod = self.isInstantPeriod = self.isForeverPeriod = False - + for aspect, aspectValue in aspectValues.items(): if aspect == Aspect.PERIOD_TYPE: if aspectValue == "forever": @@ -131,7 +131,7 @@ def clear(self): except AttributeError: pass self.__dict__.clear() # delete local attributes - + def dimValue(self, dimQname): """(ModelDimension or QName) -- ModelDimension object if dimension is reported (in either context element), or QName of dimension default if there is a default, otherwise None""" try: @@ -147,13 +147,13 @@ def dimValues(self, contextElement, oppositeContextElement=False): return self.segDimVals if contextElement == "segment" else self.scenDimVals else: return self.scenDimVals if contextElement == "segment" else self.segDimVals - + def nonDimValues(self, contextElement): return [] def isEntityIdentifierEqualTo(self, cntx2): return self.entityIdentifierHash is None or self.entityIdentifierHash == cntx2.entityIdentifierHash - + def isPeriodEqualTo(self, cntx2): if self.isForeverPeriod: return cntx2.isForeverPeriod @@ -167,7 +167,7 @@ def isPeriodEqualTo(self, cntx2): return self.instantDatetime == cntx2.instantDatetime else: return False - + class DimValuePrototype(): def __init__(self, v, dimConcept, dimQname, mem, contextElement): from arelle.ModelValue import QName @@ -197,7 +197,7 @@ def propertyView(self): if self.isExplicit: return (str(self.dimensionQname),str(self.memberQname)) else: - return (str(self.dimensionQname), + return (str(self.dimensionQname), XmlUtil.xmlstring( self.typedMember, stripXmlns=True, prettyPrint=True ) if isinstance(self.typedMember, ModelObject) else "None" ) @@ -214,7 +214,7 @@ def clear(self): self.__dict__.clear() # delete local attributes def isEqualTo(self, unit2): - if unit2 is None or unit2.hash != self.hash: + if unit2 is None or unit2.hash != self.hash: return False return unit2 is self or self.measures == unit2.measures @@ -223,7 +223,7 @@ def propertyView(self): measures = self.measures if measures[1]: return tuple(('mul',m) for m in measures[0]) + \ - tuple(('div',d) for d in measures[1]) + tuple(('div',d) for d in measures[1]) else: return tuple(('measure',m) for m in measures[0]) @@ -238,4 +238,4 @@ def __init__(self, modelManager, uri, *arg, **kwarg): def close(self): self.modelDocument.clear() self.__dict__.clear() # delete local attributes - \ No newline at end of file + diff --git a/arelle/RenderingEvaluator.py b/arelle/RenderingEvaluator.py index 029dc272b9..252b5b4f63 100644 --- a/arelle/RenderingEvaluator.py +++ b/arelle/RenderingEvaluator.py @@ -9,7 +9,7 @@ from arelle.ModelRenderingObject import (CHILD_ROLLUP_FIRST, CHILD_ROLLUP_LAST, ModelDefinitionNode, ModelEuAxisCoord, ModelBreakdown, - ModelClosedDefinitionNode, + ModelClosedDefinitionNode, ModelRuleDefinitionNode, ModelFilterDefinitionNode, ModelDimensionRelationshipDefinitionNode) @@ -21,9 +21,9 @@ def init(modelXbrl): # dimension defaults required in advance of validation from arelle import ValidateXbrlDimensions, ValidateFormula, FormulaEvaluator, ModelDocument ValidateXbrlDimensions.loadDimensionDefaults(modelXbrl) - + hasXbrlTables = False - + # validate table linkbase dimensions for baseSetKey in modelXbrl.baseSets.keys(): arcrole, ELR, linkqname, arcqname = baseSetKey @@ -36,37 +36,37 @@ def init(modelXbrl): if modelXbrl.modelDocument.type == ModelDocument.Type.INSTANCE: instance = None # use instance of the entry pont else: # need dummy instance - instance = ModelDocument.create(modelXbrl, ModelDocument.Type.INSTANCE, - "dummy.xml", # fake URI and fake schemaRef + instance = ModelDocument.create(modelXbrl, ModelDocument.Type.INSTANCE, + "dummy.xml", # fake URI and fake schemaRef ("http://www.xbrl.org/2003/xbrl-instance-2003-12-31.xsd",)) - + if hasXbrlTables: # formula processor is needed for 2011 XBRL tables but not for 2010 Eurofiling tables FormulaEvaluator.init() modelXbrl.rendrCntx = XPathContext.create(modelXbrl, instance) - + modelXbrl.profileStat(None) - + # setup fresh parameters from formula options modelXbrl.parameters = modelXbrl.modelManager.formulaOptions.typedParameters(modelXbrl.prefixedNamespaces) - + # validate parameters and custom function signatures ValidateFormula.validate(modelXbrl, xpathContext=modelXbrl.rendrCntx, parametersOnly=True, statusMsg=_("compiling rendering tables")) - + # deprecated as of 2013-05-17 # check and extract message expressions into compilable programs for msgArcrole in (XbrlConst.tableDefinitionNodeMessage201301, XbrlConst.tableDefinitionNodeSelectionMessage201301, XbrlConst.tableAxisMessage2011, XbrlConst.tableAxisSelectionMessage2011): for msgRel in modelXbrl.relationshipSet(msgArcrole).modelRelationships: ValidateFormula.checkMessageExpressions(modelXbrl, msgRel.toModelObject) - + # compile and validate tables for modelTable in modelXbrl.modelRenderingTables: modelTable.fromInstanceQnames = None # required if referred to by variables scope chaining modelTable.compile() - hasNsWithAspectModel = modelTable.namespaceURI in (XbrlConst.euRend, XbrlConst.table2011, XbrlConst.table201301, XbrlConst.table201305) - + hasNsWithAspectModel = modelTable.namespaceURI in (XbrlConst.euRend, XbrlConst.table2011, XbrlConst.table201301, XbrlConst.table201305) + # check aspectModel (attribute removed 2013-06, now always dimensional) if modelTable.aspectModel not in ("non-dimensional", "dimensional") and hasNsWithAspectModel: modelXbrl.error("xbrlte:unknownAspectModel", @@ -108,7 +108,7 @@ def init(modelXbrl): modelObject=(modelTable,tblParamRel,parameterNames[parameterName]), xlinkLabel=modelTable.xlinkLabel, name=parameterName) else: parameterNames[parameterName] = tblParamRel - + modelXbrl.profileStat(_("compileTables")) def checkBreakdownDefinitionNode(modelXbrl, modelTable, tblAxisRel, tblAxisDisposition, uncoverableAspects, aspectsCovered): @@ -129,7 +129,7 @@ def checkBreakdownDefinitionNode(modelXbrl, modelTable, tblAxisRel, tblAxisDispo if tblAxisDisposition != otherAxisDisposition and aspect != Aspect.DIMENSIONS: modelXbrl.error("xbrlte:aspectClashBetweenBreakdowns", _("%(definitionNode)s %(xlinkLabel)s, aspect %(aspect)s defined on axes of disposition %(axisDisposition)s and %(axisDisposition2)s"), - modelObject=(modelTable, definitionNode, otherDefinitionNode), definitionNode=definitionNode.localName, xlinkLabel=definitionNode.xlinkLabel, + modelObject=(modelTable, definitionNode, otherDefinitionNode), definitionNode=definitionNode.localName, xlinkLabel=definitionNode.xlinkLabel, axisDisposition=tblAxisDisposition, axisDisposition2=otherAxisDisposition, aspect=str(aspect) if isinstance(aspect,QName) else Aspect.label[aspect]) else: @@ -139,13 +139,13 @@ def checkBreakdownDefinitionNode(modelXbrl, modelTable, tblAxisRel, tblAxisDispo if ruleSetChildren: modelXbrl.error("xbrlte:mergedRuleNodeWithTaggedRuleSet", _("Merged %(definitionNode)s %(xlinkLabel)s has tagged rule set(s)"), - modelObject=[modelTable, definitionNode] + ruleSetChildren, + modelObject=[modelTable, definitionNode] + ruleSetChildren, definitionNode=definitionNode.localName, xlinkLabel=definitionNode.xlinkLabel) labelRels = modelXbrl.relationshipSet(XbrlConst.elementLabel).fromModelObject(definitionNode) if labelRels: modelXbrl.error("xbrlte:invalidUseOfLabel", _("Merged %(definitionNode)s %(xlinkLabel)s has label(s)"), - modelObject=[modelTable, definitionNode] + [r.toModelObject for r in labelRels], + modelObject=[modelTable, definitionNode] + [r.toModelObject for r in labelRels], definitionNode=definitionNode.localName, xlinkLabel=definitionNode.xlinkLabel) if not definitionNode.isAbstract: modelXbrl.error("xbrlte:nonAbstractMergedRuleNode", @@ -164,7 +164,7 @@ def checkBreakdownDefinitionNode(modelXbrl, modelTable, tblAxisRel, tblAxisDispo if tag in tagConstraintSets: modelXbrl.error("xbrlte:duplicateTag", _("%(definitionNode)s %(xlinkLabel)s duplicate rule set tags %(tag)s"), - modelObject=(modelTable, definitionNode, tagConstraintSets[tag], ruleSet), + modelObject=(modelTable, definitionNode, tagConstraintSets[tag], ruleSet), definitionNode=definitionNode.localName, xlinkLabel=definitionNode.xlinkLabel, tag=tag) else: tagConstraintSets[tag] = ruleSet @@ -174,10 +174,10 @@ def checkBreakdownDefinitionNode(modelXbrl, modelTable, tblAxisRel, tblAxisDispo elif otherConstraintSet.aspectsCovered() != constraintSet.aspectsCovered(): modelXbrl.error("xbrlte:constraintSetAspectMismatch", _("%(definitionNode)s %(xlinkLabel)s constraint set mismatches between %(tag1)s and %(tag2)s in constraints %(aspects)s"), - modelObject=(modelTable, definitionNode, otherConstraintSet, constraintSet), - definitionNode=definitionNode.localName, xlinkLabel=definitionNode.xlinkLabel, + modelObject=(modelTable, definitionNode, otherConstraintSet, constraintSet), + definitionNode=definitionNode.localName, xlinkLabel=definitionNode.xlinkLabel, tag1=getattr(otherConstraintSet,"tagName","(no tag)"), tag2=getattr(constraintSet, "tagName", "(no tag)"), - aspects=", ".join(aspectStr(aspect) + aspects=", ".join(aspectStr(aspect) for aspect in otherConstraintSet.aspectsCovered() ^ constraintSet.aspectsCovered() if aspect != Aspect.DIMENSIONS)) if isinstance(definitionNode, ModelDimensionRelationshipDefinitionNode): @@ -199,9 +199,9 @@ def checkBreakdownDefinitionNode(modelXbrl, modelTable, tblAxisRel, tblAxisDispo modelXbrl.error("xbrlte:invalidDimensionQNameOnAspectNode", _("Aspect node %(xlinkLabel)s dimensional aspect %(dimension)s is not a dimension"), modelObject=(modelTable,definitionNode), xlinkLabel=definitionNode.xlinkLabel, dimension=aspect) - + if not definitionNodeHasChild: - if (definitionNode.namespaceURI in ("http://www.eurofiling.info/2010/rendering", "http://xbrl.org/2011/table") + if (definitionNode.namespaceURI in ("http://www.eurofiling.info/2010/rendering", "http://xbrl.org/2011/table") and not hasCoveredAspect): modelXbrl.error("xbrlte:aspectValueNotDefinedByOrdinate", _("%(definitionNode)s %(xlinkLabel)s does not define an aspect"), @@ -223,7 +223,7 @@ def checkBreakdownLeafNodeAspects(modelXbrl, modelTable, tblAxisRel, parentAspec for axisSubtreeRel in modelXbrl.relationshipSet((XbrlConst.tableBreakdownTree, XbrlConst.tableBreakdownTreeMMDD, XbrlConst.tableBreakdownTree201305, XbrlConst.tableDefinitionNodeSubtree, XbrlConst.tableDefinitionNodeSubtreeMMDD, XbrlConst.tableDefinitionNodeSubtree201305, XbrlConst.tableDefinitionNodeSubtree201301, XbrlConst.tableAxisSubtree2011)).fromModelObject(definitionNode): checkBreakdownLeafNodeAspects(modelXbrl, modelTable, axisSubtreeRel, aspectsCovered, breakdownAspects) definitionNodeHasChild = True - + if not definitionNode.isAbstract and not isinstance(definitionNode, ModelBreakdown): # this is a leaf node missingAspects = set(aspect for aspect in breakdownAspects @@ -234,4 +234,4 @@ def checkBreakdownLeafNodeAspects(modelXbrl, modelTable, tblAxisRel, parentAspec _("%(definitionNode)s %(xlinkLabel)s does not define an aspect for %(aspect)s"), modelObject=(modelTable,definitionNode), xlinkLabel=definitionNode.xlinkLabel, definitionNode=definitionNode.localName, aspect=', '.join(aspectStr(aspect) for aspect in missingAspects)) - \ No newline at end of file + diff --git a/arelle/RenderingResolver.py b/arelle/RenderingResolver.py index c50274ebdf..45116313a3 100644 --- a/arelle/RenderingResolver.py +++ b/arelle/RenderingResolver.py @@ -35,7 +35,7 @@ def __repr__(self): def resolveAxesStructure(view, viewTblELR): if isinstance(viewTblELR, (ModelEuTable, ModelTable)): # called with a modelTable instead of an ELR - + # find an ELR for this table object table = viewTblELR for rel in view.modelXbrl.relationshipSet((XbrlConst.tableBreakdown, XbrlConst.tableBreakdownMMDD, XbrlConst.tableBreakdown201305, XbrlConst.tableBreakdown201301, XbrlConst.tableAxis2011)).fromModelObject(table): @@ -45,7 +45,7 @@ def resolveAxesStructure(view, viewTblELR): view.modelXbrl.relationshipSet((XbrlConst.tableBreakdown, XbrlConst.tableBreakdownMMDD, XbrlConst.tableBreakdown201305, XbrlConst.tableBreakdown201301, XbrlConst.tableAxis2011), rel.linkrole)) # no relationships from table found return (None, None, None, None) - + # called with an ELR or list of ELRs tblAxisRelSet = view.modelXbrl.relationshipSet(XbrlConst.euTableAxis, viewTblELR) if len(tblAxisRelSet.modelRelationships) > 0: @@ -56,19 +56,19 @@ def resolveAxesStructure(view, viewTblELR): if tblAxisRelSet is None or len(tblAxisRelSet.modelRelationships) == 0: view.modelXbrl.modelManager.addToLog(_("no table relationships for {0}").format(viewTblELR)) return (None, None, None, None) - + # table name modelRoleTypes = view.modelXbrl.roleTypes.get(viewTblELR) if modelRoleTypes is not None and len(modelRoleTypes) > 0: view.roledefinition = modelRoleTypes[0].definition if view.roledefinition is None or view.roledefinition == "": - view.roledefinition = os.path.basename(viewTblELR) + view.roledefinition = os.path.basename(viewTblELR) try: for table in tblAxisRelSet.rootConcepts: return resolveTableAxesStructure(view, table, tblAxisRelSet) except ResolutionException as ex: - view.modelXbrl.error(ex.code, ex.message, exc_info=True, **ex.kwargs); - + view.modelXbrl.error(ex.code, ex.message, exc_info=True, **ex.kwargs); + return (None, None, None, None) def resolveTableAxesStructure(view, table, tblAxisRelSet): @@ -95,7 +95,7 @@ def resolveTableAxesStructure(view, table, tblAxisRelSet): view.modelTable = table view.rendrCntx = table.renderingXPathContext - + xTopStructuralNode = yTopStructuralNode = zTopStructuralNode = None # must be cartesian product of top level relationships tblAxisRels = tblAxisRelSet.fromModelObject(table) @@ -106,12 +106,12 @@ def resolveTableAxesStructure(view, table, tblAxisRelSet): for tblAxisRel in tblAxisRels: definitionNode = tblAxisRel.toModelObject addBreakdownNode(view, tblAxisRel.axisDisposition, definitionNode) - + # do z's first to set variables needed by x and y axes expressions for disposition in ("z", "x", "y"): for i, tblAxisRel in enumerate(tblAxisRels): definitionNode = tblAxisRel.toModelObject - if (tblAxisRel.axisDisposition == disposition and + if (tblAxisRel.axisDisposition == disposition and isinstance(definitionNode, (ModelEuAxisCoord, ModelBreakdown, ModelDefinitionNode))): if disposition == "x" and xTopStructuralNode is None: xTopStructuralNode = StructuralNode(None, definitionNode, definitionNode, view.zmostOrdCntx, tableNode=table, rendrCntx=view.rendrCntx) @@ -140,7 +140,7 @@ def resolveTableAxesStructure(view, table, tblAxisRelSet): #addBreakdownNode(view, disposition, definitionNode) expandDefinition(view, zTopStructuralNode, definitionNode, definitionNode, 1, disposition, facts, i, tblAxisRels) break - ''' + ''' def jsonDefaultEncoder(obj): if isinstance(obj, StructuralNode): return {'1StructNode': str(obj), @@ -149,22 +149,22 @@ def jsonDefaultEncoder(obj): '3Label': obj.header() or obj.xlinkLabel, '4ChildNodes': obj.childStructuralNodes} raise TypeError("Type {} is not supported for json output".format(type(obj).__name__)) - + with io.open(r"c:\temp\test.json", 'wt') as fh: - json.dump({"x":xTopStructuralNode, "y":yTopStructuralNode, "z":zTopStructuralNode}, + json.dump({"x":xTopStructuralNode, "y":yTopStructuralNode, "z":zTopStructuralNode}, fh, sort_keys=True, - ensure_ascii=False, - indent=2, + ensure_ascii=False, + indent=2, default=jsonDefaultEncoder) ''' - + view.colHdrTopRow = view.zAxisRows + 1 # need rest if combobox used (2 if view.zAxisRows else 1) for i in range(view.rowHdrCols): if view.rowNonAbstractHdrSpanMin[i]: lastRowMinWidth = view.rowNonAbstractHdrSpanMin[i] - sum(view.rowHdrColWidth[i] for j in range(i, view.rowHdrCols - 1)) if lastRowMinWidth > view.rowHdrColWidth[view.rowHdrCols - 1]: - view.rowHdrColWidth[view.rowHdrCols - 1] = lastRowMinWidth + view.rowHdrColWidth[view.rowHdrCols - 1] = lastRowMinWidth #view.rowHdrColWidth = (60,60,60,60,60,60,60,60,60,60,60,60,60,60) # use as wraplength for all row hdr name columns 200 + fixed indent and abstract mins (not incl last name col) view.rowHdrWrapLength = 200 + sum(view.rowHdrColWidth[:view.rowHdrCols + 1]) @@ -176,7 +176,7 @@ def jsonDefaultEncoder(obj): # view.gridView.rowconfigure(i) #for i in range(view.dataFirstCol + view.dataCols): # view.gridView.columnconfigure(i) - + # organize hdrNonStdRoles so code (if any) is after documentation (if any) for hdrNonStdRoles in (view.colHdrNonStdRoles, view.rowHdrNonStdRoles): iCodeRole = -1 @@ -221,12 +221,12 @@ def childContainsOpenNodes(childStructuralNode): def expandDefinition(view, structuralNode, breakdownNode, definitionNode, depth, axisDisposition, facts, i=None, tblAxisRels=None, processOpenDefinitionNode=True): subtreeRelationships = view.axisSubtreeRelSet.fromModelObject(definitionNode) - + def checkLabelWidth(structuralNode, checkBoundFact=False): if axisDisposition == "y": # messages can't be evaluated, just use the text portion of format string - label = structuralNode.header(lang=view.lang, - returnGenLabel=not checkBoundFact, + label = structuralNode.header(lang=view.lang, + returnGenLabel=not checkBoundFact, returnMsgFormatString=not checkBoundFact) if label: # need to et more exact word length in screen units @@ -234,18 +234,18 @@ def checkLabelWidth(structuralNode, checkBoundFact=False): # abstract only pertains to subtree of closed nodesbut not cartesian products or open nodes while structuralNode.depth >= len(view.rowHdrColWidth): view.rowHdrColWidth.append(0) - if definitionNode.isAbstract or not subtreeRelationships: # isinstance(definitionNode, ModelOpenDefinitionNode): + if definitionNode.isAbstract or not subtreeRelationships: # isinstance(definitionNode, ModelOpenDefinitionNode): if widestWordLen > view.rowHdrColWidth[structuralNode.depth]: view.rowHdrColWidth[structuralNode.depth] = widestWordLen else: if widestWordLen > view.rowNonAbstractHdrSpanMin[structuralNode.depth]: view.rowNonAbstractHdrSpanMin[structuralNode.depth] = widestWordLen - + if axisDisposition == "z" and structuralNode.aspects is None: structuralNode.aspects = view.zOrdinateChoices.get(definitionNode, None) if structuralNode and isinstance(definitionNode, (ModelBreakdown, ModelEuAxisCoord, ModelDefinitionNode)): try: - + #cartesianProductNestedArgs = (view, depth, axisDisposition, facts, tblAxisRels, i) try: ordCardinality, ordDepth = definitionNode.cardinalityAndDepth(structuralNode, handleXPathException=False) @@ -257,7 +257,7 @@ def checkLabelWidth(structuralNode, checkBoundFact=False): xpathError=str(ex)) return if (not definitionNode.isAbstract and - isinstance(definitionNode, ModelClosedDefinitionNode) and + isinstance(definitionNode, ModelClosedDefinitionNode) and ordCardinality == 0): view.modelXbrl.error("xbrlte:closedDefinitionNodeZeroCardinality", _("Closed definition node %(xlinkLabel)s does not contribute at least one structural node"), @@ -267,7 +267,7 @@ def checkLabelWidth(structuralNode, checkBoundFact=False): cartesianProductNestedArgs = [view, nestedDepth, axisDisposition, facts, tblAxisRels, i] if axisDisposition == "z": if depth == 1: # choices (combo boxes) don't add to z row count - view.zAxisRows += 1 + view.zAxisRows += 1 elif axisDisposition == "x": if ordDepth: if nestedDepth - 1 > view.colHdrRows: @@ -275,10 +275,10 @@ def checkLabelWidth(structuralNode, checkBoundFact=False): ''' if not view.colHdrDocRow: if definitionNode.header(role="http://www.xbrl.org/2008/role/documentation", - lang=view.lang): + lang=view.lang): view.colHdrDocRow = True if not view.colHdrCodeRow: - if definitionNode.header(role="http://www.eurofiling.info/role/2010/coordinate-code"): + if definitionNode.header(role="http://www.eurofiling.info/role/2010/coordinate-code"): view.colHdrCodeRow = True ''' hdrNonStdRoles = view.colHdrNonStdRoles @@ -286,19 +286,19 @@ def checkLabelWidth(structuralNode, checkBoundFact=False): if ordDepth: #if not definitionNode.isAbstract: # view.dataRows += ordCardinality - if nestedDepth - 1 > view.rowHdrCols: + if nestedDepth - 1 > view.rowHdrCols: view.rowHdrCols = nestedDepth - 1 for j in range(1 + ordDepth): view.rowHdrColWidth.append(RENDER_UNITS_PER_CHAR) # min width for 'tail' of nonAbstract coordinate view.rowNonAbstractHdrSpanMin.append(0) checkLabelWidth(structuralNode, checkBoundFact=False) - ''' + ''' if not view.rowHdrDocCol: if definitionNode.header(role="http://www.xbrl.org/2008/role/documentation", - lang=view.lang): + lang=view.lang): view.rowHdrDocCol = True if not view.rowHdrCodeCol: - if definitionNode.header(role="http://www.eurofiling.info/role/2010/coordinate-code"): + if definitionNode.header(role="http://www.eurofiling.info/role/2010/coordinate-code"): view.rowHdrCodeCol = True ''' hdrNonStdRoles = view.rowHdrNonStdRoles @@ -325,7 +325,7 @@ def checkLabelWidth(structuralNode, checkBoundFact=False): structuralNode.rollUpStructuralNode = StructuralNode(structuralNode, breakdownNode, childDefinitionNode, ) if not structuralNode.childStructuralNodes: # first sub ordinate is the roll up structuralNode.subtreeRollUp = CHILD_ROLLUP_FIRST - else: + else: structuralNode.subtreeRollUp = CHILD_ROLLUP_LAST if not view.topRollup.get(axisDisposition): view.topRollup[axisDisposition] = structuralNode.subtreeRollUp @@ -350,7 +350,7 @@ def checkLabelWidth(structuralNode, checkBoundFact=False): # required when switching from abstract to roll up to determine abstractness #if not structuralNode.subtreeRollUp and structuralNode.childStructuralNodes and definitionNode.tag.endswith("Node"): # structuralNode.subtreeRollUp = CHILDREN_BUT_NO_ROLLUP - + #if not hasattr(structuralNode, "indent"): # probably also for multiple open axes if processOpenDefinitionNode: if isinstance(definitionNode, ModelRelationshipDefinitionNode): @@ -397,14 +397,14 @@ def flattenChildNodesToChoices(childStructuralNodes, indent): view.modelXbrl.error("xbrlte:relationshipNodeTooManyGenerations ", _("Relationship rule node %(xlinkLabel)s formulaAxis %(axis)s implies a single generation tree walk but generations %(generations)s is greater than one."), modelObject=definitionNode, xlinkLabel=definitionNode.xlinkLabel, axis=definitionNode._axis, generations=definitionNode._generations) - + elif isinstance(definitionNode, ModelSelectionDefinitionNode): structuralNode.setHasOpenNode() structuralNode.isLabeled = False isCartesianProductExpanded = True varQn = definitionNode.variableQname if varQn: - selections = sorted(structuralNode.evaluate(definitionNode, definitionNode.evaluate) or [], + selections = sorted(structuralNode.evaluate(definitionNode, definitionNode.evaluate) or [], key=lambda obj:sortkey(obj)) if isinstance(selections, (list,set,tuple)) and len(selections) > 1: for selection in selections: # nested choices from selection list @@ -425,20 +425,20 @@ def flattenChildNodesToChoices(childStructuralNodes, indent): structuralNode.isLabeled = False isCartesianProductExpanded = True structuralNode.abstract = True # spanning ordinate acts as a subtitle - filteredFactsPartitions = structuralNode.evaluate(definitionNode, - definitionNode.filteredFactsPartitions, + filteredFactsPartitions = structuralNode.evaluate(definitionNode, + definitionNode.filteredFactsPartitions, evalArgs=(facts,)) if structuralNode._rendrCntx.formulaOptions.traceVariableFilterWinnowing: view.modelXbrl.info("table:trace", - _("Filter node %(xlinkLabel)s facts partitions: %(factsPartitions)s"), + _("Filter node %(xlinkLabel)s facts partitions: %(factsPartitions)s"), modelObject=definitionNode, xlinkLabel=definitionNode.xlinkLabel, factsPartitions=str(filteredFactsPartitions)) - + # ohly for fact entry (true if no parent open nodes or all are on entry prototype row) if axisDisposition != "z": childList = structuralNode.childStructuralNodes if structuralNode.isEntryPrototype(default=True): - for i in range(getattr(view, "openBreakdownLines", + for i in range(getattr(view, "openBreakdownLines", # for file output, 1 entry row if no facts 0 if filteredFactsPartitions else 1)): view.aspectEntryObjectId += 1 @@ -449,10 +449,10 @@ def flattenChildNodesToChoices(childStructuralNodes, indent): childList = structuralNode.choiceStructuralNodes for factsPartition in filteredFactsPartitions: childStructuralNode = StructuralNode(structuralNode, breakdownNode, definitionNode, contextItemFact=factsPartition[0]) - + # store the partition for later reuse when spreading facts in body cells childStructuralNode.factsPartition = factsPartition - + childStructuralNode.indent = 0 childStructuralNode.depth -= 1 # for label width; parent is merged/invisible childList.append(childStructuralNode) @@ -470,17 +470,17 @@ def flattenChildNodesToChoices(childStructuralNodes, indent): else: cartesianProductExpander(childStructuralNode, *cartesianProductNestedArgs) # sort by header (which is likely to be typed dim value, for example) - childList.sort(key=lambda childStructuralNode: - childStructuralNode.header(lang=view.lang, - returnGenLabel=False, - returnMsgFormatString=False) + childList.sort(key=lambda childStructuralNode: + childStructuralNode.header(lang=view.lang, + returnGenLabel=False, + returnMsgFormatString=False) or '') # exception on trying to sort if header returns None - - # TBD if there is no abstract 'sub header' for these subOrdCntxs, move them in place of parent structuralNode + + # TBD if there is no abstract 'sub header' for these subOrdCntxs, move them in place of parent structuralNode elif isinstance(definitionNode, ModelTupleDefinitionNode): structuralNode.abstract = True # spanning ordinate acts as a subtitle - matchingTupleFacts = structuralNode.evaluate(definitionNode, - definitionNode.filteredFacts, + matchingTupleFacts = structuralNode.evaluate(definitionNode, + definitionNode.filteredFacts, evalArgs=(facts,)) for tupleFact in matchingTupleFacts: childStructuralNode = StructuralNode(structuralNode, breakdownNode, definitionNode, contextItemFact=tupleFact) @@ -525,10 +525,10 @@ def flattenChildNodesToChoices(childStructuralNodes, indent): else: structuralNode.choiceNodeIndex = 0 view.zmostOrdCntx = structuralNode - + if not isCartesianProductExpanded or (axisDisposition == "z" and structuralNode.choiceStructuralNodes is not None): cartesianProductExpander(structuralNode, *cartesianProductNestedArgs) - + if not structuralNode.childStructuralNodes: # childless root ordinate, make a child to iterate in producing table subOrdContext = StructuralNode(structuralNode, breakdownNode, definitionNode) except ResolutionException as ex: @@ -547,12 +547,12 @@ def flattenChildNodesToChoices(childStructuralNodes, indent): raise e.with_traceback(ex.__traceback__) # provide original traceback information else: raise e - + def cartesianProductExpander(childStructuralNode, view, depth, axisDisposition, facts, tblAxisRels, i): if i is not None: # recurse table relationships for cartesian product for j, tblRel in enumerate(tblAxisRels[i+1:]): tblObj = tblRel.toModelObject - if isinstance(tblObj, (ModelEuAxisCoord, ModelDefinitionNode)) and axisDisposition == tblRel.axisDisposition: + if isinstance(tblObj, (ModelEuAxisCoord, ModelDefinitionNode)) and axisDisposition == tblRel.axisDisposition: #addBreakdownNode(view, axisDisposition, tblObj) #if tblObj.cardinalityAndDepth(childStructuralNode)[1] or axisDisposition == "z": if axisDisposition == "z": @@ -565,18 +565,18 @@ def cartesianProductExpander(childStructuralNode, view, depth, axisDisposition, subOrdTblCntx = childStructuralNode # predefined axes need facts sub-filtered if isinstance(childStructuralNode.definitionNode, ModelClosedDefinitionNode): - matchingFacts = childStructuralNode.evaluate(childStructuralNode.definitionNode, - childStructuralNode.definitionNode.filteredFacts, + matchingFacts = childStructuralNode.evaluate(childStructuralNode.definitionNode, + childStructuralNode.definitionNode.filteredFacts, evalArgs=(facts,)) else: matchingFacts = facts # returns whether there were no structural node results subOrdTblCntx.abstract = True # can't be abstract across breakdown expandDefinition(view, subOrdTblCntx, tblObj, tblObj, - depth, # depth + (0 if axisDisposition == 'z' else 1), + depth, # depth + (0 if axisDisposition == 'z' else 1), axisDisposition, matchingFacts, j + i + 1, tblAxisRels) #cartesian product break - + def addRelationship(breakdownNode, relDefinitionNode, rel, structuralNode, cartesianProductNestedArgs, selfStructuralNodes=None): variableQname = relDefinitionNode.variableQname conceptQname = relDefinitionNode.conceptQname @@ -628,6 +628,6 @@ def addRelationships(breakdownNode, relDefinitionNode, rels, structuralNode, car addRelationships(breakdownNode, relDefinitionNode, rel, childStructuralNode, cartesianProductNestedArgs) else: addRelationships(breakdownNode, relDefinitionNode, rel, childStructuralNode, cartesianProductNestedArgs) - + diff --git a/arelle/TableStructure.py b/arelle/TableStructure.py index 647db858f7..19a44ab0b2 100644 --- a/arelle/TableStructure.py +++ b/arelle/TableStructure.py @@ -29,7 +29,7 @@ parenthentical prenthetical parenethetical - + use a regular expression that is forgiving on at least the above and doens't match variations of parent, transparent, etc. ''' @@ -47,7 +47,7 @@ def RE(*args): EFMtableCodes = [ # ELRs are parsed for these patterns in sort order until there is one match per code # sheet(s) may be plural - + # statement detection including root element of presentation link role ("BS", RE(STMT, notDET, notPAR), ("StatementOfFinancialPositionAbstract",)), ("BSP", RE(STMT, notDET, isPAR), ("StatementOfFinancialPositionAbstract",)), @@ -63,7 +63,7 @@ def RE(*args): ("CAP", RE(STMT, notDET, isPAR), ("CapitalizationLongtermDebtAndEquityAbstract",)), ("IN", RE(STMT, notDET, notPAR), ("ScheduleOfInvestmentsAbstract",)), ("INP", RE(STMT, notDET, isPAR), ("ScheduleOfInvestmentsAbstract",)), - + # statement detection without considering root elements ("DEI", RE(r".* - (document|statement) - .*document\W+.*entity\W+.*information"), None), ("BS", RE(STMT, notDET, notPAR, r".*balance\W+sheet"), None), @@ -104,7 +104,7 @@ def RE(*args): ("EQP", RE(STMT, notDET, isPAR, r"(?=.*reserve).*trust"), None), ("LC", RE(STMT, notDET, notPAR, r"(?=.*activities).*liquidati"), None), ("EQP", RE(STMT, notDET, isPAR, r".*def[ei][cs]it"), None), - ("BSV", RE(STMT, notDET,notPAR, r".*net\W+asset\W+value"), None), + ("BSV", RE(STMT, notDET,notPAR, r".*net\W+asset\W+value"), None), ("CFS", RE(STMT, notDET,notPAR, r".*cash\W*flows\W+supplemental"), None), ("LAP", RE(STMT, notDET, isPAR, r".*(?!.*changes)(?=.*assets).*liquidati"), None) ] @@ -120,7 +120,7 @@ def RE(*args): def evaluateRoleTypesTableCodes(modelXbrl): disclosureSystem = modelXbrl.modelManager.disclosureSystem - + if disclosureSystem.validationType in ("EFM", "HMRC"): detectMultipleOfCode = False if disclosureSystem.validationType == "EFM": @@ -132,10 +132,10 @@ def evaluateRoleTypesTableCodes(modelXbrl): for v in (docTypeFact.value,)) elif disclosureSystem.validationType == "HMRC": tableCodes = list( HMRCtableCodes ) # separate copy of list so entries can be deleted - + codeRoleURI = {} # lookup by code for roleURI roleURICode = {} # lookup by roleURI - + # resolve structural model roleTypes = [roleType for roleURI in modelXbrl.relationshipSet(XbrlConst.parentChild).linkRoleUris @@ -201,7 +201,7 @@ def evaluateTableIndex(modelXbrl, lang=None): for roleDefinition, roleType in sortedRoleTypes: roleType._tableChildren = [] match = usgaapRoleDefinitionPattern.match(roleDefinition) if roleDefinition else None - if not match: + if not match: roleType._tableIndex = (UNCATEG, "", roleType.roleURI) continue seq, tblType, tblName = match.groups() @@ -220,13 +220,13 @@ def evaluateTableIndex(modelXbrl, lang=None): "(Polic" in tblName and NOTES or "(Table" in tblName and TABLES or "(Detail" in tblName and DETAILS or NOTES) elif tableGroup == NOTES: - tableGroup = ("(Polic" in tblName and POLICIES or "(Table" in tblName and TABLES or + tableGroup = ("(Polic" in tblName and POLICIES or "(Table" in tblName and TABLES or "(Detail" in tblName and DETAILS or tblType == "Disclosure" and NOTES or UNCATEG) elif tableGroup == POLICIES: - tableGroup = ("(Table" in tblName and TABLES or "(Detail" in tblName and DETAILS or + tableGroup = ("(Table" in tblName and TABLES or "(Detail" in tblName and DETAILS or ("Paren" in tblName or "(Polic" in tblName) and POLICIES or UNCATEG) elif tableGroup == TABLES: - tableGroup = ("(Detail" in tblName and DETAILS or + tableGroup = ("(Detail" in tblName and DETAILS or ("Paren" in tblName or "(Table" in tblName) and TABLES or UNCATEG) elif tableGroup == DETAILS: tableGroup = (("Paren" in tblName or "(Detail" in tblName) and DETAILS or UNCATEG) @@ -315,7 +315,7 @@ def evaluateTableIndex(modelXbrl, lang=None): break elif (cntx.isInstantPeriod and not cntx.qnameDims and thisEnd == cntx.endDatetime): reportingPeriods.add((None, cntx.endDatetime)) - stmtReportingPeriods = set(reportingPeriods) + stmtReportingPeriods = set(reportingPeriods) sortedRoleTypes.reverse() # now in descending order for i, roleTypes in enumerate(sortedRoleTypes): @@ -345,7 +345,7 @@ def evaluateTableIndex(modelXbrl, lang=None): tableFacts.add(fact) reportedFacts.add(fact) roleType._tableFacts = tableFacts - + # find parent if any closestParentType = None closestParentMatchLength = 0 @@ -356,7 +356,7 @@ def evaluateTableIndex(modelXbrl, lang=None): closestParentType = parentRoleType if closestParentType is not None: closestParentType._tableChildren.insert(0, roleType) - + # remove lesser-matched children if there was a parent match unmatchedChildRoles = set() longestChildMatchLen = 0 @@ -368,10 +368,10 @@ def evaluateTableIndex(modelXbrl, lang=None): elif matchLen > longestChildMatchLen: longestChildMatchLen = matchLen numChildren += 1 - if numChildren > 1: + if numChildren > 1: # remove children that don't have the full match pattern length to parent for childRoleType in roleType._tableChildren: - if (childRoleType not in unmatchedChildRoles and + if (childRoleType not in unmatchedChildRoles and parentNameMatchLen(tableName, childRoleType) < longestChildMatchLen): unmatchedChildRoles.add(childRoleType) @@ -380,14 +380,14 @@ def evaluateTableIndex(modelXbrl, lang=None): for childRoleType in roleType._tableChildren: childRoleType._tableParent = roleType - + unmatchedChildRoles = None # dereference - + global UGT_TOPICS if UGT_TOPICS is None: try: from arelle import FileSource - fh = FileSource.openFileStream(modelXbrl.modelManager.cntlr, + fh = FileSource.openFileStream(modelXbrl.modelManager.cntlr, os.path.join(modelXbrl.modelManager.cntlr.configDir, "ugt-topics.zip/ugt-topics.json"), 'r', 'utf-8') UGT_TOPICS = json.load(fh) @@ -412,10 +412,10 @@ def roleUgtConcepts(roleType): roleConcepts |= roleUgtConcepts(_tableChild) return roleConcepts topicMatches = {} # topicNum: (best score, roleType) - + for roleDefinition, roleType in sortedRoleTypes: roleTopicType = 'S' if roleDefinition.startswith('S') else 'D' - if getattr(roleType, "_tableParent", None) is None: + if getattr(roleType, "_tableParent", None) is None: # rooted tables in reverse order concepts = roleUgtConcepts(roleType) for i, ugtTopic in enumerate(UGT_TOPICS): @@ -482,11 +482,11 @@ def addRoleIdentifiers(fromConcept, parentRoleType, visited): addRoleIdentifiers(rootConcept, None, set()) if not linkroleUri and len(roleType._tableChildren) > 0: linkroleUri = roleURI - return linkroleUri, linkroleUri # only show linkroleUri in index table - elif _ifrsStyleELRs: + return linkroleUri, linkroleUri # only show linkroleUri in index table + elif _ifrsStyleELRs: for roleType in definitionElrs.values(): roleType._tableChildren = [] - return sortedRoleTypes[0][1].roleURI, None # first link role in order + return sortedRoleTypes[0][1].roleURI, None # first link role in order return None, None def parentNameMatchLen(tableName, parentRoleType): @@ -508,7 +508,7 @@ def EFMlinkRoleURIstructure(modelXbrl, roleURI): for rootConcept in relSet.rootConcepts: EFMlinkRoleDescendants(relSet, rootConcept, dimMems, priItems) return dimMems, priItems - + def EFMlinkRoleDescendants(relSet, concept, dimMems, priItems): if concept is not None: if concept.isDimensionItem: diff --git a/arelle/TkTableWrapper.py b/arelle/TkTableWrapper.py index 0cd438bd6e..d5e84aaef2 100644 --- a/arelle/TkTableWrapper.py +++ b/arelle/TkTableWrapper.py @@ -631,7 +631,7 @@ def comboValueChanged(event): numrows, numcols = 6250,40 - #Using ArrayVar consumes double as much memory as NumPy+command + #Using ArrayVar consumes double as much memory as NumPy+command #var = ArrayVar(root) #for y in range(0, numrows): # for x in range(0, numcols): @@ -695,7 +695,7 @@ def comboValueChanged(event): test.tag_cell('border-left-right', index) index = "%i,%i" % (y, 1) test.tag_cell('border', index) - + for y in range(2, numrows): cities = ('Brussels', 'Luxembourg', 'Strasbourg', 'Trier', 'Rome') combobox = ttk.Combobox(test, values=cities, state='readonly') diff --git a/arelle/UITkTable.py b/arelle/UITkTable.py index 413611158b..8f21b5fdc5 100644 --- a/arelle/UITkTable.py +++ b/arelle/UITkTable.py @@ -43,13 +43,13 @@ def __ne__(self, other): def __lt__(self, other): - return self._ne_(other) and (self.y < other.y + return self._ne_(other) and (self.y < other.y or (self.y == other.y and self.x < other.x)) def __gt__(self, other): - return self._ne_(other) and (self.y > other.y + return self._ne_(other) and (self.y > other.y or (self.y == other.y and self.x > other.x)) @@ -119,7 +119,7 @@ class XbrlTable(TkTableWrapper.Table): } TG_DISABLED = 'disabled-cells' - + TG_NO_BORDER = 'no-border' TG_BORDER_ALL = 'border-all' TG_BORDER_LEFT = 'border-left' @@ -136,7 +136,7 @@ class XbrlTable(TkTableWrapper.Table): TG_BORDER_RIGHT_BOTTOM_LEFT = 'border-right-bottom-left' TG_BORDER_BOTTOM_LEFT_TOP = 'border-bottom-left-top' TG_BORDER_LEFT_TOP_RIGHT = 'border-left-top-right' - + # (left, right, top, bottom) BORDERWIDTHS = { TG_NO_BORDER : (0, 0, 0, 0), @@ -550,14 +550,14 @@ def getTableValue(self, coordinate): def isHeaderCell(self, coordinate): - return (coordinate.y < self.titleRows + return (coordinate.y < self.titleRows or coordinate.x < self.titleColumns) def getCoordinatesOfModifiedCells(self): return self.modifiedCells.keys() - - + + def getCurrentCellCoordinates(self): return self.currentCellCoordinates @@ -570,7 +570,7 @@ def initCellValue(self, value, x, y, cellIndex = '%i,%i'% (y, x) if justification in XbrlTable.ANCHOR_POSITIONS: self.format_cell(justification, cellIndex) - if ((backgroundColourTag is not None) + if ((backgroundColourTag is not None) and backgroundColourTag in XbrlTable.COLOURS): self.format_cell(backgroundColourTag, cellIndex) self.format_cell(XbrlTable.TG_BORDER_ALL, cellIndex) @@ -588,7 +588,7 @@ def _setValueFromCombobox(self, event): def initCellCombobox(self, value, values, x, y, isOpen=False, - objectId=None, selectindex=None, + objectId=None, selectindex=None, comboboxselected=None, codes=dict()): ''' Initialise the content of a cell as a combobox. diff --git a/arelle/UiUtil.py b/arelle/UiUtil.py index ec6ef18465..96468daffe 100644 --- a/arelle/UiUtil.py +++ b/arelle/UiUtil.py @@ -20,8 +20,8 @@ borderImage = None class gridBorder(Separator): - def __init__(self, master, x, y, border, columnspan=None, rowspan=None): - Separator.__init__(self, master=master) + def __init__(self, master, x, y, border, columnspan=None, rowspan=None): + Separator.__init__(self, master=master) if border in (TOPBORDER, BOTTOMBORDER): x = x * 2 - 1 if columnspan: columnspan = columnspan * 2 + 1 @@ -67,7 +67,7 @@ def __init__(self, master, x, y, border, columnspan=None, rowspan=None): elif rowspan and rowspan > 1: self.grid(column=x, row=y, sticky=sticky, rowspan=rowspan) else: - self.grid(column=x, row=y, sticky=sticky) + self.grid(column=x, row=y, sticky=sticky) self.x = x self.y = y self.columnspan = columnspan @@ -81,16 +81,16 @@ def __init__(self, master, x, y, border, columnspan=None, rowspan=None): pass #if isinstance(master.master.master, scrolledHeaderedFrame): # self.bind("", master.master.master._configure_cell) - + class gridSpacer(Frame): - def __init__(self, master, x, y, where): + def __init__(self, master, x, y, where): Frame.__init__(self, master=master) if where == CENTERCELL: offset = 0 elif where in (TOPBORDER, LEFTBORDER): offset = -1 else: - offset = 1 + offset = 1 x = x * 2 + offset y = y * 2 + offset self.grid(column=x, row=y) # same dimensions as separator in col/row headers @@ -110,12 +110,12 @@ def __init__(self, master, x, y, where): pass #if isinstance(master.master.master, scrolledHeaderedFrame): # self.bind("", master.master.master._configure_cell) - -class gridHdr(Label): - def __init__(self, master, x, y, text, columnspan=None, rowspan=None, anchor='center', padding=None, + +class gridHdr(Label): + def __init__(self, master, x, y, text, columnspan=None, rowspan=None, anchor='center', padding=None, wraplength=None, width=None, minwidth=None, stretchCols=True, stretchRows=True, - objectId=None, onClick=None): - Label.__init__(self, master=master) + objectId=None, onClick=None): + Label.__init__(self, master=master) if isinstance(master.master.master, scrolledHeaderedFrame): x = x * 2 y = y * 2 @@ -124,13 +124,13 @@ def __init__(self, master, x, y, text, columnspan=None, rowspan=None, anchor='ce # #master.columnconfigure(x, weight=1, uniform='stretchX') #master.rowconfigure(y, weight=1, uniform='stretch') self.config(text=text if text is not None else "", - #relief="solid", use border instead to effect row-col spanned cells properly - #bg="#ffffff000", fg="#000000fff", + #relief="solid", use border instead to effect row-col spanned cells properly + #bg="#ffffff000", fg="#000000fff", #readonlybackground="#ddddddddd", - - #background="#888000000", + + #background="#888000000", width=width, - anchor=anchor) + anchor=anchor) if padding: self.config(padding=padding) if wraplength: @@ -142,16 +142,16 @@ def __init__(self, master, x, y, text, columnspan=None, rowspan=None, anchor='ce elif rowspan and rowspan > 1: self.grid(column=x, row=y, sticky=(E,W,N,S), rowspan=rowspan) else: - self.grid(column=x, row=y, sticky=(E,W,N,S)) + self.grid(column=x, row=y, sticky=(E,W,N,S)) self.x = x self.y = y self.columnspan = columnspan self.rowspan = rowspan self.objectId = objectId - + if minwidth: master.columnconfigure(x, minsize=minwidth) - + if stretchCols: master.columnconfigure(x, weight=1) else: @@ -171,25 +171,25 @@ def __init__(self, master, x, y, text, columnspan=None, rowspan=None, anchor='ce self.bind("", master.master.master._configure_cell) if onClick: self.bind("<1>", onClick) - -class gridCell(Entry): - def __init__(self, master, x, y, value="", width=None, justify=None, objectId=None, onClick=None): + +class gridCell(Entry): + def __init__(self, master, x, y, value="", width=None, justify=None, objectId=None, onClick=None): Entry.__init__(self, master=master) - self.valueVar = StringVar() + self.valueVar = StringVar() self.valueVar.trace('w', self.valueChanged) self.config(textvariable=self.valueVar, - #relief="ridge", - #bg="#ff8ff8ff8", fg="#000000000", + #relief="ridge", + #bg="#ff8ff8ff8", fg="#000000000", justify=justify, width=width, - ) + ) if isinstance(master.master.master, scrolledHeaderedFrame): x = x * 2 y = y * 2 self.grid(column=x, row=y, sticky=(N,S,E,W)) self.x = x self.y = y - if value is not None: + if value is not None: self.valueVar.set(value) self.objectId = objectId # copy bindings @@ -204,29 +204,29 @@ def __init__(self, master, x, y, value="", width=None, justify=None, objectId=No if onClick: self.bind("<1>", onClick) self.isChanged = False - + @property def value(self): return self.valueVar.get() - + def setValue(self, value): return self.valueVar.set(value) - + def valueChanged(self, *args): self.isChanged = True - -class gridCombobox(_Combobox): - def __init__(self, master, x, y, value="", values=(), width=None, objectId=None, columnspan=None, selectindex=None, comboboxselected=None, state=None, padx=None, attr=None): - _Combobox.__init__(self, master=master) - self.attr = attr - self.valueVar = StringVar() + +class gridCombobox(_Combobox): + def __init__(self, master, x, y, value="", values=(), width=None, objectId=None, columnspan=None, selectindex=None, comboboxselected=None, state=None, padx=None, attr=None): + _Combobox.__init__(self, master=master) + self.attr = attr + self.valueVar = StringVar() self.valueVar.trace('w', self.valueChanged) self.config(textvariable=self.valueVar, - background="#ff8ff8ff8", foreground="#000000000", + background="#ff8ff8ff8", foreground="#000000000", # justify='center' width=width, state=state - ) + ) self["values"] = values if isinstance(master.master.master, scrolledHeaderedFrame): x = x * 2 @@ -235,10 +235,10 @@ def __init__(self, master, x, y, value="", values=(), width=None, objectId=None, if columnspan and columnspan > 1: self.grid(column=x, row=y, sticky=(E,W), columnspan=columnspan, padx=padx) else: - self.grid(column=x, row=y, sticky=(E,W), padx=padx) + self.grid(column=x, row=y, sticky=(E,W), padx=padx) if selectindex is not None: self.valueVar.set(values[selectindex]) - elif value: + elif value: self.valueVar.set(value) elif attr: try: @@ -258,11 +258,11 @@ def __init__(self, master, x, y, value="", values=(), width=None, objectId=None, if comboboxselected: self.bind("<>", comboboxselected) self.isChanged = False - + @property def value(self): return self.valueVar.get() - + @property def valueIndex(self): value = self.valueVar.get() @@ -270,24 +270,24 @@ def valueIndex(self): if value in values: return values.index(value) return -1 - + def valueChanged(self, *args): self.isChanged = True - + class label(Label): def __init__(self, master, x, y, text): - Label.__init__(self, master=master, text=text) - #self.config(justify='left') - self.grid(column=x, row=y, sticky=W, padx=8) - + Label.__init__(self, master=master, text=text) + #self.config(justify='left') + self.grid(column=x, row=y, sticky=W, padx=8) + class checkbox(Checkbutton): def __init__(self, master, x, y, text, attr=None, columnspan=None, onclick=None): - self.attr = attr + self.attr = attr self.onclick = onclick - self.valueVar = StringVar() + self.valueVar = StringVar() self.valueVar.trace('w', self.valueChanged) - Checkbutton.__init__(self, master=master, text=text, variable=self.valueVar) - self.grid(column=x, row=y, sticky=W, padx=24) + Checkbutton.__init__(self, master=master, text=text, variable=self.valueVar) + self.grid(column=x, row=y, sticky=W, padx=24) if columnspan: self.grid(columnspan=columnspan) try: @@ -297,39 +297,39 @@ def __init__(self, master, x, y, text, attr=None, columnspan=None, onclick=None) except AttributeError: pass self.isChanged = False - + @property def value(self): if self.valueVar.get() == "1": return True else: return False - + def valueChanged(self, *args): self.isChanged = True if self.onclick is not None: self.onclick(self) - + class radiobutton(Radiobutton): def __init__(self, master, x, y, text, value, attr=None, valueVar=None): - self.attr = attr - self.valueVar = valueVar if valueVar else StringVar() - Radiobutton.__init__(self, master=master, text=text, variable=self.valueVar, value=value) - self.grid(column=x, row=y, sticky=W, padx=24) + self.attr = attr + self.valueVar = valueVar if valueVar else StringVar() + Radiobutton.__init__(self, master=master, text=text, variable=self.valueVar, value=value) + self.grid(column=x, row=y, sticky=W, padx=24) try: options = master.master.options if attr in options: self.valueVar.set( options[attr] ) except AttributeError: pass - + @property def value(self): return self.valueVar.get() - + class scrolledFrame(Frame): def __init__(self, parent, *args, **kw): - Frame.__init__(self, parent, *args, **kw) + Frame.__init__(self, parent, *args, **kw) vscrollbar = Scrollbar(self, orient=VERTICAL) hscrollbar = Scrollbar(self, orient=HORIZONTAL) @@ -356,7 +356,7 @@ def __init__(self, parent, *args, **kw): self.interior_id = canvas.create_window(0, 0, window=interior, anchor=NW) interior.bind('', self._configure_interior) canvas.bind('', self._configure_canvas) - + def _configure_interior(self,event): # update the scrollbars to match the size of the inner frame interiorW = self.interior.winfo_reqwidth() @@ -380,7 +380,7 @@ def _configure_canvas(self, event): if self.interior.winfo_reqheight() != canvasH: self.canvas.itemconfigure(self.interior_id, height=canvasH) ''' - + def clearGrid(self): x,y = self.size() for widget in self.winfo_children(): @@ -394,7 +394,7 @@ def clearGrid(self): class scrolledHeaderedFrame(Frame): def __init__(self, parent, *args, **kw): - Frame.__init__(self, parent, *args, **kw) + Frame.__init__(self, parent, *args, **kw) self.colsConfigured = False self.bodyCellsConfigured = False @@ -465,21 +465,21 @@ def __init__(self, parent, *args, **kw): # on linux Button-4, Button-5 events #self.rowHdrCanvas.bind("", self._mousewheel) #self.bodyCanvas.bind("", self._mousewheel) - + def _vscroll_body(self, *args): self.rowHdrCanvas.yview(*args) self.bodyCanvas.yview(*args) - + def _hscroll_body(self, *args): self.colHdrCanvas.xview(*args) self.bodyCanvas.xview(*args) - + def _mousewheel(self, event): # on linux: if (event.num == 4): delta = -1 elif (event.num == 5): delta = 1 else: delta = event.delta self.rowHdrCanvas.yview("scroll", event.delta, "units") self.bodyCanvas.yview("scroll", event.delta, "units") return "break" #don't do default scrolling - + def clearGrid(self): self.colHdrCanvas.xview_moveto(0) self.colHdrCanvas.yview_moveto(0) @@ -525,7 +525,7 @@ def _configure_rowHdrInterior(self,event): # also: mac won't display at all without this trick self.rowHdrCanvas.config(width=interiorW, scrollregion=(0,0,interiorW,interiorH)) if widenWidth: # update the canvas's width to fit the inner frame - + self.rowHdrCanvas.config(width=interiorW + 1) # remove if tkinter issue gets solved #if interiorW != self.tblHdrInterior.winfo_width() or \ # interiorW != self.tblHdrInterior.tk.call( ('grid', 'columnconfigure', self.tblHdrInterior._w, 1, '-minsize' ) ): @@ -579,7 +579,7 @@ def _configure_canvases(self, event): canvasW = self.rowHdrCanvas.winfo_width() if self.rowHdrInterior.winfo_reqwidth() != canvasW: self.rowHdrCanvas.itemconfigure(self.rowHdrInterior_id, width=canvasW) - + def _configure_cell(self, event): #if self.blockConfigureCell: # return @@ -646,11 +646,11 @@ def _configure_cell(self, event): #print("...rowHdrH={} colHdrW={}".format(rowHdrH, colHdrW)) #self.bodyInterior.update() self.blockConfigureCell = False - + def conformHdrsToBody(self): self.colsConfigured = True # non-spanned cells - + ''' for hdrCell in self.rowHdrInterior.children.values(): hdrCellH = hdrCell.winfo_reqheight() @@ -676,7 +676,7 @@ def conformHdrsToBody(self): for hdrCellId, hdrCell in hdrCells.items(): if not hdrCell.x & 1: colspan = hdrCell.columnspan if hasattr(hdrCell,'columnspan') and hdrCell.columnspan else 1 - hdrCellSortKeys.append( (colspan, hdrCell.x, -hdrCell.y, hdrCellId) ) + hdrCellSortKeys.append( (colspan, hdrCell.x, -hdrCell.y, hdrCellId) ) hdrCellSortKeys.sort() for columnspan, x, y, hdrCellId in hdrCellSortKeys: hdrCell = hdrCells[hdrCellId] @@ -696,11 +696,11 @@ def conformHdrsToBody(self): if W > bodyColW: # even (body) cells only self.bodyInterior.tk.call( ('grid', 'columnconfigure', self.bodyInterior._w, X, '-minsize', W ) ) #self.bodyInterior.update() - + def conformBodyCellsToHeader(self): #print("conformBodyCellsToHeader") self.bodyCellsConfigured = True - + for bodyCell in self.bodyInterior.children.values(): if isinstance(bodyCell,gridSpacer): continue @@ -715,8 +715,8 @@ def conformBodyCellsToHeader(self): #print("conform row=" + str(y) + " rowH=" + str(rowColH) + " cellH=" + str(bodyCellH)) if bodyCellH < rowColH: self.bodyInterior.tk.call( ('grid', 'rowconfigure', self.bodyInterior._w, y, '-minsize', rowColH ) ) - + #self.colHdrInterior.update() #self.rowHdrInterior.update() #self.bodyInterior.update() - \ No newline at end of file + diff --git a/arelle/Updater.py b/arelle/Updater.py index 46a17fd6fa..601740cec3 100644 --- a/arelle/Updater.py +++ b/arelle/Updater.py @@ -16,17 +16,17 @@ def checkForUpdates(cntlr): def backgroundCheckForUpdates(cntlr): actualUrl = None - cntlr.showStatus(_("Checking for updates to Arelle")) + cntlr.showStatus(_("Checking for updates to Arelle")) try: attachmentFileName = cntlr.webCache.getAttachmentFilename(cntlr.updateURL) if attachmentFileName: - cntlr.showStatus("") # clear web loading status entry + cntlr.showStatus("") # clear web loading status entry cntlr.uiThreadQueue.put((checkUpdateUrl, [cntlr, attachmentFileName])) except: pass - cntlr.showStatus("") # clear web loading status entry + cntlr.showStatus("") # clear web loading status entry -def checkUpdateUrl(cntlr, attachmentFileName): +def checkUpdateUrl(cntlr, attachmentFileName): # get latest header file try: from arelle import WebCache, Version @@ -40,7 +40,7 @@ def checkUpdateUrl(cntlr, attachmentFileName): reply = tkinter.messagebox.askyesnocancel( _("arelle\u2122 - Updater"), _("Update {0} is available, running version is {1}. \n\nDownload now? \n\n(Arelle will exit before installing.)").format( - filenameDate, versionDate), + filenameDate, versionDate), parent=cntlr.parent) if reply is None: return False @@ -55,11 +55,11 @@ def checkUpdateUrl(cntlr, attachmentFileName): else: msg = _("Arelle running version, {0}, is the same as the downloadable version.").format( versionDate) - tkinter.messagebox.showwarning(_("arelle\u2122 - Updater"), msg, parent=cntlr.parent) - + tkinter.messagebox.showwarning(_("arelle\u2122 - Updater"), msg, parent=cntlr.parent) + except: pass - + return def backgroundDownload(cntlr, url): @@ -68,7 +68,7 @@ def backgroundDownload(cntlr, url): filepath = os.path.join(os.path.dirname(filepathtmp), os.path.basename(url)) os.rename(filepathtmp, filepath) cntlr.uiThreadQueue.put((install, [cntlr,filepath])) - + def install(cntlr,filepath): import sys if sys.platform.startswith("win"): diff --git a/arelle/Validate.py b/arelle/Validate.py index 3904c0f93f..ea20216b4f 100644 --- a/arelle/Validate.py +++ b/arelle/Validate.py @@ -6,7 +6,7 @@ ''' import os, sys, traceback, re, logging from collections import defaultdict, OrderedDict -from arelle import (FileSource, ModelXbrl, ModelDocument, ModelVersReport, XbrlConst, +from arelle import (FileSource, ModelXbrl, ModelDocument, ModelVersReport, XbrlConst, ValidateXbrl, ValidateVersReport, ValidateFormula, ValidateInfoset, RenderingEvaluator, ViewFileRenderedGrid, UrlUtil) from arelle.ModelDocument import Type, ModelDocumentReference, load as modelDocumentLoad @@ -30,15 +30,15 @@ def __init__(self, message, severity, code): self.code = code def __repr__(self): return "{0}({1})={2}".format(self.code,self.severity,self.message) - + commaSpaceSplitPattern = re.compile(r",\s*") - + class Validate: - """Validation operations are separated from the objects that are validated, because the operations are - complex, interwoven, and factored quite differently than the objects being validated. - There are these validation modules at present: validation infrastructure, test suite and submission control, - versioning report validation, XBRL base spec, dimensions, and formula linkbase validation, - Edgar and Global Filer Manual validation. + """Validation operations are separated from the objects that are validated, because the operations are + complex, interwoven, and factored quite differently than the objects being validated. + There are these validation modules at present: validation infrastructure, test suite and submission control, + versioning report validation, XBRL base spec, dimensions, and formula linkbase validation, + Edgar and Global Filer Manual validation. """ def __init__(self, modelXbrl): self.modelXbrl = modelXbrl @@ -52,12 +52,12 @@ def __init__(self, modelXbrl): self.useFileSource = modelXbrl.fileSource else: self.useFileSource = None - + def close(self): self.instValidator.close(reusable=False) self.formulaValidator.close(reusable=False) self.__dict__.clear() # dereference variables - + def validate(self): if not self.modelXbrl.modelDocument: self.modelXbrl.info("arelle:notValidated", @@ -117,7 +117,7 @@ def validate(self): # traceback=traceback.format_tb(sys.exc_info()[2]), exc_info=(type(err) is not AssertionError)) self.close() - + def validateRssFeed(self): self.modelXbrl.info("info", "RSS Feed", modelDocument=self.modelXbrl) from arelle.FileSource import openFileSource @@ -131,11 +131,11 @@ def validateRssFeed(self): modelObject=rssItem, accessionNumber=rssItem.accessionNumber, formType=rssItem.formType, companyName=rssItem.companyName, period=rssItem.period) modelXbrl = None try: - modelXbrl = ModelXbrl.load(self.modelXbrl.modelManager, + modelXbrl = ModelXbrl.load(self.modelXbrl.modelManager, openFileSource(rssItem.zippedUrl, self.modelXbrl.modelManager.cntlr, reloadCache=reloadCache), _("validating"), rssItem=rssItem) - for pluginXbrlMethod in pluginClassMethods("RssItem.Xbrl.Loaded"): - pluginXbrlMethod(modelXbrl, {}, rssItem) + for pluginXbrlMethod in pluginClassMethods("RssItem.Xbrl.Loaded"): + pluginXbrlMethod(modelXbrl, {}, rssItem) if getattr(rssItem, "doNotProcessRSSitem", False) or modelXbrl.modelDocument is None: modelXbrl.close() continue # skip entry based on processing criteria @@ -159,7 +159,7 @@ def validateRssFeed(self): except Exception as err: pass del modelXbrl # completely dereference - + def validateTestcase(self, testcase): self.modelXbrl.info("info", "Testcase", modelDocument=testcase) self.modelXbrl.viewModelObject(testcase.objectId()) @@ -179,11 +179,11 @@ def validateTestcase(self, testcase): baseForElement = testcase.baseForElement(modelTestcaseVariation) # try to load instance document self.modelXbrl.info("info", _("Variation %(id)s%(name)s%(target)s: %(expected)s - %(description)s"), - modelObject=modelTestcaseVariation, - id=modelTestcaseVariation.id, - name=(" {}".format(modelTestcaseVariation.name) if modelTestcaseVariation.name else ""), + modelObject=modelTestcaseVariation, + id=modelTestcaseVariation.id, + name=(" {}".format(modelTestcaseVariation.name) if modelTestcaseVariation.name else ""), target=(" target {}".format(modelTestcaseVariation.ixdsTarget) if modelTestcaseVariation.ixdsTarget else ""), - expected=modelTestcaseVariation.expected, + expected=modelTestcaseVariation.expected, description=modelTestcaseVariation.description) if self.modelXbrl.modelManager.formulaOptions.testcaseResultsCaptureWarnings: errorCaptureLevel = logging._checkLevel("WARNING") @@ -192,7 +192,7 @@ def validateTestcase(self, testcase): parameters = modelTestcaseVariation.parameters.copy() for i, readMeFirstUri in enumerate(modelTestcaseVariation.readMeFirstUris): readMeFirstElements = modelTestcaseVariation.readMeFirstElements - expectTaxonomyPackage = (i < len(readMeFirstElements) and + expectTaxonomyPackage = (i < len(readMeFirstElements) and readMeFirstElements[i] is not None and readMeFirstElements[i].qname.localName == "taxonomyPackage") if isinstance(readMeFirstUri,tuple): @@ -207,7 +207,7 @@ def validateTestcase(self, testcase): if dtsName in inputDTSes: dtsName = inputDTSes[dtsName] else: - modelXbrl = ModelXbrl.create(self.modelXbrl.modelManager, + modelXbrl = ModelXbrl.create(self.modelXbrl.modelManager, Type.DTSENTRIES, self.modelXbrl.modelManager.cntlr.webCache.normalizeUrl(readMeFirstUri[:-4] + ".dts", baseForElement), isEntry=True, @@ -225,9 +225,9 @@ def validateTestcase(self, testcase): PackageManager.packageInfo(self.modelXbrl.modelManager.cntlr, readMeFirstUri, reload=True, errors=modelXbrl.errors) else: # not a multi-schemaRef versioning report if self.useFileSource.isArchive and (os.path.isabs(readMeFirstUri) or not readMeFirstUri.endswith(".zip")): - modelXbrl = ModelXbrl.load(self.modelXbrl.modelManager, + modelXbrl = ModelXbrl.load(self.modelXbrl.modelManager, readMeFirstUri, - _("validating"), + _("validating"), base=baseForElement, useFileSource=self.useFileSource, errorCaptureLevel=errorCaptureLevel, @@ -259,9 +259,9 @@ def validateTestcase(self, testcase): _("Testcase variation validation exception: %(error)s, entry URL: %(instance)s"), modelXbrl=self.modelXbrl, instance=readMeFirstUri, error=err) continue # don't try to load this entry URL - modelXbrl = ModelXbrl.load(self.modelXbrl.modelManager, + modelXbrl = ModelXbrl.load(self.modelXbrl.modelManager, filesource, - _("validating"), + _("validating"), base=baseForElement, errorCaptureLevel=errorCaptureLevel, ixdsTarget=modelTestcaseVariation.ixdsTarget, @@ -285,7 +285,7 @@ def validateTestcase(self, testcase): modelXbrl.close() elif testcase.type == Type.REGISTRYTESTCASE: self.instValidator.validate(modelXbrl) # required to set up dimensions, etc - self.instValidator.executeCallTest(modelXbrl, modelTestcaseVariation.id, + self.instValidator.executeCallTest(modelXbrl, modelTestcaseVariation.id, modelTestcaseVariation.cfcnCall, modelTestcaseVariation.cfcnTest) self.determineTestStatus(modelTestcaseVariation, modelXbrl.errors) self.instValidator.close() @@ -353,7 +353,7 @@ def validateTestcase(self, testcase): if expectedDataFiles - foundDataFiles: modelXbrl.info("arelle:testcaseDataNotUsed", _("Variation %(id)s %(name)s data files not used: %(missingDataFiles)s"), - modelObject=modelTestcaseVariation, name=modelTestcaseVariation.name, id=modelTestcaseVariation.id, + modelObject=modelTestcaseVariation, name=modelTestcaseVariation.name, id=modelTestcaseVariation.id, missingDataFiles=", ".join(sorted(os.path.basename(f) for f in expectedDataFiles - foundDataFiles))) if foundDataFiles - expectedDataFiles: modelXbrl.info("arelle:testcaseDataUnexpected", @@ -380,16 +380,16 @@ def validateTestcase(self, testcase): if modelTestcaseVariation.resultIsInfoset and self.modelXbrl.modelManager.validateInfoset: for pluginXbrlMethod in pluginClassMethods("Validate.Infoset"): pluginXbrlMethod(modelXbrl, modelTestcaseVariation.resultInfosetUri) - infoset = ModelXbrl.load(self.modelXbrl.modelManager, + infoset = ModelXbrl.load(self.modelXbrl.modelManager, modelTestcaseVariation.resultInfosetUri, - _("loading result infoset"), + _("loading result infoset"), base=baseForElement, useFileSource=self.useFileSource, errorCaptureLevel=errorCaptureLevel) if infoset.modelDocument is None: modelXbrl.error("arelle:notLoaded", _("Variation %(id)s %(name)s result infoset not loaded: %(file)s"), - modelXbrl=testcase, id=modelTestcaseVariation.id, name=modelTestcaseVariation.name, + modelXbrl=testcase, id=modelTestcaseVariation.id, name=modelTestcaseVariation.name, file=os.path.basename(modelTestcaseVariation.resultXbrlInstance)) modelTestcaseVariation.status = "result infoset not loadable" else: # check infoset @@ -411,7 +411,7 @@ def validateTestcase(self, testcase): for pluginXbrlMethod in pluginClassMethods("TestcaseVariation.Validated"): pluginXbrlMethod(self.modelXbrl, modelXbrl, extraErrors, inputDTSes) self.determineTestStatus(modelTestcaseVariation, [e for inputDTSlist in inputDTSes.values() for inputDTS in inputDTSlist for e in inputDTS.errors] + extraErrors) # include infoset errors in status - if modelXbrl.formulaOutputInstance and self.noErrorCodes(modelTestcaseVariation.actual): + if modelXbrl.formulaOutputInstance and self.noErrorCodes(modelTestcaseVariation.actual): # if an output instance is created, and no string error codes, ignoring dict of assertion results, validate it modelXbrl.formulaOutputInstance.hasFormulae = False # block formulae on output instance (so assertion of input is not lost) self.instValidator.validate(modelXbrl.formulaOutputInstance, modelTestcaseVariation.parameters) @@ -420,7 +420,7 @@ def validateTestcase(self, testcase): formulaOutputInstance = modelXbrl.formulaOutputInstance modelXbrl.formulaOutputInstance = None # prevent it from being closed now self.instValidator.close() - compareIxResultInstance = (modelXbrl.modelDocument.type in (Type.INLINEXBRL, Type.INLINEXBRLDOCUMENTSET) and + compareIxResultInstance = (modelXbrl.modelDocument.type in (Type.INLINEXBRL, Type.INLINEXBRLDOCUMENTSET) and modelTestcaseVariation.resultXbrlInstanceUri is not None) if compareIxResultInstance: formulaOutputInstance = modelXbrl # compare modelXbrl to generated output instance @@ -433,16 +433,16 @@ def validateTestcase(self, testcase): errMsgPrefix = "formula" if resultIsXbrlInstance and formulaOutputInstance and formulaOutputInstance.modelDocument: _matchExpectedResultIDs = not modelXbrlHasFormulae # formula restuls have inconsistent IDs - expectedInstance = ModelXbrl.load(self.modelXbrl.modelManager, + expectedInstance = ModelXbrl.load(self.modelXbrl.modelManager, modelTestcaseVariation.resultXbrlInstanceUri, - _("loading expected result XBRL instance"), + _("loading expected result XBRL instance"), base=baseForElement, useFileSource=self.useFileSource, errorCaptureLevel=errorCaptureLevel) if expectedInstance.modelDocument is None: self.modelXbrl.error("{}:expectedResultNotLoaded".format(errMsgPrefix), _("Testcase \"%(name)s\" %(id)s expected result instance not loaded: %(file)s"), - modelXbrl=testcase, id=modelTestcaseVariation.id, name=modelTestcaseVariation.name, + modelXbrl=testcase, id=modelTestcaseVariation.id, name=modelTestcaseVariation.name, file=os.path.basename(modelTestcaseVariation.resultXbrlInstanceUri), messageCodes=("formula:expectedResultNotLoaded","ix:expectedResultNotLoaded")) modelTestcaseVariation.status = "result not loadable" @@ -521,17 +521,17 @@ def factFootnotes(fact, footnotesRelSet): del inputDTSes # dereference # update ui thread via modelManager (running in background here) self.modelXbrl.modelManager.viewModelObject(self.modelXbrl, modelTestcaseVariation.objectId()) - + _statusCounts = OrderedDict((("pass",0),("fail",0))) for tv in getattr(testcase, "testcaseVariations", ()): - _statusCounts[tv.status] = _statusCounts.get(tv.status, 0) + 1 + _statusCounts[tv.status] = _statusCounts.get(tv.status, 0) + 1 self.modelXbrl.info("arelle:testCaseResults", ", ".join("{}={}".format(k,c) for k, c in _statusCounts.items() if k)) - + self.modelXbrl.modelManager.showStatus(_("ready"), 2000) - + def noErrorCodes(self, modelTestcaseVariationActual): return not any(not isinstance(actual,dict) for actual in modelTestcaseVariationActual) - + def determineTestStatus(self, modelTestcaseVariation, errors): testcaseResultOptions = self.modelXbrl.modelManager.formulaOptions.testcaseResultOptions matchAllExpected = testcaseResultOptions == "match-all" @@ -579,10 +579,10 @@ def determineTestStatus(self, modelTestcaseVariation, errors): if isinstance(_exp,QName) and isinstance(testErr,_STR_BASE): errPrefix, sep, errLocalName = testErr.rpartition(":") if ((not sep and errLocalName in commaSpaceSplitPattern.split(_exp.localName.strip())) or # ESEF has comma separated list of localnames of errors - (_exp == qname(XbrlConst.errMsgPrefixNS.get(errPrefix) or - (errPrefix == _exp.prefix and _exp.namespaceURI), + (_exp == qname(XbrlConst.errMsgPrefixNS.get(errPrefix) or + (errPrefix == _exp.prefix and _exp.namespaceURI), errLocalName)) or - # XDT xml schema tests expected results + # XDT xml schema tests expected results (_exp.namespaceURI == XbrlConst.xdtSchemaErrorNS and errPrefix == "xmlSchema")): _expMatched = True elif type(testErr) == type(_exp): @@ -607,7 +607,7 @@ def determineTestStatus(self, modelTestcaseVariation, errors): _expectedList.remove(_exp) break if _passCount > 0: - if expectedCount is not None and (expectedCount != _passCount or + if expectedCount is not None and (expectedCount != _passCount or (matchAllExpected and expectedCount != numErrors)): status = "fail" else: @@ -630,7 +630,7 @@ def determineTestStatus(self, modelTestcaseVariation, errors): elif (isinstance(expected,dict) and # no assertions fired, are all the expected zero counts? all(countSatisfied == 0 and countNotSatisfied == 0 for countSatisfied, countNotSatisfied in expected.values())): status = "pass" # passes due to no counts expected - + else: status = "fail" modelTestcaseVariation.status = status @@ -647,7 +647,7 @@ def determineTestStatus(self, modelTestcaseVariation, errors): for error in _errors: if isinstance(error,dict): modelTestcaseVariation.actual.append(error) - + def determineNotLoadedTestStatus(self, modelTestcaseVariation, errors): if errors: self.determineTestStatus(modelTestcaseVariation, errors) @@ -657,7 +657,7 @@ def determineNotLoadedTestStatus(self, modelTestcaseVariation, errors): if expected in ("EFM.6.03.04", "EFM.6.03.05"): status = "pass" modelTestcaseVariation.status = status - + import logging class ValidationLogListener(logging.Handler): def __init__(self, logView): @@ -666,9 +666,9 @@ def __init__(self, logView): def flush(self): ''' Nothing to flush ''' def emit(self, logRecord): - # add to logView - msg = self.format(logRecord) - try: + # add to logView + msg = self.format(logRecord) + try: self.logView.append(msg) except: pass diff --git a/arelle/ValidateFilingText.py b/arelle/ValidateFilingText.py index 7346676443..387d0492c5 100644 --- a/arelle/ValidateFilingText.py +++ b/arelle/ValidateFilingText.py @@ -18,12 +18,12 @@ #EFM table 5-1 and all &xxx; patterns docCheckPattern = re.compile(r"&\w+;|[^0-9A-Za-z`~!@#$%&\*\(\)\.\-+ \[\]\{\}\|\\:;\"'<>,_?/=\t\n\r\f]") # won't match &#nnn; namedEntityPattern = re.compile("&[_A-Za-z\xC0-\xD6\xD8-\xF6\xF8-\xFF\u0100-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]" - r"[_\-\.:" + r"[_\-\.:" "\xB7A-Za-z0-9\xC0-\xD6\xD8-\xF6\xF8-\xFF\u0100-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u0300-\u036F\u203F-\u2040]*;") -#entityPattern = re.compile("&#[0-9]+;|" -# "&#x[0-9a-fA-F]+;|" +#entityPattern = re.compile("&#[0-9]+;|" +# "&#x[0-9a-fA-F]+;|" # "&[_A-Za-z\xC0-\xD6\xD8-\xF6\xF8-\xFF\u0100-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]" -# r"[_\-\.:" +# r"[_\-\.:" # "\xB7A-Za-z0-9\xC0-\xD6\xD8-\xF6\xF8-\xFF\u0100-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u0300-\u036F\u203F-\u2040]*;") inlinePattern = re.compile(r"xmlns:[\w.-]+=['\"]http://www.xbrl.org/2013/inlineXBRL['\"]") @@ -405,7 +405,7 @@ "area", "base", "basefont", "br", "col", "frame", "hr", "img", "input", "isindex", "link", "meta", "param", # xhtml # elements which can have no text node siblings, tested with IE, Chrome and Safari "td", "tr" - } + } ModelDocumentTypeINLINEXBRL = None @@ -442,7 +442,7 @@ def close(self): pass _parser = XMLParser(recover=True, huge_tree=True, target=checkFileType()) _isTestcase = False mayBeInline = isInline = False - + with file as f: while True: line = f.readline() @@ -460,7 +460,7 @@ def close(self): pass if len(text) == 1: modelXbrl.error(("EFM.5.02.01.01", "FERC.5.02.01.01"), _("Disallowed character '%(text)s' (%(unicodeIndex)s) in file %(file)s at line %(line)s col %(column)s"), - modelDocument=filepath, text=text, unicodeIndex="U+{:04X}".format(ord(text)), + modelDocument=filepath, text=text, unicodeIndex="U+{:04X}".format(ord(text)), file=os.path.basename(filepath), line=lineNum, column=match.start()) else: modelXbrl.error(("EFM.5.02.01.01", "FERC.5.02.01.01"), @@ -509,10 +509,10 @@ def loadDTD(modelXbrl): _isInline = modelXbrl.modelDocument.type == ModelDocumentTypeINLINEXBRL if isInlineDTD is None or isInlineDTD != _isInline: isInlineDTD = _isInline - with open(os.path.join(modelXbrl.modelManager.cntlr.configDir, + with open(os.path.join(modelXbrl.modelManager.cntlr.configDir, "xhtml1-strict-ix.dtd" if _isInline else "edbody.dtd")) as fh: edbodyDTD = DTD(fh) - + def removeEntities(text): ''' ARELLE-128 entitylessText = [] @@ -535,14 +535,14 @@ def validateTextBlockFacts(modelXbrl): checkedGraphicsFiles = set() # only check any graphics file reference once per fact allowedExternalHrefPattern = modelXbrl.modelManager.disclosureSystem.allowedExternalHrefPattern allowedImageTypes = modelXbrl.modelManager.disclosureSystem.allowedImageTypes - + if isInlineDTD: htmlBodyTemplate = "
\n{0}\n
\n" else: htmlBodyTemplate = "\n{0}\n\n" _xhtmlNs = "{{{}}}".format(xhtml) _xhtmlNsLen = len(_xhtmlNs) - + for f1 in modelXbrl.facts: # build keys table for 6.5.14 concept = f1.concept @@ -579,11 +579,11 @@ def validateTextBlockFacts(modelXbrl): textblockXml = XML(xmlBodyWithoutEntities) if not edbodyDTD.validate( textblockXml ): errors = edbodyDTD.error_log.filter_from_errors() - htmlError = any(e.type_name in ("DTD_INVALID_CHILD", "DTD_UNKNOWN_ATTRIBUTE") + htmlError = any(e.type_name in ("DTD_INVALID_CHILD", "DTD_UNKNOWN_ATTRIBUTE") for e in errors) modelXbrl.error(("EFM.6.05.16","FERC.6.05.16") if htmlError else ("EFM.6.05.15.dtdError", "GFM.1.02.14", "FERC.6.05.15.dtdError"), _("Fact %(fact)s contextID %(contextID)s has text which causes the XML error %(error)s"), - modelObject=f1, fact=f1.qname, contextID=f1.contextID, + modelObject=f1, fact=f1.qname, contextID=f1.contextID, error=', '.join(e.message for e in errors), messageCodes=("EFM.6.05.16", "EFM.6.05.15.dtdError", "GFM.1.02.14", "FERC.6.05.16", "FERC.6.05.15.dtdError")) for elt in textblockXml.iter(): @@ -608,7 +608,7 @@ def validateTextBlockFacts(modelXbrl): _("%(validatedObjectLabel)s has disallowed attribute on element <%(element)s>: %(attribute)s=\"%(value)s\""), modelObject=elt, validatedObjectLabel=validatedObjectLabel, element=eltTag, attribute=attrTag, value=attrValue) - if ((attrTag == "href" and eltTag == "a") or + if ((attrTag == "href" and eltTag == "a") or (attrTag == "src" and eltTag == "img")): if "javascript:" in attrValue: modelXbrl.error(("EFM.6.05.16.activeContent", "FERC.6.05.16.activeContent"), @@ -667,12 +667,12 @@ def validateTextBlockFacts(modelXbrl): modelXbrl.error(("EFM.6.05.15", "GFM.1.02.14", "FERC.6.05.15"), _("Fact %(fact)s contextID %(contextID)s has text which causes the XML error %(error)s"), modelObject=f1, fact=f1.qname, contextID=f1.contextID, error=err) - + checkedGraphicsFiles.clear() - + #handler.fact = None #handler.modelXbrl = None - + def copyHtml(sourceXml, targetHtml): for sourceChild in sourceXml.iterchildren(): if isinstance(sourceChild, ModelObject): @@ -681,12 +681,12 @@ def copyHtml(sourceXml, targetHtml): for attrTag, attrValue in sourceChild.items(): targetChild.set("lang" if attrTag == "{http://www.w3.org/XML/1998/namespace}lang" else attrTag, attrValue) copyHtml(sourceChild, targetChild) - + def validateFootnote(modelXbrl, footnote): #handler = TextBlockHandler(modelXbrl) loadDTD(modelXbrl) validatedObjectLabel = _("Footnote {}").format(footnote.get("{http://www.w3.org/1999/xlink}label")) - + try: footnoteHtml = XML("") copyHtml(footnote, footnoteHtml) # convert from xhtml to html (with no prefixes) for DTD validation @@ -730,7 +730,7 @@ def validateHtmlContent(modelXbrl, referenceElt, htmlEltTree, validatedObjectLab if eltTag == "a" and "href" not in elt.keys() and any(a.tag not in _anchorAncestorTags for a in elt.iterancestors()): modelXbrl.warning(("EFM.5.02.05.anchorElementPosition", "FERC.5.02.05.anchorElementPosition"), _("If element does not have attribute @href, it should not have any ancestors other than html, body, or div. Disallowed ancestors: %(disallowedAncestors)s"), - modelObject=elt, disallowedAncestors=", ".join(a.tag.rpartition('}')[2] for a in elt.iterancestors() if a.tag not in _anchorAncestorTags)) + modelObject=elt, disallowedAncestors=", ".join(a.tag.rpartition('}')[2] for a in elt.iterancestors() if a.tag not in _anchorAncestorTags)) for attrTag, attrValue in elt.items(): if isInline: if attrTag in efmBlockedInlineHtmlElementAttributes.get(eltTag,()): @@ -748,7 +748,7 @@ def validateHtmlContent(modelXbrl, referenceElt, htmlEltTree, validatedObjectLab _("%(validatedObjectLabel)s has disallowed attribute on element <%(element)s>: xsi:schemaLocation=\"%(value)s\""), modelObject=elt, validatedObjectLabel=validatedObjectLabel, element=eltTag, value=attrValue) - if ((attrTag == "href" and eltTag == "a") or + if ((attrTag == "href" and eltTag == "a") or (attrTag == "src" and eltTag == "img")): if "javascript:" in attrValue: modelXbrl.error(messageCodePrefix + "activeContent", @@ -818,7 +818,7 @@ def validateHtmlContent(modelXbrl, referenceElt, htmlEltTree, validatedObjectLab ''' if parent is None: parent = footnote - + if parent != footnote: for attrName, attrValue in footnote.items(): if not (attrName in htmlAttributes and \ @@ -841,7 +841,7 @@ def validateHtmlContent(modelXbrl, referenceElt, htmlEltTree, validatedObjectLab _("Footnote %(xlinkLabel)s has an invalid external reference in '%(attribute)s' for <%(element)s>: %(value)s"), modelObject=parent, xlinkLabel=parent.get("{http://www.w3.org/1999/xlink}label"), attribute=attrName, element=footnote.localName, value=attrValue) - + for child in footnote.iterchildren(): if isinstance(child,ModelObject): #element if not child.localName in bodyTags: @@ -855,15 +855,15 @@ def validateHtmlContent(modelXbrl, referenceElt, htmlEltTree, validatedObjectLab #handler.modelXbrl = None -class TextBlockHandler(xml.sax.ContentHandler, xml.sax.ErrorHandler): +class TextBlockHandler(xml.sax.ContentHandler, xml.sax.ErrorHandler): + + def __init__ (self, modelXbrl): + self.modelXbrl = modelXbrl - def __init__ (self, modelXbrl): - self.modelXbrl = modelXbrl - def startDocument(self): self.nestedBodyCount = 0 - - def startElement(self, name, attrs): + + def startElement(self, name, attrs): if name == "body": self.nestedBodyCount += 1 if self.nestedBodyCount == 1: # outer body is ok @@ -905,30 +905,30 @@ def characters (self, ch): def endElement(self, name): if name == "body": self.nestedBodyCount -= 1 - + def error(self, err): self.modelXbrl.error("EFM.6.05.15", _("Fact %(fact)s of context %(contextID)s has text which causes the XML error %(error)s line %(line)s column %(column)s"), - modelObject=self.fact, fact=self.fact.qname, contextID=self.fact.contextID, + modelObject=self.fact, fact=self.fact.qname, contextID=self.fact.contextID, error=err.getMessage(), line=err.getLineNumber(), column=err.getColumnNumber()) - + def fatalError(self, err): msg = err.getMessage() self.modelXbrl.error("EFM.6.05.15", _("Fact %(fact)s of context %(contextID)s has text which causes the XML fatal error %(error)s line %(line)s column %(column)s"), - modelObject=self.fact, fact=self.fact.qname, contextID=self.fact.contextID, + modelObject=self.fact, fact=self.fact.qname, contextID=self.fact.contextID, error=err.getMessage(), line=err.getLineNumber(), column=err.getColumnNumber()) - + def warning(self, err): self.modelXbrl.warning("EFM.6.05.15", _("Fact %(fact)s of context %(contextID)s has text which causes the XML warning %(error)s line %(line)s column %(column)s"), - modelObject=self.fact, fact=self.fact.qname, contextID=self.fact.contextID, + modelObject=self.fact, fact=self.fact.qname, contextID=self.fact.contextID, error=err.getMessage(), line=err.getLineNumber(), column=err.getColumnNumber()) ''' - + def validateGraphicHeaderType(data): # Support both JFIF APP0 (0xffe0 + 'JFIF') and APP1 Exif (0xffe1 + 'Exif') JPEG application segment types - if ((data[:4] == b'\xff\xd8\xff\xe0' and data[6:11] == b'JFIF\0') or + if ((data[:4] == b'\xff\xd8\xff\xe0' and data[6:11] == b'JFIF\0') or (data[:4] == b'\xff\xd8\xff\xe1' and data[6:11] == b'Exif\0')): return "jpg" elif data[:3] == b"GIF" and data[3:6] in (b'89a', b'89b', b'87a'): @@ -968,9 +968,9 @@ def referencedFiles(modelXbrl, localFilesOnly=True): def addReferencedFile(docElt, elt): if elt.tag in ("a", "img", "{http://www.w3.org/1999/xhtml}a", "{http://www.w3.org/1999/xhtml}img"): for attrTag, attrValue in elt.items(): - if (attrTag in ("href", "src") and + if (attrTag in ("href", "src") and scheme(attrValue) not in ("data", "javascript") and ( - not localFilesOnly or + not localFilesOnly or (not isHttpUrl(attrValue) and not os.path.isabs(attrValue)))): attrValue = attrValue.partition('#')[0].strip() # remove anchor if attrValue not in ("", "."): # ignore anchor references to base document @@ -998,4 +998,4 @@ def addReferencedFile(docElt, elt): for xbrlInstRoot in xbrlInstRoots: for elt in xbrlInstRoot.iter("{http://www.w3.org/1999/xhtml}a", "{http://www.w3.org/1999/xhtml}img"): addReferencedFile(elt, elt) - return referencedFiles \ No newline at end of file + return referencedFiles diff --git a/arelle/ValidateFormula.py b/arelle/ValidateFormula.py index 6734175676..a2b13a566f 100644 --- a/arelle/ValidateFormula.py +++ b/arelle/ValidateFormula.py @@ -9,7 +9,7 @@ from threading import Timer from arelle.ModelFormulaObject import (ModelParameter, ModelInstance, ModelVariableSet, - ModelFormula, ModelTuple, ModelVariable, ModelFactVariable, + ModelFormula, ModelTuple, ModelVariable, ModelFactVariable, ModelVariableSetAssertion, ModelConsistencyAssertion, ModelExistenceAssertion, ModelValueAssertion, ModelAssertionSeverity, ModelPrecondition, ModelConceptName, Trace, @@ -22,7 +22,7 @@ from arelle.PythonUtil import normalizeSpace from arelle.XmlValidate import validate as xml_validate from arelle import (XbrlConst, XmlUtil, ModelXbrl, ModelDocument, XPathParser, XPathContext, FunctionXs, - ValidateXbrlDimensions) + ValidateXbrlDimensions) formulaIdWhitespacesSeparatedPattern = re.compile(r"(\w+\s)*(\w+)$") # prenormalized IDs list @@ -34,8 +34,8 @@ def __repr__(self): arcroleChecks = { - XbrlConst.equalityDefinition: (None, - XbrlConst.qnEqualityDefinition, + XbrlConst.equalityDefinition: (None, + XbrlConst.qnEqualityDefinition, "xbrlve:info"), XbrlConst.assertionSet: (XbrlConst.qnAssertionSet, (XbrlConst.qnAssertion, XbrlConst.qnVariableSetAssertion), @@ -53,17 +53,17 @@ def __repr__(self): XbrlConst.variableSet: (XbrlConst.qnVariableSet, (XbrlConst.qnVariableVariable, XbrlConst.qnParameter), "xbrlve:info"), - XbrlConst.variableSetFilter: (XbrlConst.qnVariableSet, - XbrlConst.qnVariableFilter, + XbrlConst.variableSetFilter: (XbrlConst.qnVariableSet, + XbrlConst.qnVariableFilter, "xbrlve:info"), - XbrlConst.variableFilter: (XbrlConst.qnFactVariable, - XbrlConst.qnVariableFilter, + XbrlConst.variableFilter: (XbrlConst.qnFactVariable, + XbrlConst.qnVariableFilter, "xbrlve:info"), - XbrlConst.booleanFilter: (XbrlConst.qnVariableFilter, - XbrlConst.qnVariableFilter, + XbrlConst.booleanFilter: (XbrlConst.qnVariableFilter, + XbrlConst.qnVariableFilter, "xbrlbfe:info"), - XbrlConst.consistencyAssertionFormula: (XbrlConst.qnConsistencyAssertion, - None, + XbrlConst.consistencyAssertionFormula: (XbrlConst.qnConsistencyAssertion, + None, "xbrlca:info"), XbrlConst.functionImplementation: (XbrlConst.qnCustomFunctionSignature, XbrlConst.qnCustomFunctionImplementation, @@ -77,7 +77,7 @@ def __repr__(self): XbrlConst.qnTableAspectNode), "xbrlte:breakdownTreeSourceError", "xbrlte:breakdownTreeTargetError"), - XbrlConst.tableDefinitionNodeSubtree: (XbrlConst.qnTableDefinitionNode, + XbrlConst.tableDefinitionNodeSubtree: (XbrlConst.qnTableDefinitionNode, XbrlConst.qnTableDefinitionNode, "xbrlte:definitionNodeSubtreeSourceError", "xbrlte:definitionNodeSubtreeTargetError", @@ -86,16 +86,16 @@ def __repr__(self): None, "xbrlte:prohibitedDefinitionNodeSubtreeSourceError", None), - XbrlConst.tableFilter: (XbrlConst.qnTableTable, + XbrlConst.tableFilter: (XbrlConst.qnTableTable, XbrlConst.qnVariableFilter, "xbrlte:tableFilterSourceError", "xbrlte:tableFilterTargetError"), - XbrlConst.tableParameter: (XbrlConst.qnTableTable, + XbrlConst.tableParameter: (XbrlConst.qnTableTable, XbrlConst.qnParameter, "xbrlte:tableParameterSourceError", "xbrlte:tableParameterTargetError"), XbrlConst.tableAspectNodeFilter:(XbrlConst.qnTableAspectNode, - XbrlConst.qnVariableFilter, + XbrlConst.qnVariableFilter, "xbrlte:aspectNodeFilterSourceError", "xbrlte:aspectNodeFilterTargetError"), XbrlConst.tableBreakdownMMDD: (XbrlConst.qnTableTableMMDD, @@ -107,7 +107,7 @@ def __repr__(self): XbrlConst.qnTableAspectNodeMMDD), "xbrlte:breakdownTreeSourceError", "xbrlte:breakdownTreeTargetError"), - XbrlConst.tableDefinitionNodeSubtreeMMDD: (XbrlConst.qnTableDefinitionNodeMMDD, + XbrlConst.tableDefinitionNodeSubtreeMMDD: (XbrlConst.qnTableDefinitionNodeMMDD, XbrlConst.qnTableDefinitionNodeMMDD, "xbrlte:definitionNodeSubtreeSourceError", "xbrlte:definitionNodeSubtreeTargetError", @@ -116,83 +116,83 @@ def __repr__(self): None, "xbrlte:prohibitedDefinitionNodeSubtreeSourceError", None), - XbrlConst.tableFilterMMDD: (XbrlConst.qnTableTableMMDD, + XbrlConst.tableFilterMMDD: (XbrlConst.qnTableTableMMDD, XbrlConst.qnVariableFilter, "xbrlte:tableFilterSourceError", "xbrlte:tableFilterTargetError"), - XbrlConst.tableParameterMMDD: (XbrlConst.qnTableTableMMDD, + XbrlConst.tableParameterMMDD: (XbrlConst.qnTableTableMMDD, XbrlConst.qnParameter, "xbrlte:tableParameterSourceError", "xbrlte:tableParameterTargetError"), XbrlConst.tableAspectNodeFilterMMDD:(XbrlConst.qnTableAspectNodeMMDD, - XbrlConst.qnVariableFilter, + XbrlConst.qnVariableFilter, "xbrlte:aspectNodeFilterSourceError", "xbrlte:aspectNodeFilterTargetError"), XbrlConst.tableBreakdown201305: (XbrlConst.qnTableTable201305, - XbrlConst.qnTableBreakdown201305, + XbrlConst.qnTableBreakdown201305, "xbrlte:info"), XbrlConst.tableBreakdownTree201305:(XbrlConst.qnTableBreakdown201305, (XbrlConst.qnTableClosedDefinitionNode201305, XbrlConst.qnTableAspectNode201305), "xbrlte:info"), - XbrlConst.tableDefinitionNodeSubtree201305: (XbrlConst.qnTableClosedDefinitionNode201305, - XbrlConst.qnTableClosedDefinitionNode201305, + XbrlConst.tableDefinitionNodeSubtree201305: (XbrlConst.qnTableClosedDefinitionNode201305, + XbrlConst.qnTableClosedDefinitionNode201305, "xbrlte:info"), - XbrlConst.tableFilter201305: (XbrlConst.qnTableTable201305, - XbrlConst.qnVariableFilter, + XbrlConst.tableFilter201305: (XbrlConst.qnTableTable201305, + XbrlConst.qnVariableFilter, "xbrlte:info"), XbrlConst.tableAspectNodeFilter201305:(XbrlConst.qnTableAspectNode201305, - XbrlConst.qnVariableFilter, + XbrlConst.qnVariableFilter, "xbrlte:info"), XbrlConst.tableBreakdown201301: (XbrlConst.qnTableTable201301, - (XbrlConst.qnTableClosedDefinitionNode201301, - XbrlConst.qnTableFilterNode201301, - XbrlConst.qnTableSelectionNode201301, + (XbrlConst.qnTableClosedDefinitionNode201301, + XbrlConst.qnTableFilterNode201301, + XbrlConst.qnTableSelectionNode201301, XbrlConst.qnTableTupleNode201301), "xbrlte:info"), XbrlConst.tableAxis2011: (XbrlConst.qnTableTable2011, - (XbrlConst.qnTablePredefinedAxis2011, + (XbrlConst.qnTablePredefinedAxis2011, XbrlConst.qnTableFilterAxis2011, - XbrlConst.qnTableSelectionAxis2011, + XbrlConst.qnTableSelectionAxis2011, XbrlConst.qnTableTupleAxis2011), "xbrlte:info"), - XbrlConst.tableFilter201301: (XbrlConst.qnTableTable201301, - XbrlConst.qnVariableFilter, + XbrlConst.tableFilter201301: (XbrlConst.qnTableTable201301, + XbrlConst.qnVariableFilter, "xbrlte:info"), - XbrlConst.tableFilter2011: (XbrlConst.qnTableTable2011, - XbrlConst.qnVariableFilter, + XbrlConst.tableFilter2011: (XbrlConst.qnTableTable2011, + XbrlConst.qnVariableFilter, "xbrlte:info"), - XbrlConst.tableDefinitionNodeSubtree201301: (XbrlConst.qnTableClosedDefinitionNode201301, - XbrlConst.qnTableClosedDefinitionNode201301, + XbrlConst.tableDefinitionNodeSubtree201301: (XbrlConst.qnTableClosedDefinitionNode201301, + XbrlConst.qnTableClosedDefinitionNode201301, "xbrlte:info"), - XbrlConst.tableAxisSubtree2011: (XbrlConst.qnTablePredefinedAxis2011, - XbrlConst.qnTablePredefinedAxis2011, + XbrlConst.tableAxisSubtree2011: (XbrlConst.qnTablePredefinedAxis2011, + XbrlConst.qnTablePredefinedAxis2011, "xbrlte:info"), XbrlConst.tableFilterNodeFilter2011:(XbrlConst.qnTableFilterNode201301, - XbrlConst.qnVariableFilter, + XbrlConst.qnVariableFilter, "xbrlte:info"), XbrlConst.tableAxisFilter2011: (XbrlConst.qnTableFilterAxis2011, - XbrlConst.qnVariableFilter, + XbrlConst.qnVariableFilter, "xbrlte:info"), XbrlConst.tableAxisFilter201205:(XbrlConst.qnTableFilterAxis2011, - XbrlConst.qnVariableFilter, + XbrlConst.qnVariableFilter, "xbrlte:info"), XbrlConst.tableTupleContent201301: ((XbrlConst.qnTableTupleNode201301, - XbrlConst.qnTableTupleAxis2011), + XbrlConst.qnTableTupleAxis2011), (XbrlConst.qnTableRuleNode201301, - XbrlConst.qnTableRuleAxis2011), + XbrlConst.qnTableRuleAxis2011), "xbrlte:info"), } def checkBaseSet(val, arcrole, ELR, relsSet): # check hypercube-dimension relationships - + if arcrole in arcroleChecks: arcroleCheck = arcroleChecks[arcrole] notFromQname = notToQname = notFromErrCode = notToErrCode = None if len(arcroleCheck) == 3: fromQname, toQname, fromErrCode = arcroleCheck toErrCode = fromErrCode - elif len(arcroleCheck) == 4: + elif len(arcroleCheck) == 4: fromQname, toQname, fromErrCode, toErrCode = arcroleCheck elif len(arcroleCheck) == 8: fromQname, toQname, fromErrCode, toErrCode, notFromQname, notToQname, notFromErrCode, notToErrCode = arcroleCheck @@ -203,7 +203,7 @@ def checkBaseSet(val, arcrole, ELR, relsSet): fromMdlObj = modelRel.fromModelObject toMdlObj = modelRel.toModelObject if fromQname: - if (fromMdlObj is None or + if (fromMdlObj is None or # if not in subs group, only warn if the namespace has a loaded schema, otherwise no complaint (not val.modelXbrl.isInSubstitutionGroup(fromMdlObj.elementQname, fromQname) and fromMdlObj.elementQname.namespaceURI in val.modelXbrl.namespaceDocs)): @@ -215,7 +215,7 @@ def checkBaseSet(val, arcrole, ELR, relsSet): _("Relationship from %(xlinkFrom)s to %(xlinkTo)s should not have an %(element)s source"), modelObject=modelRel, xlinkFrom=modelRel.fromLabel, xlinkTo=modelRel.toLabel, element=fromQname) if toQname: - if (toMdlObj is None or + if (toMdlObj is None or (not val.modelXbrl.isInSubstitutionGroup(toMdlObj.elementQname, toQname) and toMdlObj.elementQname.namespaceURI in val.modelXbrl.namespaceDocs)): val.modelXbrl.log(level, toErrCode, @@ -248,35 +248,35 @@ def checkBaseSet(val, arcrole, ELR, relsSet): val.modelXbrl.error("seve:assertionSeverityTargetError", _("Target of assertion-unsatisfied-severity relationship must be a severity element in the published severities linkbase."), modelObject=[relTo] + rels) - + def executeCallTest(val, name, callTuple, testTuple): if callTuple: XPathParser.initializeParser(val.modelXbrl.modelManager) - - try: + + try: val.modelXbrl.modelManager.showStatus(_("Executing call")) callExprStack = XPathParser.parse(val, callTuple[0], callTuple[1], name + " call", Trace.CALL) xpathContext = XPathContext.create(val.modelXbrl, sourceElement=callTuple[1]) result = xpathContext.evaluate(callExprStack) - xpathContext.inScopeVars[qname('result',noPrefixIsNoNamespace=True)] = result - val.modelXbrl.info("formula:trace", - _("%(name)s result %(result)s"), + xpathContext.inScopeVars[qname('result',noPrefixIsNoNamespace=True)] = result + val.modelXbrl.info("formula:trace", + _("%(name)s result %(result)s"), modelObject=callTuple[1], name=name, result=str(result)) - + if testTuple: val.modelXbrl.modelManager.showStatus(_("Executing test")) testExprStack = XPathParser.parse(val, testTuple[0], testTuple[1], name + " test", Trace.CALL) testResult = xpathContext.effectiveBooleanValue( None, xpathContext.evaluate(testExprStack) ) - + if testResult: val.modelXbrl.info("cfcn:testPass", - _("Test %(name)s result %(result)s"), + _("Test %(name)s result %(result)s"), modelObject=testTuple[1], name=name, result=str(testResult)) else: val.modelXbrl.error("cfcn:testFail", - _("Test %(name)s result %(result)s"), + _("Test %(name)s result %(result)s"), modelObject=testTuple[1], name=name, result=str(testResult)) - + xpathContext.close() # dereference except XPathContext.XPathException as err: @@ -285,14 +285,14 @@ def executeCallTest(val, name, callTuple, testTuple): modelObject=callTuple[1], name=name, error=err.message, errorSource=err.sourceErrorIndication) val.modelXbrl.modelManager.showStatus(_("ready"), 2000) - + def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compileOnly=False): for e in ("xbrl.5.1.4.3:cycles", "xbrlgene:violatedCyclesConstraint"): if e in val.modelXbrl.errors: val.modelXbrl.info("info", _("Formula validation skipped due to %(error)s error"), modelObject=val.modelXbrl, error=e) return - + val.modelXbrl.profileStat() formulaOptions = val.modelXbrl.modelManager.formulaOptions if XPathParser.initializeParser(val.modelXbrl.modelManager): @@ -300,7 +300,7 @@ def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compile val.modelXbrl.modelManager.showStatus(statusMsg) val.modelXbrl.profileActivity() initialErrorCount = val.modelXbrl.logCount.get(logging._checkLevel('ERROR'), 0) - + # global parameter names parameterQnames = set() instanceQnames = set() @@ -317,7 +317,7 @@ def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compile if isinstance(modelParameter, ModelInstance): instanceQnames.add(paramQname) # duplicates checked on loading modelDocument - + #resolve dependencies resolvedAParameter = True while (resolvedAParameter): @@ -332,7 +332,7 @@ def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compile for paramQname in parameterQnames: if paramQname not in dependencyResolvedParameters: circularOrUndefDependencies = parameterDependencies[paramQname] - dependencyResolvedParameters - undefinedVars = circularOrUndefDependencies - parameterQnames + undefinedVars = circularOrUndefDependencies - parameterQnames paramsCircularDep = circularOrUndefDependencies - undefinedVars if len(undefinedVars) > 0: val.modelXbrl.error("xbrlve:unresolvedDependency", @@ -345,7 +345,7 @@ def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compile modelObject=val.modelXbrl.qnameParameters[paramQname], name=paramQname, dependencies=", ".join((str(d) for d in paramsCircularDep)) ) val.modelXbrl.profileActivity("... formula parameter checks", minTimeToShow=1.0) - + for custFnSig in val.modelXbrl.modelCustomFunctionSignatures.values(): # entries indexed by qname, arity are signature, by qname are just for parser (value=None) if custFnSig is not None: @@ -358,12 +358,12 @@ def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compile _outputType = custFnSig.outputType if _outputType and _outputType.namespaceURI == XbrlConst.xsd and not FunctionXs.isXsType(_outputType.localName): val.modelXbrl.error("xbrlve:invalidDatatypeInCustomFunctionSignature", - _("Custom Function Signature %(name)s output type %(type)s is not valid"), + _("Custom Function Signature %(name)s output type %(type)s is not valid"), modelObject=custFnSig, name=custFnQname, type=_outputType) for _inputType in custFnSig.inputTypes: if _inputType and _inputType.namespaceURI == XbrlConst.xsd and not FunctionXs.isXsType(_inputType.localName): val.modelXbrl.error("xbrlve:invalidDatatypeInCustomFunctionSignature", - _("Custom Function Signature %(name)s input type %(type)s is not valid"), + _("Custom Function Signature %(name)s input type %(type)s is not valid"), modelObject=custFnSig, name=custFnQname, type=_inputType) # any custom function implementations? for modelRel in val.modelXbrl.relationshipSet(XbrlConst.functionImplementation).fromModelObject(custFnSig): @@ -372,9 +372,9 @@ def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compile if len(custFnImpl.inputNames) != len(custFnSig.inputTypes): val.modelXbrl.error("xbrlcfie:inputMismatch", _("Custom function %(name)s signature has %(parameterCountSignature)s parameters but implementation has %(parameterCountImplementation)s, must be matching"), - modelObject=custFnSig, name=custFnQname, + modelObject=custFnSig, name=custFnQname, parameterCountSignature=len(custFnSig.inputTypes), parameterCountImplementation=len(custFnImpl.inputNames) ) - + for custFnImpl in val.modelXbrl.modelCustomFunctionImplementations: if not val.modelXbrl.relationshipSet(XbrlConst.functionImplementation).toModelObject(custFnImpl): val.modelXbrl.error("xbrlcfie:missingCFIRelationship", @@ -382,12 +382,12 @@ def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compile modelObject=custFnSig, xlinkLabel=custFnImpl.xlinkLabel) custFnImpl.compile() val.modelXbrl.profileActivity("... custom function checks and compilation", minTimeToShow=1.0) - + # xpathContext is needed for filter setup for expressions such as aspect cover filter # determine parameter values - + if xpathContext is None: - xpathContext = XPathContext.create(val.modelXbrl) + xpathContext = XPathContext.create(val.modelXbrl) xpathContext.parameterQnames = parameterQnames # needed for formula filters to determine variable dependencies for paramQname in orderedParameters: modelParameter = val.modelXbrl.qnameParameters[paramQname] @@ -395,7 +395,7 @@ def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compile asType = modelParameter.asType if asType and asType.namespaceURI == XbrlConst.xsd and not FunctionXs.isXsType(asType.localName): val.modelXbrl.error("xbrlve:parameterTypeMismatch", - _("Parameter %(name)s type %(type)s is not valid"), + _("Parameter %(name)s type %(type)s is not valid"), modelObject=modelParameter, name=paramQname, type=asType) asLocalName = asType.localName if asType else "string" try: @@ -417,27 +417,27 @@ def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compile result = value # use value type from parameter input if formulaOptions.traceParameterInputValue: val.modelXbrl.info("formula:trace", - _("Parameter %(name)s input value %(input)s"), + _("Parameter %(name)s input value %(input)s"), modelObject=modelParameter, name=paramQname, input=result) - xpathContext.inScopeVars[paramQname] = result # make visible to subsequent parameter expression + xpathContext.inScopeVars[paramQname] = result # make visible to subsequent parameter expression elif modelParameter.isRequired: val.modelXbrl.error("xbrlve:missingParameterValue", - _("Parameter %(name)s is required but not input"), + _("Parameter %(name)s is required but not input"), modelObject=modelParameter, name=paramQname) elif not modelParameter.selectProg: val.modelXbrl.error("xbrlve:missingParameterValue", - _("Parameter %(name)s does not have a select attribute"), + _("Parameter %(name)s does not have a select attribute"), modelObject=modelParameter, name=paramQname) else: result = modelParameter.evaluate(xpathContext, asType) if formulaOptions.traceParameterExpressionResult: val.modelXbrl.info("formula:trace", - _("Parameter %(name)s select result %(result)s"), + _("Parameter %(name)s select result %(result)s"), modelObject=modelParameter, name=paramQname, result=result) - xpathContext.inScopeVars[paramQname] = result # make visible to subsequent parameter expression + xpathContext.inScopeVars[paramQname] = result # make visible to subsequent parameter expression except XPathContext.XPathException as err: val.modelXbrl.error("xbrlve:parameterTypeMismatch" if err.code == "err:FORG0001" else err.code, - _("Parameter \n%(name)s \nException: \n%(error)s"), + _("Parameter \n%(name)s \nException: \n%(error)s"), modelObject=modelParameter, name=paramQname, error=err.message, messageCodes=("xbrlve:parameterTypeMismatch", "err:FORG0001")) ''' Removed as per WG discussion 2012-12-20. This duplication checking unfairly presupposes URI based @@ -449,17 +449,17 @@ def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compile for instanceModelXbrl in instanceModelXbrls: if instanceModelXbrl.uri in instanceUris: val.modelXbrl.error("xbrlvarinste:inputInstanceDuplication", - _("Input instance resource %(instName)s has multiple XBRL instances %(uri)s"), + _("Input instance resource %(instName)s has multiple XBRL instances %(uri)s"), modelObject=modelParameter, instName=paramQname, uri=instanceModelXbrl.uri) instanceUris.add(instanceModelXbrl.uri) if val.parameters and XbrlConst.qnStandardInputInstance in val.parameters: # standard input instance has if len(val.parameters[XbrlConst.qnStandardInputInstance][1]) != 1: val.modelXbrl.error("xbrlvarinste:standardInputInstanceNotUnique", - _("Standard input instance resource parameter has multiple XBRL instances"), + _("Standard input instance resource parameter has multiple XBRL instances"), modelObject=modelParameter) ''' val.modelXbrl.profileActivity("... parameter checks and select evaluation", minTimeToShow=1.0) - + val.modelXbrl.profileStat(_("parametersProcessing")) # check typed dimension equality test @@ -476,7 +476,7 @@ def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compile else: modelEqualityDefinition.compile() val.modelXbrl.modelFormulaEqualityDefinitions[typedDomainElt] = modelEqualityDefinition - + if parametersOnly: return @@ -486,7 +486,7 @@ def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compile produceOutputXbrlInstance = False instanceProducingVariableSets = defaultdict(list) - + for modelVariableSet in val.modelXbrl.modelVariableSets: varSetInstanceDependencies = set() if isinstance(modelVariableSet, ModelFormula): @@ -500,9 +500,9 @@ def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compile else: val.modelXbrl.info("arelle:multipleOutputInstances", _("Multiple output instances for formula %(xlinkLabel)s, to names %(instanceTo)s, %(instanceTo2)s"), - modelObject=modelVariableSet, xlinkLabel=modelVariableSet.xlinkLabel, + modelObject=modelVariableSet, xlinkLabel=modelVariableSet.xlinkLabel, instanceTo=instanceQname, instanceTo2=instance.instanceQname) - if instanceQname is None: + if instanceQname is None: instanceQname = XbrlConst.qnStandardOutputInstance instanceQnames.add(instanceQname) modelVariableSet.fromInstanceQnames = None # required if referred to by variables scope chaining @@ -526,7 +526,7 @@ def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compile _("Variable set %(xlinkLabel)s, aspect model %(aspectModel)s not recognized"), modelObject=modelVariableSet, xlinkLabel=modelVariableSet.xlinkLabel, aspectModel=modelVariableSet.aspectModel) modelVariableSet.hasConsistencyAssertion = False - + #determine dependencies within variable sets nameVariables = {} qnameRels = {} @@ -563,7 +563,7 @@ def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compile modelObject=modelRel, xlinkLabel=modelVariableSet.xlinkLabel, name=modelRel.variablename ) checkVariablesScopeVisibleQnames(val, nameVariables, definedNamesSet, modelVariableSet) definedNamesSet |= parameterQnames - + variableDependencies = {} for modelRel in val.modelXbrl.relationshipSet(XbrlConst.variableSet).fromModelObject(modelVariableSet): variable = modelRel.toModelObject @@ -574,7 +574,7 @@ def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compile if len(depVars) > 0 and formulaOptions.traceVariablesDependencies: val.modelXbrl.info("formula:trace", _("Variable set %(xlinkLabel)s, variable %(name)s, dependences %(dependencies)s"), - modelObject=modelVariableSet, xlinkLabel=modelVariableSet.xlinkLabel, + modelObject=modelVariableSet, xlinkLabel=modelVariableSet.xlinkLabel, name=varqname, dependencies=depVars) definedNamesSet.add(varqname) # check for fallback value variable references @@ -584,7 +584,7 @@ def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compile if depVar in qnameRels and isinstance(qnameRels[depVar].toModelObject,ModelVariable): val.modelXbrl.error("xbrlve:fallbackValueVariableReferenceNotAllowed", _("Variable set %(xlinkLabel)s fallbackValue '%(fallbackValue)s' cannot refer to variable %(dependency)s"), - modelObject=variable, xlinkLabel=modelVariableSet.xlinkLabel, + modelObject=variable, xlinkLabel=modelVariableSet.xlinkLabel, fallbackValue=variable.fallbackValue, dependency=depVar) # check for covering aspect not in variable set aspect model checkFilterAspectModel(val, modelVariableSet, variable.filterRelationships, xpathContext) @@ -606,24 +606,24 @@ def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compile instqname = nameVariables[varqname].instanceQname varSetInstanceDependencies.add(instqname) instanceDependencies[instanceQname].add(instqname) - + # anything unresolved? for varqname, depVars in variableDependencies.items(): if varqname not in orderedNameSet: circularOrUndefVars = depVars - parameterQnames - orderedNameSet - undefinedVars = circularOrUndefVars - definedNamesSet + undefinedVars = circularOrUndefVars - definedNamesSet varsCircularDep = circularOrUndefVars - undefinedVars if len(undefinedVars) > 0: val.modelXbrl.error("xbrlve:unresolvedDependency", _("Undefined variable dependencies in variable set %(xlinkLabel)s, from variable %(nameFrom)s to %(nameTo)s"), - modelObject=modelVariableSet, xlinkLabel=modelVariableSet.xlinkLabel, + modelObject=modelVariableSet, xlinkLabel=modelVariableSet.xlinkLabel, nameFrom=varqname, nameTo=undefinedVars) if len(varsCircularDep) > 0: val.modelXbrl.error("xbrlve:cyclicDependencies", _("Cyclic dependencies in variable set %(xlinkLabel)s, from variable %(nameFrom)s to %(nameTo)s"), - modelObject=modelVariableSet, xlinkLabel=modelVariableSet.xlinkLabel, + modelObject=modelVariableSet, xlinkLabel=modelVariableSet.xlinkLabel, nameFrom=varqname, nameTo=varsCircularDep ) - + # check unresolved variable set dependencies for varSetDepVarQname in modelVariableSet.variableRefs(): if varSetDepVarQname not in definedNamesSet and varSetDepVarQname not in parameterQnames: @@ -638,26 +638,26 @@ def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compile instqname = nameVariables[varSetDepVarQname].instanceQname varSetInstanceDependencies.add(instqname) instanceDependencies[instanceQname].add(instqname) - + if formulaOptions.traceVariablesOrder: val.modelXbrl.info("formula:trace", _("Variable set %(xlinkLabel)s, variables order: %(dependencies)s"), modelObject=modelVariableSet, xlinkLabel=modelVariableSet.xlinkLabel, dependencies=orderedNameList) - + if (formulaOptions.traceVariablesDependencies and len(varSetInstanceDependencies) > 0 and varSetInstanceDependencies != {XbrlConst.qnStandardInputInstance}): val.modelXbrl.info("formula:trace", _("Variable set %(xlinkLabel)s, instance dependences %(dependencies)s"), modelObject=modelVariableSet, xlinkLabel=modelVariableSet.xlinkLabel, dependencies=varSetInstanceDependencies) - + modelVariableSet.orderedVariableRelationships = [] for varqname in orderedNameList: if varqname in qnameRels: modelVariableSet.orderedVariableRelationships.append(qnameRels[varqname]) - - orderedNameSet.clear() - del orderedNameList[:] # dereference - + + orderedNameSet.clear() + del orderedNameList[:] # dereference + # check existence assertion @test variable dependencies (not including precondition references) if isinstance(modelVariableSet, ModelExistenceAssertion): for depVar in XPathParser.variableReferencesSet(modelVariableSet.testProg, modelVariableSet): @@ -665,7 +665,7 @@ def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compile val.modelXbrl.error("xbrleae:variableReferenceNotAllowed", _("Existence Assertion %(xlinkLabel)s, cannot refer to variable %(name)s"), modelObject=modelVariableSet, xlinkLabel=modelVariableSet.xlinkLabel, name=depVar) - + # check messages variable dependencies checkValidationMessageVariables(val, modelVariableSet, qnameRels, xpathContext.parameterQnames) @@ -675,14 +675,14 @@ def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compile if isinstance(consisAsser, ModelConsistencyAssertion): checkValidationMessages(val, consisAsser) checkValidationMessageVariables(val, consisAsser, qnameRels, xpathContext.parameterQnames) - + # check preconditions modelVariableSet.preconditions = [] for modelRel in val.modelXbrl.relationshipSet(XbrlConst.variableSetPrecondition).fromModelObject(modelVariableSet): precondition = modelRel.toModelObject if isinstance(precondition, ModelPrecondition): modelVariableSet.preconditions.append(precondition) - + # check for variable sets referencing fact or general variables for modelRel in val.modelXbrl.relationshipSet(XbrlConst.variableSetFilter).fromModelObject(modelVariableSet): varSetFilter = modelRel.toModelObject @@ -691,13 +691,13 @@ def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compile _("Variable set %(xlinkLabel)s, filter %(filterLabel)s, cannot be covered"), modelObject=varSetFilter, xlinkLabel=modelVariableSet.xlinkLabel, filterLabel=varSetFilter.xlinkLabel) modelRel._isCovered = False # block group filter from being able to covered - + for depVar in varSetFilter.variableRefs(): if depVar in qnameRels and isinstance(qnameRels[depVar].toModelObject,ModelVariable): val.modelXbrl.error("xbrlve:factVariableReferenceNotAllowed", _("Variable set %(xlinkLabel)s, filter %(filterLabel)s, cannot refer to variable %(name)s"), modelObject=varSetFilter, xlinkLabel=modelVariableSet.xlinkLabel, filterLabel=varSetFilter.xlinkLabel, name=depVar) - + # check aspects of formula if isinstance(modelVariableSet, ModelFormula): checkFormulaRules(val, modelVariableSet, nameVariables) @@ -715,7 +715,7 @@ def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compile checkValidationMessageVariables(val, consisAsser, {}, xpathContext.parameterQnames) val.modelXbrl.profileActivity("... assertion and formula checks and compilation", minTimeToShow=1.0) - + for modelTable in val.modelXbrl.modelRenderingTables: modelTable.fromInstanceQnames = None # required if referred to by variables scope chaining if modelTable.aspectModel not in ("non-dimensional", "dimensional"): @@ -726,7 +726,7 @@ def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compile checkTableRules(val, xpathContext, modelTable) val.modelXbrl.profileActivity("... rendering tables and axes checks and compilation", minTimeToShow=1.0) - + # determine instance dependency order orderedInstancesSet = set() stdInpInst = {XbrlConst.qnStandardInputInstance} @@ -751,7 +751,7 @@ def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compile if instqname not in orderedInstancesSet: # can also be satisfied from an input DTS missingDependentInstances = depInsts - stdInpInst - if val.parameters: missingDependentInstances -= _DICT_SET(val.parameters.keys()) + if val.parameters: missingDependentInstances -= _DICT_SET(val.parameters.keys()) if instqname: if missingDependentInstances: val.modelXbrl.error("xbrlvarinste:instanceVariableRecursionCycle", @@ -786,22 +786,22 @@ def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compile consisAsser.countErrorMessages = 0 if consisAsser.hasProportionalAcceptanceRadius and consisAsser.hasAbsoluteAcceptanceRadius: val.modelXbrl.error("xbrlcae:acceptanceRadiusConflict", - _("Consistency assertion %(xlinkLabel)s has both absolute and proportional acceptance radii"), + _("Consistency assertion %(xlinkLabel)s has both absolute and proportional acceptance radii"), modelObject=consisAsser, xlinkLabel=consisAsser.xlinkLabel) consisAsser.orderedVariableRelationships = [] for consisParamRel in val.modelXbrl.relationshipSet(XbrlConst.consistencyAssertionParameter).fromModelObject(consisAsser): if isinstance(consisParamRel.toModelObject, ModelVariable): val.modelXbrl.error("xbrlcae:variablesNotAllowed", _("Consistency assertion %(xlinkLabel)s has relationship to a %(elementTo)s %(xlinkLabelTo)s"), - modelObject=consisAsser, xlinkLabel=consisAsser.xlinkLabel, + modelObject=consisAsser, xlinkLabel=consisAsser.xlinkLabel, elementTo=consisParamRel.toModelObject.localName, xlinkLabelTo=consisParamRel.toModelObject.xlinkLabel) elif isinstance(consisParamRel.toModelObject, ModelParameter): consisAsser.orderedVariableRelationships.append(consisParamRel) consisAsser.compile() - + # linked consistency assertions for modelRel in val.modelXbrl.relationshipSet(XbrlConst.consistencyAssertionFormula).modelRelationships: - if (isinstance(modelRel.fromModelObject, ModelConsistencyAssertion) and + if (isinstance(modelRel.fromModelObject, ModelConsistencyAssertion) and isinstance(modelRel.toModelObject,ModelFormula)): modelRel.toModelObject.hasConsistencyAssertion = True val.modelXbrl.profileActivity("... consistency assertion setup", minTimeToShow=1.0) @@ -811,7 +811,7 @@ def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compile xpathContext.dimensionsAspectUniverse = xpathContext.defaultDimensionAspects for cntx in val.modelXbrl.contexts.values(): # note that this maybe should not include unreferenced contexts xpathContext.dimensionsAspectUniverse |= _DICT_SET(cntx.qnameDims.keys()) - + #xpathContext.reportedDimensionAspects = set() #_evaluatedContexts = set() for instanceQname in instanceQnames: @@ -823,7 +823,7 @@ def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compile xpathContext.dimensionsAspectUniverse |= _DICT_SET(namedInstance.qnameDimensionDefaults.keys()) for cntx in namedInstance.contexts.values(): xpathContext.dimensionsAspectUniverse |= _DICT_SET(cntx.qnameDims.keys()) - #for fact in namedInstance.factsInInstance: + #for fact in namedInstance.factsInInstance: # _cntx = fact.context # if fact.isItem and _cntx is not None and _cntx not in _evaluatedContexts: # xpathContext.reportedDimensionAspects |= _DICT_SET(_cntx.qnameDims.keys()) @@ -832,7 +832,7 @@ def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compile # determine reportedDimensionAspects (for which facts report any value of the dimension) - + # check for variable set dependencies across output instances produced for instanceQname, modelVariableSets in instanceProducingVariableSets.items(): for modelVariableSet in modelVariableSets: @@ -842,13 +842,13 @@ def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compile if sourceVariableSet.outputInstanceQname != instanceQname: val.modelXbrl.error("xbrlvarscopee:differentInstances", _("Variable set %(xlinkLabel1)s in instance %(instance1)s has variables scope relationship to varaible set %(xlinkLabel2)s in instance %(instance2)s"), - modelObject=modelVariableSet, + modelObject=modelVariableSet, xlinkLabel1=sourceVariableSet.xlinkLabel, instance1=sourceVariableSet.outputInstanceQname, xlinkLabel2=modelVariableSet.xlinkLabel, instance2=modelVariableSet.outputInstanceQname) if sourceVariableSet.aspectModel != modelVariableSet.aspectModel: val.modelXbrl.error("xbrlvarscopee:conflictingAspectModels", _("Variable set %(xlinkLabel1)s aspectModel (%(aspectModel1)s) differs from varaible set %(xlinkLabel2)s aspectModel (%(aspectModel2)s)"), - modelObject=modelVariableSet, + modelObject=modelVariableSet, xlinkLabel1=sourceVariableSet.xlinkLabel, aspectModel1=sourceVariableSet.aspectModel, xlinkLabel2=modelVariableSet.xlinkLabel, aspectModel2=modelVariableSet.aspectModel) val.modelXbrl.profileActivity("... instances scopes and setup", minTimeToShow=1.0) @@ -856,47 +856,47 @@ def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compile val.modelXbrl.profileStat(_("formulaValidation")) for pluginXbrlMethod in pluginClassMethods("ValidateFormula.Compiled"): pluginXbrlMethod(val.modelXbrl, xpathContext) - + if (initialErrorCount < val.modelXbrl.logCount.get(logging._checkLevel('ERROR'), 0) or - compileOnly or + compileOnly or formulaOptions.compileOnly or getattr(val, "validateFormulaCompileOnly", False)): return # don't try to execute - - # formula output instances - if instanceQnames: + + # formula output instances + if instanceQnames: val.modelXbrl.modelManager.showStatus(_("initializing formula output instances")) schemaRefs = [val.modelXbrl.modelDocument.relativeUri(referencedDoc.uri) for referencedDoc in val.modelXbrl.modelDocument.referencesDocument.keys() if referencedDoc.type == ModelDocument.Type.SCHEMA] - + outputXbrlInstance = None for instanceQname in instanceQnames: if instanceQname == XbrlConst.qnStandardInputInstance: continue # always present the standard way if val.parameters and instanceQname in val.parameters: namedInstance = val.parameters[instanceQname][1] # this is a sequence - else: # empty intermediate instance + else: # empty intermediate instance uri = val.modelXbrl.modelDocument.filepath[:-4] + "-output-XBRL-instance" if instanceQname != XbrlConst.qnStandardOutputInstance: uri = uri + "-" + instanceQname.localName uri = uri + ".xml" - namedInstance = ModelXbrl.create(val.modelXbrl.modelManager, + namedInstance = ModelXbrl.create(val.modelXbrl.modelManager, newDocumentType=ModelDocument.Type.INSTANCE, url=uri, schemaRefs=schemaRefs, isEntry=True) - ValidateXbrlDimensions.loadDimensionDefaults(namedInstance) # need dimension defaults + ValidateXbrlDimensions.loadDimensionDefaults(namedInstance) # need dimension defaults xpathContext.inScopeVars[instanceQname] = namedInstance if instanceQname == XbrlConst.qnStandardOutputInstance: outputXbrlInstance = namedInstance val.modelXbrl.profileActivity("... output instances setup", minTimeToShow=1.0) val.modelXbrl.profileStat(_("formulaInstancesSetup")) timeFormulasStarted = time.time() - + val.modelXbrl.modelManager.showStatus(_("running formulae")) - + # IDs may be a regex expression (or whitespace separated ID names if not) runIDs = None if formulaOptions.runIDs: @@ -907,13 +907,13 @@ def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compile runIDs = re.compile(_runIdPattern) if formulaOptions.traceVariableSetExpressionResult: val.modelXbrl.info("formula:trace", - _("Formula/assertion IDs restriction pattern: %(ids)s"), + _("Formula/assertion IDs restriction pattern: %(ids)s"), modelXbrl=val.modelXbrl, ids=', '.join(_runIdPattern)) except: val.modelXbrl.info("formula:invalidRunIDsPattern", - _("Formula/assertion IDs pattern is invalid: %(runIdPattern)s"), + _("Formula/assertion IDs pattern is invalid: %(runIdPattern)s"), modelXbrl=val.modelXbrl, runIdPattern=_runIdPattern) - + # evaluate consistency assertions try: if hasattr(val, "maxFormulaRunTime") and val.maxFormulaRunTime > 0: @@ -929,9 +929,9 @@ def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compile for modelVariableSet in instanceProducingVariableSets[instanceQname]: # produce variable evaluations if no dependent variables-scope relationships if not val.modelXbrl.relationshipSet(XbrlConst.variablesScope).toModelObject(modelVariableSet): - if (not runIDs or + if (not runIDs or runIDs.match(modelVariableSet.id) or - (modelVariableSet.hasConsistencyAssertion and + (modelVariableSet.hasConsistencyAssertion and any(runIDs.match(modelRel.fromModelObject.id) for modelRel in val.modelXbrl.relationshipSet(XbrlConst.consistencyAssertionFormula).toModelObject(modelVariableSet) if isinstance(modelRel.fromModelObject, ModelConsistencyAssertion)))): @@ -944,15 +944,15 @@ def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compile val.modelXbrl.profileStat(modelVariableSet.localName + "_" + varSetId) except XPathContext.XPathException as err: val.modelXbrl.error(err.code, - _("Variable set \n%(variableSet)s \nException: \n%(error)s"), + _("Variable set \n%(variableSet)s \nException: \n%(error)s"), modelObject=modelVariableSet, variableSet=str(modelVariableSet), error=err.message) if maxFormulaRunTimeTimer: maxFormulaRunTimeTimer.cancel() except XPathContext.RunTimeExceededException: val.modelXbrl.info("formula:maxRunTime", - _("Formula execution ended after %(mins)s minutes"), + _("Formula execution ended after %(mins)s minutes"), modelObject=val.modelXbrl, mins=val.maxFormulaRunTime) - + # log assertion result counts asserTests = {} for exisValAsser in val.modelXbrl.modelVariableSets: @@ -963,7 +963,7 @@ def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compile val.modelXbrl.info("formula:trace", _("%(assertionType)s Assertion %(id)s evaluations : %(satisfiedCount)s satisfied, %(notSatisfiedCount)s not satisfied"), modelObject=exisValAsser, - assertionType="Existence" if isinstance(exisValAsser, ModelExistenceAssertion) else "Value", + assertionType="Existence" if isinstance(exisValAsser, ModelExistenceAssertion) else "Value", id=exisValAsser.id, satisfiedCount=exisValAsser.countSatisfied, notSatisfiedCount=exisValAsser.countNotSatisfied) for consisAsser in val.modelXbrl.modelConsistencyAssertions: @@ -972,9 +972,9 @@ def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compile if formulaOptions.traceAssertionResultCounts: val.modelXbrl.info("formula:trace", _("Consistency Assertion %(id)s evaluations : %(satisfiedCount)s satisfied, %(notSatisfiedCount)s not satisfied"), - modelObject=consisAsser, id=consisAsser.id, + modelObject=consisAsser, id=consisAsser.id, satisfiedCount=consisAsser.countSatisfied, notSatisfiedCount=consisAsser.countNotSatisfied) - + if asserTests: # pass assertion results to validation if appropriate val.modelXbrl.log(None, "asrtNoLog", None, assertionResults=asserTests); @@ -984,11 +984,11 @@ def validate(val, xpathContext=None, parametersOnly=False, statusMsg='', compile # close prior instance, usually closed by caller to validate as it may affect UI on different thread val.modelXbrl.formulaOutputInstance.close() val.modelXbrl.formulaOutputInstance = outputXbrlInstance - + val.modelXbrl.modelManager.showStatus(_("formulae finished"), 2000) for pluginXbrlMethod in pluginClassMethods("ValidateFormula.Finished"): pluginXbrlMethod(val) - + instanceProducingVariableSets.clear() # dereference parameterQnames.clear() instanceQnames.clear() @@ -1038,7 +1038,7 @@ def checkFilterAspectModel(val, variableSet, filterRelationships, xpathContext, if otherFilterCover != varFilterRel.isCovered: val.modelXbrl.error("xbrlacfe:inconsistentAspectCoverFilters", _("Variable set %(xlinkLabel)s, aspect cover filter %(filterLabel)s, aspect %(aspect)s, conflicts with %(filterLabel2)s with inconsistent cover attribute"), - modelObject=variableSet, xlinkLabel=variableSet.xlinkLabel, filterLabel=_filter.xlinkLabel, + modelObject=variableSet, xlinkLabel=variableSet.xlinkLabel, filterLabel=_filter.xlinkLabel, aspect=str(aspect) if isinstance(aspect,QName) else Aspect.label[aspect], filterLabel2=otherFilterLabel) else: @@ -1047,20 +1047,20 @@ def checkFilterAspectModel(val, variableSet, filterRelationships, xpathContext, if True: # changed for test case 50210 v03 varFilterRel.isCovered: try: aspectsCovered = _filter.aspectsCovered(None) - if (not isAllAspectCoverFilter and + if (not isAllAspectCoverFilter and (any(isinstance(aspect,QName) for aspect in aspectsCovered) and Aspect.DIMENSIONS in uncoverableAspects or (aspectsCovered & uncoverableAspects))): val.modelXbrl.error("xbrlve:filterAspectModelMismatch", _("Variable set %(xlinkLabel)s, aspect model %(aspectModel)s filter %(filterName)s %(filterLabel)s can cover aspect not in aspect model"), - modelObject=variableSet, xlinkLabel=variableSet.xlinkLabel, aspectModel=variableSet.aspectModel, + modelObject=variableSet, xlinkLabel=variableSet.xlinkLabel, aspectModel=variableSet.aspectModel, filterName=_filter.localName, filterLabel=_filter.xlinkLabel) - result |= aspectsCovered + result |= aspectsCovered except Exception: pass if hasattr(_filter, "filterRelationships"): # check and & or filters result |= checkFilterAspectModel(val, variableSet, _filter.filterRelationships, xpathContext, uncoverableAspects) return result - + def checkFormulaRules(val, formula, nameVariables): if not (formula.hasRule(Aspect.CONCEPT) or formula.source(Aspect.CONCEPT)): if XmlUtil.hasDescendant(formula, XbrlConst.formula, "concept"): @@ -1115,7 +1115,7 @@ def checkFormulaRules(val, formula, nameVariables): val.modelXbrl.error("xbrlfe:missingUnitRule", _("Formula %(xlinkLabel)s omits a rule for the unit aspect"), modelObject=formula, xlinkLabel=formula.xlinkLabel) - elif (formula.hasRule(Aspect.MULTIPLY_BY) or formula.hasRule(Aspect.DIVIDE_BY) or + elif (formula.hasRule(Aspect.MULTIPLY_BY) or formula.hasRule(Aspect.DIVIDE_BY) or formula.source(Aspect.UNIT, acceptFormulaSource=False)): val.modelXbrl.error("xbrlfe:conflictingAspectRules", _("Formula %(xlinkLabel)s has a rule for the unit aspect of a non-numeric concept %(concept)s"), @@ -1126,7 +1126,7 @@ def checkFormulaRules(val, formula, nameVariables): val.modelXbrl.error("xbrlfe:conflictingAspectRules", _("Formula %(xlinkLabel)s has a rule for the %(aspectPeriodType)s period aspect of a %(conceptPeriodType)s concept %(concept)s"), modelObject=formula, xlinkLabel=formula.xlinkLabel, concept=concept.qname, aspectPeriodType=aspectPeriodType, conceptPeriodType=concept.periodType) - + # check dimension elements for eltName, dim, badUsageErr, missingSavErr in (("explicitDimension", "explicit", "xbrlfe:badUsageOfExplicitDimensionRule", "xbrlfe:missingSAVForExplicitDimensionRule"), ("typedDimension", "typed", "xbrlfe:badUsageOfTypedDimensionRule", "xbrlfe:missingSAVForTypedDimensionRule")): @@ -1143,7 +1143,7 @@ def checkFormulaRules(val, formula, nameVariables): _("Formula %(xlinkLabel)s %(dimension)s dimension rule does not have any child elements and does not have a SAV for the %(dimensionType)s dimension that is identified by its dimension attribute."), modelObject=formula, xlinkLabel=formula.xlinkLabel, dimensionType=dim, dimension=dimQname, messageCodes=("xbrlfe:missingSAVForExplicitDimensionRule", "xbrlfe:missingSAVForTypedDimensionRule")) - + # check aspect model expectations if formula.aspectModel == "non-dimensional": unexpectedElts = XmlUtil.descendants(formula, XbrlConst.formula, ("explicitDimension", "typedDimension")) @@ -1153,7 +1153,7 @@ def checkFormulaRules(val, formula, nameVariables): modelObject=formula, xlinkLabel=formula.xlinkLabel, aspectModel=formula.aspectModel, undefinedAspects=", ".join([elt.localName for elt in unexpectedElts])) # check source qnames - for sourceElt in ([formula] + + for sourceElt in ([formula] + XmlUtil.descendants(formula, XbrlConst.formula, "*", "source","*")): if sourceElt.get("source") is not None: qnSource = qname(sourceElt, sourceElt.get("source"), noPrefixIsNoNamespace=True) @@ -1161,7 +1161,7 @@ def checkFormulaRules(val, formula, nameVariables): if formula.implicitFiltering != "true": val.modelXbrl.error("xbrlfe:illegalUseOfUncoveredQName", _("Formula %(xlinkLabel)s, not implicit filtering element has formulaUncovered source: %(name)s"), - modelObject=formula, xlinkLabel=formula.xlinkLabel, name=sourceElt.localName) + modelObject=formula, xlinkLabel=formula.xlinkLabel, name=sourceElt.localName) elif qnSource not in nameVariables: val.modelXbrl.error("xbrlfe:nonexistentSourceVariable", _("Variable set %(xlinkLabel)s, source %(name)s is not in the variable set"), @@ -1182,13 +1182,13 @@ def checkFormulaRules(val, formula, nameVariables): val.modelXbrl.error("xbrlfe:defaultAspectValueConflicts", _("Formula %(xlinkLabel)s: formula source %(name)s is a fact variable that binds as a sequence"), modelObject=formula, xlinkLabel=formula.xlinkLabel, name=qnSource) - + def checkTableRules(val, xpathContext, table): # check for covering aspect not in variable set aspect model checkFilterAspectModel(val, table, table.filterRelationships, xpathContext) checkDefinitionNodeRules(val, table, table, (XbrlConst.tableBreakdown, XbrlConst.tableBreakdownMMDD, XbrlConst.tableBreakdown201305, XbrlConst.tableAxis2011), xpathContext) - + def checkDefinitionNodeRules(val, table, parent, arcrole, xpathContext): for rel in val.modelXbrl.relationshipSet(arcrole).fromModelObject(parent): axis = rel.toModelObject @@ -1211,17 +1211,17 @@ def checkDefinitionNodeRules(val, table, parent, arcrole, xpathContext): rulesByAspect[elt.localName, elt.xAttributes["dimension"].xValue].add(elt) elif elt.localName == "occFragments": rulesByAspect[elt.localName, elt.xAttributes["occ"].xValue].add(elt) - if ((elt.localName == ("concept","member") and not any(c.localName in ("qname", "qnameExpression") + if ((elt.localName == ("concept","member") and not any(c.localName in ("qname", "qnameExpression") for c in XmlUtil.children(elt, XbrlConst.formula, "*"))) or (elt.localName == "explicitDimension" and ( not XmlUtil.children(elt, XbrlConst.formula, "member") or not val.modelXbrl.qnameConcepts.get(elt.xAttributes["dimension"].xValue).isDimensionItem)) or - (elt.localName == "typedDimension" and not any(c.localName in ("xpath", "value") + (elt.localName == "typedDimension" and not any(c.localName in ("xpath", "value") for c in XmlUtil.children(elt, XbrlConst.formula, "*"))) or (elt.localName == "instant" and not elt.get("value")) or (elt.localName == "duration" and not (elt.get("start") or elt.get("end"))) or (elt.localName == "entityIdentifier" and not (elt.get("scheme") and elt.get("value"))) or - (elt.localName == "unit" and not any(c.localName in ("multiplyBy", "divideBy") + (elt.localName == "unit" and not any(c.localName in ("multiplyBy", "divideBy") for c in XmlUtil.children(elt, XbrlConst.formula, "*"))) or (elt.localName in ("multiplyBy", "divideBy") and not elt.get("measure"))): raise FormulaValidationException @@ -1246,9 +1246,9 @@ def checkDefinitionNodeRules(val, table, parent, arcrole, xpathContext): val.modelXbrl.error("xbrlte:axisAspectModelMismatch", _("RuleAxis %(xlinkLabel)s aspect model, %(aspectModel)s, includes an rule for aspect not defined in this aspect model: %(undefinedAspects)s"), modelObject=axis, xlinkLabel=axis.xlinkLabel, aspectModel=table.aspectModel, undefinedAspects=", ".join([elt.localName for elt in unexpectedElts])) - + # check source qnames - for sourceElt in ([axis] + + for sourceElt in ([axis] + XmlUtil.descendants(axis, XbrlConst.formula, "*", "source","*")): if sourceElt.get("source") is not None: qnSource = qname(sourceElt, sourceElt.get("source"), noPrefixIsNoNamespace=True) @@ -1261,22 +1261,22 @@ def checkDefinitionNodeRules(val, table, parent, arcrole, xpathContext): val.modelXbrl.error("xbrlte:unrecognisedAspectRule", _("RuleAxis %(xlinkLabel)s aspect model includes an unrecognized rule aspect: %(unrecognizedAspect)s"), modelObject=axis, xlinkLabel=axis.xlinkLabel, unrecognizedAspect=childElt.qname) - + elif isinstance(axis, ModelRelationshipDefinitionNode): for qnameAttr in ("relationshipSourceQname", "arcQname", "linkQname", "dimensionQname"): eltQname = axis.get(qnameAttr) - if eltQname and eltQname not in val.modelXbrl.qnameConcepts: + if eltQname and eltQname not in val.modelXbrl.qnameConcepts: val.modelXbrl.info("table:info", _("%(axis)s rule %(xlinkLabel)s contains a %(qnameAttr)s QName %(qname)s which is not in the DTS."), - modelObject=axis, axis=axis.localName.title(), xlinkLabel=axis.xlinkLabel, + modelObject=axis, axis=axis.localName.title(), xlinkLabel=axis.xlinkLabel, qnameAttr=qnameAttr, qname=eltQname) - checkDefinitionNodeRules(val, table, axis, (XbrlConst.tableBreakdownTree, XbrlConst.tableBreakdownTreeMMDD, XbrlConst.tableBreakdownTree201305, XbrlConst.tableDefinitionNodeSubtree201301, XbrlConst.tableAxisSubtree2011), xpathContext) + checkDefinitionNodeRules(val, table, axis, (XbrlConst.tableBreakdownTree, XbrlConst.tableBreakdownTreeMMDD, XbrlConst.tableBreakdownTree201305, XbrlConst.tableDefinitionNodeSubtree201301, XbrlConst.tableAxisSubtree2011), xpathContext) def checkValidationMessages(val, modelVariableSet): for msgRelationship in (XbrlConst.assertionSatisfiedMessage, XbrlConst.assertionUnsatisfiedMessage): for modelRel in val.modelXbrl.relationshipSet(msgRelationship).fromModelObject(modelVariableSet): checkMessageExpressions(val, modelRel.toModelObject) - + def checkMessageExpressions(val, message): if isinstance(message, ModelMessage) and not hasattr(message,"expressions"): formatString = [] @@ -1294,11 +1294,11 @@ def checkMessageExpressions(val, message): skipTo = c elif lastC == c and c in ('{','}'): lastC = None - elif lastC == '{': + elif lastC == '{': bracketNesting += 1 expression = [] lastC = None - elif c == '}' and expression is not None: + elif c == '}' and expression is not None: expressions.append( ''.join(expression).strip() ) expression = None formatString.append( "0[{0}]".format(expressionIndex) ) @@ -1309,17 +1309,17 @@ def checkMessageExpressions(val, message): lastC = c else: lastC = c - + if expression is not None: expression.append(c) else: formatString.append(c) - + if lastC == '}': bracketNesting -= 1 if bracketNesting != 0: val.modelXbrl.error("xbrlmsge:missingLeftCurlyBracketInMessage" if bracketNesting < 0 else "xbrlmsge:missingRightCurlyBracketInMessage", _("Message %(xlinkLabel)s: unbalanced %(character)s character(s) in: %(text)s"), - modelObject=message, xlinkLabel=message.xlinkLabel, - character='{' if bracketNesting < 0 else '}', + modelObject=message, xlinkLabel=message.xlinkLabel, + character='{' if bracketNesting < 0 else '}', text=message.text, messageCodes=("xbrlmsge:missingLeftCurlyBracketInMessage", "xbrlmsge:missingRightCurlyBracketInMessage")) else: @@ -1349,7 +1349,7 @@ def checkValidationMessageVariables(val, modelVariableSet, varNames, paramNames) val.modelXbrl.error("err:XPST0008", _("Undefined variable dependency in message %(xlinkLabel)s, %(name)s"), modelObject=message, xlinkLabel=message.xlinkLabel, name=msgVarQname) - elif (msgVarQname in varNames and + elif (msgVarQname in varNames and isinstance(modelVariableSet, ModelExistenceAssertion) and isinstance(varNames[msgVarQname].toModelObject,ModelVariable)): val.modelXbrl.error("err:XPST0008", diff --git a/arelle/ValidateInfoset.py b/arelle/ValidateInfoset.py index 84b4cb9bc3..b2f945dab3 100644 --- a/arelle/ValidateInfoset.py +++ b/arelle/ValidateInfoset.py @@ -8,7 +8,7 @@ from arelle.ModelDocument import Type from arelle.ModelValue import qname from arelle import XmlUtil, XbrlConst -from arelle.ValidateXbrlCalcs import inferredPrecision, inferredDecimals +from arelle.ValidateXbrlCalcs import inferredPrecision, inferredDecimals def validate(val, modelXbrl, infosetModelXbrl): infoset = infosetModelXbrl.modelDocument @@ -20,7 +20,7 @@ def validate(val, modelXbrl, infosetModelXbrl): if len(modelXbrl.factsInInstance) != len(infosetModelXbrl.factsInInstance): modelXbrl.error("arelle:infosetTest", _("Fact counts mismatch, testcase instance %(foundFactCount)s, infoset instance %(expectedFactCount)s"), - modelObject=(modelXbrl.modelDocument, infosetModelXbrl.modelDocument), + modelObject=(modelXbrl.modelDocument, infosetModelXbrl.modelDocument), foundFactCount=len(modelXbrl.factsInInstance), expectedFactCount=len(infosetModelXbrl.factsInInstance)) else: @@ -39,7 +39,7 @@ def validate(val, modelXbrl, infosetModelXbrl): modelXbrl.error("arelle:infosetTest", _("Fact %(factNumber)s mismatch %(concept)s"), modelObject=instFact, - factNumber=(i+1), + factNumber=(i+1), concept=instFact.qname) else: ptvPeriodType = infosetFact.get("{http://www.xbrl.org/2003/ptv}periodType") @@ -50,7 +50,7 @@ def validate(val, modelXbrl, infosetModelXbrl): modelXbrl.error("arelle:infosetTest", _("Fact %(factNumber)s periodType mismatch %(concept)s expected %(expectedPeriodType)s found %(foundPeriodType)s"), modelObject=(instFact, infosetFact), - factNumber=(i+1), + factNumber=(i+1), concept=instFact.qname, expectedPeriodType=ptvPeriodType, foundPeriodType=instFact.concept.periodType) @@ -58,7 +58,7 @@ def validate(val, modelXbrl, infosetModelXbrl): modelXbrl.error("arelle:infosetTest", _("Fact %(factNumber)s balance mismatch %(concept)s expected %(expectedBalance)s found %(foundBalance)s"), modelObject=(instFact, infosetFact), - factNumber=(i+1), + factNumber=(i+1), concept=instFact.qname, expectedBalance=ptvBalance, foundBalance=instFact.concept.balance) @@ -66,7 +66,7 @@ def validate(val, modelXbrl, infosetModelXbrl): modelXbrl.error("arelle:infosetTest", _("Fact %(factNumber)s inferred decimals mismatch %(concept)s expected %(expectedDecimals)s found %(inferredDecimals)s"), modelObject=(instFact, infosetFact), - factNumber=(i+1), + factNumber=(i+1), concept=instFact.qname, expectedDecimals=ptvDecimals, inferredDecimals=str(inferredDecimals(fact))) @@ -74,11 +74,11 @@ def validate(val, modelXbrl, infosetModelXbrl): modelXbrl.error("arelle:infosetTest", _("Fact %(factNumber)s inferred precision mismatch %(concept)s expected %(expectedPrecision)s found %(inferredPrecision)s"), modelObject=(instFact, infosetFact), - factNumber=(i+1), + factNumber=(i+1), concept=instFact.qname, expectedPrecisions=ptvPrecision, inferredPrecision=str(inferredPrecision(fact))) - + elif infoset.type == Type.ARCSINFOSET: # compare arcs for arcElt in XmlUtil.children(infoset.xmlRootElement, "http://www.xbrl.org/2003/ptv", "arc"): @@ -136,8 +136,8 @@ def validate(val, modelXbrl, infosetModelXbrl): preferredLabel = arcElt.get("preferredLabel") found = False for rel in modelXbrl.relationshipSet(arcRole, extRole).fromModelObject(fromObj): - if (rel.toModelObject == toObj and - (weight is None or rel.weight == weight) and + if (rel.toModelObject == toObj and + (weight is None or rel.weight == weight) and (order is None or rel.order == order)): found = True if not found: @@ -242,8 +242,8 @@ def validateRenderingInfoset(modelXbrl, comparisonFile, sourceDoc): if attrs1 != attrs2: modelXbrl.error("arelle:tableModelAttributesMismatch", _("Table model comparison element %(elt)s expecting attributes %(attrs1)s found %(attrs2)s source line %(elt1line)s comparison line %(elt2line)s"), - modelObject=modelXbrl, elt=sourceElt.tag, - attrs1=', '.join('{0}="{1}"'.format(k,v) for k,v in sorted(attrs1.items())), + modelObject=modelXbrl, elt=sourceElt.tag, + attrs1=', '.join('{0}="{1}"'.format(k,v) for k,v in sorted(attrs1.items())), attrs2=', '.join('{0}="{1}"'.format(k,v) for k,v in sorted(attrs2.items())), elt1line=sourceElt.sourceline, elt2line=comparisonElt.sourceline) sourceElt = next(sourceIter, None) diff --git a/arelle/ValidateUtr.py b/arelle/ValidateUtr.py index fbcf423ab8..ec5fb6e845 100644 --- a/arelle/ValidateUtr.py +++ b/arelle/ValidateUtr.py @@ -219,4 +219,4 @@ def symbols(measures, wrapMult=True): else: return symbols(multMeasures, wrapMult=False) else: - return "" \ No newline at end of file + return "" diff --git a/arelle/ValidateVersReport.py b/arelle/ValidateVersReport.py index 015e202756..01b50c778b 100644 --- a/arelle/ValidateVersReport.py +++ b/arelle/ValidateVersReport.py @@ -24,7 +24,7 @@ "conceptBlockChange": "block", "conceptDefaultChange": "default", "conceptFixedChange": "fixed", - "conceptFinalChange": "final" + "conceptFinalChange": "final" } class ValidateVersReport(): @@ -33,7 +33,7 @@ def __init__(self, testModelXbrl): def close(self): self.__dict__.clear() # dereference everything - + def validate(self, modelVersReport): self.modelVersReport = modelVersReport versReport = modelVersReport.modelDocument @@ -64,7 +64,7 @@ def validate(self, modelVersReport): self.modelVersReport.error("vere:invalidAssignmentRef", _("AssignmentRef %(assignmentRef)s does not reference an assignment"), modelObject=assignmentRef, assignmentRef=ref) - + # check namespace renames for NSrename in versReport.namespaceRenameFrom.values(): if NSrename.fromURI not in versReport.fromDTS.namespaceDocs: @@ -75,7 +75,7 @@ def validate(self, modelVersReport): self.modelVersReport.error("vere:invalidNamespaceMapping", _("NamespaceRename toURI %(uri)s does not reference a schema in toDTS"), modelObject=self, uri=NSrename.toURI) - + # check role changes for roleChange in versReport.roleChanges.values(): if roleChange.fromURI not in versReport.fromDTS.roleTypes: @@ -86,44 +86,44 @@ def validate(self, modelVersReport): self.modelVersReport.error("vere:invalidRoleChange", _("RoleChange toURI %(uri)s does not reference a roleType in toDTS"), modelObject=self, uri=roleChange.toURI) - + # check reportRefs # check actions for reportRef in versReportElt.iterdescendants(tag="{http://xbrl.org/2010/versioning-base}reportRef"): # if existing it must be valid href = reportRef.get("{http://www.w3.org/1999/xlink}href") # TBD - + if versReport.fromDTS and versReport.toDTS: # check concept changes of concept basic for conceptChange in versReport.conceptUseChanges: fromConceptQn = conceptChange.fromConceptQname toConceptQn = conceptChange.toConceptQname - if (conceptChange.name != "conceptAdd" and + if (conceptChange.name != "conceptAdd" and (fromConceptQn is None or fromConceptQn not in versReport.fromDTS.qnameConcepts)): self.modelVersReport.error("vercue:invalidConceptReference", _("%(event)s fromConcept %(concept)s does not reference a concept in fromDTS"), - modelObject=conceptChange, event=conceptChange.name, concept=conceptChange.fromConceptQname) + modelObject=conceptChange, event=conceptChange.name, concept=conceptChange.fromConceptQname) if (conceptChange.name != "conceptDelete" and (toConceptQn is None or toConceptQn not in versReport.toDTS.qnameConcepts)): self.modelVersReport.error("vercue:invalidConceptReference", _("%(event)s toConcept %(concept)s does not reference a concept in toDTS"), - modelObject=conceptChange, event=conceptChange.name, concept=conceptChange.toConceptQname) + modelObject=conceptChange, event=conceptChange.name, concept=conceptChange.toConceptQname) if (conceptChange.name == "conceptAdd" and toConceptQn is not None and conceptChange.isPhysical ^ (qname(versReport.namespaceRenameTo.get(toConceptQn.namespaceURI, toConceptQn.namespaceURI), toConceptQn.localName) not in versReport.fromDTS.qnameConcepts)): self.modelVersReport.error("vercue:inconsistentPhysicalAttribute", _("%(event)s toConcept %(concept)s physical attribute conflicts with presence in fromDTS"), - modelObject=conceptChange, event=conceptChange.name, concept=conceptChange.toConceptQname) + modelObject=conceptChange, event=conceptChange.name, concept=conceptChange.toConceptQname) if (conceptChange.name == "conceptDelete" and toConceptQn is not None and conceptChange.isPhysical ^ (qname(versReport.namespaceRenameFrom.get(fromConceptQn.namespaceURI, fromConceptQn.namespaceURI), fromConceptQn.localName) in versReport.toDTS.qnameConcepts)): self.modelVersReport.error("vercue:inconsistentPhysicalAttribute", _("%(event)s toConcept %(concept)s physical attribute conflicts with presence in toDTS"), - modelObject=conceptChange, event=conceptChange.name, concept=conceptChange.toConceptQname) - + modelObject=conceptChange, event=conceptChange.name, concept=conceptChange.toConceptQname) + # check concept changes of concept extended equivalentAttributes = {} for conceptChange in versReport.conceptDetailsChanges: @@ -137,7 +137,7 @@ def validate(self, modelVersReport): self.modelVersReport.error("vercue:invalidConceptReference", _("%(action)s %(event)s fromConcept %(concept)s does not reference a concept in fromDTS"), modelObject=conceptChange, action=conceptChange.actionId, - event=conceptChange.name, concept=conceptChange.fromConceptQname) + event=conceptChange.name, concept=conceptChange.fromConceptQname) # tuple check elif _("Child") in conceptChange.name and \ not versReport.fromDTS.qnameConcepts[fromConcept.qname] \ @@ -145,19 +145,19 @@ def validate(self, modelVersReport): self.modelVersReport.error("vercue:invalidConceptReference", _("%(action)s %(event)s fromConcept %(concept)s must be defined as a tuple"), modelObject=conceptChange, action=conceptChange.actionId, - event=conceptChange.name, concept=conceptChange.fromConceptQname) + event=conceptChange.name, concept=conceptChange.fromConceptQname) # resource check elif "Label" in conceptChange.name: if fromResource is None: self.modelVersReport.error("vercde:invalidResourceIdentifier", _("%(action)s %(event)s fromResource %(resource)s does not reference a resource in fromDTS"), modelObject=conceptChange, action=conceptChange.actionId, - event=conceptChange.name, resource=conceptChange.fromResourceValue) + event=conceptChange.name, resource=conceptChange.fromResourceValue) else: relationship = fromConcept.relationshipToResource(fromResource, XbrlConst.conceptLabel) if relationship is not None: - if (relationship.qname != XbrlConst.qnLinkLabelArc or - relationship.parentQname != XbrlConst.qnLinkLabelLink or + if (relationship.qname != XbrlConst.qnLinkLabelArc or + relationship.parentQname != XbrlConst.qnLinkLabelLink or fromResource.qname != XbrlConst.qnLinkLabel): self.modelVersReport.error("vercde:invalidConceptLabelIdentifier", _("%(action)s %(event)s fromResource %(resource)s for %(concept)s in fromDTS does not have expected link, arc, or label elements"), @@ -207,7 +207,7 @@ def validate(self, modelVersReport): _("%(action)s %(event)s fromResource %(resource)s does not have a reference relationship to %(concept)s in fromDTS"), modelObject=conceptChange, action=conceptChange.actionId, event=conceptChange.name, resource=conceptChange.fromResourceValue, concept=conceptChange.fromConceptQname) - + # toConcept checks if not conceptChange.name.endswith("Delete"): if not toConcept is not None: @@ -294,17 +294,17 @@ def validate(self, modelVersReport): _("%(action)s %(event)s toResource %(resource)s does not have a reference relationship to %(concept)s in toDTS"), modelObject=conceptChange, action=conceptChange.actionId, event=conceptChange.name, resource=conceptChange.toResourceValue, concept=conceptChange.toConceptQname) - + # check concept correspondence if fromConcept is not None and toConcept is not None: - if (versReport.toDTSqname(fromConcept.qname) != toConcept.qname and - versReport.equivalentConcepts.get(fromConcept.qname) != toConcept.qname and + if (versReport.toDTSqname(fromConcept.qname) != toConcept.qname and + versReport.equivalentConcepts.get(fromConcept.qname) != toConcept.qname and toConcept.qname not in versReport.relatedConcepts.get(fromConcept.qname,[])): self.modelVersReport.error("vercde:invalidConceptCorrespondence", _("%(action)s %(event)s fromConcept %(conceptFrom)s and toConcept %(conceptTo)s must be equivalent or related"), modelObject=conceptChange, action=conceptChange.actionId, event=conceptChange.name, conceptFrom=conceptChange.fromConceptQname, conceptTo=conceptChange.toConceptQname) - + # custom attribute events if conceptChange.name.startswith("conceptAttribute") or conceptChange.name == "attributeDefinitionChange": try: @@ -330,7 +330,7 @@ def validate(self, modelVersReport): toAttr = conceptChange.customAttributeQname("toCustomAttribute") equivalentAttributes[fromAttr] = toAttr equivalentAttributes[toAttr] = fromAttr - + # check item concept identifiers if conceptChange.name in ("conceptPeriodTypeChange", "conceptPeriodTypeChange"): for concept in (fromConcept, toConcept): @@ -338,8 +338,8 @@ def validate(self, modelVersReport): self.modelVersReport.error("vercde:invalidItemConceptIdentifier", _("%(action)s %(event)s concept %(concept)s does not reference an item concept."), modelObject=conceptChange, action=conceptChange.actionId, - event=conceptChange.name, concept=concept.qname) - + event=conceptChange.name, concept=concept.qname) + # check tuple concept identifiers if conceptChange.name in ("tupleContentModelChange", ): for concept in (fromConcept, toConcept): @@ -347,23 +347,23 @@ def validate(self, modelVersReport): self.modelVersReport.error("vercde:invalidTupleConceptIdentifier", _("%(action)s %(event)s concept %(concept)s does not reference a tuple concept."), modelObject=conceptChange, action=conceptChange.actionId, - event=conceptChange.name, concept=concept.qname) - + event=conceptChange.name, concept=concept.qname) + if conceptChange.name in schemaAttributeEventAttributes: attr = schemaAttributeEventAttributes[conceptChange.name] - if (fromConcept is not None and not fromConcept.get(attr) and + if (fromConcept is not None and not fromConcept.get(attr) and toConcept is not None and not toConcept.get(attr)): self.modelVersReport.error("vercde:illegalSchemaAttributeChangeEvent", _("%(action)s %(event)s neither concepts have a %(attribute)s attribute: %(fromConcept)s, %(toConcept)s."), modelObject=conceptChange, action=conceptChange.actionId, attribute=attr, - event=conceptChange.name, fromConcept=fromConcept.qname, toConcept=toConcept.qname) + event=conceptChange.name, fromConcept=fromConcept.qname, toConcept=toConcept.qname) # check concept changes for equivalent attributes for conceptChange in versReport.conceptDetailsChanges: if conceptChange.name == "conceptAttributeChange": fromAttr = conceptChange.customAttributeQname("fromCustomAttribute") toAttr = conceptChange.customAttributeQname("toCustomAttribute") - if (equivalentAttributes.get(fromAttr) != toAttr and + if (equivalentAttributes.get(fromAttr) != toAttr and (fromAttr.localName != toAttr.localName or (fromAttr.namespaceURI != toAttr.namespaceURI and versReport.namespaceRenameFrom.get(fromAttr.namespaceURI, fromAttr.namespaceURI) != toAttr.namespaceURI))): @@ -372,7 +372,7 @@ def validate(self, modelVersReport): modelObject=conceptChange, action=conceptChange.actionId, event=conceptChange.name, fromQname=fromAttr, toQname=toAttr) del equivalentAttributes # dereference - + # check relationship set changes for relSetChange in versReport.relationshipSetChanges: for relationshipSet, name in ((relSetChange.fromRelationshipSet, "fromRelationshipSet"), @@ -454,10 +454,10 @@ def validate(self, modelVersReport): _("%(event)s %(relSet)s no relationship found fromConcept %(conceptFrom)s in its DTS"), modelObject=relSetChange, event=relSetChange.name, relSet=name, conceptFrom=relationship.fromName) - - - + + + # check instance aspect changes for iaChange in versReport.instanceAspectChanges: for instAspects in (iaChange.fromAspects, iaChange.toAspects): @@ -522,7 +522,7 @@ def validate(self, modelVersReport): self.modelVersReport.error("verdime:invalidURI", _("%(event)s linkrole %(linkrole)s is not used in its DTS"), modelObject=aspect, event=iaChange.name, linkrole=relatedConcept.linkrole) - if (relatedConcept.arc is not None and + if (relatedConcept.arc is not None and (relatedConcept.arc not in dts.qnameConcepts or (dts.qnameConcepts[relatedConcept.arc].type is not None and not dts.qnameConcepts[relatedConcept.arc].type.isDerivedFrom(XbrlConst.qnXlArcType)))): @@ -536,5 +536,5 @@ def validate(self, modelVersReport): self.modelVersReport.error("verdime:invalidLinkElement", _("%(event)s link %(link)s is not defined in its DTS"), modelObject=aspect, event=iaChange.name, link=relatedConcept.link) - - self.close() \ No newline at end of file + + self.close() diff --git a/arelle/ValidateXbrlDTS.py b/arelle/ValidateXbrlDTS.py index a94cf4632a..81c78e8694 100644 --- a/arelle/ValidateXbrlDTS.py +++ b/arelle/ValidateXbrlDTS.py @@ -30,11 +30,11 @@ "footnoteLink":"4.11.1.1"} standard_roles_for_ext_links = ("xbrl.3.5.3", (XbrlConst.defaultLinkRole,)) standard_roles_definitions = { - XbrlConst.qnLinkDefinitionLink: standard_roles_for_ext_links, - XbrlConst.qnLinkCalculationLink: standard_roles_for_ext_links, + XbrlConst.qnLinkDefinitionLink: standard_roles_for_ext_links, + XbrlConst.qnLinkCalculationLink: standard_roles_for_ext_links, XbrlConst.qnLinkPresentationLink: standard_roles_for_ext_links, - XbrlConst.qnLinkLabelLink: standard_roles_for_ext_links, - XbrlConst.qnLinkReferenceLink: standard_roles_for_ext_links, + XbrlConst.qnLinkLabelLink: standard_roles_for_ext_links, + XbrlConst.qnLinkReferenceLink: standard_roles_for_ext_links, XbrlConst.qnLinkFootnoteLink: standard_roles_for_ext_links, XbrlConst.qnIXbrl11Relationship: standard_roles_for_ext_links, # has xlinkRole of footnoteLinks XbrlConst.qnLinkLabel: ("xbrl.5.2.2.2.2", XbrlConst.standardLabelRoles), @@ -66,15 +66,15 @@ def checkDTS(val, modelDocument, checkedModelDocuments): for referencedDocument in modelDocument.referencesDocument.keys(): if referencedDocument not in checkedModelDocuments: checkDTS(val, referencedDocument, checkedModelDocuments) - + # skip processing versioning report here if modelDocument.type == ModelDocument.Type.VERSIONINGREPORT: return - + # skip processing if skipDTS requested if modelDocument.skipDTS: return - + # skip system schemas if modelDocument.type == ModelDocument.Type.SCHEMA: if XbrlConst.isStandardNamespace(modelDocument.targetNamespace): @@ -83,7 +83,7 @@ def checkDTS(val, modelDocument, checkedModelDocuments): val.hasTuple = val.hasNonAbstractElement = val.hasType = val.hasEnumeration = \ val.hasDimension = val.hasDomain = val.hasHypercube = False - + # check for linked up hrefs isInstance = (modelDocument.type == ModelDocument.Type.INSTANCE or modelDocument.type == ModelDocument.Type.INLINEXBRL) @@ -104,13 +104,13 @@ def checkDTS(val, modelDocument, checkedModelDocuments): val.roleRefURIs = val.ixdsRoleRefURIs val.arcroleRefURIs = val.ixdsArcroleRefURIs val.ixdsDocs.append(modelDocument) - + for hrefElt, hrefedDoc, hrefId in modelDocument.hrefObjects: hrefedElt = None if hrefedDoc is None: val.modelXbrl.error("xbrl:hrefFileNotFound", _("Href %(elementHref)s file not found"), - modelObject=hrefElt, + modelObject=hrefElt, elementHref=hrefElt.get("{http://www.w3.org/1999/xlink}href")) else: if hrefedDoc.type != ModelDocument.Type.UnknownNonXML: @@ -122,25 +122,25 @@ def checkDTS(val, modelDocument, checkedModelDocuments): if hrefedElt is None: val.modelXbrl.error("xbrl.3.5.4:hrefIdNotFound", _("Href %(elementHref)s not located"), - modelObject=hrefElt, + modelObject=hrefElt, elementHref=hrefElt.get("{http://www.w3.org/1999/xlink}href")) else: hrefedElt = hrefedDoc.xmlRootElement - - if hrefId: #check scheme regardless of whether document loaded + + if hrefId: #check scheme regardless of whether document loaded # check all xpointer schemes for scheme, path in XmlUtil.xpointerSchemes(hrefId): if scheme != "element": val.modelXbrl.error("xbrl.3.5.4:hrefScheme", _("Href %(elementHref)s unsupported scheme: %(scheme)s"), - modelObject=hrefElt, + modelObject=hrefElt, elementHref=hrefElt.get("{http://www.w3.org/1999/xlink}href"), scheme=scheme) break elif val.validateEFMorGFM: val.modelXbrl.error(("EFM.6.03.06", "GFM.1.01.03"), _("Href %(elementHref)s may only have shorthand xpointers"), - modelObject=hrefElt, + modelObject=hrefElt, elementHref=hrefElt.get("{http://www.w3.org/1999/xlink}href")) # check href'ed target if a linkbaseRef if hrefElt.namespaceURI == XbrlConst.link: @@ -152,7 +152,7 @@ def checkDTS(val, modelDocument, checkedModelDocuments): hrefedElt.namespaceURI != XbrlConst.link or hrefedElt.localName != "linkbase"): val.modelXbrl.error("xbrl.4.3.2:linkbaseRefHref", _("LinkbaseRef %(linkbaseHref)s does not identify an link:linkbase element"), - modelObject=(hrefElt, hrefedDoc), + modelObject=(hrefElt, hrefedDoc), linkbaseHref=hrefElt.get("{http://www.w3.org/1999/xlink}href")) elif hrefElt.get("{http://www.w3.org/1999/xlink}role") is not None: role = hrefElt.get("{http://www.w3.org/1999/xlink}role") @@ -173,7 +173,7 @@ def checkDTS(val, modelDocument, checkedModelDocuments): (ns != XbrlConst.link or ln != "referenceLink")): val.modelXbrl.error("xbrl.4.3.4:linkbaseRefLinks", "LinkbaseRef %(linkbaseHref)s role %(role)s has wrong extended link %(link)s", - modelObject=hrefElt, + modelObject=hrefElt, linkbaseHref=hrefElt.get("{http://www.w3.org/1999/xlink}href"), role=role, link=linkNode.prefixedName) elif hrefElt.localName == "schemaRef": @@ -183,7 +183,7 @@ def checkDTS(val, modelDocument, checkedModelDocuments): val.modelXbrl.error("xbrl.4.2.2:schemaRefHref", _("SchemaRef %(schemaRef)s does not identify an xsd:schema element"), modelObject=hrefElt, schemaRef=hrefElt.get("{http://www.w3.org/1999/xlink}href")) - # check loc target + # check loc target elif hrefElt.localName == "loc": linkElt = hrefElt.getparent() if linkElt.namespaceURI == XbrlConst.link: @@ -233,7 +233,7 @@ def checkDTS(val, modelDocument, checkedModelDocuments): modelObject=hrefElt, locHref=hrefElt.get("{http://www.w3.org/1999/xlink}href")) ''' is this ever needed??? else: # generic link or other non-2.1 link element - if (hrefElt.modelDocument.inDTS and + if (hrefElt.modelDocument.inDTS and ModelDocument.Type.firstXBRLtype <= hrefElt.modelDocument.type <= ModelDocument.Type.lastXBRLtype and # is a discovered linkbase not ModelDocument.Type.firstXBRLtype <= hrefedDoc.type <= ModelDocument.Type.lastXBRLtype): # must discover schema or linkbase val.modelXbrl.error("xbrl.3.2.3:linkLocTarget", @@ -241,7 +241,7 @@ def checkDTS(val, modelDocument, checkedModelDocuments): modelObject=(hrefedElt, hrefedDoc), xlinkLabel=hrefElt.get("{http://www.w3.org/1999/xlink}label")) ''' - # non-standard link holds standard loc, href must be discovered document + # non-standard link holds standard loc, href must be discovered document if (hrefedDoc.type < ModelDocument.Type.firstXBRLtype or # range of doc types that can have linkbase hrefedDoc.type > ModelDocument.Type.lastXBRLtype or not hrefedDoc.inDTS): @@ -249,20 +249,20 @@ def checkDTS(val, modelDocument, checkedModelDocuments): _("Loc's href %(locHref)s does not identify an element in an XBRL document discovered as part of the DTS"), modelObject=hrefElt, locHref=hrefElt.get("{http://www.w3.org/1999/xlink}href")) - # used in linkbase children navigation but may be errant linkbase elements + # used in linkbase children navigation but may be errant linkbase elements val.roleRefURIs = {} val.arcroleRefURIs = {} val.elementIDs = {} val.conceptNames = {} - val.annotationsCount = 0 - + val.annotationsCount = 0 + # XML validation checks (remove if using validating XML) val.extendedElementName = None isFilingDocument = False # validate contents of entry point document or its sibling/descendant documents or in report package of entry point if ((modelDocument.uri.startswith(val.modelXbrl.uriDir) or # document uri in same subtree as entry doocument (val.modelXbrl.fileSource.isOpen and modelDocument.filepath.startswith(val.modelXbrl.fileSource.baseurl))) and # document in entry submission's package - modelDocument.targetNamespace not in val.disclosureSystem.baseTaxonomyNamespaces and + modelDocument.targetNamespace not in val.disclosureSystem.baseTaxonomyNamespaces and modelDocument.xmlDocument): isFilingDocument = True val.valUsedPrefixes = set() @@ -271,15 +271,15 @@ def checkDTS(val, modelDocument, checkedModelDocuments): val.referencedNamespaces = set() val.containsRelationship = False - + checkElements(val, modelDocument, modelDocument.xmlDocument) - - if (modelDocument.type == ModelDocument.Type.INLINEXBRL and + + if (modelDocument.type == ModelDocument.Type.INLINEXBRL and val.validateGFM and (val.documentTypeEncoding.lower() != 'utf-8' or val.metaContentTypeEncoding.lower() != 'utf-8')): val.modelXbrl.error("GFM.1.10.4", _("XML declaration encoding %(encoding)s and meta content type encoding %(metaContentTypeEncoding)s must both be utf-8"), - modelXbrl=modelDocument, encoding=val.documentTypeEncoding, + modelXbrl=modelDocument, encoding=val.documentTypeEncoding, metaContentTypeEncoding=val.metaContentTypeEncoding) if val.validateSBRNL: for pluginXbrlMethod in pluginClassMethods("Validate.SBRNL.DTS.document"): @@ -309,7 +309,7 @@ def linkbaseTopElts(): for refPass in (True, False): # do roleType and arcroleType before extended links and any other children for child in parent.iterchildren(): if refPass == (isinstance(child,ModelObject) - and child.localName in ("roleRef","arcroleRef") + and child.localName in ("roleRef","arcroleRef") and child.namespaceURI == XbrlConst.link): yield child childrenIter = linkbaseTopElts() @@ -330,7 +330,7 @@ def linkbaseTopElts(): parentIsAppinfo = False if modelDocument.type == ModelDocument.Type.INLINEXBRL: if isinstance(parent,ModelObject): # element - if (parent.localName == "meta" and parent.namespaceURI == XbrlConst.xhtml and + if (parent.localName == "meta" and parent.namespaceURI == XbrlConst.xhtml and (parent.get("http-equiv") or "").lower() == "content-type"): val.metaContentTypeEncoding = HtmlUtil.attrValue(parent.get("content"), "charset") elif isinstance(parent,etree._ElementTree): # documentNode @@ -339,7 +339,7 @@ def linkbaseTopElts(): instanceOrder = 0 if modelDocument.type == ModelDocument.Type.SCHEMA: - ncnameTests = (("id","xml.3.3.1:idMustBeUnique", val.elementIDs), + ncnameTests = (("id","xml.3.3.1:idMustBeUnique", val.elementIDs), ("name","xbrl.5.1.1:conceptName", val.conceptNames)) else: ncnameTests = (("id","xml.3.3.1:idMustBeUnique", val.elementIDs),) @@ -358,13 +358,13 @@ def linkbaseTopElts(): if attrValue in _valueItems: # 2.1 spec @id validation refers to http://www.w3.org/TR/REC-xml#NT-TokenizedType # TODO: this check should not test inline elements, those should be in ModelDocument inlineIxdsDiscover using ixdsEltById - val.modelXbrl.error(errCode, + val.modelXbrl.error(errCode, _("Element %(element)s %(attribute)s %(value)s is duplicated"), modelObject=(elt,_valueItems[attrValue]), element=elt.prefixedName, attribute=name, value=attrValue, messageCodes=("xml.3.3.1:idMustBeUnique", "xbrl.5.1.1:conceptName")) else: _valueItems[attrValue] = elt - + # checks for elements in schemas only if isSchema: if elt.namespaceURI == XbrlConst.xsd: @@ -404,7 +404,7 @@ def linkbaseTopElts(): val.modelXbrl.error("SBR.NL.2.2.0.01", _('Schema contains XSD 1.1 content "%(element)s"'), modelObject=elt, element=elt.qname) - + if localName == "element": for attr, presence, errCode in (("block", False, "2.2.2.09"), ("final", False, "2.2.2.10"), @@ -413,7 +413,7 @@ def linkbaseTopElts(): if (elt.get(attr) is not None) != presence: val.modelXbrl.error("SBR.NL.{0}".format(errCode), _('Schema element %(concept)s %(requirement)s contain attribute %(attribute)s'), - modelObject=elt, concept=elt.get("name"), + modelObject=elt, concept=elt.get("name"), requirement=(_("MUST NOT"),_("MUST"))[presence], attribute=attr, messageCodes=("SBR.NL.2.2.2.09", "SBR.NL.2.2.2.10", "SBR.NL.2.2.2.11", "SBR.NL.2.2.2.12")) eltName = elt.get("name") @@ -437,7 +437,7 @@ def linkbaseTopElts(): if (elt.get(attr) is not None) != presence: val.modelXbrl.error("SBR.NL.{0}".format(errCode), _('Schema root element %(concept)s %(requirement)s contain attribute %(attribute)s'), - modelObject=elt, concept=elt.get("name"), + modelObject=elt, concept=elt.get("name"), requirement=(_("MUST NOT"),_("MUST"))[presence], attribute=attr, messageCodes=("SBR.NL.2.2.2.08", "SBR.NL.2.2.2.13", "SBR.NL.2.2.2.15", "SBR.NL.2.2.2.18")) # semantic checks @@ -508,8 +508,8 @@ def linkbaseTopElts(): if localName in {"attribute", "element", "attributeGroup"}: ref = elt.get("ref") if ref is not None: - if qname(elt, ref) not in {"attribute":val.modelXbrl.qnameAttributes, - "element":val.modelXbrl.qnameConcepts, + if qname(elt, ref) not in {"attribute":val.modelXbrl.qnameAttributes, + "element":val.modelXbrl.qnameConcepts, "attributeGroup":val.modelXbrl.qnameAttributeGroups}[localName]: val.modelXbrl.error("xmlSchema:refNotFound", _("%(element)s ref %(ref)s not found"), @@ -517,7 +517,7 @@ def linkbaseTopElts(): if val.validateSBRNL and localName == "attribute": val.modelXbrl.error("SBR.NL.2.2.11.06", _('xs:attribute must not be used'), modelObject=elt) - + if localName == "appinfo": if val.validateSBRNL: if (parent.localName != "annotation" or parent.namespaceURI != XbrlConst.xsd or @@ -536,7 +536,7 @@ def linkbaseTopElts(): val.modelXbrl.error("SBR.NL.2.2.0.12", _('Schema file annotation missing appinfo element must be be behind schema and before import'), modelObject=elt) - + if val.validateEFM and localName in {"element", "complexType", "simpleType"}: name = elt.get("name") if name and len(name) > 64: @@ -546,9 +546,9 @@ def linkbaseTopElts(): _("Schema %(element)s has a name length (%(length)s) over 200 bytes long in utf-8, %(name)s."), edgarCode="du-0729-Name-Length-Limit", modelObject=elt, element=localName, name=name, length=l) - + if val.validateSBRNL and localName in {"all", "documentation", "any", "anyAttribute", "attributeGroup", - # comment out per R.H. 2011-11-16 "complexContent", "complexType", "extension", + # comment out per R.H. 2011-11-16 "complexContent", "complexType", "extension", "field", "group", "key", "keyref", "list", "notation", "redefine", "selector", "unique"}: val.modelXbrl.error("SBR.NL.2.2.11.{0:02}".format({"all":1, "documentation":2, "any":3, "anyAttribute":4, "attributeGroup":7, @@ -556,7 +556,7 @@ def linkbaseTopElts(): "list":17, "notation":18, "redefine":20, "selector":22, "unique":23}[localName]), _('Schema file element must not be used "%(element)s"'), modelObject=elt, element=elt.qname, - messageCodes=("SBR.NL.2.2.11.1", "SBR.NL.2.2.11.2", "SBR.NL.2.2.11.3", "SBR.NL.2.2.11.4", "SBR.NL.2.2.11.7", "SBR.NL.2.2.11.10", "SBR.NL.2.2.11.11", "SBR.NL.2.2.11.12", + messageCodes=("SBR.NL.2.2.11.1", "SBR.NL.2.2.11.2", "SBR.NL.2.2.11.3", "SBR.NL.2.2.11.4", "SBR.NL.2.2.11.7", "SBR.NL.2.2.11.10", "SBR.NL.2.2.11.11", "SBR.NL.2.2.11.12", "SBR.NL.2.2.11.13", "SBR.NL.2.2.11.14", "SBR.NL.2.2.11.15", "SBR.NL.2.2.11.16", "SBR.NL.2.2.11.17", "SBR.NL.2.2.11.18", "SBR.NL.2.2.11.20", "SBR.NL.2.2.11.22", "SBR.NL.2.2.11.23")) if val.inSchemaTop: if localName in schemaBottom: @@ -565,11 +565,11 @@ def linkbaseTopElts(): val.modelXbrl.error("xmlschema.3.4.2:contentModel", _("Element %(element)s is mis-located in schema file"), modelObject=elt, element=elt.prefixedName) - - # check schema roleTypes + + # check schema roleTypes if elt.localName in ("roleType","arcroleType") and elt.namespaceURI == XbrlConst.link: uriAttr, xbrlSection, roleTypes, localRoleTypes = { - "roleType":("roleURI","5.1.3",val.modelXbrl.roleTypes, val.schemaRoleTypes), + "roleType":("roleURI","5.1.3",val.modelXbrl.roleTypes, val.schemaRoleTypes), "arcroleType":("arcroleURI","5.1.4",val.modelXbrl.arcroleTypes, val.schemaArcroleTypes) }[elt.localName] if not parent.localName == "appinfo" and parent.namespaceURI == XbrlConst.xsd: @@ -642,7 +642,7 @@ def linkbaseTopElts(): _("%(element)s usedOn must not be link:calculationLink"), modelObject=elt, element=parent.qname, value=qName) if elt.localName == "roleType" and qName in XbrlConst.standardExtLinkQnames: - if not any((key[1] == roleURI and key[2] == qName) + if not any((key[1] == roleURI and key[2] == qName) for key in val.modelXbrl.baseSets.keys()): val.modelXbrl.error("SBR.NL.2.2.3.02", _("%(element)s usedOn %(usedOn)s not addressed for role %(role)s"), @@ -672,7 +672,7 @@ def linkbaseTopElts(): modelObject=elt) elif elt.localName in ("roleRef","arcroleRef"): uriAttr, xbrlSection, roleTypeDefs, refs, ixdsTgtRefs = { - "roleRef":("roleURI","3.5.2.4",val.modelXbrl.roleTypes,val.roleRefURIs, val.ixdsRoleRefURIs), + "roleRef":("roleURI","3.5.2.4",val.modelXbrl.roleTypes,val.roleRefURIs, val.ixdsRoleRefURIs), "arcroleRef":("arcroleURI","3.5.2.5",val.modelXbrl.arcroleTypes,val.arcroleRefURIs, val.ixdsArcroleRefURIs) }[elt.localName] if parentIsAppinfo: @@ -683,7 +683,7 @@ def linkbaseTopElts(): modelObject=elt, elementName=elt.localName, messageCodes=("info:roleRefLocation", "info:arcroleRefLocation")) else: # parent is linkbase or instance, element IS in the right location - + # check for duplicate roleRefs when parent is linkbase or instance element refUri = elt.get(uriAttr) hrefAttr = elt.get("{http://www.w3.org/1999/xlink}href") @@ -713,8 +713,8 @@ def linkbaseTopElts(): _("%(element)s %(refURI)s defined with different URI"), modelObject=(elt,roleTypeElt), element=elt.qname, refURI=refUri, messageCodes=("xbrl.3.5.2.4.5:roleRefMismatch", "xbrl.3.5.2.5.5:arcroleRefMismatch")) - - + + if val.validateEFMorGFMorSBRNL: if elt.localName == "arcroleRef": if hrefUri not in val.disclosureSystem.standardTaxonomiesDict: @@ -816,7 +816,7 @@ def linkbaseTopElts(): modelObject=elt, element=elt.elementQname) if elt.get("{http://www.w3.org/1999/xlink}arcrole") is not None: checkArcrole(val, elt, elt.qname, elt.get("{http://www.w3.org/1999/xlink}arcrole"), val.arcroleRefURIs) - + #check resources if parentXlinkType == "extended": if elt.localName not in ("documentation", "title") and \ @@ -824,13 +824,13 @@ def linkbaseTopElts(): val.modelXbrl.error("xbrl.3.5.3.8.1:resourceType", _("Element %(element)s appears to be a resource missing xlink:type=\"resource\""), modelObject=elt, element=elt.qname) - elif (xlinkType == "locator" and elt.namespaceURI != XbrlConst.link and - parent.namespaceURI == XbrlConst.link and parent.localName in link_loc_spec_sections): + elif (xlinkType == "locator" and elt.namespaceURI != XbrlConst.link and + parent.namespaceURI == XbrlConst.link and parent.localName in link_loc_spec_sections): val.modelXbrl.error("xbrl.{0}:customLocator".format(link_loc_spec_sections[parent.localName]), _("Element %(element)s is a custom locator in a standard %(link)s"), modelObject=(elt,parent), element=elt.qname, link=parent.qname, messageCodes=("xbrl.5.2.2.1:customLocator", "xbrl.5.2.3.1:customLocator", "xbrl.5.2.5.1:customLocator", "xbrl.5.2.6.1:customLocator", "xbrl.5.2.4.1:customLocator", "xbrl.4.11.1.1:customLocator")) - + if xlinkType == "resource": if not elt.get("{http://www.w3.org/1999/xlink}label"): val.modelXbrl.error("xbrl.3.5.3.8.2:resourceLabel", @@ -862,7 +862,7 @@ def linkbaseTopElts(): xlinkLabel=elt.get("{http://www.w3.org/1999/xlink}label"), lang=elt.get("{http://www.w3.org/XML/1998/namespace}lang"), messageCodes=("SBR.NL.2.3.8.01", "SBR.NL.2.3.8.02", "arelle:langError")) - + if isInstance: if elt.namespaceURI == XbrlConst.xbrli: expectedSequence = instanceSequence.get(elt.localName,9) @@ -888,8 +888,8 @@ def linkbaseTopElts(): "Schema must be a root element, and may not be nested in %(parent)s", parent=elt.parentQname, modelObject=elt) - - if modelDocument.type == ModelDocument.Type.INLINEXBRL and elt.namespaceURI in XbrlConst.ixbrlAll: + + if modelDocument.type == ModelDocument.Type.INLINEXBRL and elt.namespaceURI in XbrlConst.ixbrlAll: if elt.localName == "footnote": if val.validateGFM: if elt.get("{http://www.w3.org/1999/xlink}arcrole") != XbrlConst.factFootnote: @@ -899,9 +899,9 @@ def linkbaseTopElts(): for e in XmlUtil.ancestors(elt, ns, "div")): val.modelXbrl.error(("EFM.N/A", "GFM:1.10.16"), _("Inline XBRL footnote %(footnoteID)s must be in non-displayable div due to arcrole %(arcrole)s"), - modelObject=elt, footnoteID=elt.get("footnoteID"), + modelObject=elt, footnoteID=elt.get("footnoteID"), arcrole=elt.get("{http://www.w3.org/1999/xlink}arcrole")) - + if not elt.get("{http://www.w3.org/XML/1998/namespace}lang"): val.modelXbrl.error(("EFM.N/A", "GFM:1.10.13"), _("Inline XBRL footnote %(footnoteID)s is missing an xml:lang attribute"), @@ -909,7 +909,7 @@ def linkbaseTopElts(): if elt.namespaceURI == XbrlConst.ixbrl: val.ixdsFootnotes[elt.footnoteID] = elt else: - checkIxContinuationChain(elt) + checkIxContinuationChain(elt) if not elt.xmlLang: val.modelXbrl.error(ixMsgCode("footnoteLang", elt, sect="validation"), _("Inline XBRL footnotes require an in-scope xml:lang"), @@ -1020,7 +1020,7 @@ def linkbaseTopElts(): modelObject=elt, element=elt.qname) elif not XbrlConst.isStandardRole(xlinkRole): modelsRole = val.modelXbrl.roleTypes.get(xlinkRole) - if (modelsRole is None or len(modelsRole) == 0 or + if (modelsRole is None or len(modelsRole) == 0 or modelsRole[0].modelDocument.targetNamespace not in val.disclosureSystem.standardTaxonomiesDict): val.modelXbrl.error(("EFM.6.09.05", "GFM.1.04.05", "SBR.NL.2.3.10.14"), _("Resource %(xlinkLabel)s role %(role)s is not a standard taxonomy role"), @@ -1032,7 +1032,7 @@ def linkbaseTopElts(): if isinstance(child,ModelObject) and child.namespaceURI.startswith("http://www.xbrl.org") and child.namespaceURI != "http://www.xbrl.org/2006/ref": val.modelXbrl.error("SBR.NL.2.3.3.01", _("Reference %(xlinkLabel)s has unauthorized part element %(element)s"), - modelObject=elt, xlinkLabel=elt.get("{http://www.w3.org/1999/xlink}label"), + modelObject=elt, xlinkLabel=elt.get("{http://www.w3.org/1999/xlink}label"), element=qname(child)) id = elt.get("id") if not id: @@ -1065,7 +1065,7 @@ def linkbaseTopElts(): if int(priority) >= 10: val.modelXbrl.error(("EFM.6.09.09", "GFM.1.04.08"), _("Arc from %(xlinkFrom)s to %(xlinkTo)s priority %(priority)s must be less than 10"), - modelObject=elt, + modelObject=elt, arcElement=elt.qname, xlinkFrom=elt.get("{http://www.w3.org/1999/xlink}from"), xlinkTo=elt.get("{http://www.w3.org/1999/xlink}to"), @@ -1073,7 +1073,7 @@ def linkbaseTopElts(): except (ValueError) : val.modelXbrl.error(("EFM.6.09.09", "GFM.1.04.08"), _("Arc from %(xlinkFrom)s to %(xlinkTo)s priority %(priority)s is not an integer"), - modelObject=elt, + modelObject=elt, arcElement=elt.qname, xlinkFrom=elt.get("{http://www.w3.org/1999/xlink}from"), xlinkTo=elt.get("{http://www.w3.org/1999/xlink}to"), @@ -1082,7 +1082,7 @@ def linkbaseTopElts(): if elt.localName == "presentationArc" and not elt.get("order"): val.modelXbrl.error(("EFM.6.12.01", "GFM.1.06.01", "SBR.NL.2.3.4.04"), _("PresentationArc from %(xlinkFrom)s to %(xlinkTo)s must have an order"), - modelObject=elt, + modelObject=elt, xlinkFrom=elt.get("{http://www.w3.org/1999/xlink}from"), xlinkTo=elt.get("{http://www.w3.org/1999/xlink}to"), conceptFrom=arcFromConceptQname(elt), @@ -1091,7 +1091,7 @@ def linkbaseTopElts(): if not elt.get("order"): val.modelXbrl.error(("EFM.6.14.01", "GFM.1.07.01"), _("CalculationArc from %(xlinkFrom)s to %(xlinkTo)s must have an order"), - modelObject=elt, + modelObject=elt, xlinkFrom=elt.get("{http://www.w3.org/1999/xlink}from"), xlinkTo=elt.get("{http://www.w3.org/1999/xlink}to"), conceptFrom=arcFromConceptQname(elt), @@ -1102,7 +1102,7 @@ def linkbaseTopElts(): if not weight in (1, -1): val.modelXbrl.error(("EFM.6.14.02", "GFM.1.07.02"), _("CalculationArc from %(xlinkFrom)s to %(xlinkTo)s weight %(weight)s must be 1 or -1"), - modelObject=elt, + modelObject=elt, xlinkFrom=elt.get("{http://www.w3.org/1999/xlink}from"), xlinkTo=elt.get("{http://www.w3.org/1999/xlink}to"), conceptFrom=arcFromConceptQname(elt), @@ -1111,7 +1111,7 @@ def linkbaseTopElts(): except ValueError: val.modelXbrl.error(("EFM.6.14.02", "GFM.1.07.02"), _("CalculationArc from %(xlinkFrom)s to %(xlinkTo)s must have an weight (value error in \"%(weight)s\")"), - modelObject=elt, + modelObject=elt, xlinkFrom=elt.get("{http://www.w3.org/1999/xlink}from"), xlinkTo=elt.get("{http://www.w3.org/1999/xlink}to"), conceptFrom=arcFromConceptQname(elt), @@ -1121,32 +1121,32 @@ def linkbaseTopElts(): if not elt.get("order"): val.modelXbrl.error(("EFM.6.16.01", "GFM.1.08.01"), _("DefinitionArc from %(xlinkFrom)s to %(xlinkTo)s must have an order"), - modelObject=elt, + modelObject=elt, xlinkFrom=elt.get("{http://www.w3.org/1999/xlink}from"), xlinkTo=elt.get("{http://www.w3.org/1999/xlink}to"), conceptFrom=arcFromConceptQname(elt), conceptTo=arcToConceptQname(elt)) if val.validateSBRNL and arcrole in (XbrlConst.essenceAlias, XbrlConst.similarTuples, XbrlConst.requiresElement): val.modelXbrl.error({XbrlConst.essenceAlias: "SBR.NL.2.3.2.02", - XbrlConst.similarTuples: "SBR.NL.2.3.2.03", + XbrlConst.similarTuples: "SBR.NL.2.3.2.03", XbrlConst.requiresElement: "SBR.NL.2.3.2.04"}[arcrole], _("DefinitionArc from %(xlinkFrom)s to %(xlinkTo)s has unauthorized arcrole %(arcrole)s"), - modelObject=elt, + modelObject=elt, xlinkFrom=elt.get("{http://www.w3.org/1999/xlink}from"), - xlinkTo=elt.get("{http://www.w3.org/1999/xlink}to"), + xlinkTo=elt.get("{http://www.w3.org/1999/xlink}to"), arcrole=arcrole, - messageCodes=("SBR.NL.2.3.2.02", "SBR.NL.2.3.2.03", "SBR.NL.2.3.2.04")), + messageCodes=("SBR.NL.2.3.2.02", "SBR.NL.2.3.2.03", "SBR.NL.2.3.2.04")), elif elt.localName == "referenceArc" and val.validateSBRNL: if elt.get("order"): val.modelXbrl.error("SBR.NL.2.3.3.05", _("ReferenceArc from %(xlinkFrom)s to %(xlinkTo)s has an order"), - modelObject=elt, + modelObject=elt, xlinkFrom=elt.get("{http://www.w3.org/1999/xlink}from"), xlinkTo=elt.get("{http://www.w3.org/1999/xlink}to")) if val.validateSBRNL and elt.get("use") == "prohibited" and elt.getparent().tag in ( - "{http://www.xbrl.org/2003/linkbase}presentationLink", - "{http://www.xbrl.org/2003/linkbase}labelLink", - "{http://xbrl.org/2008/generic}link", + "{http://www.xbrl.org/2003/linkbase}presentationLink", + "{http://www.xbrl.org/2003/linkbase}labelLink", + "{http://xbrl.org/2008/generic}link", "{http://www.xbrl.org/2003/linkbase}referenceLink"): val.modelXbrl.error("SBR.NL.2.3.0.10", _("%(arc)s must not contain use='prohibited'"), @@ -1173,7 +1173,7 @@ def linkbaseTopElts(): if elt.namespaceURI not in val.disclosureSystem.baseTaxonomyNamespaces: val.modelXbrl.error("SBR.NL.2.2.0.20", _("%(fileType)s element %(element)s must not have custom namespace %(namespace)s"), - modelObject=elt, element=elt.qname, + modelObject=elt, element=elt.qname, fileType="schema" if isSchema else "linkbase" , namespace=elt.namespaceURI) for attrTag, attrValue in elt.items(): @@ -1183,7 +1183,7 @@ def linkbaseTopElts(): if ns and ns not in val.disclosureSystem.baseTaxonomyNamespaces: val.modelXbrl.error("SBR.NL.2.2.0.20", _("%(fileType)s element %(element)s must not have %(prefix)s:%(localName)s"), - modelObject=elt, element=elt.qname, + modelObject=elt, element=elt.qname, fileType="schema" if isSchema else "linkbase" , prefix=prefix, localName=localName) if isSchema and localName in ("base", "ref", "substitutionGroup", "type"): @@ -1197,20 +1197,20 @@ def linkbaseTopElts(): if prefix not in parentElt.nsmap or parentElt.nsmap[prefix] != ns: val.modelXbrl.error(("SBR.NL.2.2.0.19" if isSchema else "SBR.NL.2.3.1.01"), _("%(fileType)s element %(element)s must not have xmlns:%(prefix)s"), - modelObject=elt, element=elt.qname, + modelObject=elt, element=elt.qname, fileType="schema" if isSchema else "linkbase" , prefix=prefix, messageCodes=("SBR.NL.2.2.0.19", "SBR.NL.2.3.1.01")) - - if elt.localName == "roleType" and not elt.get("id"): + + if elt.localName == "roleType" and not elt.get("id"): val.modelXbrl.error("SBR.NL.2.3.10.11", _("RoleType %(roleURI)s missing id attribute"), modelObject=elt, roleURI=elt.get("roleURI")) - elif elt.localName == "loc" and elt.get("{http://www.w3.org/1999/xlink}role"): + elif elt.localName == "loc" and elt.get("{http://www.w3.org/1999/xlink}role"): val.modelXbrl.error("SBR.NL.2.3.10.08", _("Loc %(xlinkLabel)s has unauthorized role attribute"), modelObject=elt, xlinkLabel=elt.get("{http://www.w3.org/1999/xlink}label")) - elif elt.localName == "documentation": + elif elt.localName == "documentation": val.modelXbrl.error("SBR.NL.2.3.10.12" if elt.namespaceURI == XbrlConst.link else "SBR.NL.2.2.11.02", _("Documentation element must not be used: %(value)s"), modelObject=elt, value=XmlUtil.text(elt), @@ -1228,7 +1228,7 @@ def linkbaseTopElts(): ("{http://www.w3.org/2001/XMLSchema-instance}nil", "SBR.NL.2.3.10.05"), ("{http://www.w3.org/2001/XMLSchema-instance}noNamespaceSchemaLocation", "SBR.NL.2.3.10.06"), ("{http://www.w3.org/2001/XMLSchema-instance}type", "SBR.NL.2.3.10.07")): - if elt.get(attrName) is not None: + if elt.get(attrName) is not None: val.modelXbrl.error(errCode, _("Linkbase element %(element)s must not have attribute %(attribute)s"), modelObject=elt, element=elt.qname, attribute=attrName, @@ -1236,12 +1236,12 @@ def linkbaseTopElts(): for attrName, errCode in (("{http://www.w3.org/1999/xlink}actuate", "SBR.NL.2.3.10.01"), ("{http://www.w3.org/1999/xlink}show", "SBR.NL.2.3.10.02"), ("{http://www.w3.org/1999/xlink}title", "SBR.NL.2.3.10.03")): - if elt.get(attrName) is not None: + if elt.get(attrName) is not None: val.modelXbrl.error(errCode, _("Linkbase element %(element)s must not have attribute xlink:%(attribute)s"), modelObject=elt, element=elt.qname, attribute=attrName, messageCodes=("SBR.NL.2.3.10.01", "SBR.NL.2.3.10.02", "SBR.NL.2.3.10.03")) - + checkElements(val, modelDocument, elt) elif isinstance(elt,ModelComment): # comment node if val.validateSBRNL: @@ -1250,7 +1250,7 @@ def linkbaseTopElts(): _('%(fileType)s must have only one comment node before schema element: "%(value)s"'), modelObject=elt, fileType=modelDocument.gettype().title(), value=elt.text, messageCodes=("SBR.NL.2.2.0.05", "SBR.NL.2.3.0.05")) - + def checkLinkRole(val, elt, linkEltQname, xlinkRole, xlinkType, roleRefURIs): if xlinkRole == "" and xlinkType == "simple": val.modelXbrl.error("xbrl.3.5.1.3:emptySimpleLinkRole", @@ -1314,7 +1314,7 @@ def checkLinkRole(val, elt, linkEltQname, xlinkRole, xlinkType, roleRefURIs): val.modelXbrl.warning("lrr:unApprovedRole", _("LRR resource role %(xlinkRole)s on %(element)s has status %(status)s"), modelObject=elt, xlinkRole=xlinkRole, element=linkEltQname, status=XbrlConst.lrrUnapprovedRoles[xlinkRole]) - + def checkArcrole(val, elt, arcEltQname, arcrole, arcroleRefURIs): if arcrole == "" and \ elt.get("{http://www.w3.org/1999/xlink}type") == "simple": diff --git a/arelle/ValidateXbrlDimensions.py b/arelle/ValidateXbrlDimensions.py index 451535b842..d7cf2d7543 100644 --- a/arelle/ValidateXbrlDimensions.py +++ b/arelle/ValidateXbrlDimensions.py @@ -49,20 +49,20 @@ def checkBaseSet(val, arcrole, ELR, relsSet): if not isinstance(priItemConcept, ModelConcept) or not priItemConcept.isPrimaryItem: val.modelXbrl.error("xbrldte:HasHypercubeSourceError", _("HasHypercube %(arcroleType)s relationship from %(source)s to %(target)s in link role %(linkrole)s must have a primary item source"), - modelObject=hasHcRel, arcroleType=os.path.basename(arcrole), + modelObject=hasHcRel, arcroleType=os.path.basename(arcrole), source=priItemConcept.qname, target=hcConcept.qname, linkrole=ELR) if not isinstance(hcConcept, ModelConcept) or not hcConcept.isHypercubeItem: val.modelXbrl.error("xbrldte:HasHypercubeTargetError", _("HasHypercube %(arcroleType)s relationship from %(source)s to %(target)s in link role %(linkrole)s must have a hypercube declaration target"), - modelObject=hasHcRel, arcroleType=os.path.basename(arcrole), + modelObject=hasHcRel, arcroleType=os.path.basename(arcrole), source=priItemConcept.qname, target=hcConcept.qname, linkrole=ELR) hcContextElement = hasHcRel.contextElement if hcContextElement not in ("segment","scenario"): val.modelXbrl.error("xbrldte:HasHypercubeMissingContextElementAttributeError", _("HasHypercube %(arcroleType)s relationship from %(source)s to %(target)s in link role %(linkrole)s must have a context element"), - modelObject=hasHcRel, arcroleType=os.path.basename(arcrole), + modelObject=hasHcRel, arcroleType=os.path.basename(arcrole), source=priItemConcept.qname, target=hcConcept.qname, linkrole=ELR) - + # must check the cycles starting from hypercube ELR (primary item consec relationship dimELR = hasHcRel.targetRole if not dimELR: @@ -140,7 +140,7 @@ def checkBaseSet(val, arcrole, ELR, relsSet): if fromConcept in val.modelXbrl.dimensionDefaultConcepts and toConcept != val.modelXbrl.dimensionDefaultConcepts[fromConcept]: val.modelXbrl.error("xbrldte:TooManyDefaultMembersError", _("Dimension %(source)s has multiple defaults %(target)s and %(target2)s"), - modelObject=modelRel, source=fromConcept.qname, target=toConcept.qname, + modelObject=modelRel, source=fromConcept.qname, target=toConcept.qname, target2=val.modelXbrl.dimensionDefaultConcepts[fromConcept].qname) else: val.modelXbrl.dimensionDefaultConcepts[fromConcept] = toConcept @@ -187,7 +187,7 @@ def xdtCycle(val, ELRs, rels, fromConcepts): if rel.isUsable and relTo in fromConcepts: # don't think we want this?? and toELR == drsELR: #forms a directed cycle return [rel,] fromConcepts.add(relTo) - for ELR in ELRs: + for ELR in ELRs: domMbrRels = val.modelXbrl.relationshipSet(XbrlConst.domainMember, ELR).fromModelObject(relTo) foundCycle = xdtCycle(val, ELRs, domMbrRels, fromConcepts) if foundCycle is not None: @@ -262,7 +262,7 @@ def logDimAndFacts(modelDimValue): if len(dimAndFacts) > 10: # log up to 10 facts using this context break return dimAndFacts - + # check errorDimensions of context for modelDimValues in (cntx.segDimValues.values(), cntx.scenDimValues.values(), cntx.errorDimValues): for modelDimValue in modelDimValues: @@ -272,12 +272,12 @@ def logDimAndFacts(modelDimValue): modelDimValue.isTyped != (dimensionConcept.get("{http://xbrl.org/2005/xbrldt}typedDomainRef") is not None): val.modelXbrl.error("xbrldie:TypedMemberNotTypedDimensionError" if modelDimValue.isTyped else "xbrldie:ExplicitMemberNotExplicitDimensionError", _("Context %(contextID)s %(dimension)s %(value)s is not an appropriate dimension item"), - modelObject=logDimAndFacts(modelDimValue), contextID=cntx.id, + modelObject=logDimAndFacts(modelDimValue), contextID=cntx.id, dimension=modelDimValue.prefixedName, value=modelDimValue.dimensionQname, messageCodes=("xbrldie:TypedMemberNotTypedDimensionError", "xbrldie:ExplicitMemberNotExplicitDimensionError")) elif modelDimValue.isTyped: typedDomainConcept = dimensionConcept.typedDomainElement - problem = _("missing content") + problem = _("missing content") for element in modelDimValue: if isinstance(element,ModelObject): if problem is None: @@ -290,7 +290,7 @@ def logDimAndFacts(modelDimValue): else: problem = None # validate enumeration set typed dimension value here - if (val.validateEnum and typedDomainConcept.isEnumeration and getattr(element,"xValid", 0) == 4 and + if (val.validateEnum and typedDomainConcept.isEnumeration and getattr(element,"xValid", 0) == 4 and element.get("{http://www.w3.org/2001/XMLSchema-instance}nil") not in ("true","1")): qnEnums = element.xValue if not isinstance(qnEnums, list): qnEnums = (qnEnums,) @@ -310,19 +310,19 @@ def logDimAndFacts(modelDimValue): if problem: val.modelXbrl.error("xbrldie:IllegalTypedDimensionContentError", _("Context %(contextID)s typed dimension %(dimension)s has %(error)s"), - modelObject=logDimAndFacts(modelDimValue), contextID=cntx.id, + modelObject=logDimAndFacts(modelDimValue), contextID=cntx.id, dimension=modelDimValue.dimensionQname, error=problem) if modelDimValue.isExplicit: # this test is required even when ExplicitMemberNotExplicitDimensionError is raised memberConcept = modelDimValue.member if memberConcept is None or not memberConcept.isGlobalDeclaration: val.modelXbrl.error("xbrldie:ExplicitMemberUndefinedQNameError", _("Context %(contextID)s explicit dimension %(dimension)s member %(value)s is not a global member item"), - modelObject=logDimAndFacts(modelDimValue), contextID=cntx.id, + modelObject=logDimAndFacts(modelDimValue), contextID=cntx.id, dimension=modelDimValue.dimensionQname, value=modelDimValue.memberQname) elif val.modelXbrl.dimensionDefaultConcepts.get(dimensionConcept) == memberConcept: val.modelXbrl.error("xbrldie:DefaultValueUsedInInstanceError", _("Context %(contextID)s explicit dimension %(dimension)s member %(value)s is a default member item"), - modelObject=logDimAndFacts(modelDimValue), contextID=cntx.id, + modelObject=logDimAndFacts(modelDimValue), contextID=cntx.id, dimension=modelDimValue.dimensionQname, value=modelDimValue.memberQname) for modelDimValue in cntx.errorDimValues: @@ -339,7 +339,7 @@ def logDimAndFacts(modelDimValue): val.modelXbrl.error("xbrldie:RepeatedDimensionInInstanceError", _("Context %(contextID)s dimension %(dimension)s is a repeated dimension value"), modelObject=logDimAndFacts(modelDimValue), contextID=cntx.id, dimension=modelDimValue.dimensionQname) - + def checkFact(val, f, otherFacts=None): if not isFactDimensionallyValid(val, f, otherFacts): val.modelXbrl.error("xbrldie:PrimaryItemDimensionallyInvalidError", @@ -364,12 +364,12 @@ def isFactDimensionallyValid(val, f, setPrototypeContextElements=False, otherFac ''' if checkFactElrHcs(val, f, ELR, hcRels, setPrototypeContextElements): return True # meets hypercubes in this ELR - + if hasElrHc: # no ELR hypercubes fully met return False return True - + def priItemElrHcRels(val, priItem, ELR=None): key = (priItem, ELR) try: @@ -381,7 +381,7 @@ def priItemElrHcRels(val, priItem, ELR=None): except KeyError: rels = priItemElrHcRels[key] = findPriItemElrHcRels(val, priItem, ELR) return rels - + def findPriItemElrHcRels(val, priItem, ELR=None, elrHcRels=None): if elrHcRels is None: elrHcRels = defaultdict(list) @@ -398,7 +398,7 @@ def findPriItemElrHcRels(val, priItem, ELR=None, elrHcRels=None): return elrHcRels def priItemsOfElrHc(val, priItem, hcELR, relELR, priItems=None): - if priItems is None: + if priItems is None: priItems = set(priItem) for domMbrRel in val.modelXbrl.relationshipSet(XbrlConst.domainMember, relELR).fromModelObject(priItem): toPriItem = domMbrRel.toModelObject @@ -415,7 +415,7 @@ def priItemsOfElrHc(val, priItem, hcELR, relELR, priItems=None): def checkFactElrHcs(val, f, ELR, hcRels, setPrototypeContextElements=False): context = f.context elrValid = True # start assuming ELR is valid - + for hasHcRel in hcRels: hcConcept = hasHcRel.toModelObject hcIsClosed = hasHcRel.isClosed @@ -427,7 +427,7 @@ def checkFactElrHcs(val, f, ELR, hcRels, setPrototypeContextElements=False): contextElementDimSet = set(modelDimValues.keys()) modelNonDimValues = context.nonDimValues(hcContextElement) hcValid = True - + # if closed and any nonDim values, hc invalid if hcIsClosed and len(modelNonDimValues) > 0: hcValid = False @@ -473,7 +473,7 @@ def checkFactElrHcs(val, f, ELR, hcRels, setPrototypeContextElements=False): if not hcValid: elrValid = False return elrValid - + def dimensionMemberUsable(val, dimConcept, memConcept, domELR): try: dimensionMembersUsable = val.dimensionMembersUsable @@ -491,7 +491,7 @@ def dimensionMemberUsable(val, dimConcept, memConcept, domELR): domELR, usableMembers, unusableMembers, defaultdict(set)) usableMembers -= unusableMembers return memConcept in usableMembers - + def findUsableMembersInDomainELR(val, rels, ELR, usableMembers, unusableMembers, toConceptELRs): for rel in rels: toConcept = rel.toModelObject @@ -561,7 +561,7 @@ def memberStateInDomain(val, memConcept, rels, ELR, toConceptELRs=None): for rel in rels: toConcept = rel.toModelObject if toConcept == memConcept: - foundState = max(foundState, + foundState = max(foundState, MEMBER_USABLE if rel.isUsable else MEMBER_NOT_USABLE) toELR = (rel.targetRole or ELR) toELRs = toConceptELRs[toConcept] @@ -577,7 +577,7 @@ def memberStateInDomain(val, memConcept, rels, ELR, toConceptELRs=None): ''' removed because no valid way to check one isolated dimension for validity without full set of others # check a single dimension value for primary item (not the complete set of dimension values) # returnn the contextElement of the hypercube where valid -def checkPriItemDimValueValidity(val, priItemConcept, dimConcept, memConcept, srcCntxEltName=None): +def checkPriItemDimValueValidity(val, priItemConcept, dimConcept, memConcept, srcCntxEltName=None): if priItemConcept is not None and dimConcept is not None: _priItemElrHcRels = priItemElrHcRels(val, priItemConcept) for ELR, hcRels in _priItemElrHcRels.items(): @@ -599,7 +599,7 @@ def checkPriItemDimValueElrHcs(val, priItemConcept, matchDim, matchMem, srcCntxE hcConcept = hasHcRel.toModelObject hcIsClosed = hasHcRel.isClosed cntxElt = hasHcRel.contextElement - + dimELR = (hasHcRel.targetRole or hasHcRel.linkrole) for hcDimRel in val.modelXbrl.relationshipSet( XbrlConst.hypercubeDimension, dimELR).fromModelObject(hcConcept): diff --git a/arelle/ViewFile.py b/arelle/ViewFile.py index 981f4de900..89ab2db9d2 100644 --- a/arelle/ViewFile.py +++ b/arelle/ViewFile.py @@ -14,7 +14,7 @@ else: csvOpenMode = 'wb' # for 2.7 csvOpenNewline = None - + NoneType = type(None) # for isinstance testing # deferred opening of openpyxl so it's not needed in site-packages unless it is used @@ -73,7 +73,7 @@ def __init__(self, modelXbrl, outfile, rootElementName, lang=None, style="table" self.numHdrCols = 0 self.treeCols = 0 # set to number of tree columns for auto-tree-columns if modelXbrl: - if not lang: + if not lang: self.lang = modelXbrl.modelManager.defaultLang if self.type == NOOUT: self.xmlDoc = None @@ -82,7 +82,7 @@ def __init__(self, modelXbrl, outfile, rootElementName, lang=None, style="table" if isinstance(self.outfile, FileNamedStringIO): self.csvFile = self.outfile else: - # note: BOM signature required for Excel to open properly with characters > 0x7f + # note: BOM signature required for Excel to open properly with characters > 0x7f self.csvFile = open(outfile, csvOpenMode, newline=csvOpenNewline, encoding='utf-8-sig') self.csvWriter = csv.writer(self.csvFile, dialect="excel") elif self.type == XLSX: @@ -100,15 +100,15 @@ def __init__(self, modelXbrl, outfile, rootElementName, lang=None, style="table" -