def nonDimHash(self): try: return self._nonDimsHash except AttributeError: self._nonDimsHash = hash( (XbrlUtil.equalityHash(self.nonDimValues("segment")), XbrlUtil.equalityHash(self.nonDimValues("scenario"))) ) return self._nonDimsHash
def isEqualTo_(self, cntx2, dimensionalAspectModel): if cntx2 is None: return False if cntx2 == self: # same context return True if (self.periodHash != cntx2.periodHash or self.entityIdentifierHash != cntx2.entityIdentifierHash): return False if dimensionalAspectModel: if (self.dimsHash != cntx2.dimsHash or self.nonDimHash != cntx2.nonDimHash): return False else: if (self.segmentHash != cntx2.segmentHash or self.scenarioHash != cntx2.scenarioHash): return False if self.periodHash != cntx2.periodHash or not self.isPeriodEqualTo( cntx2) or not self.isEntityIdentifierEqualTo(cntx2): return False if dimensionalAspectModel: if _DICT_SET(self.qnameDims.keys()) != _DICT_SET( cntx2.qnameDims.keys()): return False for dimQname, ctx1Dim in self.qnameDims.items(): if not ctx1Dim.isEqualTo(cntx2.qnameDims[dimQname]): return False for nonDimVals1, nonDimVals2 in ((self.segNonDimValues, cntx2.segNonDimValues), (self.scenNonDimValues, cntx2.scenNonDimValues)): if len(nonDimVals1) != len(nonDimVals2): return False for i, nonDimVal1 in enumerate(nonDimVals1): if not XbrlUtil.sEqual(self.modelXbrl, nonDimVal1, nonDimVals2[i]): return False else: if self.hasSegment: if not cntx2.hasSegment: return False if not XbrlUtil.sEqual(self.modelXbrl, self.segment, cntx2.segment): return False elif cntx2.hasSegment: return False if self.hasScenario: if not cntx2.hasScenario: return False if not XbrlUtil.sEqual(self.modelXbrl, self.scenario, cntx2.scenario): return False elif cntx2.hasScenario: return False return True
def isEqualTo_(self, cntx2, dimensionalAspectModel): """(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 if cntx2 == self: # same context return True if (self.periodHash != cntx2.periodHash or self.entityIdentifierHash != cntx2.entityIdentifierHash): return False if dimensionalAspectModel: if (self.dimsHash != cntx2.dimsHash or self.nonDimHash != cntx2.nonDimHash): return False else: if (self.segmentHash != cntx2.segmentHash or self.scenarioHash != cntx2.scenarioHash): return False if self.periodHash != cntx2.periodHash or not self.isPeriodEqualTo(cntx2) or not self.isEntityIdentifierEqualTo(cntx2): return False if dimensionalAspectModel: if _DICT_SET(self.qnameDims.keys()) != _DICT_SET(cntx2.qnameDims.keys()): return False for dimQname, ctx1Dim in self.qnameDims.items(): if not ctx1Dim.isEqualTo(cntx2.qnameDims[dimQname]): return False for nonDimVals1, nonDimVals2 in ((self.segNonDimValues,cntx2.segNonDimValues), (self.scenNonDimValues,cntx2.scenNonDimValues)): if len(nonDimVals1) != len(nonDimVals2): return False for i, nonDimVal1 in enumerate(nonDimVals1): if not XbrlUtil.sEqual(self.modelXbrl, nonDimVal1, nonDimVals2[i]): return False else: if self.hasSegment: if not cntx2.hasSegment: return False if not XbrlUtil.sEqual(self.modelXbrl, self.segment, cntx2.segment): return False elif cntx2.hasSegment: return False if self.hasScenario: if not cntx2.hasScenario: return False if not XbrlUtil.sEqual(self.modelXbrl, self.scenario, cntx2.scenario): return False elif cntx2.hasScenario: return False return True
def isEqualTo_(self, cntx2, dimensionalAspectModel): if cntx2 is None: return False if cntx2 == self: # same context return True if (self.periodHash != cntx2.periodHash or self.entityIdentifierHash != cntx2.entityIdentifierHash): return False if dimensionalAspectModel: if (self.dimsHash != cntx2.dimsHash or self.nonDimHash != cntx2.nonDimHash): return False else: if (self.segmentHash != cntx2.segmentHash or self.scenarioHash != cntx2.scenarioHash): return False if self.periodHash != cntx2.periodHash or not self.isPeriodEqualTo(cntx2) or not self.isEntityIdentifierEqualTo(cntx2): return False if dimensionalAspectModel: if self.qnameDims.keys() != cntx2.qnameDims.keys(): return False for dimQname, ctx1Dim in self.qnameDims.items(): if not ctx1Dim.isEqualTo(cntx2.qnameDims[dimQname]): return False for nonDimVals1, nonDimVals2 in ((self.segNonDimValues,cntx2.segNonDimValues), (self.scenNonDimValues,cntx2.scenNonDimValues)): if len(nonDimVals1) != len(nonDimVals2): return False for i, nonDimVal1 in enumerate(nonDimVals1): if not XbrlUtil.sEqual(self.modelXbrl, nonDimVal1, nonDimVals2[i]): return False else: if self.hasSegment: if not cntx2.hasSegment: return False if not XbrlUtil.sEqual(self.modelXbrl, self.segment, cntx2.segment): return False elif cntx2.hasSegment: return False if self.hasScenario: if not cntx2.hasScenario: return False if not XbrlUtil.sEqual(self.modelXbrl, self.scenario, cntx2.scenario): return False elif cntx2.hasScenario: return False return True
def nonDimSegmentHash(self): try: return self._nonDimSegmentHash except AttributeError: self._nonDimSegmentHash = XbrlUtil.equalityHash( self.nonDimValues("segment")) return self._nonDimSegmentHash
def __hash__(self): if self.isExplicit: return hash((self.dimensionQname, self.memberQname)) else: # need XPath equal so that QNames aren't lexically compared (for fact and context equality in comparing formula results) return hash((self.dimensionQname, XbrlUtil.equalityHash(XmlUtil.child(self), equalMode=XbrlUtil.XPATH_EQ)))
def nonDimScenarioHash(self): """(int) -- Hash, of s-equality values, of non-XDT scenario objects""" try: return self._nonDimScenarioHash except AttributeError: self._nonDimScenarioHash = XbrlUtil.equalityHash(self.nonDimValues("scenario")) return self._nonDimScenarioHash
def nonDimScenarioHash(self): try: return self._nonDimScenarioHash except AttributeError: self._nonDimScenarioHash = XbrlUtil.equalityHash( self.nonDimValues("scenario")) return self._nonDimScenarioHash
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 # 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")) elif isinstance(node1, ModelObject): if isinstance(node2, ModelObject): return XbrlUtil.sEqual(node1.modelXbrl, node1, node2, excludeIDs=XbrlUtil.TOP_IDs_EXCLUDED, dts2=node2.modelXbrl) else: return False elif isinstance(node1, ModelAttribute): if isinstance(node2, ModelAttribute): return node1.text == node2.text return False
def nodes_correspond(xc, p, args): if len(args) != 2: raise XPathContext.FunctionNumArgs() node1 = nodeArg(xc, args, 0, "node()?", missingArgFallback=(), emptyFallback=()) node2 = nodeArg(xc, args, 1, "node()?", missingArgFallback=(), emptyFallback=()) if node1 == (): if node2 == (): return True return False if node2 == (): return False return XbrlUtil.nodesCorrespond(xc.modelXbrl, node1, node2, xc.modelXbrl)
def x_equal_test(node1, node2): if isinstance(node1, ModelObject): if isinstance(node2, ModelObject): return XbrlUtil.xEqual(node1, node2) else: return False elif isinstance(node1, ModelAttribute): if isinstance(node2, ModelAttribute): return node1.sValue == node2.sValue return False
def isEqualTo(self, other, equalMode=XbrlUtil.XPATH_EQ): if isinstance(other, ModelValue.QName): return self.isExplicit and self.memberQname == other elif other is None: return False elif self.isExplicit: return self.memberQname == other.memberQname else: return XbrlUtil.nodesCorrespond(self.modelXbrl, self.typedMember, other.typedMember, equalMode=equalMode, excludeIDs=XbrlUtil.NO_IDs_EXCLUDED)
def equivalenceKey(self): # cannot be cached because this is unique per relationship return (self.qname, self.linkQname, self.linkrole, # needed when linkrole=None merges multiple links self.fromModelObject.objectIndex if self.fromModelObject is not None else -1, self.toModelObject.objectIndex if self.toModelObject is not None else -1, self.order, self.weight, self.preferredLabel) + \ XbrlUtil.attributes(self.modelXbrl, self.arcElement, exclusions=arcCustAttrsExclusions, keyByTag=True) # use clark tag for key instead of qname
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: return False 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, equalMode=equalMode, excludeIDs=XbrlUtil.NO_IDs_EXCLUDED)
def isEqualTo(self, other, equalMode=XbrlUtil.XPATH_EQ): if isinstance(other, ModelValue.QName): return self.isExplicit and self.memberQname == other elif other is None: return False elif self.isExplicit: return self.memberQname == other.memberQname else: return XbrlUtil.nodesCorrespond( self.modelXbrl, self.typedMember, other.typedMember, equalMode=equalMode, excludeIDs=XbrlUtil.NO_IDs_EXCLUDED)
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 = {} for context in self.modelXbrl.contexts.values(): h = context.contextDimAwareHash if h in uniqueContextHashes: if context.isEqualTo(uniqueContextHashes[h]): self.mapContext[context] = uniqueContextHashes[h] else: uniqueContextHashes[h] = context del uniqueContextHashes self.modelXbrl.profileActivity("... identify equal contexts", minTimeToShow=1.0) # identify equal contexts uniqueUnitHashes = {} for unit in self.modelXbrl.units.values(): h = unit.hash if h in uniqueUnitHashes: if unit.isEqualTo(uniqueUnitHashes[h]): self.mapUnit[unit] = uniqueUnitHashes[h] else: uniqueUnitHashes[h] = unit 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(): arcrole, ELR, linkqname, arcqname = baseSetKey if ELR and linkqname and arcqname: if arcrole in (XbrlConst.essenceAlias, XbrlConst.requiresElement): conceptsSet = {XbrlConst.essenceAlias:self.conceptsInEssencesAlias, XbrlConst.requiresElement:self.conceptsInRequiresElement}[arcrole] for modelRel in self.modelXbrl.relationshipSet(arcrole,ELR,linkqname,arcqname).modelRelationships: for concept in (modelRel.fromModelObject, modelRel.toModelObject): if concept is not None and concept.qname is not None: conceptsSet.add(concept) self.modelXbrl.profileActivity("... identify requires-element and esseance-aliased concepts", minTimeToShow=1.0) 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 if ELR and linkqname and arcqname: if arcrole in (XbrlConst.summationItem, XbrlConst.essenceAlias, XbrlConst.requiresElement): relsSet = self.modelXbrl.relationshipSet(arcrole,ELR,linkqname,arcqname) if arcrole == XbrlConst.summationItem: fromRelationships = relsSet.fromModelObjects() for sumConcept, modelRels in fromRelationships.items(): sumBindingKeys = self.sumConceptBindKeys[sumConcept] dupBindingKeys = set() boundSumKeys = set() # determine boundSums for modelRel in modelRels: itemConcept = modelRel.toModelObject if itemConcept is not None and itemConcept.qname is not None: itemBindingKeys = self.itemConceptBindKeys[itemConcept] boundSumKeys |= sumBindingKeys & itemBindingKeys # add up rounded items boundSums = defaultdict(decimal.Decimal) # sum of facts meeting factKey boundSummationItems = defaultdict(list) # corresponding fact refs for messages for modelRel in modelRels: weight = modelRel.weightDecimal itemConcept = modelRel.toModelObject if itemConcept is not None: for itemBindKey in boundSumKeys: ancestor, contextHash, unit = itemBindKey factKey = (itemConcept, ancestor, contextHash, unit) if factKey in self.itemFacts: for fact in self.itemFacts[factKey]: if fact in self.duplicatedFacts: dupBindingKeys.add(itemBindKey) else: roundedValue = roundFact(fact, self.inferDecimals) boundSums[itemBindKey] += roundedValue * weight boundSummationItems[itemBindKey].append(wrappedFactWithWeight(fact,weight,roundedValue)) for sumBindKey in boundSumKeys: ancestor, contextHash, unit = sumBindKey factKey = (sumConcept, ancestor, contextHash, unit) if factKey in self.sumFacts: sumFacts = self.sumFacts[factKey] for fact in sumFacts: if fact in self.duplicatedFacts: dupBindingKeys.add(sumBindKey) elif sumBindKey not in dupBindingKeys: roundedSum = roundFact(fact, self.inferDecimals) roundedItemsSum = roundFact(fact, self.inferDecimals, vDecimal=boundSums[sumBindKey]) if roundedItemsSum != roundFact(fact, self.inferDecimals): d = inferredDecimals(fact) if isnan(d) or isinf(d): d = 4 _boundSummationItems = boundSummationItems[sumBindKey] unreportedContribingItemQnames = [] # list the missing/unreported contributors in relationship order for modelRel in modelRels: itemConcept = modelRel.toModelObject 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, 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)), contextID=fact.context.id, unitID=fact.unit.id, unreportedContributors=", ".join(unreportedContribingItemQnames) or "none") del unreportedContribingItemQnames[:] boundSummationItems.clear() # dereference facts in list elif arcrole == XbrlConst.essenceAlias: for modelRel in relsSet.modelRelationships: essenceConcept = modelRel.fromModelObject aliasConcept = modelRel.toModelObject essenceBindingKeys = self.esAlConceptBindKeys[essenceConcept] aliasBindingKeys = self.esAlConceptBindKeys[aliasConcept] for esAlBindKey in essenceBindingKeys & aliasBindingKeys: ancestor, contextHash = esAlBindKey essenceFactsKey = (essenceConcept, ancestor, contextHash) aliasFactsKey = (aliasConcept, ancestor, contextHash) if essenceFactsKey in self.esAlFacts and aliasFactsKey in self.esAlFacts: for eF in self.esAlFacts[essenceFactsKey]: for aF in self.esAlFacts[aliasFactsKey]: essenceUnit = self.mapUnit.get(eF.unit,eF.unit) aliasUnit = self.mapUnit.get(aF.unit,aF.unit) 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, 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, linkrole=ELR, linkroleDefinition=self.modelXbrl.roleTypeDefinition(ELR), contextID=eF.context.id) elif arcrole == XbrlConst.requiresElement: for modelRel in relsSet.modelRelationships: sourceConcept = modelRel.fromModelObject requiredConcept = modelRel.toModelObject if sourceConcept in self.requiresElementFacts and \ 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, linkrole=ELR, linkroleDefinition=self.modelXbrl.roleTypeDefinition(ELR)) self.modelXbrl.profileActivity("... find inconsistencies", minTimeToShow=1.0) self.modelXbrl.profileActivity() # reset
def deep_equal(xc, p, contextItem, args): if len(args) != 2: raise XPathContext.FunctionNumArgs() return XbrlUtil.nodesCorrespond(xc.modelXbrl, args[0], args[1])
def validate(self): self.modelXbrl.info( "info", "Validating calculations inferring %(inferMode)s", inferMode=_("precision") if self.inferPrecision else _("decimals"), ) # identify equal contexts self.modelXbrl.profileActivity() uniqueContextHashes = {} for context in self.modelXbrl.contexts.values(): h = context.contextDimAwareHash if h in uniqueContextHashes: if context.isEqualTo(uniqueContextHashes[h]): self.mapContext[context] = uniqueContextHashes[h] else: uniqueContextHashes[h] = context del uniqueContextHashes self.modelXbrl.profileActivity("... identify equal contexts", minTimeToShow=1.0) # identify equal contexts uniqueUnitHashes = {} for unit in self.modelXbrl.units.values(): h = unit.hash if h in uniqueUnitHashes: if unit.isEqualTo(uniqueUnitHashes[h]): self.mapUnit[unit] = uniqueUnitHashes[h] else: uniqueUnitHashes[h] = unit 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(): arcrole, ELR, linkqname, arcqname = baseSetKey if ELR and linkqname and arcqname: if arcrole in (XbrlConst.essenceAlias, XbrlConst.requiresElement): conceptsSet = { XbrlConst.essenceAlias: self.conceptsInEssencesAlias, XbrlConst.requiresElement: self.conceptsInRequiresElement, }[arcrole] for modelRel in self.modelXbrl.relationshipSet( arcrole, ELR, linkqname, arcqname ).modelRelationships: for concept in (modelRel.fromModelObject, modelRel.toModelObject): conceptsSet.add(concept) self.modelXbrl.profileActivity("... identify requires-element and esseance-aliased concepts", minTimeToShow=1.0) 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 if ELR and linkqname and arcqname: if arcrole in (XbrlConst.summationItem, XbrlConst.essenceAlias, XbrlConst.requiresElement): relsSet = self.modelXbrl.relationshipSet(arcrole, ELR, linkqname, arcqname) if arcrole == XbrlConst.summationItem: fromRelationships = relsSet.fromModelObjects() for sumConcept, modelRels in fromRelationships.items(): sumBindingKeys = self.sumConceptBindKeys[sumConcept] dupBindingKeys = set() boundSumKeys = set() # determine boundSums for modelRel in modelRels: itemBindingKeys = self.itemConceptBindKeys[modelRel.toModelObject] boundSumKeys |= sumBindingKeys & itemBindingKeys # add up rounded items boundSums = defaultdict(decimal.Decimal) for modelRel in modelRels: weight = modelRel.weightDecimal itemConcept = modelRel.toModelObject for itemBindKey in boundSumKeys: ancestor, contextHash, unit = itemBindKey factKey = (itemConcept, ancestor, contextHash, unit) if factKey in self.itemFacts: for fact in self.itemFacts[factKey]: if fact in self.duplicatedFacts: dupBindingKeys.add(itemBindKey) else: boundSums[itemBindKey] += self.roundFact(fact) * weight for sumBindKey in boundSumKeys: ancestor, contextHash, unit = sumBindKey factKey = (sumConcept, ancestor, contextHash, unit) if factKey in self.sumFacts: for fact in self.sumFacts[factKey]: if fact in self.duplicatedFacts: dupBindingKeys.add(sumBindKey) elif sumBindKey not in dupBindingKeys: roundedSum = self.roundFact(fact) roundedItemsSum = self.roundFact(fact, vDecimal=boundSums[sumBindKey]) if roundedItemsSum != self.roundFact(fact): self.modelXbrl.error( "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" ), modelObject=sumConcept, concept=sumConcept.qname, linkrole=ELR, reportedSum=roundedSum, computedSum=roundedItemsSum, contextID=context.id, unitID=unit.id, ) elif arcrole == XbrlConst.essenceAlias: for modelRel in relsSet.modelRelationships: essenceConcept = modelRel.fromModelObject aliasConcept = modelRel.toModelObject essenceBindingKeys = self.esAlConceptBindKeys[essenceConcept] aliasBindingKeys = self.esAlConceptBindKeys[aliasConcept] for esAlBindKey in essenceBindingKeys & aliasBindingKeys: ancestor, contextHash = esAlBindKey essenceFactsKey = (essenceConcept, ancestor, contextHash) aliasFactsKey = (aliasConcept, ancestor, contextHash) if essenceFactsKey in self.esAlFacts and aliasFactsKey in self.esAlFacts: for eF in self.esAlFacts[essenceFactsKey]: for aF in self.esAlFacts[aliasFactsKey]: essenceUnit = self.mapUnit.get(eF.unit, eF.unit) aliasUnit = self.mapUnit.get(aF.unit, aF.unit) if essenceUnit != aliasUnit: self.modelXbrl.error( "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=essenceConcept, essenceConcept=essenceConcept.qname, aliasConcept=aliasConcept.qname, linkrole=ELR, contextID=context.id, ) if not XbrlUtil.vEqual(eF, aF): self.modelXbrl.error( "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=essenceConcept, essenceConcept=essenceConcept.qname, aliasConcept=aliasConcept.qname, linkrole=ELR, contextID=context.id, ) elif arcrole == XbrlConst.requiresElement: for modelRel in relsSet.modelRelationships: sourceConcept = modelRel.fromModelObject requiredConcept = modelRel.toModelObject if ( sourceConcept in self.requiresElementFacts and not requiredConcept in self.requiresElementFacts ): self.modelXbrl.error( "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, linkrole=ELR, ) self.modelXbrl.profileActivity("... find inconsistencies", minTimeToShow=1.0) self.modelXbrl.profileActivity() # reset
def validate(self): self.modelXbrl.error(_("Validating calculations inferring {0}") .format(_("precision") if self.inferPrecision else _("decimals"))) # identify equal contexts contexts = tuple(self.modelXbrl.contexts.values()) for i in range(len(contexts)): cntx1 = contexts[i] for j in range(i+1, len(contexts)): cntx2 = contexts[j] if cntx1.isEqualTo(cntx2) and not cntx2 in self.mapContext: self.mapContext[cntx2] = cntx1 # identify equal contexts units = tuple(self.modelXbrl.units.values()) for i in range(len(units)): unit1 = units[i] for j in range(i+1, len(units)): unit2 = units[j] if unit1.isEqualTo(unit2) and not unit2 in self.mapUnit: self.mapUnit[unit2] = unit1 # identify concepts participating in essence-alias relationships # identify calcluation & essence-alias base sets (by key) for baseSetKey in self.modelXbrl.baseSets.keys(): arcrole, ELR, linkqname, arcqname = baseSetKey if ELR and linkqname and arcqname: if arcrole in (XbrlConst.essenceAlias, XbrlConst.requiresElement): conceptsSet = {XbrlConst.essenceAlias:self.conceptsInEssencesAlias, XbrlConst.requiresElement:self.conceptsInRequiresElement}[arcrole] for modelRel in self.modelXbrl.relationshipSet(arcrole,ELR,linkqname,arcqname).modelRelationships: for concept in (modelRel.fromModelObject, modelRel.toModelObject): conceptsSet.add(concept) self.bindFacts(self.modelXbrl.facts,[self.modelXbrl.modelDocument.xmlRootElement]) # identify calcluation & essence-alias base sets (by key) for baseSetKey in self.modelXbrl.baseSets.keys(): arcrole, ELR, linkqname, arcqname = baseSetKey if ELR and linkqname and arcqname: if arcrole in (XbrlConst.summationItem, XbrlConst.essenceAlias, XbrlConst.requiresElement): relsSet = self.modelXbrl.relationshipSet(arcrole,ELR,linkqname,arcqname) if arcrole == XbrlConst.summationItem: fromRelationships = relsSet.fromModelObjects() for sumConcept, modelRels in fromRelationships.items(): sumBindingKeys = self.sumConceptBindKeys[sumConcept] dupBindingKeys = set() boundSumKeys = set() # determine boundSums for modelRel in modelRels: itemBindingKeys = self.itemConceptBindKeys[modelRel.toModelObject] boundSumKeys |= sumBindingKeys & itemBindingKeys # add up rounded items boundSums = defaultdict(float) for modelRel in modelRels: weight = modelRel.weight itemConcept = modelRel.toModelObject for itemBindKey in boundSumKeys: ancestor, context, unit = itemBindKey factKey = (itemConcept, ancestor, context, unit) if factKey in self.itemFacts: for fact in self.itemFacts[factKey]: if fact in self.duplicatedFacts: dupBindingKeys.add(itemBindKey) else: boundSums[itemBindKey] += self.roundFact(fact) * weight for sumBindKey in boundSumKeys: ancestor, context, unit = sumBindKey factKey = (sumConcept, ancestor, context, unit) if factKey in self.sumFacts: for fact in self.sumFacts[factKey]: if fact in self.duplicatedFacts: dupBindingKeys.add(sumBindKey) elif sumBindKey not in dupBindingKeys: roundedSum = self.roundFact(fact) roundedItemsSum = self.roundFact(fact, vFloat=boundSums[sumBindKey]) if roundedItemsSum != self.roundFact(fact): self.modelXbrl.error( _("Calculation inconsistent from {0} in link role {1} reported sum {2} computed sum {3} context {4} unit {5}").format( sumConcept.qname, ELR, roundedSum, roundedItemsSum, context.id, unit.id), "err", "xbrl.5.2.5.2:calcInconsistency") elif arcrole == XbrlConst.essenceAlias: for modelRel in relsSet.modelRelationships: essenceConcept = modelRel.fromModelObject aliasConcept = modelRel.toModelObject essenceBindingKeys = self.esAlConceptBindKeys[essenceConcept] aliasBindingKeys = self.esAlConceptBindKeys[aliasConcept] for esAlBindKey in essenceBindingKeys & aliasBindingKeys: ancestor, context = esAlBindKey essenceFactsKey = (essenceConcept, ancestor, context) aliasFactsKey = (aliasConcept, ancestor, context) if essenceFactsKey in self.esAlFacts and aliasFactsKey in self.esAlFacts: for eF in self.esAlFacts[essenceFactsKey]: for aF in self.esAlFacts[aliasFactsKey]: essenceUnit = self.mapUnit.get(eF.unit,eF.unit) aliasUnit = self.mapUnit.get(aF.unit,aF.unit) if essenceUnit != aliasUnit: self.modelXbrl.error( _("Essence-Alias inconsistent units from {0} to {1} in link role {2} context {3}").format( essenceConcept.qname, aliasConcept.qname, ELR, context.id), "err", "xbrl.5.2.6.2.2:essenceAliasUnitsInconsistency") if not XbrlUtil.vEqual(essenceConcept, eF.element, aliasConcept, aF.element): self.modelXbrl.error( _("Essence-Alias inconsistent value from {0} to {1} in link role {2} context {3}").format( essenceConcept.qname, aliasConcept.qname, ELR, context.id), "err", "xbrl.5.2.6.2.2:essenceAliasUnitsInconsistency") elif arcrole == XbrlConst.requiresElement: for modelRel in relsSet.modelRelationships: sourceConcept = modelRel.fromModelObject requiredConcept = modelRel.toModelObject if sourceConcept in self.requiresElementFacts and \ not requiredConcept in self.requiresElementFacts: self.modelXbrl.error( _("Requies-Element {0} missing required fact for {1} in link role {2}").format( sourceConcept.qname, requiredConcept.qname, ELR), "err", "xbrl.5.2.6.2.4:requiresElementInconsistency")
def aspectMatches(fact1, fact2, aspects): matches = True for aspect in (aspects if hasattr(aspects,'__iter__') else (aspects,)): if aspect == Aspect.LOCATION: if (fact1.modelXbrl == fact2.modelXbrl and # test deemed true for multi-instance comparisons fact1.element.parentNode != fact2.element.parentNode): matches = False elif aspect == Aspect.CONCEPT: if fact1.concept.qname != fact2.concept.qname: matches = False elif fact1.isTuple or fact2.isTuple: return True # only match the aspects both facts have elif aspect == Aspect.PERIOD: if not fact1.context.isPeriodEqualTo(fact2.context): matches = False elif aspect == Aspect.ENTITY_IDENTIFIER: if not fact1.context.isEntityIdentifierEqualTo(fact2.context): matches = False elif aspect == Aspect.COMPLETE_SEGMENT: if not XbrlUtil.nodesCorrespond(fact1.modelXbrl, fact1.context.segment, fact2.context.segment, dts2=fact2.modelXbrl): matches = False elif aspect == Aspect.COMPLETE_SCENARIO: if not XbrlUtil.nodesCorrespond(fact1.modelXbrl, fact1.context.scenario, fact2.context.scenario, dts2=fact2.modelXbrl): matches = False elif aspect in (Aspect.NON_XDT_SEGMENT, Aspect.NON_XDT_SCENARIO): nXs1 = fact1.context.nonDimValues(aspect) nXs2 = fact2.context.nonDimValues(aspect) if len(nXs1) != len(nXs2): matches = False else: for i in range(len(nXs1)): if not XbrlUtil.nodesCorrespond(fact1.modelXbrl, nXs1[i], nXs2[i], dts2=fact2.modelXbrl): matches = False break elif aspect == Aspect.UNIT: u1 = fact1.unit u2 = fact2.unit if (u1 is None) != (u2 is None): matches = False elif u1 and u2 and u1.measures != u2.measures: matches = False elif aspect == Aspect.DIMENSIONS: ''' (no implicit filtering on ALL dimensions for now) dimQnames1 = fact1.context.dimAspects dimQnames2 = fact2.context.dimAspects if len(dimQnames1 ^ dimQnames2): # dims not in both matches = False else: for dimQname1 in dimQnames1: if dimQname1 not in dimQnames2 or \ not aspectMatches(fact1, fact2, dimQname1): matches = False break ''' elif isinstance(aspect, QName): from arelle.ModelObject import ModelDimensionValue dimValue1 = fact1.context.dimValue(aspect) dimValue2 = fact2.context.dimValue(aspect) if isinstance(dimValue1, ModelDimensionValue): if dimValue1.isExplicit: if isinstance(dimValue2, QName): if dimValue1.memberQname != dimValue2: matches = False elif isinstance(dimValue2, ModelDimensionValue): if dimValue2.isTyped: matches = False elif dimValue1.memberQname != dimValue2.memberQname: matches = False elif dimValue1.isTyped: if isinstance(dimValue2, QName): matches = False elif isinstance(dimValue2, ModelDimensionValue): if dimValue2.isExplicit: matches = False elif not XbrlUtil.nodesCorrespond(fact1.modelXbrl, dimValue1.typedMember, dimValue2.typedMember, dts2=fact2.modelXbrl): matches = False elif isinstance(dimValue1,QName): if isinstance(dimValue2, QName): if dimValue1 != dimValue2: matches = False elif isinstance(dimValue2, ModelDimensionValue): if dimValue2.isTyped: matches = False elif dimValue1 != dimValue2.memberQname: matches = False elif dimValue1 is None: if dimValue2: matches = False if not matches: break return matches
def nonDimSegmentHash(self): try: return self._nonDimSegmentHash except AttributeError: self._nonDimSegmentHash = XbrlUtil.equalityHash(self.nonDimValues("segment")) return self._nonDimSegmentHash
def segmentHash(self): # s-equality hash return XbrlUtil.equalityHash(self.segment) # self-caching
def scenarioHash(self): """(int) -- Hash of the scenario, based on s-equality values""" return XbrlUtil.equalityHash( self.scenario ) # self-caching
def validate(self, modelXbrl, parameters=None): self.parameters = parameters self.NCnamePattern = 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"[_\-\." "\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]*$" ) self.precisionPattern = re.compile("^([0-9]+|INF)$") self.decimalsPattern = re.compile("^(-?[0-9]+|INF)$") self.isoCurrencyPattern = re.compile(r"^[A-Z]{3}$") self.modelXbrl = modelXbrl self.validateDisclosureSystem = modelXbrl.modelManager.validateDisclosureSystem self.disclosureSystem = modelXbrl.modelManager.disclosureSystem self.validateEFM = self.validateDisclosureSystem and self.disclosureSystem.EFM self.validateGFM = self.validateDisclosureSystem and self.disclosureSystem.GFM self.validateEFMorGFM = self.validateDisclosureSystem and self.disclosureSystem.EFMorGFM self.validateHMRC = self.validateDisclosureSystem and self.disclosureSystem.HMRC self.validateSBRNL = self.validateDisclosureSystem and self.disclosureSystem.SBRNL self.validateXmlLang = self.validateDisclosureSystem and self.disclosureSystem.xmlLangPattern self.validateCalcLB = modelXbrl.modelManager.validateCalcLB self.validateInferDecimals = modelXbrl.modelManager.validateInferDecimals # xlink validation modelXbrl.modelManager.showStatus(_("validating links")) modelLinks = set() self.remoteResourceLocElements = set() self.genericArcArcroles = set() for baseSetExtLinks in modelXbrl.baseSets.values(): for baseSetExtLink in baseSetExtLinks: modelLinks.add( baseSetExtLink) # ext links are unique (no dups) for modelLink in modelLinks: fromToArcs = {} locLabels = {} resourceLabels = {} resourceArcTos = [] for arcElt in modelLink.element.childNodes: if arcElt.nodeType == 1: xlinkType = arcElt.getAttributeNS(XbrlConst.xlink, "type") # locator must have an href if xlinkType == "locator": if not arcElt.hasAttributeNS(XbrlConst.xlink, "href"): modelXbrl.error( _("Linkbase {0} extended link {1} locator {2} missing href" ).format( modelLink.modelDocument.basename, modelLink.role, arcElt.hasAttributeNS( XbrlConst.xlink, "label")), "err", "xlink:locatorHref") locLabels[arcElt.getAttributeNS( XbrlConst.xlink, "label")] = arcElt elif xlinkType == "resource": resourceLabels[arcElt.getAttributeNS( XbrlConst.xlink, "label")] = arcElt # can be no duplicated arcs between same from and to elif xlinkType == "arc": fromLabel = arcElt.getAttributeNS( XbrlConst.xlink, "from") toLabel = arcElt.getAttributeNS(XbrlConst.xlink, "to") fromTo = (fromLabel, toLabel) if fromTo in fromToArcs: modelXbrl.error( _("Linkbase {0} extended link {1} duplicate arcs from {2} to {3}" ).format(modelLink.modelDocument.basename, modelLink.role, fromLabel, toLabel), "err", "xlink:dupArcs") else: fromToArcs[fromTo] = arcElt if arcElt.namespaceURI == XbrlConst.link: if arcElt.localName in arcNamesTo21Resource: #("labelArc","referenceArc"): resourceArcTos.append( (toLabel, arcElt.getAttribute("use"))) elif self.isGenericArc(arcElt): arcrole = arcElt.getAttributeNS( XbrlConst.xlink, "arcrole") self.genericArcArcroles.add(arcrole) if arcrole in (XbrlConst.elementLabel, XbrlConst.elementReference): resourceArcTos.append((toLabel, arcrole)) # values of type (not needed for validating parsers) if xlinkType not in xlinkTypeValues: # ("", "simple", "extended", "locator", "arc", "resource", "title", "none"): modelXbrl.error( _("Linkbase {0} extended link {1} type {2} invalid" ).format(modelLink.modelDocument.basename, modelLink.role, xlinkType), "err", "xlink:type") # values of actuate (not needed for validating parsers) xlinkActuate = arcElt.getAttributeNS( XbrlConst.xlink, "actuate") if xlinkActuate not in xlinkActuateValues: # ("", "onLoad", "onRequest", "other", "none"): modelXbrl.error( _("Linkbase {0} extended link {1} actuate {2} invalid" ).format(modelLink.modelDocument.basename, modelLink.role, xlinkActuate), "err", "xlink:actuate") # values of show (not needed for validating parsers) xlinkShow = arcElt.getAttributeNS(XbrlConst.xlink, "show") if xlinkShow not in xlinkShowValues: # ("", "new", "replace", "embed", "other", "none"): modelXbrl.error( _("Linkbase {0} extended link {1} show {2} invalid" ).format(modelLink.modelDocument.basename, modelLink.role, xlinkShow), "err", "xlink:show") # values of label, from, to (not needed for validating parsers) for name in xlinkLabelAttributes: # ("label", "from", "to"): value = arcElt.getAttributeNS(XbrlConst.xlink, name) if value != "" and not self.NCnamePattern.match(value): modelXbrl.error( _("Linkbase {0} extended link {1} element {2} {3} '{4}' not an NCname" ).format(modelLink.modelDocument.basename, modelLink.role, arcElt.tagName, name, value), "err", "xlink:{0}".format(name)) # check from, to of arcs have a resource or loc for fromTo, arcElt in fromToArcs.items(): fromLabel, toLabel in fromTo for name, value, sect in (("from", fromLabel, "3.5.3.9.2"), ("to", toLabel, "3.5.3.9.3")): if value not in locLabels and value not in resourceLabels: modelXbrl.error( _("Arc in linkbase {0} extended link {1} from {2} to {3} attribute \"{4}\" has no matching loc or resource label" ).format(modelLink.modelDocument.basename, modelLink.role, fromLabel, toLabel, name), "err", "xbrl.{0}:arcResource".format(sect)) if arcElt.localName == "footnoteArc" and arcElt.namespaceURI == XbrlConst.link and \ arcElt.getAttributeNS(XbrlConst.xlink,"arcrole") == XbrlConst.factFootnote: if fromLabel not in locLabels: modelXbrl.error( _("FootnoteArc in {0} extended link {1} from {2} to {3} \"from\" is not a loc" ).format(modelLink.modelDocument.basename, modelLink.role, fromLabel, toLabel), "err", "xbrl.4.11.1.3.1:factFootnoteArcFrom") if toLabel not in resourceLabels or qname( resourceLabels[toLabel] ) != XbrlConst.qnLinkFootnote: modelXbrl.error( _("FootnoteArc in {0} extended link {1} from {2} to {3} \"to\" is not a footnote resource" ).format(modelLink.modelDocument.basename, modelLink.role, fromLabel, toLabel), "err", "xbrl.4.11.1.3.1:factFootnoteArcTo") # check unprohibited label arcs to remote locs for resourceArcTo in resourceArcTos: resourceArcToLabel, resourceArcUse = resourceArcTo if resourceArcToLabel in locLabels: toLabel = locLabels[resourceArcToLabel] if resourceArcUse == "prohibited": self.remoteResourceLocElements.add(toLabel) else: modelXbrl.error( _("Unprohibited labelArc in linkbase {0} extended link {1} has illegal remote resource loc labeled {2} href {3}" ).format( modelLink.modelDocument.basename, modelLink.role, resourceArcToLabel, toLabel.getAttributeNS( XbrlConst.xlink, "href")), "err", "xbrl.5.2.2.3:labelArcRemoteResource") elif resourceArcToLabel in resourceLabels: toResource = resourceLabels[resourceArcToLabel] if resourceArcUse == XbrlConst.elementLabel: if not self.isGenericLabel(toResource): modelXbrl.error( _("Generic label arc in linkbase {0} extended link {1} to {2} must target a generic label" ).format(modelLink.modelDocument.basename, modelLink.role, resourceArcToLabel), "err", "xbrlle.2.1.1:genericLabelTarget") elif resourceArcUse == XbrlConst.elementReference: if not self.isGenericReference(toResource): modelXbrl.error( _("Generic reference arc in linkbase {0} extended link {1} to {2} must target a generic reference" ).format(modelLink.modelDocument.basename, modelLink.role, resourceArcToLabel), "err", "xbrlre.2.1.1:genericReferenceTarget") self.dimensionDefaults = {} modelXbrl.qnameDimensionDefaults = {} modelXbrl.qnameDimensionContextElement = {} # check base set cycles, dimensions modelXbrl.modelManager.showStatus(_("validating relationship sets")) for baseSetKey in modelXbrl.baseSets.keys(): arcrole, ELR, linkqname, arcqname = baseSetKey if arcrole.startswith("XBRL-") or ELR is None or \ linkqname is None or arcqname is None: continue elif arcrole in XbrlConst.standardArcroleCyclesAllowed: cyclesAllowed, specSect = XbrlConst.standardArcroleCyclesAllowed[ arcrole] elif arcrole in self.modelXbrl.arcroleTypes and len( self.modelXbrl.arcroleTypes[arcrole]) > 0: cyclesAllowed = self.modelXbrl.arcroleTypes[arcrole][ 0].cyclesAllowed if arcrole in self.genericArcArcroles: specSect = "xbrlgene:violatedCyclesConstraint" else: specSect = "xbrl.5.1.4.3:cycles" else: cyclesAllowed = "any" specSect = None if cyclesAllowed != "any" or arcrole in (XbrlConst.summationItem,) \ or arcrole in self.genericArcArcroles \ or arcrole.startswith(XbrlConst.formulaStartsWith): relsSet = modelXbrl.relationshipSet(arcrole, ELR, linkqname, arcqname) if cyclesAllowed != "any" and \ (XbrlConst.isStandardExtLinkQname(linkqname) and XbrlConst.isStandardArcQname(arcqname)) \ or arcrole in self.genericArcArcroles: noUndirected = cyclesAllowed == "none" fromRelationships = relsSet.fromModelObjects() for relFrom, rels in fromRelationships.items(): cycleFound = self.fwdCycle(relsSet, rels, noUndirected, {relFrom}) if cycleFound: modelXbrl.error( _("Relationships have a {0} cycle in arcrole {1} link role {2} link {3}, arc {4} starting from {5}" ).format(cycleFound, arcrole, ELR, linkqname, arcqname, relFrom.qname), "err", "{0}".format(specSect)) break # check calculation arcs for weight issues (note calc arc is an "any" cycles) if arcrole == XbrlConst.summationItem: for modelRel in relsSet.modelRelationships: weight = modelRel.weight fromConcept = modelRel.fromModelObject toConcept = modelRel.toModelObject if fromConcept and toConcept: if weight == 0: modelXbrl.error( _("Calculation relationship has zero weight from {0} to {1} in link role {2}" ).format(fromConcept.qname, toConcept.qname, ELR), "err", "xbrl.5.2.5.2.1:zeroWeight") fromBalance = fromConcept.balance toBalance = toConcept.balance if fromBalance and toBalance: if (fromBalance == toBalance and weight < 0) or \ (fromBalance != toBalance and weight > 0): modelXbrl.error( _("Calculation relationship has illegal weight {0} from {1}, {2}, to {3}, {4} in link role {5} (per 5.1.1.2 Table 6)" ).format(weight, fromConcept.qname, fromBalance, toConcept.qname, toBalance, ELR), "err", "xbrl.5.1.1.2:balanceCalcWeight") if not fromConcept.isNumeric or not toConcept.isNumeric: modelXbrl.error( _("Calculation relationship has illegal concept from {0}{1} to {2}{3} in link role {4}" ).format( fromConcept.qname, "" if fromConcept.isNumeric else " (non-numeric)", toConcept.qname, "" if fromConcept.isNumeric else " (non-numeric)", ELR), "err", "xbrl.5.2.5.2:nonNumericCalc") # check presentation relationships for preferredLabel issues elif arcrole == XbrlConst.parentChild: for modelRel in relsSet.modelRelationships: preferredLabel = modelRel.preferredLabel toConcept = modelRel.toModelObject if preferredLabel and toConcept and \ toConcept.label(preferredLabel=preferredLabel,fallbackToQname=False) is None: modelXbrl.error( _("Presentation relationship from {0} to {1} in link role {2} missing preferredLabel {3}" ).format(modelRel.fromModelObject.qname, toConcept.qname, ELR, preferredLabel), "err", "xbrl.5.2.4.2.1:preferredLabelMissing") # check essence-alias relationships elif arcrole == XbrlConst.essenceAlias: for modelRel in relsSet.modelRelationships: fromConcept = modelRel.fromModelObject toConcept = modelRel.toModelObject if fromConcept and toConcept: if fromConcept.type != toConcept.type or fromConcept.periodType != toConcept.periodType: modelXbrl.error( _("Essence-alias relationship from {0} to {1} in link role {2} has different types or periodTypes" ).format(fromConcept.qname, toConcept.qname, ELR), "err", "xbrl.5.2.6.2.2:essenceAliasTypes") fromBalance = fromConcept.balance toBalance = toConcept.balance if fromBalance and toBalance: if fromBalance and toBalance and fromBalance != toBalance: modelXbrl.error( _("Essence-alias relationship from {0} to {1} in link role {2} has different balances" ).format(fromConcept.qname, toConcept.qname, ELR), "err", "xbrl.5.2.6.2.2:essenceAliasBalance") elif modelXbrl.hasXDT and arcrole.startswith( XbrlConst.dimStartsWith): ValidateXbrlDimensions.checkBaseSet(self, arcrole, ELR, relsSet) elif modelXbrl.hasFormulae and arcrole.startswith( XbrlConst.formulaStartsWith): ValidateFormula.checkBaseSet(self, arcrole, ELR, relsSet) # instance checks modelXbrl.modelManager.showStatus(_("validating instance")) self.footnoteRefs = set() if modelXbrl.modelDocument.type == ModelDocument.Type.INSTANCE or \ modelXbrl.modelDocument.type == ModelDocument.Type.INLINEXBRL: for f in modelXbrl.facts: concept = f.concept if concept: if concept.isNumeric: unit = f.unit if f.unitID is None or unit is None: self.modelXbrl.error( _("Fact {0} context {1} is numeric and must have a unit" ).format(modelXbrl.modelDocument.basename, f.qname, f.contextID), "err", "xbrl.4.6.2:numericUnit") else: if concept.isMonetary: measures = unit.measures if not measures or len(measures[0]) != 1 or len(measures[1]) != 0 or \ measures[0][0].namespaceURI != XbrlConst.iso4217 or \ not self.isoCurrencyPattern.match(measures[0][0].localName): self.modelXbrl.error( _("Fact {0} context {1} must have a monetary unit {2}" ).format( modelXbrl.modelDocument.basename, f.qname, f.contextID, f.unitID), "err", "xbrl.4.8.2:monetaryFactUnit") elif concept.isShares: measures = unit.measures if not measures or len(measures[0]) != 1 or len(measures[1]) != 0 or \ measures[0][0] != XbrlConst.qnXbrliShares: self.modelXbrl.error( _("Fact {0} context {1} must have a xbrli:shares unit {2}" ).format(f.qname, f.contextID, f.unitID), "err", "xbrl.4.8.2:sharesFactUnit") precision = f.precision hasPrecision = precision is not None if hasPrecision and precision != "INF" and not precision.isdigit( ): self.modelXbrl.error( _("Fact {0} context {1} precision {2} is invalid" ).format(f.qname, f.contextID, precision), "err", "xbrl.4.6.4:precision") decimals = f.decimals hasDecimals = decimals is not None if hasPrecision and not self.precisionPattern.match( precision): self.modelXbrl.error( _("Fact {0} context {1} precision {2} is invalid" ).format(f.qname, f.contextID, precision), "err", "xbrl.4.6.4:precision") if hasPrecision and hasDecimals: self.modelXbrl.error( _("Fact {0} context {1} can not have both precision and decimals" ).format(f.qname, f.contextID), "err", "xbrl.4.6.3:bothPrecisionAndDecimals") if hasDecimals and not self.decimalsPattern.match( decimals): self.modelXbrl.error( _("Fact {0} context {1} decimals {2} is invalid" ).format(f.qname, f.contextID, decimals), "err", "xbrl.4.6.5:decimals") if concept.isItem: context = f.context if context is None: self.modelXbrl.error( _("Item {0} must have a context").format( f.qname), "err", "xbrl.4.6.1:itemContextRef") else: periodType = concept.periodType if (periodType == "instant" and not context.isInstantPeriod) or \ (periodType == "duration" and not (context.isStartEndPeriod or context.isForeverPeriod)): self.modelXbrl.error( _("Fact {0} context {1} has period type {2} conflict with context" ).format(f.qname, f.contextID, periodType), "err", "xbrl.4.7.2:contextPeriodType") if modelXbrl.hasXDT: ValidateXbrlDimensions.checkFact(self, f) # check precision and decimals if f.xsiNil == "true": if hasPrecision or hasDecimals: self.modelXbrl.error( _("Fact {0} context {1} can not be nil and have either precision or decimals" ).format(f.qname, f.contextID), "err", "xbrl.4.6.3:nilPrecisionDecimals") elif concept.isFraction: if hasPrecision or hasDecimals: self.modelXbrl.error( _("Fact {0} context {1} is a fraction concept and cannot have either precision or decimals" ).format(f.qname, f.contextID), "err", "xbrl.4.6.3:fractionPrecisionDecimals") numerator, denominator = f.fractionValue if not (numerator == "INF" or numerator.isnumeric()): self.modelXbrl.error( _("Fact {0} context {1} is a fraction with invalid numerator {2}" ).format(f.qname, f.contextID, numerator), "err", "xbrl.5.1.1:fractionPrecisionDecimals") if not denominator.isnumeric() or int( denominator) == 0: self.modelXbrl.error( _("Fact {0} context {1} is a fraction with invalid denominator {2}" ).format(f.qname, f.contextID, denominator), "err", "xbrl.5.1.1:fractionPrecisionDecimals") else: if modelXbrl.modelDocument.type != ModelDocument.Type.INLINEXBRL: for child in f.element.childNodes: if child.nodeType == 1: self.modelXbrl.error( _("Fact {0} context {1} may not have child elements {2}" ).format(f.qname, f.contextID, child.tagName), "err", "xbrl.5.1.1:itemMixedContent") break if concept.isNumeric and not hasPrecision and not hasDecimals: self.modelXbrl.error( _("Fact {0} context {1} is a numeric concept and must have either precision or decimals" ).format(f.qname, f.contextID), "err", "xbrl.4.6.3:missingPrecisionDecimals") elif concept.isTuple: if f.contextID: self.modelXbrl.error( _("Tuple {0} must not have a context").format( f.qname), "err", "xbrl.4.6.1:tupleContextRef") if hasPrecision or hasDecimals: self.modelXbrl.error( _("Fact {0} is a tuple and cannot have either precision or decimals" ).format(f.qname), "err", "xbrl.4.6.3:tuplePrecisionDecimals") # custom attributes may be allowed by anyAttribute but not by 2.1 for attrQname, attrValue in XbrlUtil.attributes( self.modelXbrl, concept, f.element): if attrQname.namespaceURI in (XbrlConst.xbrli, XbrlConst.link, XbrlConst.xlink, XbrlConst.xl): self.modelXbrl.error( _("Fact {0} is a tuple and must not have attribute in this namespace {1}" ).format(f.qname, attrQname), "err", "xbrl.4.9:tupleAttribute") else: self.modelXbrl.error( _("Fact {0} must be an item or tuple").format( f.qname), "err", "xbrl.4.6:notItemOrTuple") from arelle.ModelObject import ModelInlineFact if isinstance(f, ModelInlineFact): self.footnoteRefs.update(f.footnoteRefs) #instance checks for cntx in modelXbrl.contexts.values(): if cntx.isStartEndPeriod: try: if cntx.endDatetime <= cntx.startDatetime: self.modelXbrl.error( _("Context {0} must have startDate less than endDate" ).format(cntx.id), "err", "xbrl.4.7.2:periodStartBeforeEnd") except ValueError as err: self.modelXbrl.error( _("Context {0} startDate or endDate: {1}").format( cntx.id, err), "err", "xbrl.4.7.2:contextDateError") elif cntx.isInstantPeriod: try: cntx.instantDatetime #parse field except ValueError as err: self.modelXbrl.error( _("Context {0} instant date: {1}").format( cntx.id, err), "err", "xbrl.4.7.2:contextDateError") self.segmentScenario(cntx.segment, cntx.id, "segment", "4.7.3.2") self.segmentScenario(cntx.scenario, cntx.id, "scenario", "4.7.4") if modelXbrl.hasXDT: ValidateXbrlDimensions.checkContext(self, cntx) for unit in modelXbrl.units.values(): mulDivMeasures = unit.measures if mulDivMeasures: for measures in mulDivMeasures: for measure in measures: if measure.namespaceURI == XbrlConst.xbrli and not \ measure in (XbrlConst.qnXbrliPure, XbrlConst.qnXbrliShares): self.modelXbrl.error( _("Unit {0} illegal measure: {1}").format( unit.id, measure), "err", "xbrl.4.8.2:measureElement") for numeratorMeasure in mulDivMeasures[0]: if numeratorMeasure in mulDivMeasures[1]: self.modelXbrl.error( _("Unit {0} numerator measure: {1} also appears as denominator measure" ).format(unit.id, numeratorMeasure), "err", "xbrl.4.8.4:measureBothNumDenom") #concepts checks modelXbrl.modelManager.showStatus(_("validating concepts")) for concept in modelXbrl.qnameConcepts.values(): conceptType = concept.type if XbrlConst.isStandardNamespace(concept.namespaceURI) or \ not concept.modelDocument.inDTS: continue if concept.isTuple: # must be global if not concept.element.parentNode.localName == "schema": self.modelXbrl.error( _("Tuple {0} must be declared globally").format( concept.qname), "err", "xbrl.4.9:tupleGloballyDeclared") if concept.periodType: self.modelXbrl.error( _("Tuple {0} must not have periodType").format( concept.qname), "err", "xbrl.4.9:tuplePeriodType") if concept.balance: self.modelXbrl.error( _("Tuple {0} must not have balance").format( concept.qname), "err", "xbrl.4.9:tupleBalance") # check attribute declarations for attributeQname in conceptType.attributes: if attributeQname.namespaceURI in (XbrlConst.xbrli, XbrlConst.link, XbrlConst.xlink, XbrlConst.xl): self.modelXbrl.error( _("Tuple {0} must not have attribute in this namespace {1}" ).format(concept.qname, attributeQname), "err", "xbrl.4.9:tupleAttribute") # check for mixed="true" or simple content if XmlUtil.descendantAttr(conceptType.element, XbrlConst.xsd, ("complexType", "complexContent"), "mixed") == "true": self.modelXbrl.error( _("Tuple {0} must not have mixed content").format( concept.qname), "err", "xbrl.4.9:tupleMixedContent") if XmlUtil.descendant(conceptType.element, XbrlConst.xsd, "simpleContent"): self.modelXbrl.error( _("Tuple {0} must not have simple content").format( concept.qname), "err", "xbrl.4.9:tupleSimpleContent") # child elements must be item or tuple for elementQname in conceptType.elements: childConcept = self.modelXbrl.qnameConcepts.get( elementQname) if childConcept is None: self.modelXbrl.error( _("Tuple {0} element {1} not defined").format( concept.qname, elementQname), "err", "xbrl.4.9:tupleElementUndefined") elif not ( childConcept.isItem or childConcept.isTuple or # isItem/isTuple do not include item or tuple itself childConcept.qname == XbrlConst.qnXbrliItem or # subs group includes item as member childConcept.qname == XbrlConst.qnXbrliTuple): self.modelXbrl.error( _("Tuple {0} must not have element {1} not an item or tuple" ).format(concept.qname, elementQname), "err", "xbrl.4.9:tupleElementItemOrTuple") elif concept.isItem: if concept.periodType not in periodTypeValues: #("instant","duration"): self.modelXbrl.error( _("Item {0} must have a valid periodType").format( concept.qname), "err", "xbrl.5.1.1.1:itemPeriodType") if concept.isMonetary: if concept.balance not in balanceValues: #(None, "credit","debit"): self.modelXbrl.error( _("Item {0} must have a valid balance {1}").format( concept.qname, concept.balance), "err", "xbrl.5.1.1.2:itemBalance") else: if concept.balance: self.modelXbrl.error( _("Item {0} may not have a balance").format( concept.qname), "err", "xbrl.5.1.1.2:itemBalance") if concept.baseXbrliType not in baseXbrliTypes: self.modelXbrl.error( _("Item {0} type {1} invalid").format( concept.qname, concept.baseXbrliType), "err", "xbrl.5.1.1.3:itemType") if modelXbrl.hasXDT: if concept.isHypercubeItem and not concept.abstract == "true": self.modelXbrl.error( _("Hypercube item {0} must be abstract").format( concept.qname), "err", "xbrldte:HypercubeElementIsNotAbstractError") elif concept.isDimensionItem and not concept.abstract == "true": self.modelXbrl.error( _("Dimension item {0} must be abstract").format( concept.qname), "err", "xbrldte:DimensionElementIsNotAbstractError") if modelXbrl.hasXDT: ValidateXbrlDimensions.checkConcept(self, concept) modelXbrl.modelManager.showStatus(_("validating DTS")) self.DTSreferenceResourceIDs = {} ValidateXbrlDTS.checkDTS(self, modelXbrl.modelDocument, []) del self.DTSreferenceResourceIDs if self.validateCalcLB: modelXbrl.modelManager.showStatus( _("Validating instance calculations")) ValidateXbrlCalcs.validate( modelXbrl, inferPrecision=(not self.validateInferDecimals)) if modelXbrl.modelManager.validateUtr: ValidateUtr.validate(modelXbrl) if modelXbrl.hasFormulae: ValidateFormula.validate(self) modelXbrl.modelManager.showStatus(_("ready"), 2000)
def segmentHash(self): # s-equality hash return XbrlUtil.equalityHash( self.segment ) # self-caching
def validate(self, modelXbrl, parameters=None): self.parameters = parameters self.precisionPattern = re.compile("^([0-9]+|INF)$") self.decimalsPattern = re.compile("^(-?[0-9]+|INF)$") self.isoCurrencyPattern = re.compile(r"^[A-Z]{3}$") self.modelXbrl = modelXbrl self.validateDisclosureSystem = modelXbrl.modelManager.validateDisclosureSystem self.disclosureSystem = modelXbrl.modelManager.disclosureSystem self.validateEFM = self.validateDisclosureSystem and self.disclosureSystem.EFM self.validateGFM = self.validateDisclosureSystem and self.disclosureSystem.GFM self.validateEFMorGFM = self.validateDisclosureSystem and self.disclosureSystem.EFMorGFM self.validateHMRC = self.validateDisclosureSystem and self.disclosureSystem.HMRC self.validateSBRNL = self.validateDisclosureSystem and self.disclosureSystem.SBRNL self.validateXmlLang = self.validateDisclosureSystem and self.disclosureSystem.xmlLangPattern self.validateCalcLB = modelXbrl.modelManager.validateCalcLB self.validateInferDecimals = modelXbrl.modelManager.validateInferDecimals # xlink validation modelXbrl.modelManager.showStatus(_("validating links")) modelLinks = set() self.remoteResourceLocElements = set() self.genericArcArcroles = set() for baseSetExtLinks in modelXbrl.baseSets.values(): for baseSetExtLink in baseSetExtLinks: modelLinks.add(baseSetExtLink) # ext links are unique (no dups) for modelLink in modelLinks: fromToArcs = {} locLabels = {} resourceLabels = {} resourceArcTos = [] for arcElt in modelLink.iterchildren(): if isinstance(arcElt,ModelObject): xlinkType = arcElt.get("{http://www.w3.org/1999/xlink}type") # locator must have an href if xlinkType == "locator": if arcElt.get("{http://www.w3.org/1999/xlink}href") is None: modelXbrl.error("xlink:locatorHref", _("Xlink locator %(xlinkLabel)s missing href in extended link %(linkrole)s"), modelObject=arcElt, linkrole=modelLink.role, xlinkLabel=arcElt.get("{http://www.w3.org/1999/xlink}label")) locLabels[arcElt.get("{http://www.w3.org/1999/xlink}label")] = arcElt elif xlinkType == "resource": resourceLabels[arcElt.get("{http://www.w3.org/1999/xlink}label")] = arcElt # can be no duplicated arcs between same from and to elif xlinkType == "arc": fromLabel = arcElt.get("{http://www.w3.org/1999/xlink}from") toLabel = arcElt.get("{http://www.w3.org/1999/xlink}to") fromTo = (fromLabel,toLabel) if fromTo in fromToArcs: modelXbrl.error("xlink:dupArcs", _("Duplicate xlink arcs in extended link %(linkrole)s from %(xlinkLabelFrom)s to %(xlinkLabelTo)s"), modelObject=arcElt, linkrole=modelLink.role, xlinkLabelFrom=fromLabel, xlinkLabelTo=toLabel) else: fromToArcs[fromTo] = arcElt if arcElt.namespaceURI == XbrlConst.link: if arcElt.localName in arcNamesTo21Resource: #("labelArc","referenceArc"): resourceArcTos.append((toLabel, arcElt.get("use"), arcElt)) elif self.isGenericArc(arcElt): arcrole = arcElt.get("{http://www.w3.org/1999/xlink}arcrole") self.genericArcArcroles.add(arcrole) if arcrole in (XbrlConst.elementLabel, XbrlConst.elementReference): resourceArcTos.append((toLabel, arcrole, arcElt)) # values of type (not needed for validating parsers) if xlinkType not in xlinkTypeValues: # ("", "simple", "extended", "locator", "arc", "resource", "title", "none"): modelXbrl.error("xlink:type", _("Xlink type %(xlinkType)s invalid in extended link %(linkrole)s"), modelObject=arcElt, linkrole=modelLink.role, xlinkType=xlinkType) # values of actuate (not needed for validating parsers) xlinkActuate = arcElt.get("{http://www.w3.org/1999/xlink}actuate") if xlinkActuate not in xlinkActuateValues: # ("", "onLoad", "onRequest", "other", "none"): modelXbrl.error("xlink:actuate", _("Actuate %(xlinkActuate)s invalid in extended link %(linkrole)s"), modelObject=arcElt, linkrole=modelLink.role, xlinkActuate=xlinkActuate) # values of show (not needed for validating parsers) xlinkShow = arcElt.get("{http://www.w3.org/1999/xlink}show") if xlinkShow not in xlinkShowValues: # ("", "new", "replace", "embed", "other", "none"): modelXbrl.error("xlink:show", _("Show %(xlinkShow)s invalid in extended link %(linkrole)s"), modelObject=arcElt, linkrole=modelLink.role, xlinkShow=xlinkShow) # check from, to of arcs have a resource or loc for fromTo, arcElt in fromToArcs.items(): fromLabel, toLabel in fromTo for name, value, sect in (("from", fromLabel, "3.5.3.9.2"),("to",toLabel, "3.5.3.9.3")): if value not in locLabels and value not in resourceLabels: modelXbrl.error("xbrl.{0}:arcResource".format(sect), _("Arc in extended link %(linkrole)s from %(xlinkLabelFrom)s to %(xlinkLabelTo)s attribute '%(attribute)s' has no matching loc or resource label"), modelObject=arcElt, linkrole=modelLink.role, xlinkLabelFrom=fromLabel, xlinkLabelTo=toLabel, attribute=name) if arcElt.localName == "footnoteArc" and arcElt.namespaceURI == XbrlConst.link and \ arcElt.get("{http://www.w3.org/1999/xlink}arcrole") == XbrlConst.factFootnote: if fromLabel not in locLabels: modelXbrl.error("xbrl.4.11.1.3.1:factFootnoteArcFrom", _("Footnote arc in extended link %(linkrole)s from %(xlinkLabelFrom)s to %(xlinkLabelTo)s \"from\" is not a loc"), modelObject=arcElt, linkrole=modelLink.role, xlinkLabelFrom=fromLabel, xlinkLabelTo=toLabel) if toLabel not in resourceLabels or qname(resourceLabels[toLabel]) != XbrlConst.qnLinkFootnote: modelXbrl.error("xbrl.4.11.1.3.1:factFootnoteArcTo", _("Footnote arc in extended link %(linkrole)s from %(xlinkLabelFrom)s to %(xlinkLabelTo)s \"to\" is not a footnote resource"), modelObject=arcElt, linkrole=modelLink.role, xlinkLabelFrom=fromLabel, xlinkLabelTo=toLabel) # check unprohibited label arcs to remote locs for resourceArcTo in resourceArcTos: resourceArcToLabel, resourceArcUse, arcElt = resourceArcTo if resourceArcToLabel in locLabels: toLabel = locLabels[resourceArcToLabel] if resourceArcUse == "prohibited": self.remoteResourceLocElements.add(toLabel) else: modelXbrl.error("xbrl.5.2.2.3:labelArcRemoteResource", _("Unprohibited labelArc in extended link %(linkrole)s has illegal remote resource loc labeled %(xlinkLabel)s href %(xlinkHref)s"), modelObject=arcElt, linkrole=modelLink.role, xlinkLabel=resourceArcToLabel, xlinkHref=toLabel.get("{http://www.w3.org/1999/xlink}href")) elif resourceArcToLabel in resourceLabels: toResource = resourceLabels[resourceArcToLabel] if resourceArcUse == XbrlConst.elementLabel: if not self.isGenericLabel(toResource): modelXbrl.error("xbrlle.2.1.1:genericLabelTarget", _("Generic label arc in extended link %(linkrole)s to %(xlinkLabel)s must target a generic label"), modelObject=arcElt, linkrole=modelLink.role, xlinkLabel=resourceArcToLabel) elif resourceArcUse == XbrlConst.elementReference: if not self.isGenericReference(toResource): modelXbrl.error("xbrlre.2.1.1:genericReferenceTarget", _("Generic reference arc in extended link %(linkrole)s to %(xlinkLabel)s must target a generic reference"), modelObject=arcElt, linkrole=modelLink.role, xlinkLabel=resourceArcToLabel) resourceArcTos = None # dereference arcs modelXbrl.dimensionDefaultConcepts = {} modelXbrl.qnameDimensionDefaults = {} modelXbrl.qnameDimensionContextElement = {} # check base set cycles, dimensions modelXbrl.modelManager.showStatus(_("validating relationship sets")) for baseSetKey in modelXbrl.baseSets.keys(): arcrole, ELR, linkqname, arcqname = baseSetKey if arcrole.startswith("XBRL-") or ELR is None or \ linkqname is None or arcqname is None: continue elif arcrole in XbrlConst.standardArcroleCyclesAllowed: # TODO: table should be in this module, where it is used cyclesAllowed, specSect = XbrlConst.standardArcroleCyclesAllowed[arcrole] elif arcrole in self.modelXbrl.arcroleTypes and len(self.modelXbrl.arcroleTypes[arcrole]) > 0: cyclesAllowed = self.modelXbrl.arcroleTypes[arcrole][0].cyclesAllowed if arcrole in self.genericArcArcroles: specSect = "xbrlgene:violatedCyclesConstraint" else: specSect = "xbrl.5.1.4.3:cycles" else: cyclesAllowed = "any" specSect = None if cyclesAllowed != "any" or arcrole in (XbrlConst.summationItem,) \ or arcrole in self.genericArcArcroles \ or arcrole.startswith(XbrlConst.formulaStartsWith): relsSet = modelXbrl.relationshipSet(arcrole,ELR,linkqname,arcqname) if cyclesAllowed != "any" and \ (XbrlConst.isStandardExtLinkQname(linkqname) and XbrlConst.isStandardArcQname(arcqname)) \ or arcrole in self.genericArcArcroles: noUndirected = cyclesAllowed == "none" fromRelationships = relsSet.fromModelObjects() for relFrom, rels in fromRelationships.items(): cycleFound = self.fwdCycle(relsSet, rels, noUndirected, {relFrom}) if cycleFound is not None: path = str(relFrom.qname) + " " + " - ".join( "{0}:{1} {2}".format(rel.modelDocument.basename, rel.sourceline, rel.toModelObject.qname) for rel in reversed(cycleFound[1:])) modelXbrl.error(specSect, _("Relationships have a %(cycle)s cycle in arcrole %(arcrole)s \nlink role %(linkrole)s \nlink %(linkname)s, \narc %(arcname)s, \npath %(path)s"), modelObject=cycleFound[1], cycle=cycleFound[0], path=path, arcrole=arcrole, linkrole=ELR, linkname=linkqname, arcname=arcqname), break # check calculation arcs for weight issues (note calc arc is an "any" cycles) if arcrole == XbrlConst.summationItem: for modelRel in relsSet.modelRelationships: weight = modelRel.weight fromConcept = modelRel.fromModelObject toConcept = modelRel.toModelObject if fromConcept is not None and toConcept is not None: if weight == 0: modelXbrl.error("xbrl.5.2.5.2.1:zeroWeight", _("Calculation relationship has zero weight from %(source)s to %(target)s in link role %(linkrole)s"), modelObject=modelRel, source=fromConcept.qname, target=toConcept.qname, linkrole=ELR), fromBalance = fromConcept.balance toBalance = toConcept.balance if fromBalance and toBalance: if (fromBalance == toBalance and weight < 0) or \ (fromBalance != toBalance and weight > 0): modelXbrl.error("xbrl.5.1.1.2:balanceCalcWeight", _("Calculation relationship has illegal weight %(weight)s from %(source)s, %(sourceBalance)s, to %(target)s, %(targetBalance)s, in link role %(linkrole)s (per 5.1.1.2 Table 6)"), modelObject=modelRel, weight=weight, source=fromConcept.qname, target=toConcept.qname, linkrole=ELR, sourceBalance=fromBalance, targetBalance=toBalance) if not fromConcept.isNumeric or not toConcept.isNumeric: modelXbrl.error("xbrl.5.2.5.2:nonNumericCalc", _("Calculation relationship has illegal concept from %(source)s%(sourceNumericDecorator)s to %(target)s%(targetNumericDecorator)s in link role %(linkrole)s"), modelObject=modelRel, source=fromConcept.qname, target=toConcept.qname, linkrole=ELR, sourceNumericDecorator="" if fromConcept.isNumeric else _(" (non-numeric)"), targetNumericDecorator="" if toConcept.isNumeric else _(" (non-numeric)")) # check presentation relationships for preferredLabel issues elif arcrole == XbrlConst.parentChild: for modelRel in relsSet.modelRelationships: preferredLabel = modelRel.preferredLabel toConcept = modelRel.toModelObject if preferredLabel is not None and toConcept is not None and \ toConcept.label(preferredLabel=preferredLabel,fallbackToQname=False) is None: modelXbrl.error("xbrl.5.2.4.2.1:preferredLabelMissing", _("Presentation relationship from %(source)s to %(target)s in link role %(linkrole)s missing preferredLabel %(preferredLabel)s"), modelObject=modelRel, source=modelRel.fromModelObject.qname, target=toConcept.qname, linkrole=ELR, preferredLabel=preferredLabel) # check essence-alias relationships elif arcrole == XbrlConst.essenceAlias: for modelRel in relsSet.modelRelationships: fromConcept = modelRel.fromModelObject toConcept = modelRel.toModelObject if fromConcept is not None and toConcept is not None: if fromConcept.type != toConcept.type or fromConcept.periodType != toConcept.periodType: modelXbrl.error("xbrl.5.2.6.2.2:essenceAliasTypes", _("Essence-alias relationship from %(source)s to %(target)s in link role %(linkrole)s has different types or periodTypes"), modelObject=modelRel, source=fromConcept.qname, target=toConcept.qname, linkrole=ELR) fromBalance = fromConcept.balance toBalance = toConcept.balance if fromBalance and toBalance: if fromBalance and toBalance and fromBalance != toBalance: modelXbrl.error("xbrl.5.2.6.2.2:essenceAliasBalance", _("Essence-alias relationship from %(source)s to %(target)s in link role %(linkrole)s has different balances")).format( modelObject=modelRel, source=fromConcept.qname, target=toConcept.qname, linkrole=ELR) elif modelXbrl.hasXDT and arcrole.startswith(XbrlConst.dimStartsWith): ValidateXbrlDimensions.checkBaseSet(self, arcrole, ELR, relsSet) elif modelXbrl.hasFormulae and arcrole.startswith(XbrlConst.formulaStartsWith): ValidateFormula.checkBaseSet(self, arcrole, ELR, relsSet) # instance checks modelXbrl.modelManager.showStatus(_("validating instance")) self.footnoteRefs = set() if modelXbrl.modelDocument.type == ModelDocument.Type.INSTANCE or \ modelXbrl.modelDocument.type == ModelDocument.Type.INLINEXBRL: for f in modelXbrl.facts: concept = f.concept if concept is not None: if concept.isNumeric: unit = f.unit if f.unitID is None or unit is None: self.modelXbrl.error("xbrl.4.6.2:numericUnit", _("Fact %(fact)s context %(contextID)s is numeric and must have a unit"), modelObject=f, fact=f.qname, contextID=f.contextID) else: if concept.isMonetary: measures = unit.measures if not measures or len(measures[0]) != 1 or len(measures[1]) != 0 or \ measures[0][0].namespaceURI != XbrlConst.iso4217 or \ not self.isoCurrencyPattern.match(measures[0][0].localName): self.modelXbrl.error("xbrl.4.8.2:monetaryFactUnit", _("Fact %(fact)s context %(contextID)s must have a monetary unit %(unitID)s"), modelObject=f, fact=f.qname, contextID=f.contextID, unitID=f.unitID) elif concept.isShares: measures = unit.measures if not measures or len(measures[0]) != 1 or len(measures[1]) != 0 or \ measures[0][0] != XbrlConst.qnXbrliShares: self.modelXbrl.error("xbrl.4.8.2:sharesFactUnit", _("Fact %(fact)s context %(contextID)s must have a xbrli:shares unit %(unitID)s"), modelObject=f, fact=f.qname, contextID=f.contextID, unitID=f.unitID) precision = f.precision hasPrecision = precision is not None if hasPrecision and precision != "INF" and not precision.isdigit(): self.modelXbrl.error("xbrl.4.6.4:precision", _("Fact %(fact)s context %(contextID)s precision %(precision)s is invalid"), modelObject=f, fact=f.qname, contextID=f.contextID, precision=precision) decimals = f.decimals hasDecimals = decimals is not None if hasPrecision and not self.precisionPattern.match(precision): self.modelXbrl.error("xbrl.4.6.4:precision", _("Fact %(fact)s context %(contextID)s precision %(precision)s is invalid"), modelObject=f, fact=f.qname, contextID=f.contextID, precision=precision) if hasPrecision and hasDecimals: self.modelXbrl.error("xbrl.4.6.3:bothPrecisionAndDecimals", _("Fact %(fact)s context %(contextID)s can not have both precision and decimals"), modelObject=f, fact=f.qname, contextID=f.contextID) if hasDecimals and not self.decimalsPattern.match(decimals): self.modelXbrl.error(_("xbrl.4.6.5:decimals"), _("Fact %(fact)s context %(contextID)s decimals %(decimals)s is invalid"), modelObject=f, fact=f.qname, contextID=f.contextID, decimals=decimals) if concept.isItem: context = f.context if context is None: self.modelXbrl.error("xbrl.4.6.1:itemContextRef", _("Item %(fact)s must have a context"), modelObject=f, fact=f.qname) else: periodType = concept.periodType if (periodType == "instant" and not context.isInstantPeriod) or \ (periodType == "duration" and not (context.isStartEndPeriod or context.isForeverPeriod)): self.modelXbrl.error("xbrl.4.7.2:contextPeriodType", _("Fact %(fact)s context %(contextID)s has period type %(periodType)s conflict with context"), modelObject=f, fact=f.qname, contextID=f.contextID, periodType=periodType) if modelXbrl.hasXDT: ValidateXbrlDimensions.checkFact(self, f) # check precision and decimals if f.xsiNil == "true": if hasPrecision or hasDecimals: self.modelXbrl.error("xbrl.4.6.3:nilPrecisionDecimals", _("Fact %(fact)s context %(contextID)s can not be nil and have either precision or decimals"), modelObject=f, fact=f.qname, contextID=f.contextID) elif concept.isFraction: if hasPrecision or hasDecimals: self.modelXbrl.error("xbrl.4.6.3:fractionPrecisionDecimals", _("Fact %(fact)s context %(contextID)s is a fraction concept and cannot have either precision or decimals"), modelObject=f, fact=f.qname, contextID=f.contextID) numerator, denominator = f.fractionValue if not (numerator == "INF" or numerator.isnumeric()): self.modelXbrl.error("xbrl.5.1.1:fractionPrecisionDecimals", _("Fact %(fact)s context %(contextID)s is a fraction with invalid numerator %(numerator)s"), modelObject=f, fact=f.qname, contextID=f.contextID, numerator=numerator) if not denominator.isnumeric() or int(denominator) == 0: self.modelXbrl.error("xbrl.5.1.1:fractionPrecisionDecimals", _("Fact %(fact)s context %(contextID)s is a fraction with invalid denominator %(denominator)")).format( modelObject=f, fact=f.qname, contextID=f.contextID, denominator=denominator) else: if modelXbrl.modelDocument.type != ModelDocument.Type.INLINEXBRL: for child in f.iterchildren(): if isinstance(child,ModelObject): self.modelXbrl.error("xbrl.5.1.1:itemMixedContent", _("Fact %(fact)s context %(contextID)s may not have child elements %(childElementName)s"), modelObject=f, fact=f.qname, contextID=f.contextID, childElementName=child.prefixedName) break if concept.isNumeric and not hasPrecision and not hasDecimals: self.modelXbrl.error("xbrl.4.6.3:missingPrecisionDecimals", _("Fact %(fact)s context %(contextID)s is a numeric concept and must have either precision or decimals"), modelObject=f, fact=f.qname, contextID=f.contextID) elif concept.isTuple: if f.contextID: self.modelXbrl.error("xbrl.4.6.1:tupleContextRef", _("Tuple %(fact)s must not have a context"), modelObject=f, fact=f.qname) if hasPrecision or hasDecimals: self.modelXbrl.error("xbrl.4.6.3:tuplePrecisionDecimals", _("Fact %(fact)s is a tuple and cannot have either precision or decimals"), modelObject=f, fact=f.qname) # custom attributes may be allowed by anyAttribute but not by 2.1 for attrQname, attrValue in XbrlUtil.attributes(self.modelXbrl, f): if attrQname.namespaceURI in (XbrlConst.xbrli, XbrlConst.link, XbrlConst.xlink, XbrlConst.xl): self.modelXbrl.error(_("xbrl.4.9:tupleAttribute"), _("Fact %(fact)s is a tuple and must not have attribute in this namespace %(attribute)s"), modelObject=f, fact=f.qname, attribute=attrQname), else: self.modelXbrl.error("xbrl.4.6:notItemOrTuple", _("Fact %(fact)s must be an item or tuple"), modelObject=f, fact=f.qname) if isinstance(f, ModelInlineFact): self.footnoteRefs.update(f.footnoteRefs) #instance checks for cntx in modelXbrl.contexts.values(): if cntx.isStartEndPeriod: try: if cntx.endDatetime <= cntx.startDatetime: self.modelXbrl.error("xbrl.4.7.2:periodStartBeforeEnd", _("Context %(contextID)s must have startDate less than endDate"), modelObject=cntx, contextID=cntx.id) except (TypeError, ValueError) as err: self.modelXbrl.error("xbrl.4.7.2:contextDateError", _("Context %(contextID) startDate or endDate: %(error)s"), modelObject=cntx, contextID=cntx.id, error=err) elif cntx.isInstantPeriod: try: cntx.instantDatetime #parse field except ValueError as err: self.modelXbrl.error("xbrl.4.7.2:contextDateError", _("Context %(contextID)s instant date: %(error)s"), modelObject=cntx, contextID=cntx.id, error=err) self.segmentScenario(cntx.segment, cntx.id, "segment", "4.7.3.2") self.segmentScenario(cntx.scenario, cntx.id, "scenario", "4.7.4") if modelXbrl.hasXDT: ValidateXbrlDimensions.checkContext(self,cntx) for unit in modelXbrl.units.values(): mulDivMeasures = unit.measures if mulDivMeasures: for measures in mulDivMeasures: for measure in measures: if measure.namespaceURI == XbrlConst.xbrli and not \ measure in (XbrlConst.qnXbrliPure, XbrlConst.qnXbrliShares): self.modelXbrl.error("xbrl.4.8.2:measureElement", _("Unit %(unitID)s illegal measure: %(measure)s"), modelObject=unit, unitID=unit.id, measure=measure) for numeratorMeasure in mulDivMeasures[0]: if numeratorMeasure in mulDivMeasures[1]: self.modelXbrl.error("xbrl.4.8.4:measureBothNumDenom", _("Unit %(unitID)s numerator measure: %(measure)s also appears as denominator measure"), modelObject=unit, unitID=unit.id, measure=numeratorMeasure) #concepts checks modelXbrl.modelManager.showStatus(_("validating concepts")) for concept in modelXbrl.qnameConcepts.values(): conceptType = concept.type if XbrlConst.isStandardNamespace(concept.qname.namespaceURI) or \ not concept.modelDocument.inDTS: continue if concept.isTuple: # must be global if not concept.getparent().localName == "schema": self.modelXbrl.error("xbrl.4.9:tupleGloballyDeclared", _("Tuple %(concept)s must be declared globally"), modelObject=concept, concept=concept.qname) if concept.periodType: self.modelXbrl.error("xbrl.4.9:tuplePeriodType", _("Tuple %(concept)s must not have periodType"), modelObject=concept, concept=concept.qname) if concept.balance: self.modelXbrl.error("xbrl.4.9:tupleBalance", _("Tuple %(concept)s must not have balance"), modelObject=concept, concept=concept.qname) if conceptType is not None: # check attribute declarations for attribute in conceptType.attributes.values(): if attribute.qname.namespaceURI in (XbrlConst.xbrli, XbrlConst.link, XbrlConst.xlink, XbrlConst.xl): self.modelXbrl.error("xbrl.4.9:tupleAttribute", _("Tuple %(concept)s must not have attribute in this namespace %(attribute)s"), modelObject=concept, concept=concept.qname, attribute=attribute.qname) # check for mixed="true" or simple content if XmlUtil.descendantAttr(conceptType, XbrlConst.xsd, ("complexType", "complexContent"), "mixed") == "true": self.modelXbrl.error("xbrl.4.9:tupleMixedContent", _("Tuple %(concept)s must not have mixed content"), modelObject=concept, concept=concept.qname) if XmlUtil.descendant(conceptType, XbrlConst.xsd, "simpleContent"): self.modelXbrl.error("xbrl.4.9:tupleSimpleContent", _("Tuple %(concept)s must not have simple content"), modelObject=concept, concept=concept.qname) # child elements must be item or tuple for elementQname in conceptType.elements: childConcept = self.modelXbrl.qnameConcepts.get(elementQname) if childConcept is None: self.modelXbrl.error("xbrl.4.9:tupleElementUndefined", _("Tuple %(concept)s element %(tupleElement)s not defined"), modelObject=concept, concept=str(concept.qname), tupleElement=elementQname) elif not (childConcept.isItem or childConcept.isTuple or # isItem/isTuple do not include item or tuple itself childConcept.qname == XbrlConst.qnXbrliItem or # subs group includes item as member childConcept.qname == XbrlConst.qnXbrliTuple): self.modelXbrl.error("xbrl.4.9:tupleElementItemOrTuple", _("Tuple %(concept)s must not have element %(tupleElement)s not an item or tuple"), modelObject=concept, concept=concept.qname, tupleElement=elementQname) elif concept.isItem: if concept.periodType not in periodTypeValues: #("instant","duration"): self.modelXbrl.error("xbrl.5.1.1.1:itemPeriodType", _("Item %(concept)s must have a valid periodType"), modelObject=concept, concept=concept.qname) if concept.isMonetary: if concept.balance not in balanceValues: #(None, "credit","debit"): self.modelXbrl.error("xbrl.5.1.1.2:itemBalance", _("Item %(concept)s must have a valid balance %(balance)s"), modelObject=concept, concept=concept.qname, balance=concept.balance) else: if concept.balance: self.modelXbrl.error("xbrl.5.1.1.2:itemBalance", _("Item %(concept)s may not have a balance"), modelObject=concept, concept=concept.qname) if concept.baseXbrliType not in baseXbrliTypes: self.modelXbrl.error("xbrl.5.1.1.3:itemType", _("Item %(concept)s type %(itemType)s invalid"), modelObject=concept, concept=concept.qname, itemType=concept.baseXbrliType) if modelXbrl.hasXDT: if concept.isHypercubeItem and not concept.abstract == "true": self.modelXbrl.error("xbrldte:HypercubeElementIsNotAbstractError", _("Hypercube item %(concept)s must be abstract"), modelObject=concept, concept=concept.qname) elif concept.isDimensionItem and not concept.abstract == "true": self.modelXbrl.error("xbrldte:DimensionElementIsNotAbstractError", _("Dimension item %(concept)s must be abstract"), modelObject=concept, concept=concept.qname) if modelXbrl.hasXDT: ValidateXbrlDimensions.checkConcept(self, concept) modelXbrl.modelManager.showStatus(_("validating DTS")) self.DTSreferenceResourceIDs = {} ValidateXbrlDTS.checkDTS(self, modelXbrl.modelDocument, []) del self.DTSreferenceResourceIDs if self.validateCalcLB: modelXbrl.modelManager.showStatus(_("Validating instance calculations")) ValidateXbrlCalcs.validate(modelXbrl, inferPrecision=(not self.validateInferDecimals)) if (modelXbrl.modelManager.validateUtr or (self.validateEFM and any((concept.namespaceURI in self.disclosureSystem.standardTaxonomiesDict) for concept in self.modelXbrl.nameConcepts.get("UTR",())))): ValidateUtr.validate(modelXbrl) if modelXbrl.hasFormulae: ValidateFormula.validate(self) modelXbrl.modelManager.showStatus(_("ready"), 2000)
def scenarioHash(self): # s-equality hash return XbrlUtil.equalityHash( self.scenario ) # self-caching
def scenarioHash(self): # s-equality hash return XbrlUtil.equalityHash(self.scenario) # self-caching
def segmentHash(self): """(int) -- Hash of the segment, based on s-equality values""" return XbrlUtil.equalityHash( self.segment ) # self-caching
def checkFacts(self, facts, inTuple=False): # do in document order for f in facts: concept = f.concept if concept is not None: if concept.isNumeric: unit = f.unit if f.unitID is None or unit is None: self.modelXbrl.error("xbrl.4.6.2:numericUnit", _("Fact %(fact)s context %(contextID)s is numeric and must have a unit"), modelObject=f, fact=f.qname, contextID=f.contextID) else: if concept.isMonetary: measures = unit.measures if not measures or len(measures[0]) != 1 or len(measures[1]) != 0: self.modelXbrl.error("xbrl.4.8.2:monetaryFactUnit-notSingleMeasure", _("Fact %(fact)s context %(contextID)s must have a single unit measure which is monetary %(unitID)s"), modelObject=f, fact=f.qname, contextID=f.contextID, unitID=f.unitID) elif (measures[0][0].namespaceURI != XbrlConst.iso4217 or not self.isoCurrencyPattern.match(measures[0][0].localName)): self.modelXbrl.error("xbrl.4.8.2:monetaryFactUnit-notMonetaryMeasure", _("Fact %(fact)s context %(contextID)s must have a monetary unit measure %(unitID)s"), modelObject=f, fact=f.qname, contextID=f.contextID, unitID=f.unitID) elif concept.isShares: measures = unit.measures if not measures or len(measures[0]) != 1 or len(measures[1]) != 0: self.modelXbrl.error("xbrl.4.8.2:sharesFactUnit-notSingleMeasure", _("Fact %(fact)s context %(contextID)s must have a single xbrli:shares unit %(unitID)s"), modelObject=f, fact=f.qname, contextID=f.contextID, unitID=f.unitID) elif measures[0][0] != XbrlConst.qnXbrliShares: self.modelXbrl.error("xbrl.4.8.2:sharesFactUnit-notSharesMeasure", _("Fact %(fact)s context %(contextID)s must have a xbrli:shares unit %(unitID)s"), modelObject=f, fact=f.qname, contextID=f.contextID, unitID=f.unitID) precision = f.precision hasPrecision = precision is not None if hasPrecision and precision != "INF" and not precision.isdigit(): self.modelXbrl.error("xbrl.4.6.4:precision", _("Fact %(fact)s context %(contextID)s precision %(precision)s is invalid"), modelObject=f, fact=f.qname, contextID=f.contextID, precision=precision) decimals = f.decimals hasDecimals = decimals is not None if hasPrecision and not self.precisionPattern.match(precision): self.modelXbrl.error("xbrl.4.6.4:precision", _("Fact %(fact)s context %(contextID)s precision %(precision)s is invalid"), modelObject=f, fact=f.qname, contextID=f.contextID, precision=precision) if hasPrecision and hasDecimals: self.modelXbrl.error("xbrl.4.6.3:bothPrecisionAndDecimals", _("Fact %(fact)s context %(contextID)s can not have both precision and decimals"), modelObject=f, fact=f.qname, contextID=f.contextID) if hasDecimals and not self.decimalsPattern.match(decimals): self.modelXbrl.error(_("xbrl.4.6.5:decimals"), _("Fact %(fact)s context %(contextID)s decimals %(decimals)s is invalid"), modelObject=f, fact=f.qname, contextID=f.contextID, decimals=decimals) if concept.isItem: context = f.context if context is None: self.modelXbrl.error("xbrl.4.6.1:itemContextRef", _("Item %(fact)s must have a context"), modelObject=f, fact=f.qname) else: periodType = concept.periodType if (periodType == "instant" and not context.isInstantPeriod) or \ (periodType == "duration" and not (context.isStartEndPeriod or context.isForeverPeriod)): self.modelXbrl.error("xbrl.4.7.2:contextPeriodType", _("Fact %(fact)s context %(contextID)s has period type %(periodType)s conflict with context"), modelObject=f, fact=f.qname, contextID=f.contextID, periodType=periodType) # check precision and decimals if f.xsiNil == "true": if hasPrecision or hasDecimals: self.modelXbrl.error("xbrl.4.6.3:nilPrecisionDecimals", _("Fact %(fact)s context %(contextID)s can not be nil and have either precision or decimals"), modelObject=f, fact=f.qname, contextID=f.contextID) elif concept.isFraction: if hasPrecision or hasDecimals: self.modelXbrl.error("xbrl.4.6.3:fractionPrecisionDecimals", _("Fact %(fact)s context %(contextID)s is a fraction concept and cannot have either precision or decimals"), modelObject=f, fact=f.qname, contextID=f.contextID) numerator, denominator = f.fractionValue if not (numerator == "INF" or numerator.isnumeric()): self.modelXbrl.error("xbrl.5.1.1:fractionPrecisionDecimals", _("Fact %(fact)s context %(contextID)s is a fraction with invalid numerator %(numerator)s"), modelObject=f, fact=f.qname, contextID=f.contextID, numerator=numerator) if not denominator.isnumeric() or _INT(denominator) == 0: self.modelXbrl.error("xbrl.5.1.1:fractionPrecisionDecimals", _("Fact %(fact)s context %(contextID)s is a fraction with invalid denominator %(denominator)")).format( modelObject=f, fact=f.qname, contextID=f.contextID, denominator=denominator) else: if self.modelXbrl.modelDocument.type != ModelDocument.Type.INLINEXBRL: for child in f.iterchildren(): if isinstance(child,ModelObject): self.modelXbrl.error("xbrl.5.1.1:itemMixedContent", _("Fact %(fact)s context %(contextID)s may not have child elements %(childElementName)s"), modelObject=f, fact=f.qname, contextID=f.contextID, childElementName=child.prefixedName) break if concept.isNumeric and not hasPrecision and not hasDecimals: self.modelXbrl.error("xbrl.4.6.3:missingPrecisionDecimals", _("Fact %(fact)s context %(contextID)s is a numeric concept and must have either precision or decimals"), modelObject=f, fact=f.qname, contextID=f.contextID) elif concept.isTuple: if f.contextID: self.modelXbrl.error("xbrl.4.6.1:tupleContextRef", _("Tuple %(fact)s must not have a context"), modelObject=f, fact=f.qname) if hasPrecision or hasDecimals: self.modelXbrl.error("xbrl.4.6.3:tuplePrecisionDecimals", _("Fact %(fact)s is a tuple and cannot have either precision or decimals"), modelObject=f, fact=f.qname) # custom attributes may be allowed by anyAttribute but not by 2.1 for attrQname, attrValue in XbrlUtil.attributes(self.modelXbrl, f): if attrQname.namespaceURI in (XbrlConst.xbrli, XbrlConst.link, XbrlConst.xlink, XbrlConst.xl): self.modelXbrl.error(_("xbrl.4.9:tupleAttribute"), _("Fact %(fact)s is a tuple and must not have attribute in this namespace %(attribute)s"), modelObject=f, fact=f.qname, attribute=attrQname), else: self.modelXbrl.error("xbrl.4.6:notItemOrTuple", _("Fact %(fact)s must be an item or tuple"), modelObject=f, fact=f.qname) if isinstance(f, ModelInlineFact): if concept is None: self.modelXbrl.error("xbrl:schemaImportMissing", _("Fact %(fact)s missing schema definition or missing name attribute"), modelObject=f, fact=f.qname) if f.localName in {"fraction", "nonFraction", "nonNumeric"} and not f.contextID: self.modelXbrl.error("ix:missingContextRef", _("Fact %(fact)s is missing a contextRef"), modelObject=f, fact=f.qname) if f.localName in {"fraction", "nonFraction"} and not f.unitID: self.modelXbrl.error("ix:missingUnitRef", _("Fact %(fact)s is missing a unitRef"), modelObject=f, fact=f.qname) self.footnoteRefs.update(f.footnoteRefs) fmt = f.format if fmt: if fmt.namespaceURI not in FunctionIxt.ixtNamespaceURIs: self.modelXbrl.error("ix.14.2:invalidTransformation", _("Fact %(fact)s has unrecognized transformation namespace %(namespace)s"), modelObject=f, fact=f.qname, namespace=fmt.namespaceURI) elif fmt.localName not in FunctionIxt.ixtFunctions: self.modelXbrl.error("ix.14.2:invalidTransformation", _("Fact %(fact)s has unrecognized transformation name %(name)s"), modelObject=f, fact=f.qname, name=fmt.localName) if fmt.namespaceURI == FunctionIxt.deprecatedNamespaceURI: self.factsWithDeprecatedIxNamespace.append(f) if f.isTuple or f.tupleID: self.checkIxTupleContent(f, set()) if not inTuple and f.order is not None: self.modelXbrl.error("ix.13.1.2:tupleOrder", _("Fact %(fact)s must not have an order (%(order)s) unless in a tuple"), modelObject=f, fact=f.qname, order=f.order) if f.modelTupleFacts: self.checkFacts(f.modelTupleFacts, inTuple=True)
def aspectMatches(xpCtx, fact1, fact2, aspects): if fact1 is None or fact2 is None: # fallback (atomic) never matches any aspect return False matches = True for aspect in (aspects if hasattr(aspects,'__iter__') else (aspects,)): if aspect == Aspect.LOCATION: if (fact1.modelXbrl == fact2.modelXbrl and # test deemed true for multi-instance comparisons fact1.getparent() != fact2.getparent()): matches = False elif aspect == Aspect.CONCEPT: if fact1.concept.qname != fact2.concept.qname: matches = False elif fact1.isTuple or fact2.isTuple: return True # only match the aspects both facts have elif aspect == Aspect.PERIOD: if not fact1.context.isPeriodEqualTo(fact2.context): matches = False elif aspect == Aspect.ENTITY_IDENTIFIER: if not fact1.context.isEntityIdentifierEqualTo(fact2.context): matches = False elif aspect == Aspect.COMPLETE_SEGMENT: if not XbrlUtil.nodesCorrespond(fact1.modelXbrl, fact1.context.segment, fact2.context.segment, dts2=fact2.modelXbrl): matches = False elif aspect == Aspect.COMPLETE_SCENARIO: if not XbrlUtil.nodesCorrespond(fact1.modelXbrl, fact1.context.scenario, fact2.context.scenario, dts2=fact2.modelXbrl): matches = False elif aspect in (Aspect.NON_XDT_SEGMENT, Aspect.NON_XDT_SCENARIO): nXs1 = fact1.context.nonDimValues(aspect) nXs2 = fact2.context.nonDimValues(aspect) if len(nXs1) != len(nXs2): matches = False else: for i in range(len(nXs1)): if not XbrlUtil.nodesCorrespond(fact1.modelXbrl, nXs1[i], nXs2[i], dts2=fact2.modelXbrl): matches = False break elif aspect == Aspect.UNIT: u1 = fact1.unit u2 = fact2.unit if (u1 is None) != (u2 is None): matches = False elif u1 is not None and u2 is not None and u1.measures != u2.measures: matches = False elif aspect == Aspect.DIMENSIONS: ''' (no implicit filtering on ALL dimensions for now) dimQnames1 = fact1.context.dimAspects dimQnames2 = fact2.context.dimAspects if len(dimQnames1 ^ dimQnames2): # dims not in both matches = False else: for dimQname1 in dimQnames1: if dimQname1 not in dimQnames2 or \ not aspectMatches(fact1, fact2, dimQname1): matches = False break ''' elif isinstance(aspect, QName): from arelle.ModelInstanceObject import ModelDimensionValue dimValue1 = fact1.context.dimValue(aspect) dimValue2 = fact2.context.dimValue(aspect) if isinstance(dimValue1, ModelDimensionValue): if dimValue1.isExplicit: if isinstance(dimValue2, QName): if dimValue1.memberQname != dimValue2: matches = False elif isinstance(dimValue2, ModelDimensionValue): if dimValue2.isTyped: matches = False elif dimValue1.memberQname != dimValue2.memberQname: matches = False elif dimValue2 is None: matches = False elif dimValue1.isTyped: if isinstance(dimValue2, QName): matches = False elif isinstance(dimValue2, ModelDimensionValue): if dimValue2.isExplicit: matches = False elif dimValue1.dimension.typedDomainElement in xpCtx.modelXbrl.modelFormulaEqualityDefinitions: equalityDefinition = xpCtx.modelXbrl.modelFormulaEqualityDefinitions[dimValue1.dimension.typedDomainElement] matches = equalityDefinition.evalTest(xpCtx, fact1, fact2) elif not XbrlUtil.nodesCorrespond(fact1.modelXbrl, dimValue1.typedMember, dimValue2.typedMember, dts2=fact2.modelXbrl): matches = False elif dimValue2 is None: matches = False elif isinstance(dimValue1,QName): # first dim is default value of an explicit dim if isinstance(dimValue2, QName): # second dim is default value of an explicit dim # multi-instance does not consider member's qname here where it is a default # only check if qnames match if the facts are from same instance if fact1.modelXbrl == fact2.modelXbrl and dimValue1 != dimValue2: matches = False elif isinstance(dimValue2, ModelDimensionValue): if dimValue2.isTyped: matches = False elif dimValue1 != dimValue2.memberQname: matches = False elif dimValue2 is None: # no dim aspect for fact 2 if fact1.modelXbrl == fact2.modelXbrl: # only allowed for multi-instance matches = False elif dimValue1 is None: # absent dim member from fact1 allowed if fact2 is default in different instance if isinstance(dimValue2,QName): if fact1.modelXbrl == fact2.modelXbrl: matches = False elif dimValue2 is not None: matches = False # else if both are None, matches True for single and multiple instance if not matches: break return matches
def aspectMatches(fact1, fact2, aspects): matches = True for aspect in (aspects if hasattr(aspects, '__iter__') else (aspects, )): if aspect == Aspect.LOCATION: if (fact1.modelXbrl == fact2.modelXbrl and # test deemed true for multi-instance comparisons fact1.element.parentNode != fact2.element.parentNode): matches = False elif aspect == Aspect.CONCEPT: if fact1.concept.qname != fact2.concept.qname: matches = False elif fact1.isTuple or fact2.isTuple: return True # only match the aspects both facts have elif aspect == Aspect.PERIOD: if not fact1.context.isPeriodEqualTo(fact2.context): matches = False elif aspect == Aspect.ENTITY_IDENTIFIER: if not fact1.context.isEntityIdentifierEqualTo(fact2.context): matches = False elif aspect == Aspect.COMPLETE_SEGMENT: if not XbrlUtil.nodesCorrespond(fact1.modelXbrl, fact1.context.segment, fact2.context.segment, dts2=fact2.modelXbrl): matches = False elif aspect == Aspect.COMPLETE_SCENARIO: if not XbrlUtil.nodesCorrespond(fact1.modelXbrl, fact1.context.scenario, fact2.context.scenario, dts2=fact2.modelXbrl): matches = False elif aspect in (Aspect.NON_XDT_SEGMENT, Aspect.NON_XDT_SCENARIO): nXs1 = fact1.context.nonDimValues(aspect) nXs2 = fact2.context.nonDimValues(aspect) if len(nXs1) != len(nXs2): matches = False else: for i in range(len(nXs1)): if not XbrlUtil.nodesCorrespond(fact1.modelXbrl, nXs1[i], nXs2[i], dts2=fact2.modelXbrl): matches = False break elif aspect == Aspect.UNIT: u1 = fact1.unit u2 = fact2.unit if (u1 is None) != (u2 is None): matches = False elif u1 and u2 and u1.measures != u2.measures: matches = False elif aspect == Aspect.DIMENSIONS: ''' (no implicit filtering on ALL dimensions for now) dimQnames1 = fact1.context.dimAspects dimQnames2 = fact2.context.dimAspects if len(dimQnames1 ^ dimQnames2): # dims not in both matches = False else: for dimQname1 in dimQnames1: if dimQname1 not in dimQnames2 or \ not aspectMatches(fact1, fact2, dimQname1): matches = False break ''' elif isinstance(aspect, QName): from arelle.ModelObject import ModelDimensionValue dimValue1 = fact1.context.dimValue(aspect) dimValue2 = fact2.context.dimValue(aspect) if isinstance(dimValue1, ModelDimensionValue): if dimValue1.isExplicit: if isinstance(dimValue2, QName): if dimValue1.memberQname != dimValue2: matches = False elif isinstance(dimValue2, ModelDimensionValue): if dimValue2.isTyped: matches = False elif dimValue1.memberQname != dimValue2.memberQname: matches = False elif dimValue1.isTyped: if isinstance(dimValue2, QName): matches = False elif isinstance(dimValue2, ModelDimensionValue): if dimValue2.isExplicit: matches = False elif not XbrlUtil.nodesCorrespond( fact1.modelXbrl, dimValue1.typedMember, dimValue2.typedMember, dts2=fact2.modelXbrl): matches = False elif isinstance(dimValue1, QName): if isinstance(dimValue2, QName): if dimValue1 != dimValue2: matches = False elif isinstance(dimValue2, ModelDimensionValue): if dimValue2.isTyped: matches = False elif dimValue1 != dimValue2.memberQname: matches = False elif dimValue1 is None: if dimValue2: matches = False if not matches: break return matches
def __hash__(self): if self.isExplicit: return hash( (self.dimensionQname, self.memberQname) ) else: # need XPath equal so that QNames aren't lexically compared (for fact and context equality in comparing formula results) return hash( (self.dimensionQname, XbrlUtil.equalityHash(XmlUtil.child(self), equalMode=XbrlUtil.XPATH_EQ)) )
def nonDimScenarioHash(self): try: return self._nonDimScenarioHash except AttributeError: self._nonDimScenarioHash = XbrlUtil.equalityHash(self.nonDimValues("scenario")) return self._nonDimScenarioHash