def aspectsCovered(self): aspectsCovered = set() if XmlUtil.hasChild(self, XbrlConst.euRend, "primaryItem"): aspectsCovered.add(Aspect.CONCEPT) if XmlUtil.hasChild(self, XbrlConst.euRend, "timeReference"): aspectsCovered.add(Aspect.INSTANT) for e in XmlUtil.children(self, XbrlConst.euRend, "explicitDimCoord"): aspectsCovered.add(self.prefixedNameQname(e.get("dimension"))) return aspectsCovered
def hasAspect(self, structuralNode, aspect): if aspect == Aspect.CONCEPT: return XmlUtil.hasChild(self, XbrlConst.euRend, "primaryItem") elif aspect == Aspect.DIMENSIONS: return XmlUtil.hasChild(self, XbrlConst.euRend, "explicitDimCoord") elif aspect in (Aspect.PERIOD_TYPE, Aspect.INSTANT): return XmlUtil.hasChild(self, XbrlConst.euRend, "timeReference") elif isinstance(aspect, QName): for e in XmlUtil.children(self, XbrlConst.euRend, "explicitDimCoord"): if self.prefixedNameQname(e.get("dimension")) == aspect: return True return False
def aspectValue(self, xpCtx, aspect, inherit=False): if aspect == Aspect.DIMENSIONS: dims = set(self.prefixedNameQname(e.get("dimension")) for e in XmlUtil.children(self, XbrlConst.euRend, "explicitDimCoord")) if inherit and self.parentDefinitionNode is not None: dims |= self.parentDefinitionNode.aspectValue(None, aspect, inherit) return dims if inherit and not self.hasAspect(None, aspect): if self.parentDefinitionNode is not None: return self.parentDefinitionNode.aspectValue(None, aspect, inherit) return None if aspect == Aspect.CONCEPT: priItem = XmlUtil.childAttr(self, XbrlConst.euRend, "primaryItem", "name") if priItem is not None: return self.prefixedNameQname(priItem) return None elif aspect == Aspect.PERIOD_TYPE: if XmlUtil.hasChild(self, XbrlConst.euRend, "timeReference"): return "instant" elif aspect == Aspect.INSTANT: return XmlUtil.datetimeValue(XmlUtil.childAttr(self, XbrlConst.euRend, "timeReference", "instant"), addOneDay=True) elif isinstance(aspect, QName): for e in XmlUtil.children(self, XbrlConst.euRend, "explicitDimCoord"): if self.prefixedNameQname(e.get("dimension")) == aspect: return self.prefixedNameQname(e.get("value")) return None
def isForeverPeriod(self): """(bool) -- True for forever period""" try: return self._isForeverPeriod except AttributeError: self._isForeverPeriod = XmlUtil.hasChild(self.period, XbrlConst.xbrli, "forever") return self._isForeverPeriod
def isStartEndPeriod(self): """(bool) -- True for startDate/endDate period""" try: return self._isStartEndPeriod except AttributeError: self._isStartEndPeriod = XmlUtil.hasChild(self.period, XbrlConst.xbrli, ("startDate","endDate")) return self._isStartEndPeriod
def isInstantPeriod(self): """(bool) -- True for instant period""" try: return self._isInstantPeriod except AttributeError: self._isInstantPeriod = XmlUtil.hasChild(self.period, XbrlConst.xbrli, "instant") return self._isInstantPeriod
def isInstantPeriod(self): try: return self._isInstantPeriod except AttributeError: self._isInstantPeriod = XmlUtil.hasChild(self.period, XbrlConst.xbrli, "instant") return self._isInstantPeriod
def is_period_type(args, periodElement): if len(args) != 1: raise XPathContext.FunctionNumArgs() if len(args[0]) != 1: raise XPathContext.FunctionArgType(1, "xbrl:period") period = args[0][0] if isinstance(period,xml.dom.Node) and period.nodeType == 1 and \ period.localName == "period" and period.namespaceURI == XbrlConst.xbrli: return XmlUtil.hasChild(period, XbrlConst.xbrli, periodElement) raise XPathContext.FunctionArgType(1, "xbrl:period")
def isForeverPeriod(self): try: return self._isForeverPeriod except AttributeError: self._isForeverPeriod = XmlUtil.hasChild(self.period, XbrlConst.xbrli, "forever") return self._isForeverPeriod
def is_period_type(args, periodElement): if len(args) != 1: raise XPathContext.FunctionNumArgs() if len(args[0]) != 1: raise XPathContext.FunctionArgType(1,"xbrl:period") period = args[0][0] if isinstance(period,ModelObject) and \ period.localName == "period" and period.namespaceURI == XbrlConst.xbrli: return XmlUtil.hasChild(period, XbrlConst.xbrli, periodElement) raise XPathContext.FunctionArgType(1,"xbrl:period")
def isStartEndPeriod(self): try: return self._isStartEndPeriod except AttributeError: self._isStartEndPeriod = XmlUtil.hasChild(self.period, XbrlConst.xbrli, ("startDate", "endDate")) return self._isStartEndPeriod
def validateXbrlFinally(val, *args, **kwargs): if not (val.validateFERCplugin): return _xhtmlNs = "{{{}}}".format(xhtml) _xhtmlNsLen = len(_xhtmlNs) modelXbrl = val.modelXbrl modelDocument = modelXbrl.modelDocument if not modelDocument: return # never loaded properly disclosureSystem = val.disclosureSystem _statusMsg = _("validating {0} filing rules").format( val.disclosureSystem.name) modelXbrl.profileActivity() modelXbrl.modelManager.showStatus(_statusMsg) isInlineXbrl = modelXbrl.modelDocument.type in ( ModelDocument.Type.INLINEXBRL, ModelDocument.Type.INLINEXBRLDOCUMENTSET) requiredFactLang = disclosureSystem.defaultXmlLang.lower( ) if disclosureSystem.defaultXmlLang else disclosureSystem.defaultXmlLang # inline doc set has multiple instance names to check if modelXbrl.modelDocument.type == ModelDocument.Type.INLINEXBRLDOCUMENTSET: instanceNames = [ ixDoc.basename for ixDoc in modelXbrl.modelDocument.referencesDocument.keys() if ixDoc.type == ModelDocument.Type.INLINEXBRL ] xbrlInstRoots = modelXbrl.ixdsHtmlElements else: # single instance document to check is the entry point document instanceNames = [modelXbrl.modelDocument.basename] xbrlInstRoots = [modelXbrl.modelDocument.xmlDocument.getroot()] #6.5.15 facts with xml in text blocks ValidateFilingText.validateTextBlockFacts( modelXbrl, { True: ("gif", "jpg", "jpeg", "png"), # img file extensions False: ("gif", "jpeg", "png") # mime types: jpg is not a valid mime type }) # check footnotes text if isInlineXbrl: _linkEltIter = ( linkPrototype for linkKey, links in modelXbrl.baseSets.items() for linkPrototype in links if linkPrototype.modelDocument.type in ( ModelDocument.Type.INLINEXBRL, ModelDocument.Type.INLINEXBRLDOCUMENTSET) and linkKey[1] and linkKey[2] and linkKey[3] # fully specified roles and linkKey[0] != "XBRL-footnotes") else: _linkEltIter = xbrlInstRoots[0].iterdescendants( tag="{http://www.xbrl.org/2003/linkbase}footnoteLink") for footnoteLinkElt in _linkEltIter: if isinstance(footnoteLinkElt, (ModelObject, LinkPrototype)): for child in footnoteLinkElt: if isinstance(child, (ModelObject, LocPrototype, ArcPrototype)): xlinkType = child.get("{http://www.w3.org/1999/xlink}type") if xlinkType == "resource" or isinstance( child, ModelInlineFootnote): # footnote if not isInlineXbrl: # inline content was validated before and needs continuations assembly ValidateFilingText.validateFootnote( modelXbrl, child) # same identifier in all contexts (EFM 6.5.3) entityIdentifiers = set() for xbrlInstRoot in xbrlInstRoots: # check all inline docs in ix doc set for entityIdentifierElt in xbrlInstRoot.iterdescendants( "{http://www.xbrl.org/2003/instance}identifier"): if isinstance(entityIdentifierElt, ModelObject): entityIdentifiers.add("{}#{}".format( entityIdentifierElt.get("scheme"), XmlUtil.text(entityIdentifierElt))) if len(entityIdentifiers) > 1: modelXbrl.error( "FERC.6.05.03", _("There are more than one entity identifiers: %(entityIdentifiers)s." ), modelObject=modelXbrl, entityIdentifiers=", ".join(sorted(entityIdentifiers))) for ei in sorted(entityIdentifiers): scheme, _sep, identifier = ei.rpartition("#") if not disclosureSystem.identifierSchemePattern.match( scheme) or not disclosureSystem.identifierValuePattern.match( identifier): modelXbrl.error( "FERC.6.05.01", _("Entity identifier %(identifier)s, or scheme %(scheme)s does not adhere " "to the standard naming convention of <identifier scheme='http://www.ferc.gov/CID'>Cnnnnnn</identifier>'. " ), modelObject=modelXbrl, scheme=scheme, identifier=identifier) #6.5.4 scenario segContexts = set() uniqueContextHashes = {} contextIDs = set() precisionFacts = set() formType = None formEntrySchema = None factsForLang = {} keysNotDefaultLang = {} allFormEntryXsd = () for c in modelXbrl.contexts.values(): if XmlUtil.hasChild(c, xbrli, "segment"): segContexts.add(c) h = c.contextDimAwareHash if h in uniqueContextHashes: if c.isEqualTo(uniqueContextHashes[h]): modelXbrl.error( "FERC.6.05.07", _("The instance document contained more than one context equivalent to %(context)s (%(context2)s). " "Please remove duplicate contexts from the instance."), modelObject=(c, uniqueContextHashes[h]), context=c.id, context2=uniqueContextHashes[h].id) else: uniqueContextHashes[h] = c contextIDs.add(c.id) if segContexts: modelXbrl.error( "FERC.6.05.04", _("There must be no contexts with segment, but %(count)s was(were) found: %(context)s." ), modelObject=segContexts, count=len(segContexts), context=", ".join(sorted(c.id for c in segContexts))) # unused contexts for f in modelXbrl.facts: factContextID = f.contextID contextIDs.discard(factContextID) if f.isNumeric: if f.precision is not None: precisionFacts.add(f) elif not f.isNil: langTestKey = "{0},{1}".format(f.qname, f.contextID) factsForLang.setdefault(langTestKey, []).append(f) lang = f.xmlLang if lang and lang.lower( ) != requiredFactLang: # not lang.startswith(factLangStartsWith): keysNotDefaultLang[langTestKey] = f if getattr(f, "xValid", 0) >= VALID: if f.qname.localName == "FormType": formType = f.xValue formNum = re.sub("([0-9]+).*", r"\1", formType) formLtr = re.match("[^A-Z]*([A-Z]?)", formType).group(1) txDate = re.sub( "http://ferc.gov/form/([0-9]{4}-[0-9]{2}-[0-9]{2})/ferc", r"\1", f.qname.namespaceURI) formEntryXsd = "https://eCollection.ferc.gov/taxonomy/form{}/{}/form/form{}{}/form-{}{}_{}.xsd".format( formNum, txDate, formNum, formLtr, formNum, formLtr, txDate) formEntryXsdUAT = formEntryXsd.replace("eCollection", "uat.eforms") formEntryXsdTest = formEntryXsd.replace( "eCollection", "test.eforms") formEntryXsdDev = formEntryXsd.replace("eCollection", "dev.eforms") confFormEntryXsd = "https://eCollection.ferc.gov/taxonomy/form{}/{}/ferc-core-footnote-roles_{}.xsd".format( formNum, txDate, txDate) confFormEntryXsdUAT = confFormEntryXsd.replace( "eCollection", "uat.eforms") confFormEntryXsdTest = confFormEntryXsd.replace( "eCollection", "test.eforms") confFormEntryXsdDev = confFormEntryXsd.replace( "eCollection", "dev.eforms") allFormEntryXsd = [ formEntryXsd, formEntryXsdUAT, formEntryXsdTest, formEntryXsdDev, confFormEntryXsd, confFormEntryXsdUAT, confFormEntryXsdTest, confFormEntryXsdDev ] unexpectedXsds = set(doc.modelDocument.uri for doc, referencingDoc in modelXbrl.modelDocument.referencesDocument.items() if "href" in referencingDoc.referenceTypes if doc.modelDocument.uri not in allFormEntryXsd) if unexpectedXsds: modelXbrl.error( "FERC.22.00", _("The instance document contained unexpected schema references %(schemaReferences)s." ), modelXbrl=modelXbrl, schemaReferences=", ".join(sorted(unexpectedXsds))) if contextIDs: # check if contextID is on any undefined facts for undefinedFact in modelXbrl.undefinedFacts: contextIDs.discard(undefinedFact.get("contextRef")) if contextIDs: modelXbrl.error( "FERC.6.05.08", _("The instance document contained context(s) %(contextIDs)s that was(were) not used in any fact." ), modelXbrl=modelXbrl, contextIDs=", ".join(str(c) for c in contextIDs)) if precisionFacts: modelXbrl.error( "FERC.6.05.17", _("The instance document contains elements using the precision attribute." ), modelObject=precisionFacts) #6.5.14 facts without english text for keyNotDefaultLang, factNotDefaultLang in keysNotDefaultLang.items(): anyDefaultLangFact = False for fact in factsForLang[keyNotDefaultLang]: if fact.xmlLang.lower( ) == requiredFactLang: #.startswith(factLangStartsWith): anyDefaultLangFact = True break if not anyDefaultLangFact: val.modelXbrl.error( "FERC.6.05.14", _("Element %(fact)s in context %(contextID)s has text with xml:lang other than '%(lang2)s' (%(lang)s) without matching English text. " ), modelObject=factNotDefaultLang, fact=factNotDefaultLang.qname, contextID=factNotDefaultLang.contextID, lang=factNotDefaultLang.xmlLang, lang2=disclosureSystem.defaultXmlLang ) # report lexical format default lang modelXbrl.profileActivity(_statusMsg, minTimeToShow=0.0) modelXbrl.modelManager.showStatus(None)
def isDivide(self): return XmlUtil.hasChild(self, XbrlConst.xbrli, "divide")
def validateXbrlFinally(val, *args, **kwargs): if not (val.validateESMAplugin): return _xhtmlNs = "{{{}}}".format(xhtml) _xhtmlNsLen = len(_xhtmlNs) modelXbrl = val.modelXbrl modelDocument = modelXbrl.modelDocument _statusMsg = _("validating {0} filing rules").format( val.disclosureSystem.name) modelXbrl.profileActivity() modelXbrl.modelManager.showStatus(_statusMsg) if modelDocument.type == ModelDocument.Type.INSTANCE: modelXbrl.error("esma:instanceShallBeInlineXBRL", _("RTS on ESEF requires inline XBRL instances."), modelObject=modelXbrl) checkFilingDimensions( val) # sets up val.primaryItems and val.domainMembers val.hasExtensionSchema = val.hasExtensionPre = val.hasExtensionCal = val.hasExtensionDef = val.hasExtensionLbl = False checkFilingDTS(val, modelXbrl.modelDocument, []) modelXbrl.profileActivity("... filer DTS checks", minTimeToShow=1.0) if not (val.hasExtensionSchema and val.hasExtensionPre and val.hasExtensionCal and val.hasExtensionDef and val.hasExtensionLbl): missingFiles = [] if not val.hasExtensionSchema: missingFiles.append("schema file") if not val.hasExtensionPre: missingFiles.append("presentation linkbase") if not val.hasExtensionCal: missingFiles.append("calculation linkbase") if not val.hasExtensionDef: missingFiles.append("definition linkbase") if not val.hasExtensionLbl: missingFiles.append("label linkbase") modelXbrl.warning( "esma:3.1.1.extensionTaxonomyWrongFilesStructure", _("Extension taxonomies MUST consist of at least a schema file and presentation, calculation, definition and label linkbases" ": missing %(missingFiles)s"), modelObject=modelXbrl, missingFiles=", ".join(missingFiles)) if modelDocument.type in (ModelDocument.Type.INLINEXBRL, ModelDocument.Type.INSTANCE): footnotesRelationshipSet = modelXbrl.relationshipSet("XBRL-footnotes") orphanedFootnotes = set() noLangFootnotes = set() foonoteRoleErrors = set() transformRegistryErrors = set() def checkFootnote(elt, text): if text: # non-empty footnote must be linked to a fact if not empty if not any( isinstance(rel.fromModelObject, ModelFact) for rel in footnotesRelationshipSet.toModelObject(elt)): orphanedFootnotes.add(elt) if not elt.xmlLang: noLangFootnotes.add(elt) if elt.role != XbrlConst.footnote or not all( rel.arcrole == XbrlConst.factFootnote and rel.linkrole == XbrlConst.defaultLinkRole for rel in footnotesRelationshipSet.toModelObject(elt)): footnoteRoleErrors.add(elt) if modelDocument.type == ModelDocument.Type.INLINEXBRL: _baseName, _baseExt = os.path.splitext(modelDocument.basename) if _baseExt not in (".xhtml", ): modelXbrl.warning( "esma:TBD.fileNameExtension", _("FileName should have the extension .xhtml: %(fileName)s" ), modelObject=modelXbrl, fileName=modelDocument.basename) ixNStag = modelXbrl.modelDocument.ixNStag ixTags = set(ixNStag + ln for ln in ("nonNumeric", "nonFraction", "references", "relationship")) ixTextTags = set(ixNStag + ln for ln in ("nonFraction", "continuation", "footnote")) ixExcludeTag = ixNStag + "exclude" ixTupleTag = ixNStag + "tuple" ixFractionTag = ixNStag + "fraction" rootElt = modelDocument.xmlRootElement for elt in rootElt.iter(): eltTag = elt.tag if isinstance(elt, ModelObject) and elt.namespaceURI == xhtml: eltTag = elt.localName elif isinstance( elt, (_ElementTree, _Comment, _ProcessingInstruction)): continue # comment or other non-parsed element else: eltTag = elt.tag if eltTag.startswith(_xhtmlNs): eltTag = eltTag[_xhtmlNsLen:] if ((eltTag in ("object", "script")) or (eltTag == "a" and "javascript:" in elt.get("href", "")) or (eltTag == "img" and "javascript:" in elt.get("src", ""))): modelXbrl.error( "esma.2.5.1.executableCodePresent", _("Inline XBRL documents MUST NOT contain executable code: %(element)s" ), modelObject=elt, element=eltTag) elif eltTag == "img": src = elt.get("src", "").strip() hasParentIxTextTag = False # check if image is in an ix text-bearing element _ancestorElt = elt while (_ancestorElt is not None): if _ancestorElt.tag == ixExcludeTag: # excluded from any parent text-bearing ix element break if _ancestorElt.tag in ixTextTags: hasParentIxTextTag = True break _ancestorElt = _ancestorElt.getparent() if scheme(href) in ("http", "https", "ftp"): modelXbrl.error( "esma.3.5.1.inlinXbrlContainsExternalReferences", _("Inline XBRL instance documents MUST NOT contain any reference pointing to resources outside the reporting package: %(element)s" ), modelObject=elt, element=eltTag) if not src.startswith("data:image"): if hasParentIxTextTag: modelXbrl.error( "esma.2.5.1.embeddedImageNotUsingBase64Encoding", _("Images MUST be included in the XHTML document as a base64 encoded string unless their size exceeds support of browsers." ), modelObject=elt) else: # presume it to be an image file, check image contents try: base = elt.modelDocument.baseForElement( elt) normalizedUri = elt.modelXbrl.modelManager.cntlr.webCache.normalizeUrl( graphicFile, base) if not elt.modelXbrl.fileSource.isInArchive( normalizedUri): normalizedUri = elt.modelXbrl.modelManager.cntlr.webCache.getfilename( normalizedUri) imglen = 0 with elt.modelXbrl.fileSource.file( normalizedUri, binary=True)[0] as fh: imglen += len(fh.read()) if imglen < browserMaxBase64ImageLength: modelXbrl.error( "esma.2.5.1.embeddedImageNotUsingBase64Encoding", _("Images MUST be included in the XHTML document as a base64 encoded string unless their size exceeds support of browsers." ), modelObject=elt) except IOError as err: modelXbrl.error( "esma.2.5.1.imageFileCannotBeLoaded", _("Image file which isn't openable '%(src)s', error: %(error)s" ), modelObject=elt, src=src, error=err) elif not any( src.startswith(m) for m in allowedImgMimeTypes): modelXbrl.error( "esma.2.5.1.embeddedImageNotUsingBase64Encoding", _("Images MUST be included in the XHTML document as a base64 encoded string, encoding disallowed: %(src)s." ), modelObject=elt, src=attrValue[:128]) elif eltTag == "a": href = elt.get("href", "").strip() if scheme(href) in ("http", "https", "ftp"): modelXbrl.error( "esma.3.5.1.inlinXbrlContainsExternalReferences", _("Inline XBRL instance documents MUST NOT contain any reference pointing to resources outside the reporting package: %(element)s" ), modelObject=elt, element=eltTag) if eltTag in ixTags and elt.get("target"): modelXbrl.error( "esma.2.5.3.targetAttributeUsed", _("Target attribute MUST not be used: element %(localName)s, target attribute %(target)s." ), modelObject=elt, localName=elt.elementQname, target=elt.get("target")) if eltTag == ixTupleTag: modelXbrl.error( "esma.2.5.3.tupleElementUsed", _("The ix:tuple element MUST not be used."), modelObject=elt) if eltTag == ixFractionTag: modelXbrl.error( "esma.2.5.3.fractionElementUsed", _("The ix:fraction element MUST not be used."), modelObject=elt) if elt.get("{http://www.w3.org/XML/1998/namespace}base" ) is not None: modelXbrl.error( "esma.2.4.1.xmlBaseUsed", _("xml:base attributes MUST NOT be used in the Inline XBRL document: element %(localName)s, base attribute %(base)s." ), modelObject=elt, localName=elt.elementQname, base=elt.get( "{http://www.w3.org/XML/1998/namespace}base")) if isinstance(elt, ModelInlineFootnote): checkFootnote(elt, elt.value) elif isinstance(elt, ModelResource ) and elt.qname == XbrlConst.qnLinkFootnote: checkFootnote(elt, elt.value) elif isinstance(elt, ModelInlineFact): if elt.format is not None and elt.format.namespaceURI != 'http://www.xbrl.org/inlineXBRL/transformation/2015-02-26': transformRegistryErrors.add(elt) elif modelDocument.type == ModelDocument.Type.INSTANCE: for elt in modelDocument.xmlRootElement.iter(): if elt.qname == XbrlConst.qnLinkFootnote: # for now assume no private elements extend link:footnote checkFootnote(elt, elt.stringValue) contextsWithDisallowedOCEs = [] contextsWithDisallowedOCEcontent = [] contextsWithPeriodTime = [] contextsWithPeriodTimeZone = [] contextIdentifiers = defaultdict(list) nonStandardTypedDimensions = defaultdict(set) for context in modelXbrl.contexts.values(): if XmlUtil.hasChild(context, XbrlConst.xbrli, "segment"): contextsWithDisallowedOCEs.append(context) for segScenElt in context.iterdescendants( "{http://www.xbrl.org/2003/instance}scenario"): if isinstance(segScenElt, ModelObject): if any(True for child in segScenElt.iterchildren() if isinstance(child, ModelObject) and child.tag not in ( "{http://xbrl.org/2006/xbrldi}explicitMember", "{http://xbrl.org/2006/xbrldi}typedMember")): contextsWithDisallowedOCEcontent.append(context) # check periods here contextIdentifiers[context.entityIdentifier].append(context) if contextsWithDisallowedOCEs: modelXbrl.error( "esma.2.1.3.segmentUsed", _("xbrli:segment container MUST NOT be used in contexts: %(contextIds)s" ), modelObject=contextsWithDisallowedOCEs, contextIds=", ".join(c.id for c in contextsWithDisallowedOCEs)) if contextsWithDisallowedOCEcontent: modelXbrl.error( "esma.2.1.3.scenarioContainsNonDimensionalContent", _("xbrli:scenario in contexts MUST NOT contain any other content than defined in XBRL Dimensions specification: %(contextIds)s" ), modelObject=contextsWithDisallowedOCEcontent, contextIds=", ".join( c.id for c in contextsWithDisallowedOCEcontent)) if len(contextIdentifiers) > 1: modelXbrl.error( "esma.2.1.4.multipleIdentifiers", _("All entity identifiers in contexts MUST have identical content: %(contextIdentifiers)s" ), modelObject=modelXbrl, contextIds=", ".join(i[1] for i in contextIdentifiers)) for (contextScheme, contextIdentifier), contextElts in contextIdentifiers.items(): if contextScheme != "http://standards.iso.org/iso/17442": modelXbrl.warning( "esma.2.1.1.nonLEIContextScheme", _("The scheme attribute of the xbrli:identifier element should have \"http://standards.iso.org/iso/17442\" as its content: %(scheme)s" ), modelObject=contextElts, scheme=contextScheme) else: leiValidity = LeiUtil.checkLei(contextIdentifier) if leiValidity == LeiUtil.LEI_INVALID_LEXICAL: modelXbrl.warning( "esma.2.1.1.invalidIdentifierFormat", _("The LEI context idenntifier has an invalid format: %(identifier)s" ), modelObject=contextElts, identifier=contextIdentifier) elif leiValidity == LeiUtil.LEI_INVALID_CHECKSUM: modelXbrl.warning( "esma.2.1.1.invalidIdentifier", _("The LEI context idenntifier has checksum error: %(identifier)s" ), modelObject=contextElts, identifier=contextIdentifier) if contextsWithPeriodTime: modelXbrl.warning( "esma.2.1.2.periodWithTimeContent", _("Context period startDate, endDate and instant elements should be in whole days without time: %(contextIds)s" ), modelObject=contextsWithPeriodTime, contextIds=", ".join(c.id for c in contextsWithPeriodTime)) if contextsWithPeriodTimeZone: modelXbrl.warning( "esma.2.1.2.periodWithTimeZone", _("Context period startDate, endDate and instant elements should be in whole days without a timezone: %(contextIds)s" ), modelObject=contextsWithPeriodTimeZone, contextIds=", ".join(c.id for c in contextsWithPeriodTimeZone)) reportedMandatory = set() footnotesRelationshipSet = modelXbrl.relationshipSet( XbrlConst.factFootnote, XbrlConst.defaultLinkRole) for qn, facts in modelXbrl.factsByQname.items(): if qn in mandatory: reportedMandatory.add(qn) missingElements = (mandatory - reportedMandatory) if missingElements: modelXbrl.error( "esma:???.missingRequiredElements", _("Required elements missing from document: %(elements)s."), modelObject=modelXbrl, elements=", ".join(sorted(str(qn) for qn in missingElements))) if transformRegistryErrors: modelXbrl.info( "esma:???.transformRegistry", _("Transformation Registry 3 should be used for facts: %(elements)s." ), modelObject=transformRegistryErrors, elements=", ".join( sorted( str(fact.qname) for fact in transformRegistryErrors))) if orphanedFootnotes: modelXbrl.error( "esma.2.3.1.unusedFootnote", _("Non-empty footnotes must be connected to fact(s)."), modelObject=orphanedFootnotes) if noLangFootnotes: modelXbrl.error( "esma.2.3.2.undefinedLanguageForFootnote", _("FEach footnote MUST have the 'xml:lang' attribute whose value corresponds to the language of the text in the content of the respective footnote." ), modelObject=noLangFootnotes) if foonoteRoleErrors: modelXbrl.error( "esma.2.3.2.nonStandardRoleForFootnote", _("The xlink:role attribute of a link:footnote and link:loc element as well as xlink:arcrole attribute of a link:footnoteArc MUST be defined in the XBRL Specification 2.1." ), modelObject=foonoteRoleErrors) modelXbrl.profileActivity(_statusMsg, minTimeToShow=0.0) modelXbrl.modelManager.showStatus(None)
def validateXbrlFinally(val, *args, **kwargs): if not (val.validateHMRCplugin) or not val.txmyType: return modelXbrl = val.modelXbrl modelDocument = modelXbrl.modelDocument _statusMsg = _("validating {0} filing rules").format(val.disclosureSystem.name) modelXbrl.profileActivity() modelXbrl.modelManager.showStatus(_statusMsg) if modelDocument.type in (ModelDocument.Type.INSTANCE, ModelDocument.Type.INLINEXBRL): labelHasNegativeTermPattern = re.compile(r".*[(].*\w.*[)].*") companyReferenceNumberContexts = defaultdict(list) for c1 in modelXbrl.contexts.values(): scheme, identifier = c1.entityIdentifier if scheme == "http://www.companieshouse.gov.uk/": companyReferenceNumberContexts[identifier].append(c1.id) uniqueFacts = {} # key = (qname, context hash, unit hash, lang) mandatoryFacts = {} mandatoryGDV = defaultdict(set) factForConceptContextUnitLangHash = defaultdict(list) hasCompaniesHouseContext = any(cntx.entityIdentifier[0] == "http://www.companieshouse.gov.uk/" for cntx in val.modelXbrl.contexts.values()) contextsUsed = set(f.context for f in modelXbrl.factsInInstance if f.context is not None) contextsWithScenario = [] for cntx in contextsUsed: for dim in cntx.qnameDims.values(): if dim.isExplicit: _memName = dim.memberQname.localName m = memNameNumPattern.match(_memName) if m: l = m.group(1) n = int(m.group(2)) else: l = _memName n = None for _gdvType in (val.txmyType, "business"): gdv = genericDimensionValidation.get(_gdvType,EMPTYDICT).get(l) if gdv: # take first match break if (gdv and (n is None or (isinstance(gdv[0],int) and isinstance(gdv[1],int) and n >= gdv[0] and n <= gdv[1]))): gdvFacts = [f for f in gdv if isinstance(f,str)] if len(gdvFacts) == 1: mandatoryGDV[gdvFacts[0]].add(GDV(gdvFacts[0], None, _memName)) elif len(gdvFacts) == 2: mandatoryGDV[gdvFacts[0]].add(GDV(gdvFacts[0], gdvFacts[1], _memName)) mandatoryGDV[gdvFacts[1]].add(GDV(gdvFacts[1], gdvFacts[0], _memName)) if XmlUtil.hasChild(cntx, xbrli, "scenario"): contextsWithScenario.append(cntx) if contextsWithScenario: modelXbrl.error("FRC.TG.3.6.1", _("Context(s) %(identifiers)s is reported with scenario element."), modelObject=contextsWithScenario, identifiers=", ".join(c.id for c in contextsWithScenario)) del contextsWithScenario def checkFacts(facts): for f in facts: cntx = f.context unit = f.unit if getattr(f,"xValid", 0) >= 4 and cntx is not None and f.concept is not None: factNamespaceURI = f.qname.namespaceURI factLocalName = f.qname.localName if factLocalName in mandatoryItems[val.txmyType]: mandatoryFacts[factLocalName] = f if factLocalName == "UKCompaniesHouseRegisteredNumber" and val.isAccounts: if hasCompaniesHouseContext: mandatoryFacts[factLocalName] = f for _cntx in contextsUsed: _scheme, _identifier = _cntx.entityIdentifier if _scheme == "http://www.companieshouse.gov.uk/" and f.xValue != _identifier: modelXbrl.error("JFCVC.3316", _("Context entity identifier %(identifier)s does not match Company Reference Number (UKCompaniesHouseRegisteredNumber) Location: Accounts (context id %(id)s)"), modelObject=(f, _cntx), identifier=_identifier, id=_cntx.id) if not f.isNil: factForConceptContextUnitLangHash[f.conceptContextUnitLangHash].append(f) if f.isNumeric: if f.precision: modelXbrl.error("HMRC.5.4", _("Numeric fact %(fact)s of context %(contextID)s has a precision attribute '%(precision)s'"), modelObject=f, fact=f.qname, contextID=f.contextID, precision=f.precision) try: # only process validated facts if not f.isNil: if f.xValue < 0: label = f.concept.label(lang="en") if not labelHasNegativeTermPattern.match(label): modelXbrl.error("HMRC.5.3", _("Numeric fact %(fact)s of context %(contextID)s has a negative value '%(value)s' but label does not have a bracketed negative term (using parentheses): %(label)s"), modelObject=f, fact=f.qname, contextID=f.contextID, value=f.value, label=label) # 6.5.37 test (insignificant digits due to rounding) if f.decimals and f.decimals != "INF": try: insignificance = insignificantDigits(f.xValue, decimals=f.decimals) if insignificance: # if not None, returns (truncatedDigits, insiginficantDigits) modelXbrl.error("HMRC.SG.4.5", _("Fact %(fact)s of context %(contextID)s decimals %(decimals)s value %(value)s has nonzero digits in insignificant portion %(insignificantDigits)s."), modelObject=f, fact=f.qname, contextID=f.contextID, decimals=f.decimals, value=f.xValue, truncatedDigits=insignificance[0], insignificantDigits=insignificance[1]) except (ValueError,TypeError): modelXbrl.error("HMRC.SG.4.5", _("Fact %(fact)s of context %(contextID)s decimals %(decimals)s value %(value)s causes Value Error exception."), modelObject=f1, fact=f1.qname, contextID=f1.contextID, decimals=f1.decimals, value=f1.value) except AttributeError: pass # if not validated it should have failed with a schema error # check GDV if f.qname.localName in mandatoryGDV: _gdvReqList = mandatoryGDV[factLocalName] _gdvReqRemovals = [] for _gdvReq in _gdvReqList: if any(_gdvReq.memLocalName == dim.memberQname.localName for dim in cntx.qnameDims.values() if dim.isExplicit): _gdvReqRemovals.append(_gdvReq) if _gdvReq.altFact in mandatoryGDV: _gdvAltList = mandatoryGDV[_gdvReq.altFact] _gdvAltRemovals = [] for _gdvAlt in _gdvAltList: if any(_gdvAlt.memLocalName == dim.memberQname.localName for dim in cntx.qnameDims.values() if dim.isExplicit): _gdvAltRemovals.append(_gdvAlt) for _gdvAlt in _gdvAltRemovals: _gdvAltList.remove(_gdvAlt) if _gdvReqRemovals and not f.xValue: # fact was a mandatory name or description modelXbrl.error("JFCVC.3315", _("Generic dimension members associated name/description has no text: %(fact)s"), modelObject=f, fact=f.qname) for _gdvReq in _gdvReqRemovals: _gdvReqList.remove(_gdvReq) if f.modelTupleFacts: checkFacts(f.modelTupleFacts) checkFacts(modelXbrl.facts) if val.isAccounts: _missingItems = mandatoryItems[val.txmyType] - mandatoryFacts.keys() if hasCompaniesHouseContext and "UKCompaniesHouseRegisteredNumber" not in mandatoryFacts: _missingItems.add("UKCompaniesHouseRegisteredNumber") if _missingItems: modelXbrl.error("JFCVC.3312", _("Mandatory facts missing: %(missingItems)s"), modelObject=modelXbrl, missingItems=", ".join(_missingItems)) f = mandatoryFacts.get("StartDateForPeriodCoveredByReport") if f is not None and (f.isNil or f.xValue < _6_APR_2008): modelXbrl.error("JFCVC.3313", _("Period Start Date (StartDateForPeriodCoveredByReport) must be 6 April 2008 or later, but is %(value)s"), modelObject=f, value=f.value) memLocalNamesMissing = set("{}({})".format(_gdvRec.memLocalName, _gdvRec.factNames) for _gdv in mandatoryGDV.values() for _gdvRec in _gdv) if memLocalNamesMissing: modelXbrl.error("JFCVC.3315", _("Generic dimension members have no associated name or description item, member names (name or description item): %(memberNames)s"), modelObject=modelXbrl, memberNames=", ".join(sorted(memLocalNamesMissing))) aspectEqualFacts = defaultdict(list) for hashEquivalentFacts in factForConceptContextUnitLangHash.values(): if len(hashEquivalentFacts) > 1: for f in hashEquivalentFacts: aspectEqualFacts[(f.qname,f.contextID,f.unitID,f.xmlLang)].append(f) for fList in aspectEqualFacts.values(): f0 = fList[0] if any(not f.isVEqualTo(f0) for f in fList[1:]): modelXbrl.error("JFCVC.3314", "Inconsistent duplicate fact values %(fact)s: %(values)s.", modelObject=fList, fact=f0.qname, contextID=f0.contextID, values=", ".join(f.value for f in fList)) aspectEqualFacts.clear() del factForConceptContextUnitLangHash, aspectEqualFacts if modelXbrl.modelDocument.type == ModelDocument.Type.INLINEXBRL: rootElt = modelXbrl.modelDocument.xmlRootElement if rootElt.tag in ("html", "xhtml") or not rootElt.tag.startswith("{http://www.w3.org/1999/xhtml}"): modelXbrl.error("HMRC.SG.3.3", _("InlineXBRL root element <%(element)s> MUST be html and have the xhtml namespace."), modelObject=rootElt, element=rootElt.tag) for elt in rootElt.iterdescendants(tag="{http://www.w3.org/1999/xhtml}script"): modelXbrl.error("HMRC.SG.3.3", _("Script element is disallowed."), modelObject=elt) for tag, localName, attr in (("{http://www.w3.org/1999/xhtml}a", "a", "href"), ("{http://www.w3.org/1999/xhtml}img", "img", "src")): for elt in rootElt.iterdescendants(tag=tag): attrValue = (elt.get(attr) or "").strip() if "javascript:" in attrValue: modelXbrl.error("HMRC.SG.3.3", _("Element %(localName)s javascript %(javascript)s is disallowed."), modelObject=elt, localName=localName, javascript=attrValue[:64]) if localName == "img" and not any(attrValue.startswith(m) for m in allowedImgMimeTypes): modelXbrl.error("HMRC.SG.3.8", _("Image scope must be base-64 encoded string (starting with data:image/*;base64), *=gif, jpeg or png. src disallowed: %(src)s."), modelObject=elt, src=attrValue[:128]) for elt in rootElt.iterdescendants(tag="{http://www.w3.org/1999/xhtml}style"): if elt.text and styleImgUrlPattern.match(elt.text): modelXbrl.error("HMRC.SG.3.8", _("Style element has disallowed image reference: %(styleImage)s."), modelObject=elt, styleImage=styleImgUrlPattern.match(elt.text).group(1)) for elt in rootElt.xpath("//xhtml:*[@style]", namespaces={"xhtml": "http://www.w3.org/1999/xhtml"}): for match in styleImgUrlPattern.findall(elt.get("style")): modelXbrl.error("HMRC.SG.3.8", _("Element %(elt)s style attribute has disallowed image reference: %(styleImage)s."), modelObject=elt, elt=elt.tag.rpartition("}")[2], styleImage=match) modelXbrl.profileActivity(_statusMsg, minTimeToShow=0.0) modelXbrl.modelManager.showStatus(None)
def hasDrsNetwork(self): return XmlUtil.hasChild(self, XbrlConst.verdim, "drsNetwork")
def hasScenario(self): return XmlUtil.hasChild(self, XbrlConst.xbrli, "scenario")
def hasSegment(self): return XmlUtil.hasChild(self.entity, XbrlConst.xbrli, "segment")
def isStartEndPeriod(self): try: return self._isStartEndPeriod except AttributeError: self._isStartEndPeriod = XmlUtil.hasChild(self.period, XbrlConst.xbrli, ("startDate","endDate")) return self._isStartEndPeriod
def validateFacts(val, factsToCheck): # may be called in streaming batches or all at end (final) if not streaming modelXbrl = val.modelXbrl modelDocument = modelXbrl.modelDocument # note EBA 2.1 is in ModelDocument.py timelessDatePattern = re.compile( r"\s*([0-9]{4})-([0-9]{2})-([0-9]{2})\s*$") for cntx in modelXbrl.contexts.values(): if getattr(cntx, "_batchChecked", False): continue # prior streaming batch already checked cntx._batchChecked = True val.cntxEntities.add(cntx.entityIdentifier) dateElts = XmlUtil.descendants(cntx, XbrlConst.xbrli, ("startDate", "endDate", "instant")) if any(not timelessDatePattern.match(e.textValue) for e in dateElts): modelXbrl.error( "EBA.2.10", _('Period dates must be whole dates without time or timezone: %(dates)s.' ), modelObject=cntx, dates=", ".join(e.text for e in dateElts)) if cntx.isForeverPeriod: modelXbrl.error("EBA.2.11", _('Forever context period is not allowed.'), modelObject=cntx) elif cntx.isStartEndPeriod: modelXbrl.error( "EBA.2.13", _('Start-End (flow) context period is not allowed.'), modelObject=cntx) elif cntx.isInstantPeriod: # cannot pass context object to final() below, for error logging, if streaming mode val.cntxDates[cntx.instantDatetime].add(modelXbrl if getattr( val.modelXbrl, "isStreamingMode", False) else cntx) if XmlUtil.hasChild(cntx, XbrlConst.xbrli, "segment"): modelXbrl.error( "EBA.2.14", _("The segment element not allowed in context Id: %(context)s" ), modelObject=cntx, context=cntx.contextID) for scenElt in XmlUtil.descendants(cntx, XbrlConst.xbrli, "scenario"): childTags = ", ".join([ child.prefixedName for child in scenElt.iterchildren() if isinstance(child, ModelObject) and child.tag != "{http://xbrl.org/2006/xbrldi}explicitMember" and child.tag != "{http://xbrl.org/2006/xbrldi}typedMember" ]) if len(childTags) > 0: modelXbrl.error( "EBA.2.15", _("Scenario of context Id %(context)s has disallowed content: %(content)s" ), modelObject=cntx, context=cntx.id, content=childTags) val.unusedCntxIDs.add(cntx.id) for unit in modelXbrl.units.values(): if getattr(unit, "_batchChecked", False): continue # prior streaming batch already checked unit._batchChecked = True val.unusedUnitIDs.add(unit.id) factsByQname = defaultdict(set) # top level for this for f in factsToCheck: factsByQname[f.qname].add(f) val.unusedCntxIDs.discard(f.contextID) val.unusedUnitIDs.discard(f.unitID) for fIndicators in factsByQname[qnFIndicators]: val.numFilingIndicatorTuples += 1 for fIndicator in fIndicators.modelTupleFacts: _value = (fIndicator.xValue or fIndicator.value ) # use validated xValue if DTS else value for skipDTS if _value in val.filingIndicators: modelXbrl.error( "EBA.1.6.1", _('Multiple filing indicators facts for indicator %(filingIndicator)s.' ), modelObject=(fIndicator, val.filingIndicators[_value]), filingIndicator=_value) val.filingIndicators[_value] = fIndicator val.unusedCntxIDs.discard(fIndicator.contextID) otherFacts = {} # (contextHash, unitHash, xmlLangHash) : fact nilFacts = [] # removed in current draft: stringFactsWithoutXmlLang = [] nonMonetaryNonPureFacts = [] for qname, facts in factsByQname.items(): for f in facts: if modelXbrl.skipDTS: c = f.qname.localName[0] isNumeric = c in ('m', 'p', 'i') isMonetary = c == 'm' isInteger = c == 'i' isPercent = c == 'p' isString = c == 's' else: concept = f.concept if concept is not None: isNumeric = concept.isNumeric isMonetary = concept.isMonetary isInteger = concept.baseXbrliType in integerItemTypes isPercent = concept.typeQname == qnPercentItemType isString = concept.baseXbrliType in ( "stringItemType", "normalizedStringItemType") else: isNumeric = isString = False # error situation k = (f.getparent().objectIndex, f.qname, f.context.contextDimAwareHash if f.context is not None else None, f.unit.hash if f.unit is not None else None, hash(f.xmlLang)) if f.qname == qnFIndicators and val.validateEIOPA: pass elif k not in otherFacts: otherFacts[k] = {f} else: matches = [ o for o in otherFacts[k] if (f.getparent().objectIndex == o.getparent().objectIndex and f.qname == o.qname and f.context. isEqualTo(o.context) if f.context is not None and o.context is not None else True) and # (f.unit.isEqualTo(o.unit) if f.unit is not None and o.unit is not None else True) and (f.xmlLang == o.xmlLang) ] if matches: contexts = [f.contextID] + [o.contextID for o in matches] modelXbrl.error( "EBA.2.16", _('Facts are duplicates %(fact)s contexts %(contexts)s.' ), modelObject=[f] + matches, fact=f.qname, contexts=', '.join(contexts)) else: otherFacts[k].add(f) if isNumeric: if f.precision: modelXbrl.error( "EBA.2.17", _("Numeric fact %(fact)s of context %(contextID)s has a precision attribute '%(precision)s'" ), modelObject=f, fact=f.qname, contextID=f.contextID, precision=f.precision) if f.decimals and f.decimals != "INF": try: dec = int(f.decimals) if isMonetary: if dec < -3: modelXbrl.error( "EBA.2.17", _("Monetary fact %(fact)s of context %(contextID)s has a decimal attribute < -3: '%(decimals)s'" ), modelObject=f, fact=f.qname, contextID=f.contextID, decimals=f.decimals) elif isInteger: if dec != 0: modelXbrl.error( "EBA.2.17", _("Integer fact %(fact)s of context %(contextID)s has a decimal attribute \u2260 0: '%(decimals)s'" ), modelObject=f, fact=f.qname, contextID=f.contextID, decimals=f.decimals) elif isPercent: if dec < 4: modelXbrl.error( "EBA.2.17", _("Percent fact %(fact)s of context %(contextID)s has a decimal attribute < 4: '%(decimals)s'" ), modelObject=f, fact=f.qname, contextID=f.contextID, decimals=f.decimals) except ValueError: pass # should have been reported as a schema error by loader '''' (not intended by EBA 2.18, paste here is from EFM) if not f.isNil and getattr(f,"xValid", 0) == 4: try: insignificance = insignificantDigits(f.xValue, decimals=f.decimals) if insignificance: # if not None, returns (truncatedDigits, insiginficantDigits) modelXbrl.error(("EFM.6.05.37", "GFM.1.02.26"), _("Fact %(fact)s of context %(contextID)s decimals %(decimals)s value %(value)s has nonzero digits in insignificant portion %(insignificantDigits)s."), modelObject=f1, fact=f1.qname, contextID=f1.contextID, decimals=f1.decimals, value=f1.xValue, truncatedDigits=insignificance[0], insignificantDigits=insignificance[1]) except (ValueError,TypeError): modelXbrl.error(("EBA.2.18"), _("Fact %(fact)s of context %(contextID)s decimals %(decimals)s value %(value)s causes Value Error exception."), modelObject=f1, fact=f1.qname, contextID=f1.contextID, decimals=f1.decimals, value=f1.value) ''' unit = f.unit if unit is not None: if isMonetary: if unit.measures[0]: val.currenciesUsed[unit.measures[0][0]] = unit elif not unit.isSingleMeasure or unit.measures[0][ 0] != XbrlConst.qnXbrliPure: nonMonetaryNonPureFacts.append(f) ''' removed in current draft elif isString: if not f.xmlLang: stringFactsWithoutXmlLang.append(f) ''' if f.isNil: nilFacts.append(f) if nilFacts: modelXbrl.error( "EBA.2.19", _('Nil facts MUST NOT be present in the instance: %(nilFacts)s.'), modelObject=nilFacts, nilFacts=", ".join(str(f.qname) for f in nilFacts)) ''' removed in current draft if stringFactsWithoutXmlLang: modelXbrl.error("EBA.2.20", _("String facts need to report xml:lang: '%(langLessFacts)s'"), modelObject=stringFactsWithoutXmlLang, langLessFacts=", ".join(set(str(f.qname) for f in stringFactsWithoutXmlLang))) ''' if nonMonetaryNonPureFacts: modelXbrl.error( "EBA.3.2", _("Non monetary (numeric) facts MUST use the pure unit: '%(langLessFacts)s'" ), modelObject=nonMonetaryNonPureFacts, langLessFacts=", ".join( set(str(f.qname) for f in nonMonetaryNonPureFacts))) val.utrValidator.validateFacts( ) # validate facts for UTR at logLevel WARNING unitHashes = {} for unit in modelXbrl.units.values(): h = hash(unit) if h in unitHashes and unit.isEqualTo(unitHashes[h]): modelXbrl.warning( "EBA.2.22", _("Duplicate units SHOULD NOT be reported, units %(unit1)s and %(unit2)s have same measures.'" ), modelObject=(unit, unitHashes[h]), unit1=unit.id, unit2=unitHashes[h].id) else: unitHashes[h] = unit for elt in modelDocument.xmlRootElement.iter(): if isinstance( elt, ModelObject): # skip comments and processing instructions val.namespacePrefixesUsed[elt.qname.namespaceURI].add( elt.qname.prefix) val.prefixesUnused.discard(elt.qname.prefix)
def validateFacts(val, factsToCheck): # may be called in streaming batches or all at end (final) if not streaming modelXbrl = val.modelXbrl modelDocument = modelXbrl.modelDocument # note EBA 2.1 is in ModelDocument.py timelessDatePattern = re.compile(r"\s*([0-9]{4})-([0-9]{2})-([0-9]{2})\s*$") for cntx in modelXbrl.contexts.values(): if getattr(cntx, "_batchChecked", False): continue # prior streaming batch already checked cntx._batchChecked = True val.cntxEntities.add(cntx.entityIdentifier) dateElts = XmlUtil.descendants(cntx, XbrlConst.xbrli, ("startDate","endDate","instant")) if any(not timelessDatePattern.match(e.textValue) for e in dateElts): modelXbrl.error(("EBA.2.10","EIOPA.2.10"), _('Period dates must be whole dates without time or timezone: %(dates)s.'), modelObject=cntx, dates=", ".join(e.text for e in dateElts)) if cntx.isForeverPeriod: modelXbrl.error("EBA.2.11", _('Forever context period is not allowed.'), modelObject=cntx) elif cntx.isStartEndPeriod: modelXbrl.error(("EBA.2.13", "EIOPA.2.13"), _('Start-End (flow) context period is not allowed.'), modelObject=cntx) elif cntx.isInstantPeriod: # cannot pass context object to final() below, for error logging, if streaming mode val.cntxDates[cntx.instantDatetime].add(modelXbrl if getattr(val.modelXbrl, "isStreamingMode", False) else cntx) if XmlUtil.hasChild(cntx, XbrlConst.xbrli, "segment"): modelXbrl.error("EBA.2.14", _("The segment element not allowed in context Id: %(context)s"), modelObject=cntx, context=cntx.contextID) for scenElt in XmlUtil.descendants(cntx, XbrlConst.xbrli, "scenario"): childTags = ", ".join([child.prefixedName for child in scenElt.iterchildren() if isinstance(child,ModelObject) and child.tag != "{http://xbrl.org/2006/xbrldi}explicitMember" and child.tag != "{http://xbrl.org/2006/xbrldi}typedMember"]) if len(childTags) > 0: modelXbrl.error("EBA.2.15", _("Scenario of context Id %(context)s has disallowed content: %(content)s"), modelObject=cntx, context=cntx.id, content=childTags) val.unusedCntxIDs.add(cntx.id) for unit in modelXbrl.units.values(): if getattr(unit, "_batchChecked", False): continue # prior streaming batch already checked unit._batchChecked = True val.unusedUnitIDs.add(unit.id) factsByQname = defaultdict(set) # top level for this for f in factsToCheck: factsByQname[f.qname].add(f) val.unusedCntxIDs.discard(f.contextID) val.unusedUnitIDs.discard(f.unitID) if f.objectIndex < val.firstFactObjectIndex: val.firstFactObjectIndex = f.objectIndex val.firstFact = f for fIndicators in factsByQname[qnFIndicators]: val.numFilingIndicatorTuples += 1 for fIndicator in fIndicators.modelTupleFacts: _value = (getattr(fIndicator, "xValue", None) or fIndicator.value) # use validated xValue if DTS else value for skipDTS if _value in val.filingIndicators: modelXbrl.error(("EBA.1.6.1", "EIOPA.1.6.1"), _('Multiple filing indicators facts for indicator %(filingIndicator)s.'), modelObject=(fIndicator, val.filingIndicators[_value]), filingIndicator=_value) val.filingIndicators[_value] = fIndicator.get("{http://www.eurofiling.info/xbrl/ext/filing-indicators}filed", "true") in ("true", "1") val.unusedCntxIDs.discard(fIndicator.contextID) cntx = fIndicator.context if cntx is not None and (cntx.hasSegment or cntx.hasScenario): modelXbrl.error("EIOPA.S.1.6.d", _('Filing indicators must not contain segment or scenario elements %(filingIndicator)s.'), modelObject=fIndicator, filingIndicator=_value) if fIndicators.objectIndex > val.firstFactObjectIndex: modelXbrl.warning("EIOPA.1.6.2", _('Filing indicators should precede first fact %(firstFact)s.'), modelObject=(fIndicators, val.firstFact), firstFact=val.firstFact.qname) otherFacts = {} # (contextHash, unitHash, xmlLangHash) : fact nilFacts = [] # removed in current draft: stringFactsWithoutXmlLang = [] nonMonetaryNonPureFacts = [] for qname, facts in factsByQname.items(): for f in facts: if modelXbrl.skipDTS: c = f.qname.localName[0] isNumeric = c in ('m', 'p', 'i') isMonetary = c == 'm' isInteger = c == 'i' isPercent = c == 'p' isString = c == 's' isEnum = c == 'e' else: concept = f.concept if concept is not None: isNumeric = concept.isNumeric isMonetary = concept.isMonetary isInteger = concept.baseXbrliType in integerItemTypes isPercent = concept.typeQname in (qnPercentItemType, qnPureItemType) isString = concept.baseXbrliType in ("stringItemType", "normalizedStringItemType") isEnum = concept.typeQname == qnEnumerationItemType else: isNumeric = isString = isEnum = False # error situation k = (f.getparent().objectIndex, f.qname, f.context.contextDimAwareHash if f.context is not None else None, f.unit.hash if f.unit is not None else None, hash(f.xmlLang)) if f.qname == qnFIndicators and val.validateEIOPA: pass elif k not in otherFacts: otherFacts[k] = {f} else: matches = [o for o in otherFacts[k] if (f.getparent().objectIndex == o.getparent().objectIndex and f.qname == o.qname and f.context.isEqualTo(o.context) if f.context is not None and o.context is not None else True) and # (f.unit.isEqualTo(o.unit) if f.unit is not None and o.unit is not None else True) and (f.xmlLang == o.xmlLang)] if matches: contexts = [f.contextID] + [o.contextID for o in matches] modelXbrl.error(("EBA.2.16", "EIOPA.S.2.16.a"), _('Facts are duplicates %(fact)s contexts %(contexts)s.'), modelObject=[f] + matches, fact=f.qname, contexts=', '.join(contexts)) else: otherFacts[k].add(f) if isNumeric: if f.precision: modelXbrl.error(("EBA.2.17", "EIOPA.2.18.a"), _("Numeric fact %(fact)s of context %(contextID)s has a precision attribute '%(precision)s'"), modelObject=f, fact=f.qname, contextID=f.contextID, precision=f.precision) if f.decimals and not f.isNil: if f.decimals == "INF": modelXbrl.error("EIOPA.S.2.18.f", _("Monetary fact %(fact)s of context %(contextID)s has a decimal attribute INF: '%(decimals)s'"), modelObject=f, fact=f.qname, contextID=f.contextID, decimals=f.decimals) else: try: xValue = f.xValue dec = int(f.decimals) if isMonetary: if dec < -3: modelXbrl.error(("EBA.2.18","EIOPA.S.2.18.c"), _("Monetary fact %(fact)s of context %(contextID)s has a decimals attribute < -3: '%(decimals)s'"), modelObject=f, fact=f.qname, contextID=f.contextID, decimals=f.decimals) else: # apply dynamic decimals check if -.1 < xValue < .1: dMin = 2 elif -1 < xValue < 1: dMin = 1 elif -10 < xValue < 10: dMin = 0 elif -100 < xValue < 100: dMin = -1 elif -1000 < xValue < 1000: dMin = -2 else: dMin = -3 if dMin > dec: modelXbrl.warning("EIOPA:factDecimalsWarning", _("Monetary fact %(fact)s of context %(contextID)s value %(value)s has an imprecise decimals attribute: %(decimals)s, minimum is %(mindec)s"), modelObject=f, fact=f.qname, contextID=f.contextID, value=xValue, decimals=f.decimals, mindec=dMin) elif isInteger: if dec != 0: modelXbrl.error(("EBA.2.18","EIOPA.S.2.18.d"), _("Integer fact %(fact)s of context %(contextID)s has a decimals attribute \u2260 0: '%(decimals)s'"), modelObject=f, fact=f.qname, contextID=f.contextID, decimals=f.decimals) elif isPercent: if dec < 4: modelXbrl.error(("EBA.2.18","EIOPA.S.2.18.e"), _("Percent fact %(fact)s of context %(contextID)s has a decimals attribute < 4: '%(decimals)s'"), modelObject=f, fact=f.qname, contextID=f.contextID, decimals=f.decimals) else: if -.001 < xValue < .001: dMin = 4 elif -.01 < xValue < .01: dMin = 3 elif -.1 < xValue < .1: dMin = 2 elif -1 < xValue < 1: dMin = 1 else: dMin = 0 if dMin > dec: modelXbrl.warning("EIOPA:factDecimalsWarning", _("Numeric fact %(fact)s of context %(contextID)s value %(value)s has an imprecise decimals attribute: %(decimals)s, minimum is %(mindec)s"), modelObject=f, fact=f.qname, contextID=f.contextID, value=xValue, decimals=f.decimals, mindec=dMin) except (AttributeError, ValueError): pass # should have been reported as a schema error by loader '''' (not intended by EBA 2.18, paste here is from EFM) if not f.isNil and getattr(f,"xValid", 0) == 4: try: insignificance = insignificantDigits(f.xValue, decimals=f.decimals) if insignificance: # if not None, returns (truncatedDigits, insiginficantDigits) modelXbrl.error(("EFM.6.05.37", "GFM.1.02.26"), _("Fact %(fact)s of context %(contextID)s decimals %(decimals)s value %(value)s has nonzero digits in insignificant portion %(insignificantDigits)s."), modelObject=f1, fact=f1.qname, contextID=f1.contextID, decimals=f1.decimals, value=f1.xValue, truncatedDigits=insignificance[0], insignificantDigits=insignificance[1]) except (ValueError,TypeError): modelXbrl.error(("EBA.2.18"), _("Fact %(fact)s of context %(contextID)s decimals %(decimals)s value %(value)s causes Value Error exception."), modelObject=f1, fact=f1.qname, contextID=f1.contextID, decimals=f1.decimals, value=f1.value) ''' unit = f.unit if unit is not None: if isMonetary: if unit.measures[0]: val.currenciesUsed[unit.measures[0][0]] = unit elif not unit.isSingleMeasure or unit.measures[0][0] != XbrlConst.qnXbrliPure: nonMonetaryNonPureFacts.append(f) if isEnum: _eQn = getattr(f,"xValue", None) or qnameEltPfxName(f, f.value) if _eQn: val.namespacePrefixesUsed[_eQn.namespaceURI].add(_eQn.prefix) val.prefixesUnused.discard(_eQn.prefix) ''' removed in current draft elif isString: if not f.xmlLang: stringFactsWithoutXmlLang.append(f) ''' if f.isNil: nilFacts.append(f) if val.footnotesRelationshipSet.fromModelObject(f): modelXbrl.warning("EIOPA.S.19", _("Fact %(fact)s of context %(contextID)s has footnotes.'"), modelObject=f, fact=f.qname, contextID=f.contextID) if nilFacts: modelXbrl.error(("EBA.2.19", "EIOPA.S.2.19"), _('Nil facts MUST NOT be present in the instance: %(nilFacts)s.'), modelObject=nilFacts, nilFacts=", ".join(str(f.qname) for f in nilFacts)) ''' removed in current draft if stringFactsWithoutXmlLang: modelXbrl.error("EBA.2.20", _("String facts need to report xml:lang: '%(langLessFacts)s'"), modelObject=stringFactsWithoutXmlLang, langLessFacts=", ".join(set(str(f.qname) for f in stringFactsWithoutXmlLang))) ''' if nonMonetaryNonPureFacts: modelXbrl.error(("EBA.3.2","EIOPA.3.2.a"), _("Non monetary (numeric) facts MUST use the pure unit: '%(langLessFacts)s'"), modelObject=nonMonetaryNonPureFacts, langLessFacts=", ".join(set(str(f.qname) for f in nonMonetaryNonPureFacts))) val.utrValidator.validateFacts() # validate facts for UTR at logLevel WARNING unitHashes = {} for unit in modelXbrl.units.values(): h = unit.hash if h in unitHashes and unit.isEqualTo(unitHashes[h]): modelXbrl.warning("EBA.2.21", _("Duplicate units SHOULD NOT be reported, units %(unit1)s and %(unit2)s have same measures.'"), modelObject=(unit, unitHashes[h]), unit1=unit.id, unit2=unitHashes[h].id) if not getattr(modelXbrl, "isStreamingMode", False): modelXbrl.error("EIOPA.2.21", _("Duplicate units MUST NOT be reported, units %(unit1)s and %(unit2)s have same measures.'"), modelObject=(unit, unitHashes[h]), unit1=unit.id, unit2=unitHashes[h].id) else: unitHashes[h] = unit for _measures in unit.measures: for _measure in _measures: val.namespacePrefixesUsed[_measure.namespaceURI].add(_measure.prefix) val.prefixesUnused.discard(_measure.prefix) del unitHashes cntxHashes = {} for cntx in modelXbrl.contexts.values(): h = cntx.contextDimAwareHash if h in cntxHashes and cntx.isEqualTo(cntxHashes[h]): if not getattr(modelXbrl, "isStreamingMode", False): modelXbrl.error("EIOPA.S.2.7.b", _("Duplicate contexts MUST NOT be reported, contexts %(cntx1)s and %(cntx2)s are equivalent.'"), modelObject=(cntx, cntxHashes[h]), cntx1=cntx.id, cntx2=cntxHashes[h].id) else: cntxHashes[h] = cntx for _dim in cntx.qnameDims.values(): _dimQn = _dim.dimensionQname val.namespacePrefixesUsed[_dimQn.namespaceURI].add(_dimQn.prefix) val.prefixesUnused.discard(_dimQn.prefix) if _dim.isExplicit: _memQn = _dim.memberQname else: _memQn = _dim.typedMember.qname if _memQn: val.namespacePrefixesUsed[_memQn.namespaceURI].add(_memQn.prefix) val.prefixesUnused.discard(_memQn.prefix) if cntx.hasSegment: modelXbrl.error("EIOPA.S.1.6.d", _("Contexts MUST NOT contain xbrli:segment values: %(cntx)s.'"), modelObject=(cntx, cntxHashes[h]), cntx=cntx.id) if cntx.nonDimValues("scenario"): modelXbrl.error("EIOPA.S.1.6.d", _("Contexts MUST NOT contain non-dimensional xbrli:scenario values: %(cntx)s.'"), modelObject=(cntx, cntxHashes[h]), cntx=cntx.id) for elt in modelDocument.xmlRootElement.iter(): if isinstance(elt, ModelObject): # skip comments and processing instructions val.namespacePrefixesUsed[elt.qname.namespaceURI].add(elt.qname.prefix) val.prefixesUnused.discard(elt.qname.prefix) for attrTag in elt.keys(): if attrTag.startswith("{"): _prefix, _NS, _localName = XmlUtil.clarkNotationToPrefixNsLocalname(elt, attrTag, isAttribute=True) if _prefix: val.namespacePrefixesUsed[_NS].add(_prefix) val.prefixesUnused.discard(_prefix)
def validateXbrlFinally(val, *args, **kwargs): if not (val.validateESMAplugin): return _xhtmlNs = "{{{}}}".format(xhtml) _xhtmlNsLen = len(_xhtmlNs) modelXbrl = val.modelXbrl modelDocument = modelXbrl.modelDocument _statusMsg = _("validating {0} filing rules").format(val.disclosureSystem.name) modelXbrl.profileActivity() modelXbrl.modelManager.showStatus(_statusMsg) if modelDocument.type == ModelDocument.Type.INSTANCE: modelXbrl.error("esma:instanceShallBeInlineXBRL", _("RTS on ESEF requires inline XBRL instances."), modelObject=modelXbrl) checkFilingDimensions(val) # sets up val.primaryItems and val.domainMembers val.hasExtensionSchema = val.hasExtensionPre = val.hasExtensionCal = val.hasExtensionDef = val.hasExtensionLbl = False checkFilingDTS(val, modelXbrl.modelDocument, []) modelXbrl.profileActivity("... filer DTS checks", minTimeToShow=1.0) if not (val.hasExtensionSchema and val.hasExtensionPre and val.hasExtensionCal and val.hasExtensionDef and val.hasExtensionLbl): missingFiles = [] if not val.hasExtensionSchema: missingFiles.append("schema file") if not val.hasExtensionPre: missingFiles.append("presentation linkbase") if not val.hasExtensionCal: missingFiles.append("calculation linkbase") if not val.hasExtensionDef: missingFiles.append("definition linkbase") if not val.hasExtensionLbl: missingFiles.append("label linkbase") modelXbrl.warning("esma:3.1.1.extensionTaxonomyWrongFilesStructure", _("Extension taxonomies MUST consist of at least a schema file and presentation, calculation, definition and label linkbases" ": missing %(missingFiles)s"), modelObject=modelXbrl, missingFiles=", ".join(missingFiles)) if modelDocument.type in (ModelDocument.Type.INLINEXBRL, ModelDocument.Type.INSTANCE): footnotesRelationshipSet = modelXbrl.relationshipSet("XBRL-footnotes") orphanedFootnotes = set() noLangFootnotes = set() foonoteRoleErrors = set() transformRegistryErrors = set() def checkFootnote(elt, text): if text: # non-empty footnote must be linked to a fact if not empty if not any(isinstance(rel.fromModelObject, ModelFact) for rel in footnotesRelationshipSet.toModelObject(elt)): orphanedFootnotes.add(elt) if not elt.xmlLang: noLangFootnotes.add(elt) if elt.role != XbrlConst.footnote or not all( rel.arcrole == XbrlConst.factFootnote and rel.linkrole == XbrlConst.defaultLinkRole for rel in footnotesRelationshipSet.toModelObject(elt)): footnoteRoleErrors.add(elt) if modelDocument.type == ModelDocument.Type.INLINEXBRL: _baseName, _baseExt = os.path.splitext(modelDocument.basename) if _baseExt not in (".xhtml",): modelXbrl.warning("esma:TBD.fileNameExtension", _("FileName should have the extension .xhtml: %(fileName)s"), modelObject=modelXbrl, fileName=modelDocument.basename) ixNStag = modelXbrl.modelDocument.ixNStag ixTags = set(ixNStag + ln for ln in ("nonNumeric", "nonFraction", "references", "relationship")) ixTextTags = set(ixNStag + ln for ln in ("nonFraction", "continuation", "footnote")) ixExcludeTag = ixNStag + "exclude" ixTupleTag = ixNStag + "tuple" ixFractionTag = ixNStag + "fraction" rootElt = modelDocument.xmlRootElement for elt in rootElt.iter(): eltTag = elt.tag if isinstance(elt, ModelObject) and elt.namespaceURI == xhtml: eltTag = elt.localName elif isinstance(elt, (_ElementTree, _Comment, _ProcessingInstruction)): continue # comment or other non-parsed element else: eltTag = elt.tag if eltTag.startswith(_xhtmlNs): eltTag = eltTag[_xhtmlNsLen:] if ((eltTag in ("object", "script")) or (eltTag == "a" and "javascript:" in elt.get("href","")) or (eltTag == "img" and "javascript:" in elt.get("src",""))): modelXbrl.error("esma.2.5.1.executableCodePresent", _("Inline XBRL documents MUST NOT contain executable code: %(element)s"), modelObject=elt, element=eltTag) elif eltTag == "img": src = elt.get("src","").strip() hasParentIxTextTag = False # check if image is in an ix text-bearing element _ancestorElt = elt while (_ancestorElt is not None): if _ancestorElt.tag == ixExcludeTag: # excluded from any parent text-bearing ix element break if _ancestorElt.tag in ixTextTags: hasParentIxTextTag = True break _ancestorElt = _ancestorElt.getparent() if scheme(href) in ("http", "https", "ftp"): modelXbrl.error("esma.3.5.1.inlinXbrlContainsExternalReferences", _("Inline XBRL instance documents MUST NOT contain any reference pointing to resources outside the reporting package: %(element)s"), modelObject=elt, element=eltTag) if not src.startswith("data:image"): if hasParentIxTextTag: modelXbrl.error("esma.2.5.1.embeddedImageNotUsingBase64Encoding", _("Images MUST be included in the XHTML document as a base64 encoded string unless their size exceeds support of browsers."), modelObject=elt) else: # presume it to be an image file, check image contents try: base = elt.modelDocument.baseForElement(elt) normalizedUri = elt.modelXbrl.modelManager.cntlr.webCache.normalizeUrl(graphicFile, base) if not elt.modelXbrl.fileSource.isInArchive(normalizedUri): normalizedUri = elt.modelXbrl.modelManager.cntlr.webCache.getfilename(normalizedUri) imglen = 0 with elt.modelXbrl.fileSource.file(normalizedUri,binary=True)[0] as fh: imglen += len(fh.read()) if imglen < browserMaxBase64ImageLength: modelXbrl.error("esma.2.5.1.embeddedImageNotUsingBase64Encoding", _("Images MUST be included in the XHTML document as a base64 encoded string unless their size exceeds support of browsers."), modelObject=elt) except IOError as err: modelXbrl.error("esma.2.5.1.imageFileCannotBeLoaded", _("Image file which isn't openable '%(src)s', error: %(error)s"), modelObject=elt, src=src, error=err) elif not any(src.startswith(m) for m in allowedImgMimeTypes): modelXbrl.error("esma.2.5.1.embeddedImageNotUsingBase64Encoding", _("Images MUST be included in the XHTML document as a base64 encoded string, encoding disallowed: %(src)s."), modelObject=elt, src=attrValue[:128]) elif eltTag == "a": href = elt.get("href","").strip() if scheme(href) in ("http", "https", "ftp"): modelXbrl.error("esma.3.5.1.inlinXbrlContainsExternalReferences", _("Inline XBRL instance documents MUST NOT contain any reference pointing to resources outside the reporting package: %(element)s"), modelObject=elt, element=eltTag) if eltTag in ixTags and elt.get("target"): modelXbrl.error("esma.2.5.3.targetAttributeUsed", _("Target attribute MUST not be used: element %(localName)s, target attribute %(target)s."), modelObject=elt, localName=elt.elementQname, target=elt.get("target")) if eltTag == ixTupleTag: modelXbrl.error("esma.2.5.3.tupleElementUsed", _("The ix:tuple element MUST not be used."), modelObject=elt) if eltTag == ixFractionTag: modelXbrl.error("esma.2.5.3.fractionElementUsed", _("The ix:fraction element MUST not be used."), modelObject=elt) if elt.get("{http://www.w3.org/XML/1998/namespace}base") is not None: modelXbrl.error("esma.2.4.1.xmlBaseUsed", _("xml:base attributes MUST NOT be used in the Inline XBRL document: element %(localName)s, base attribute %(base)s."), modelObject=elt, localName=elt.elementQname, base=elt.get("{http://www.w3.org/XML/1998/namespace}base")) if isinstance(elt, ModelInlineFootnote): checkFootnote(elt, elt.value) elif isinstance(elt, ModelResource) and elt.qname == XbrlConst.qnLinkFootnote: checkFootnote(elt, elt.value) elif isinstance(elt, ModelInlineFact): if elt.format is not None and elt.format.namespaceURI != 'http://www.xbrl.org/inlineXBRL/transformation/2015-02-26': transformRegistryErrors.add(elt) elif modelDocument.type == ModelDocument.Type.INSTANCE: for elt in modelDocument.xmlRootElement.iter(): if elt.qname == XbrlConst.qnLinkFootnote: # for now assume no private elements extend link:footnote checkFootnote(elt, elt.stringValue) contextsWithDisallowedOCEs = [] contextsWithDisallowedOCEcontent = [] contextsWithPeriodTime = [] contextsWithPeriodTimeZone = [] contextIdentifiers = defaultdict(list) nonStandardTypedDimensions = defaultdict(set) for context in modelXbrl.contexts.values(): if XmlUtil.hasChild(context, XbrlConst.xbrli, "segment"): contextsWithDisallowedOCEs.append(context) for segScenElt in context.iterdescendants("{http://www.xbrl.org/2003/instance}scenario"): if isinstance(segScenElt,ModelObject): if any(True for child in segScenElt.iterchildren() if isinstance(child,ModelObject) and child.tag not in ("{http://xbrl.org/2006/xbrldi}explicitMember", "{http://xbrl.org/2006/xbrldi}typedMember")): contextsWithDisallowedOCEcontent.append(context) # check periods here contextIdentifiers[context.entityIdentifier].append(context) if contextsWithDisallowedOCEs: modelXbrl.error("esma.2.1.3.segmentUsed", _("xbrli:segment container MUST NOT be used in contexts: %(contextIds)s"), modelObject=contextsWithDisallowedOCEs, contextIds=", ".join(c.id for c in contextsWithDisallowedOCEs)) if contextsWithDisallowedOCEcontent: modelXbrl.error("esma.2.1.3.scenarioContainsNonDimensionalContent", _("xbrli:scenario in contexts MUST NOT contain any other content than defined in XBRL Dimensions specification: %(contextIds)s"), modelObject=contextsWithDisallowedOCEcontent, contextIds=", ".join(c.id for c in contextsWithDisallowedOCEcontent)) if len(contextIdentifiers) > 1: modelXbrl.error("esma.2.1.4.multipleIdentifiers", _("All entity identifiers in contexts MUST have identical content: %(contextIdentifiers)s"), modelObject=modelXbrl, contextIds=", ".join(i[1] for i in contextIdentifiers)) for (contextScheme, contextIdentifier), contextElts in contextIdentifiers.items(): if contextScheme != "http://standards.iso.org/iso/17442": modelXbrl.warning("esma.2.1.1.nonLEIContextScheme", _("The scheme attribute of the xbrli:identifier element should have \"http://standards.iso.org/iso/17442\" as its content: %(scheme)s"), modelObject=contextElts, scheme=contextScheme) else: leiValidity = LeiUtil.checkLei(contextIdentifier) if leiValidity == LeiUtil.LEI_INVALID_LEXICAL: modelXbrl.warning("esma.2.1.1.invalidIdentifierFormat", _("The LEI context idenntifier has an invalid format: %(identifier)s"), modelObject=contextElts, identifier=contextIdentifier) elif leiValidity == LeiUtil.LEI_INVALID_CHECKSUM: modelXbrl.warning("esma.2.1.1.invalidIdentifier", _("The LEI context idenntifier has checksum error: %(identifier)s"), modelObject=contextElts, identifier=contextIdentifier) if contextsWithPeriodTime: modelXbrl.warning("esma.2.1.2.periodWithTimeContent", _("Context period startDate, endDate and instant elements should be in whole days without time: %(contextIds)s"), modelObject=contextsWithPeriodTime, contextIds=", ".join(c.id for c in contextsWithPeriodTime)) if contextsWithPeriodTimeZone: modelXbrl.warning("esma.2.1.2.periodWithTimeZone", _("Context period startDate, endDate and instant elements should be in whole days without a timezone: %(contextIds)s"), modelObject=contextsWithPeriodTimeZone, contextIds=", ".join(c.id for c in contextsWithPeriodTimeZone)) reportedMandatory = set() footnotesRelationshipSet = modelXbrl.relationshipSet(XbrlConst.factFootnote, XbrlConst.defaultLinkRole) for qn, facts in modelXbrl.factsByQname.items(): if qn in mandatory: reportedMandatory.add(qn) missingElements = (mandatory - reportedMandatory) if missingElements: modelXbrl.error("esma:???.missingRequiredElements", _("Required elements missing from document: %(elements)s."), modelObject=modelXbrl, elements=", ".join(sorted(str(qn) for qn in missingElements))) if transformRegistryErrors: modelXbrl.info("esma:???.transformRegistry", _("Transformation Registry 3 should be used for facts: %(elements)s."), modelObject=transformRegistryErrors, elements=", ".join(sorted(str(fact.qname) for fact in transformRegistryErrors))) if orphanedFootnotes: modelXbrl.error("esma.2.3.1.unusedFootnote", _("Non-empty footnotes must be connected to fact(s)."), modelObject=orphanedFootnotes) if noLangFootnotes: modelXbrl.error("esma.2.3.2.undefinedLanguageForFootnote", _("FEach footnote MUST have the 'xml:lang' attribute whose value corresponds to the language of the text in the content of the respective footnote."), modelObject=noLangFootnotes) if foonoteRoleErrors: modelXbrl.error("esma.2.3.2.nonStandardRoleForFootnote", _("The xlink:role attribute of a link:footnote and link:loc element as well as xlink:arcrole attribute of a link:footnoteArc MUST be defined in the XBRL Specification 2.1."), modelObject=foonoteRoleErrors) modelXbrl.profileActivity(_statusMsg, minTimeToShow=0.0) modelXbrl.modelManager.showStatus(None)
def doObject(self, fObj, fromRel, pIndent, visited): if fObj is None: return cIndent = pIndent + " " if isinstance(fObj, (ModelValueAssertion, ModelExistenceAssertion, ModelFormula)): varSetType = "formula" if isinstance(fObj, ModelFormula) else "assertion" eltNbr = self.eltTypeCount[varSetType] = self.eltTypeCount.get(varSetType, 0) + 1 _id = fObj.id or "{}{}".format(varSetType, eltNbr) self.xf = "{}{} {} {{".format(pIndent, varSetType, _id) for arcrole in (XbrlConst.elementLabel, XbrlConst.assertionSatisfiedMessage, XbrlConst.assertionUnsatisfiedMessage): for modelRel in self.modelXbrl.relationshipSet(arcrole).fromModelObject(fObj): self.doObject(modelRel.toModelObject, modelRel, cIndent, visited) if fObj.aspectModel == "non-dimensional": self.xf = "{}aspect-model-non-dimensional;".format(cIndent) if fObj.implicitFiltering == "false": self.xf = "{}no-implicit-filtering;".format(cIndent) if isinstance(fObj, ModelFormula): for attr in ("decimals", "precision", "value"): if fObj.get(attr): self.xf = "{}{} {{{}}};".format(cIndent, attr, fObj.get(attr)) if fObj.get("source"): self.xf = "{}source {};".format(cIndent, fObj.get("source")) for aspectsElt in XmlUtil.children(fObj, XbrlConst.formula, "aspects"): self.xf = "{}aspect-rules{} {{".format(cIndent, "source {}".format(aspectsElt.get("source")) if aspectsElt.get("source") else "") for ruleElt in XmlUtil.children(aspectsElt, XbrlConst.formula, "*"): self.doObject(ruleElt, None, cIndent + " ", visited) self.xf = "{}}};".format(cIndent) for arcrole in (XbrlConst.variableSetFilter, XbrlConst.variableSet, XbrlConst.variableSetPrecondition): for modelRel in self.modelXbrl.relationshipSet(arcrole).fromModelObject(fObj): self.doObject(modelRel.toModelObject, modelRel, cIndent, visited) if isinstance(fObj, ModelValueAssertion): self.xf = "{}test {{{}}}".format(cIndent, fObj.viewExpression) elif isinstance(fObj, ModelExistenceAssertion): self.xf = "{}evaluation-count {{{}}}".format(cIndent, fObj.viewExpression or ". gt 0") self.xf = "{}}};".format(pIndent) elif isinstance(fObj, ModelConsistencyAssertion): eltNbr = self.eltTypeCount["consistencyAssertion"] = self.eltTypeCount.get("consistencyAssertion", 0) + 1 _id = fObj.id or "{}{}".format("consistencyAssertion", eltNbr) self.xf = "{}consistency-assertion {} {{".format(pIndent, _id) for arcrole in (XbrlConst.elementLabel, XbrlConst.assertionSatisfiedMessage, XbrlConst.assertionUnsatisfiedMessage): for modelRel in self.modelXbrl.relationshipSet(arcrole).fromModelObject(fObj): self.doObject(modelRel.toModelObject, modelRel, cIndent, visited) if fObj.isStrict: self.xf = "{}strict;".format(cIndent) if fObj.get("proportionalAcceptanceRadius"): self.xf = "{}proportional-acceptance-radius {{{}}};".format(cIndent, fObj.get("proportionalAcceptanceRadius")) if fObj.get("absoluteAcceptanceRadius"): self.xf = "{}absolute-acceptance-radius {{{}}};".format(cIndent, fObj.get("absoluteAcceptanceRadius")) for modelRel in self.modelXbrl.relationshipSet(XbrlConst.consistencyAssertionFormula).fromModelObject(fObj): self.doObject(modelRel.toModelObject, modelRel, cIndent, visited) self.xf = "{}}};".format(pIndent) elif isinstance(fObj, ModelFactVariable) and fromRel is not None: self.xf = "{}variable ${} {{".format(pIndent, fromRel.variableQname) if fromRel.variableQname.prefix: self.xmlns[fromRel.variableQname.prefix] = fromRel.variableQname.namespaceURI if fObj.bindAsSequence == "true": self.xf = "{}bind-as-sequence".format(cIndent) if fObj.nils == "true": self.xf = "{}nils".format(cIndent) if fObj.matches == "true": self.xf = "{}matches".format(cIndent) if fObj.fallbackValue: self.xf = "{}fallback {{{}}}".format(cIndent, fObj.fallbackValue) for modelRel in self.modelXbrl.relationshipSet(XbrlConst.variableFilter).fromModelObject(fObj): self.doObject(modelRel.toModelObject, modelRel, cIndent, visited) self.xf = "{}}};".format(pIndent) elif isinstance(fObj, ModelGeneralVariable) and fromRel is not None: self.xf = "{}variable ${} {{".format(pIndent, fromRel.variableQname) if fromRel.variableQname.prefix: self.xmlns[fromRel.variableQname.prefix] = fromRel.variableQname.namespaceURI if fObj.bindAsSequence: self.xf = "{}bind-as-sequence".format(cIndent) self.xf = "{}select {{{}}}".format(cIndent, fObj.select) elif isinstance(fObj, ModelParameter): if fromRel is not None: # parameter is referenced by a different QName on arc if fromRel.variableQname.prefix: self.xmlns[fromRel.variableQname.prefix] = fromRel.variableQname.namespaceURI self.xf = "{}parameter ${} references ${};".format(pIndent, fromRel.variableQname, fObj.parameterQname) else: # root level parameter if fObj.parameterQname.prefix: self.xmlns[fObj.parameterQname.prefix] = fObj.parameterQname.namespaceURI self.xf = "{}parameter {} {{".format(pIndent, fObj.parameterQname) if fObj.isRequired: self.xf = "{}required".format(cIndent) self.xf = "{} select {{{}}}".format(cIndent, fObj.select) if fObj.asType: self.xf = "{} as {{{}}}".format(cIndent, fObj.asType) if fObj.asType.prefix: self.xmlns[fObj.asType.prefix] = fObj.asType.namespaceURI self.xf = "{}}};".format(pIndent) elif isinstance(fObj, ModelFilter): if fromRel.isComplemented: self.xf = "{}complemented".format(pIndent) if not fromRel.isCovered and fromRel.localName == "variableFilterArc": self.xf = "{}non-covering".format(pIndent) if isinstance(fObj, ModelConceptName): if len(fObj.conceptQnames) == 1 and not fObj.qnameExpressions: qn = next(iter(fObj.conceptQnames)) self.xmlns[qn.prefix] = qn.namespaceURI self.xf = "{}concept-name {};".format(pIndent, qn) elif len(fObj.qnameExpressions) == 1 and not fObj.conceptQnames: self.xf = "{}concept-name {{{}}};".format(pIndent, fObj.qnameExpressions[0]) else: self.xf = "{}concept-name".format(pIndent) for qn in fObj.conceptQnames: self.xmlns[qn.prefix] = qn.namespaceURI self.xf = "{} {}".format(pIndent, qn) for qnExpr in fObj.qnameExpressions: self.xf = "{} {}".format(pIndent, qnExpr) self.xf = "{} ;".format(pIndent) elif isinstance(fObj, ModelConceptPeriodType): self.xf = "{}concept-period {};".format(pIndent, fObj.periodType) elif isinstance(fObj, ModelConceptBalance): self.xf = "{}concept-balance {};".format(pIndent, fObj.balance) elif isinstance(fObj, (ModelConceptDataType, ModelConceptSubstitutionGroup)): self.xf = "{}{} {} {};".format(pIndent, kebabCase(fObj.localName), "strict" if fObj.strict == "true" else "non-strict", fObj.filterQname if fObj.filterQname else "{{{}}}".format(fObj.qnameExpression)) elif isinstance(fObj, ModelExplicitDimension): members = [] for memberElt in XmlUtil.children(fObj, XbrlConst.df, "member"): members.append("member") member = XmlUtil.childText(memberElt, XbrlConst.df, "qname") if member: member = str(member) # qname, must coerce to string else: member = XmlUtil.childText(memberElt, XbrlConst.df, "qnameExpression") if member: member = "{{{}}}".format(member) else: member = "$" + XmlUtil.childText(memberElt, XbrlConst.df, "variable") members.append(member) linkrole = XmlUtil.childText(memberElt, XbrlConst.df, "linkrole") if linkrole: members.append("linkrole") members.append("\"{}\"".format(linkrole)) arcrole = XmlUtil.childText(memberElt, XbrlConst.df, "arcrole") if arcrole: members.append("arcrole") members.append("\"{}\"".format(arcrole)) axis = XmlUtil.childText(memberElt, XbrlConst.df, "axis") if axis: members.append("axis") members.append(axis) self.xf = "{}explicit-dimension {}{};".format(pIndent, fObj.dimQname or "{{{}}}".format(fObj.dimQnameExpression) if fObj.dimQnameExpression else "", " ".join(members)) elif isinstance(fObj, ModelTypedDimension): # this is a ModelTestFilter not same as genera/unit/period self.xf = "{}typed-dimension {}{};".format(pIndent, fObj.dimQname or "{{{}}}".format(fObj.dimQnameExpression) if fObj.dimQnameExpression else "", " {{{}}}".format(fObj.test) if fObj.test else "") elif isinstance(fObj, ModelTestFilter): self.xf = "{}{} {{{}}};".format(pIndent, "general" if isinstance(fObj, ModelGeneral) else "unit-general-measures" if isinstance(fObj, ModelGeneralMeasures) else "period" if isinstance(fObj, ModelPeriod) else "entity-identifier" if isinstance(fObj, ModelIdentifier) else None, fObj.test) elif isinstance(fObj, ModelDateTimeFilter): self.xf = "{}{} {{{}}}{};".format(pIndent, kebabCase(fObj.localName), fObj.date, " {{{}}}".format(fObj.time) if fObj.time else "") elif isinstance(fObj, ModelInstantDuration): self.xf = "{}instant-duration {} {};".format(pIndent, fObj.boundary, fObj.variable) elif isinstance(fObj, ModelSingleMeasure): self.xf = "{}unit {} {};".format(pIndent, fObj.boundary, fObj.variable) elif isinstance(fObj, ModelEntitySpecificIdentifier): self.xf = "{}entity scheme {{{}}} value {{{}}};".format(pIndent, fObj.scheme, fObj.value) elif isinstance(fObj, ModelEntityScheme): self.xf = "{}entity-scheme {{{}}};".format(pIndent, fObj.scheme) elif isinstance(fObj, ModelEntityRegexpScheme): self.xf = "{}entity-scheme-pattern \"{}\";".format(pIndent, fObj.pattern) elif isinstance(fObj, ModelEntityRegexpIdentifier): self.xf = "{}entity-identifier-pattern \"{}\";".format(pIndent, fObj.pattern) elif isinstance(fObj, ModelMatchFilter): self.xf = "{}{} ${} {}{};".format(pIndent, kebabCase(fObj.localName), fObj.variable, fObj.dimension or "", " match-any" if fObj.matchAny else "") elif isinstance(fObj, ModelRelativeFilter): self.xf = "{}relative ${};".format(pIndent, fObj.variable) elif isinstance(fObj, ModelAncestorFilter): self.xf = "{}ancestor {};".format(pIndent, fObj.ancestorQname or "{{{}}}".format(fObj.qnameExpression) if fObj.qnameExpression else "") elif isinstance(fObj, ModelParentFilter): self.xf = "{}parent {};".format(pIndent, fObj.parentQname or "{{{}}}".format(fObj.qnameExpression) if fObj.qnameExpression else "") elif isinstance(fObj, ModelSiblingFilter): self.xf = "{}sibling ${};".format(pIndent, fObj.variable) elif isinstance(fObj, ModelNilFilter): self.xf = "{}nilled;".format(pIndent) elif isinstance(fObj, ModelAspectCover): aspects = [] for aspectElt in XmlUtil.children(fObj, XbrlConst.acf, "aspect"): aspects.append(XmlUtil.text(aspectElt)) for dimElt in XmlUtil.descendants(fObj, XbrlConst.acf, ("qname", "qnameExpression")): dimAspect = qname( dimElt, XmlUtil.text(dimElt) ) aspects.append("exclude-dimension" if dimElt.getparent().localName == "excludeDimension" else "dimension") if dimElt.localName == "qname": aspects.append(str(qname( dimElt, XmlUtil.text(dimElt) ))) else: aspects.append("{{{}}}".format(XmlUtil.text(dimElt))) self.xf = "{}aspect-cover {};".format(pIndent, " ".join(aspects)) elif isinstance(fObj, ModelConceptRelation): conceptRelationTerms = [] if fObj.sourceQname: conceptRelationTerms.append(fObj.sourceQname) elif fObj.variable: conceptRelationTerms.append("$" + fObj.variable) else: conceptRelationTerms.append("{{{}}}".format(fObj.sourceQnameExpression)) if fObj.linkrole: conceptRelationTerms.append("linkrole") conceptRelationTerms.append(fObj.linkrole) elif fObj.linkroleExpression: conceptRelationTerms.append("linkrole") conceptRelationTerms.append("{{{}}}".format(fObj.linkroleExpression)) if fObj.arcrole: conceptRelationTerms.append("arcrole") conceptRelationTerms.append(fObj.arcrole) elif fObj.arcroleExpression: conceptRelationTerms.append("arcrole") conceptRelationTerms.append("{{{}}}".format(fObj.arcroleExpression)) if fObj.axis: conceptRelationTerms.append("axis") conceptRelationTerms.append(fObj.axis) if fObj.generations is not None: conceptRelationTerms.append("generations {}".format(fObj.generations)) if fObj.test: conceptRelationTerms.append("test") conceptRelationTerms.append("{{{}}}".format(fObj.test)) self.xf = "{}concept-relation {};".format(pIndent, " ".join(conceptRelationTerms)) elif isinstance(fObj, (ModelAndFilter, ModelOrFilter)): self.xf = "{}{} {{".format(pIndent, "and" if isinstance(fObj, ModelAndFilter)else "or") if fObj not in visited: visited.add(fObj) for modelRel in self.modelXbrl.relationshipSet(XbrlConst.booleanFilter).fromModelObject(fObj): self.doObject(modelRel.toModelObject, modelRel, cIndent, visited) visited.remove(fObj) self.xf = "{}}};".format(pIndent) elif isinstance(fObj, ModelMessage): self.xf = "{}{}{} \"{}\";".format( pIndent, "satisfied-message" if fromRel.arcrole == XbrlConst.assertionSatisfiedMessage else "unsatisfied-message", " ({})".format(fObj.xmlLang) if fObj.xmlLang else "", fObj.text) elif isinstance(fObj, ModelCustomFunctionSignature): hasImplememntation = False if fObj not in visited: visited.add(fObj) for modelRel in self.modelXbrl.relationshipSet(XbrlConst.functionImplementation).fromModelObject(fObj): self.doObject(modelRel.toModelObject, modelRel, pIndent, visited) # note: use pIndent as parent doesn't show hasImplementation = True visited.remove(fObj) if not hasImplementation: self.xf = "{}abstract-function {}({}) as {};".format(pIndent, fObj.name, ", ".join(str(t) for t in fObj.inputTypes), fObj.outputType) elif isinstance(fObj, ModelCustomFunctionImplementation): sigObj = fromRel.fromModelObject self.xf = "{}function {}({}) as {} {{;".format(pIndent, sigObj.name, ", ".join("{} as {}".format(inputName, sigObj.inputTypes[i]) for i, inputName in enumerate(fObj.inputNames)), sigObj.outputType) for name, stepExpr in fObj.stepExpressions: if "\n" not in stepExpr: self.xf = "{}step ${} {{{}}};".format(cIndent, name, stepExpr) else: self.xf = "{}step ${} {{".format(cIndent, name) for exprLine in stepExpr.split("\n"): self.xf = "{} {}".format(cIndent, exprLine.lstrip()) self.xf = "{}}};".format(cIndent) self.xf = "{}return {{{}}};".format(cIndent, fObj.outputExpression) self.xf = "{}}};".format(pIndent) elif fObj.getparent().tag == "{http://xbrl.org/2008/formula}aspects": # aspect rules arg = "" if fObj.localName == "concept": if XmlUtil.hasChild(fObj, None, "qname"): arg += " " + XmlUtil.childText(fObj, None, "qname") elif XmlUtil.hasChild(fObj, None, "qnameExpression"): arg += " {" + XmlUtil.childText(fObj, None, "qnameExpression") + "}" elif fObj.localName == "entityIdentifier": if fObj.get("scheme"): arg += " scheme {" + fObj.get("scheme") + "}" if fObj.get("identifier"): arg += " identifier {" + fObj.get("identifier") + "}" elif fObj.localName == "period": if XmlUtil.hasChild(fObj, None, "forever"): arg += " forever" if XmlUtil.hasChild(fObj, None, "instant"): arg += " instant" attr = XmlUtil.childAttr(fObj, None, "instant", "value") if attr: arg += "{" + attr + "}" if XmlUtil.hasChild(fObj, None, "duration"): arg += " duration" attr = XmlUtil.childAttr(fObj, None, "duration", "start") if attr: arg += " start {" + attr + "}" attr = XmlUtil.childAttr(fObj, None, "duration", "end") if attr: arg += " end {" + attr + "}" elif fObj.localName == "unit": if fObj.get("augment") == "true": arg += " augment" if fObj.localName in ("explicitDimension", "typedDimension"): arg += " dimension " + fObj.get("dimension") if fObj.localName in ("concept", "entityIdentifier", "period"): arg += ";" else: arg += " {" self.xf = "{}{}{}".format(pIndent, kebabCase(fObj.localName), arg) if fObj.localName == "unit": for elt in fObj.iterchildren(): arg = "" if elt.get("source"): arg += " source " + elt.get("source") if elt.get("measure"): arg += " measure {" + elt.get("measure") + "}" self.xf = "{}{}{};".format(cIndent, kebabCase(elt.localName), arg) elif fObj.localName == "explicitDimension": for elt in fObj.iterchildren(): arg = "" if XmlUtil.hasChild(elt, None, "qname"): arg += " " + XmlUtil.childText(elt, None, "qname") elif XmlUtil.hasChild(elt, None, "qnameExpression"): arg += " {" + XmlUtil.childText(elt, None, "qnameExpression") + "}" self.xf = "{}{}{};".format(cIndent, kebabCase(elt.localName), arg) elif fObj.localName == "typedDimension": for elt in fObj.iterchildren(): arg = "" if XmlUtil.hasChild(elt, None, "xpath"): arg += " xpath {" + ModelXbrl.childText(elt, None, "xpath") + "}" elif XmlUtil.hasChild(elt, None, "value"): arg += " value " + strQoute(XmlUtil.xmlstring(XmlUtil.child(elt, None, "value"), stripXmlns=True, contentsOnly=False)) self.xf = "{}{}{};".format(cIndent, kebabCase(elt.localName), arg) if fObj.localName not in ("concept", "entityIdentifier", "period"): self.xf = "{}}};".format(pIndent)
def hasScenario(self): """(bool) -- True if a xbrli:scenario element is present""" return XmlUtil.hasChild(self, XbrlConst.xbrli, "scenario")
def validateXbrlFinally(val, *args, **kwargs): if not (val.validateHMRCplugin) or not val.txmyType: return modelXbrl = val.modelXbrl modelDocument = modelXbrl.modelDocument _statusMsg = _("validating {0} filing rules").format( val.disclosureSystem.name) modelXbrl.profileActivity() modelXbrl.modelManager.showStatus(_statusMsg) if modelDocument.type in (ModelDocument.Type.INSTANCE, ModelDocument.Type.INLINEXBRL): labelHasNegativeTermPattern = re.compile(r".*[(].*\w.*[)].*") companyReferenceNumberContexts = defaultdict(list) for c1 in modelXbrl.contexts.values(): scheme, identifier = c1.entityIdentifier if scheme == "http://www.companieshouse.gov.uk/": companyReferenceNumberContexts[identifier].append(c1.id) uniqueFacts = {} # key = (qname, context hash, unit hash, lang) mandatoryFacts = {} mandatoryGDV = defaultdict(set) factForConceptContextUnitHash = defaultdict(list) hasCompaniesHouseContext = any( cntx.entityIdentifier[0] == "http://www.companieshouse.gov.uk/" for cntx in val.modelXbrl.contexts.values()) contextsUsed = set(f.context for f in modelXbrl.factsInInstance if f.context is not None) contextsWithScenario = [] for cntx in contextsUsed: for dim in cntx.qnameDims.values(): if dim.isExplicit: _memName = dim.memberQname.localName m = memNameNumPattern.match(_memName) if m: l = m.group(1) n = int(m.group(2)) else: l = _memName n = None for _gdvType in (val.txmyType, "business"): gdv = genericDimensionValidation.get( _gdvType, EMPTYDICT).get(l) if gdv: # take first match break if (gdv and (n is None or (isinstance(gdv[0], int) and isinstance(gdv[1], int) and n >= gdv[0] and n <= gdv[1]))): gdvFacts = [f for f in gdv if isinstance(f, str)] if len(gdvFacts) == 1: mandatoryGDV[gdvFacts[0]].add( GDV(gdvFacts[0], None, _memName)) elif len(gdvFacts) == 2: mandatoryGDV[gdvFacts[0]].add( GDV(gdvFacts[0], gdvFacts[1], _memName)) mandatoryGDV[gdvFacts[1]].add( GDV(gdvFacts[1], gdvFacts[0], _memName)) if XmlUtil.hasChild(cntx, xbrli, "scenario"): contextsWithScenario.append(cntx) if contextsWithScenario: modelXbrl.error( "FRC.TG.3.6.1", _("Context(s) %(identifiers)s is reported with scenario element." ), modelObject=contextsWithScenario, identifiers=", ".join(c.id for c in contextsWithScenario)) del contextsWithScenario def checkFacts(facts): for f in facts: cntx = f.context unit = f.unit if ( f.isNil or getattr(f, "xValid", 0) >= 4 ) and cntx is not None and f.concept is not None and f.concept.type is not None: factNamespaceURI = f.qname.namespaceURI factLocalName = f.qname.localName if factLocalName in mandatoryItems[val.txmyType]: mandatoryFacts[factLocalName] = f if factLocalName == "UKCompaniesHouseRegisteredNumber" and val.isAccounts: if hasCompaniesHouseContext: mandatoryFacts[factLocalName] = f for _cntx in contextsUsed: _scheme, _identifier = _cntx.entityIdentifier if _scheme == "http://www.companieshouse.gov.uk/" and f.xValue != _identifier: modelXbrl.error( "JFCVC.3316", _("Context entity identifier %(identifier)s does not match Company Reference Number (UKCompaniesHouseRegisteredNumber) Location: Accounts (context id %(id)s)" ), modelObject=(f, _cntx), identifier=_identifier, id=_cntx.id) if f.parentElement.qname == qnXbrliXbrl: # JFCVC v4.0 - only check non-tuple facts factForConceptContextUnitHash[ f.conceptContextUnitHash].append(f) if f.isNumeric: if f.precision: modelXbrl.error( "HMRC.5.4", _("Numeric fact %(fact)s of context %(contextID)s has a precision attribute '%(precision)s'" ), modelObject=f, fact=f.qname, contextID=f.contextID, precision=f.precision) try: # only process validated facts if not f.isNil: if f.xValue < 0: label = f.concept.label(lang="en") if not labelHasNegativeTermPattern.match( label): modelXbrl.error( "HMRC.5.3", _("Numeric fact %(fact)s of context %(contextID)s has a negative value '%(value)s' but label does not have a bracketed negative term (using parentheses): %(label)s" ), modelObject=f, fact=f.qname, contextID=f.contextID, value=f.value, label=label) # 6.5.37 test (insignificant digits due to rounding) if f.decimals and f.decimals != "INF": try: insignificance = insignificantDigits( f.xValue, decimals=f.decimals) if insignificance: # if not None, returns (truncatedDigits, insiginficantDigits) modelXbrl.error( "HMRC.SG.4.5", _("Fact %(fact)s of context %(contextID)s decimals %(decimals)s value %(value)s has nonzero digits in insignificant portion %(insignificantDigits)s." ), modelObject=f, fact=f.qname, contextID=f.contextID, decimals=f.decimals, value=f.xValue, truncatedDigits=insignificance[ 0], insignificantDigits= insignificance[1]) except (ValueError, TypeError): modelXbrl.error( "HMRC.SG.4.5", _("Fact %(fact)s of context %(contextID)s decimals %(decimals)s value %(value)s causes Value Error exception." ), modelObject=f1, fact=f1.qname, contextID=f1.contextID, decimals=f1.decimals, value=f1.value) except AttributeError: pass # if not validated it should have failed with a schema error # check GDV if f.qname.localName in mandatoryGDV: _gdvReqList = mandatoryGDV[factLocalName] _gdvReqRemovals = [] for _gdvReq in _gdvReqList: if any(_gdvReq.memLocalName == dim.memberQname.localName for dim in cntx.qnameDims.values() if dim.isExplicit): _gdvReqRemovals.append(_gdvReq) if _gdvReq.altFact in mandatoryGDV: _gdvAltList = mandatoryGDV[_gdvReq.altFact] _gdvAltRemovals = [] for _gdvAlt in _gdvAltList: if any(_gdvAlt.memLocalName == dim.memberQname.localName for dim in cntx.qnameDims.values() if dim.isExplicit): _gdvAltRemovals.append(_gdvAlt) for _gdvAlt in _gdvAltRemovals: _gdvAltList.remove(_gdvAlt) if _gdvReqRemovals and not f.xValue: # fact was a mandatory name or description modelXbrl.error( "JFCVC.3315", _("Generic dimension members associated name/description has no text: %(fact)s" ), modelObject=f, fact=f.qname) for _gdvReq in _gdvReqRemovals: _gdvReqList.remove(_gdvReq) if f.modelTupleFacts: checkFacts(f.modelTupleFacts) checkFacts(modelXbrl.facts) if val.isAccounts: _missingItems = mandatoryItems[ val.txmyType] - mandatoryFacts.keys() if hasCompaniesHouseContext and "UKCompaniesHouseRegisteredNumber" not in mandatoryFacts: _missingItems.add("UKCompaniesHouseRegisteredNumber") if _missingItems: modelXbrl.error("JFCVC.3312", _("Facts are MANDATORY: %(missingItems)s"), modelObject=modelXbrl, missingItems=", ".join(sorted(_missingItems))) ''' removed with JFCVC v4.0 2020-06-09 f = mandatoryFacts.get("StartDateForPeriodCoveredByReport") if f is not None and (f.isNil or f.xValue < _6_APR_2008): modelXbrl.error("JFCVC.3313", _("Period Start Date (StartDateForPeriodCoveredByReport) must be 6 April 2008 or later, but is %(value)s"), modelObject=f, value=f.value) ''' memLocalNamesMissing = set( "{}({})".format(_gdvRec.memLocalName, _gdvRec.factNames) for _gdv in mandatoryGDV.values() for _gdvRec in _gdv) if memLocalNamesMissing: modelXbrl.error( "JFCVC.3315", _("Generic dimension members have no associated name or description item, member names (name or description item): %(memberNames)s" ), modelObject=modelXbrl, memberNames=", ".join(sorted(memLocalNamesMissing))) aspectEqualFacts = defaultdict( dict) # dict [(qname,lang)] of dict(cntx,unit) of [fact, fact] for hashEquivalentFacts in factForConceptContextUnitHash.values(): if len(hashEquivalentFacts) > 1: for f in hashEquivalentFacts: # check for hash collision by value checks on context and unit if getattr(f, "xValid", 0) >= 4: cuDict = aspectEqualFacts[( f.qname, (f.xmlLang or "").lower() if f.concept.type.isWgnStringFactType else None)] _matched = False for (_cntx, _unit), fList in cuDict.items(): if (((_cntx is None and f.context is None) or (f.context is not None and f.context.isEqualTo(_cntx))) and ((_unit is None and f.unit is None) or (f.unit is not None and f.unit.isEqualTo(_unit)))): _matched = True fList.append(f) break if not _matched: cuDict[(f.context, f.unit)] = [f] decVals = {} for cuDict in aspectEqualFacts.values(): # dups by qname, lang for fList in cuDict.values( ): # dups by equal-context equal-unit if len(fList) > 1: f0 = fList[0] if f0.concept.isNumeric: if any(f.isNil for f in fList): _inConsistent = not all(f.isNil for f in fList) else: # not all have same decimals _d = inferredDecimals(f0) _v = f0.xValue _inConsistent = isnan( _v ) # NaN is incomparable, always makes dups inconsistent decVals[_d] = _v aMax, bMin = rangeValue(_v, _d) for f in fList[1:]: _d = inferredDecimals(f) _v = f.xValue if isnan(_v): _inConsistent = True break if _d in decVals: _inConsistent |= _v != decVals[_d] else: decVals[_d] = _v a, b = rangeValue(_v, _d) if a > aMax: aMax = a if b < bMin: bMin = b if not _inConsistent: _inConsistent = (bMin < aMax) decVals.clear() if _inConsistent: modelXbrl.error( "JFCVC.3314", "Inconsistent duplicate fact values %(fact)s: %(values)s.", modelObject=fList, fact=f0.qname, contextID=f0.contextID, values=", ".join(f.value for f in fList)) aspectEqualFacts.clear() del factForConceptContextUnitHash, aspectEqualFacts if modelXbrl.modelDocument.type == ModelDocument.Type.INLINEXBRL: rootElt = modelXbrl.modelDocument.xmlRootElement if rootElt.tag in ("html", "xhtml") or not rootElt.tag.startswith( "{http://www.w3.org/1999/xhtml}"): modelXbrl.error( "HMRC.SG.3.3", _("InlineXBRL root element <%(element)s> MUST be html and have the xhtml namespace." ), modelObject=rootElt, element=rootElt.tag) for elt in rootElt.iterdescendants( tag="{http://www.w3.org/1999/xhtml}script"): modelXbrl.error("HMRC.SG.3.3", _("Script element is disallowed."), modelObject=elt) for tag, localName, attr in (("{http://www.w3.org/1999/xhtml}a", "a", "href"), ("{http://www.w3.org/1999/xhtml}img", "img", "src")): for elt in rootElt.iterdescendants(tag=tag): attrValue = (elt.get(attr) or "").strip() if "javascript:" in attrValue: modelXbrl.error( "HMRC.SG.3.3", _("Element %(localName)s javascript %(javascript)s is disallowed." ), modelObject=elt, localName=localName, javascript=attrValue[:64]) if localName == "img" and not any( attrValue.startswith(m) for m in allowedImgMimeTypes): modelXbrl.error( "HMRC.SG.3.8", _("Image scope must be base-64 encoded string (starting with data:image/*;base64), *=gif, jpeg or png. src disallowed: %(src)s." ), modelObject=elt, src=attrValue[:128]) for elt in rootElt.iterdescendants( tag="{http://www.w3.org/1999/xhtml}style"): if elt.text and styleImgUrlPattern.match(elt.text): modelXbrl.error( "HMRC.SG.3.8", _("Style element has disallowed image reference: %(styleImage)s." ), modelObject=elt, styleImage=styleImgUrlPattern.match(elt.text).group(1)) for elt in rootElt.xpath( "//xhtml:*[@style]", namespaces={"xhtml": "http://www.w3.org/1999/xhtml"}): for match in styleImgUrlPattern.findall(elt.get("style")): modelXbrl.error( "HMRC.SG.3.8", _("Element %(elt)s style attribute has disallowed image reference: %(styleImage)s." ), modelObject=elt, elt=elt.tag.rpartition("}")[2], styleImage=match) modelXbrl.profileActivity(_statusMsg, minTimeToShow=0.0) modelXbrl.modelManager.showStatus(None)
def checkFormulaRules(val, formula, nameVariables): from arelle.ModelFormulaObject import (Aspect) if not (formula.hasRule(Aspect.CONCEPT) or formula.source(Aspect.CONCEPT)): if XmlUtil.hasDescendant(formula.element, XbrlConst.formula, "concept"): val.modelXbrl.error(_("Formula {0} concept rule does not have a nearest source and does not have a child element").format(formula.xlinkLabel), "err", "xbrlfe:incompleteConceptRule") else: val.modelXbrl.error(_("Formula {0} omits a rule for the concept aspect").format(formula.xlinkLabel), "err", "xbrlfe:missingConceptRule") if (not (formula.hasRule(Aspect.SCHEME) or formula.source(Aspect.SCHEME)) or not (formula.hasRule(Aspect.VALUE) or formula.source(Aspect.VALUE))): if XmlUtil.hasDescendant(formula.element, XbrlConst.formula, "entityIdentifier"): val.modelXbrl.error(_("Formula {0} entity identifier rule does not have a nearest source and does not have either a @scheme or a @value attribute").format(formula.xlinkLabel), "err", "xbrlfe:incompleteEntityIdentifierRule") else: val.modelXbrl.error(_("Formula {0} omits a rule for the entity identifier aspect").format(formula.xlinkLabel), "err", "xbrlfe:missingEntityIdentifierRule") if not (formula.hasRule(Aspect.PERIOD_TYPE) or formula.source(Aspect.PERIOD_TYPE)): if XmlUtil.hasDescendant(formula.element, XbrlConst.formula, "period"): val.modelXbrl.error(_("Formula {0} period rule does not have a nearest source and does not have a child element").format(formula.xlinkLabel), "err", "xbrlfe:incompletePeriodRule") else: val.modelXbrl.error(_("Formula {0} omits a rule for the period aspect").format(formula.xlinkLabel), "err", "xbrlfe:missingPeriodRule") # for unit need to see if the qname is statically determinable to determine if numeric concept = val.modelXbrl.qnameConcepts.get(formula.evaluateRule(None, Aspect.CONCEPT)) if not concept: # is there a source with a static QName filter sourceFactVar = nameVariables.get(formula.source(Aspect.CONCEPT)) if isinstance(sourceFactVar, ModelFactVariable): for varFilterRels in (formula.groupFilterRelationships, sourceFactVar.filterRelationships): for varFilterRel in varFilterRels: filter = varFilterRel.toModelObject if isinstance(filter,ModelConceptName): # relationship not constrained to real filters for conceptQname in filter.conceptQnames: concept = val.modelXbrl.qnameConcepts.get(conceptQname) if concept and concept.isNumeric: break if concept: # from concept aspect rule or from source factVariable concept Qname filter if concept.isNumeric: if not (formula.hasRule(Aspect.MULTIPLY_BY) or formula.hasRule(Aspect.DIVIDE_BY) or formula.source(Aspect.UNIT)): if XmlUtil.hasDescendant(formula.element, XbrlConst.formula, "unit"): val.modelXbrl.error(_("Formula {0} unit rule does not have a source and does not have a child element").format(formula.xlinkLabel), "err", "xbrlfe:missingSAVForUnitRule") else: val.modelXbrl.error(_("Formula {0} omits a rule for the unit aspect").format(formula.xlinkLabel), "err", "xbrlfe:missingUnitRule") elif (formula.hasRule(Aspect.MULTIPLY_BY) or formula.hasRule(Aspect.DIVIDE_BY) or formula.source(Aspect.UNIT, acceptFormulaSource=False)): val.modelXbrl.error(_("Formula {0} has a rule for the unit aspect of a non-numeric concept {1}").format(formula.xlinkLabel, str(concept.qname)), "err", "xbrlfe:conflictingAspectRules") aspectPeriodType = formula.evaluateRule(None, Aspect.PERIOD_TYPE) if ((concept.periodType == "duration" and aspectPeriodType == "instant") or (concept.periodType == "instant" and aspectPeriodType in ("duration","forever"))): val.modelXbrl.error(_("Formula {0} has a rule for the {2} period aspect of a {3} concept {1}").format(formula.xlinkLabel, str(concept.qname), aspectPeriodType, concept.periodType), "err", "xbrlfe:conflictingAspectRules") # check dimension elements for eltName, dim, badUsageErr, missingSavErr in (("explicitDimension", "explicit", "xbrlfe:badUsageOfExplicitDimensionRule", "xbrlfe:missingSAVForExplicitDimensionRule"), ("typedDimension", "typed", "xbrlfe:badUsageOfTypedDimensionRule", "xbrlfe:missingSAVForTypedDimensionRule")): for dimElt in XmlUtil.descendants(formula.element, XbrlConst.formula, eltName): dimQname = qname(dimElt, dimElt.getAttribute("dimension")) dimConcept = val.modelXbrl.qnameConcepts.get(dimQname) if dimQname and (not dimConcept or (not dimConcept.isExplicitDimension if dim == "explicit" else not dimConcept.isTypedDimension)): val.modelXbrl.error(_("Formula {0} dimension attribute {1} on the {2} dimension rule contains a QName that does not identify an {2} dimension.").format(formula.xlinkLabel, dimQname, dim), "err", badUsageErr) elif not XmlUtil.hasChild(dimElt, XbrlConst.formula, "*") and not formula.source(Aspect.DIMENSIONS, dimElt): val.modelXbrl.error(_("Formula {0} {1} dimension rule does not have any child elements and does not have a SAV for the {2} dimension that is identified by its dimension attribute.").format(formula.xlinkLabel, dim, dimQname), "err", missingSavErr) # check aspect model expectations if formula.aspectModel == "non-dimensional": unexpectedElts = XmlUtil.descendants(formula.element, XbrlConst.formula, ("explicitDimension", "typedDimension")) if unexpectedElts: val.modelXbrl.error(_("Formula {0} aspect model, {1}, includes an rule for aspect not defined in this aspect model: {2}").format( formula.xlinkLabel, formula.aspectModel, ", ".join([elt.localName for elt in unexpectedElts])), "err", "xbrlfe:unrecognisedAspectRule") # check source qnames for sourceElt in ([formula.element] + XmlUtil.descendants(formula.element, XbrlConst.formula, "*", "source","*")): if sourceElt.hasAttribute("source"): qnSource = qname(sourceElt, sourceElt.getAttribute("source"), noPrefixIsNoNamespace=True) if qnSource == XbrlConst.qnFormulaUncovered: if formula.implicitFiltering != "true": val.modelXbrl.error(_("Formula {0}, not implicit filtering element has formulaUncovered source: {1}").format( formula.xlinkLabel, sourceElt.localName), "err", "xbrlfe:illegalUseOfUncoveredQName") elif qnSource not in nameVariables: val.modelXbrl.error(_("Variable set {0}, source {1} is not in the variable set").format( formula.xlinkLabel, qnSource), "err", "xbrlfe:nonexistentSourceVariable") else: factVariable = nameVariables.get(qnSource) if not isinstance(factVariable, ModelFactVariable): val.modelXbrl.error(_("Variable set {0}, source {1} not a factVariable but is a {2}").format( formula.xlinkLabel, qnSource, factVariable.localName), "err", "xbrlfe:nonexistentSourceVariable") elif factVariable.fallbackValue is not None: val.modelXbrl.error(_("Formula {0}: source {1} is a fact variable that has a fallback value").format(formula.xlinkLabel, str(qnSource)), "err", "xbrlfe:bindEmptySourceVariable") elif sourceElt.localName == "formula" and factVariable.bindAsSequence == "true": val.modelXbrl.error(_("Formula {0}: formula source {1} is a fact variable that binds as a sequence").format(formula.xlinkLabel, str(qnSource)), "err", "xbrlfe:defaultAspectValueConflicts")
def validateFacts(val, factsToCheck): # may be called in streaming batches or all at end (final) if not streaming modelXbrl = val.modelXbrl modelDocument = modelXbrl.modelDocument # note EBA 2.1 is in ModelDocument.py timelessDatePattern = re.compile(r"\s*([0-9]{4})-([0-9]{2})-([0-9]{2})\s*$") for cntx in modelXbrl.contexts.values(): if getattr(cntx, "_batchChecked", False): continue # prior streaming batch already checked cntx._batchChecked = True val.cntxEntities.add(cntx.entityIdentifier) dateElts = XmlUtil.descendants(cntx, XbrlConst.xbrli, ("startDate","endDate","instant")) if any(not timelessDatePattern.match(e.textValue) for e in dateElts): modelXbrl.error("EBA.2.10", _('Period dates must be whole dates without time or timezone: %(dates)s.'), modelObject=cntx, dates=", ".join(e.text for e in dateElts)) if cntx.isForeverPeriod: modelXbrl.error("EBA.2.11", _('Forever context period is not allowed.'), modelObject=cntx) elif cntx.isStartEndPeriod: modelXbrl.error("EBA.2.13", _('Start-End (flow) context period is not allowed.'), modelObject=cntx) elif cntx.isInstantPeriod: # cannot pass context object to final() below, for error logging, if streaming mode val.cntxDates[cntx.instantDatetime].add(modelXbrl if getattr(val.modelXbrl, "isStreamingMode", False) else cntx) if XmlUtil.hasChild(cntx, XbrlConst.xbrli, "segment"): modelXbrl.error("EBA.2.14", _("The segment element not allowed in context Id: %(context)s"), modelObject=cntx, context=cntx.contextID) for scenElt in XmlUtil.descendants(cntx, XbrlConst.xbrli, "scenario"): childTags = ", ".join([child.prefixedName for child in scenElt.iterchildren() if isinstance(child,ModelObject) and child.tag != "{http://xbrl.org/2006/xbrldi}explicitMember" and child.tag != "{http://xbrl.org/2006/xbrldi}typedMember"]) if len(childTags) > 0: modelXbrl.error("EBA.2.15", _("Scenario of context Id %(context)s has disallowed content: %(content)s"), modelObject=cntx, context=cntx.id, content=childTags) val.unusedCntxIDs.add(cntx.id) for unit in modelXbrl.units.values(): if getattr(unit, "_batchChecked", False): continue # prior streaming batch already checked unit._batchChecked = True val.unusedUnitIDs.add(unit.id) factsByQname = defaultdict(set) # top level for this for f in factsToCheck: factsByQname[f.qname].add(f) val.unusedCntxIDs.discard(f.contextID) val.unusedUnitIDs.discard(f.unitID) for fIndicators in factsByQname[qnFIndicators]: val.numFilingIndicatorTuples += 1 for fIndicator in fIndicators.modelTupleFacts: _value = (fIndicator.xValue or fIndicator.value) # use validated xValue if DTS else value for skipDTS if _value in val.filingIndicators: modelXbrl.error("EBA.1.6.1", _('Multiple filing indicators facts for indicator %(filingIndicator)s.'), modelObject=(fIndicator, val.filingIndicators[_value]), filingIndicator=_value) val.filingIndicators[_value] = fIndicator val.unusedCntxIDs.discard(fIndicator.contextID) otherFacts = {} # (contextHash, unitHash, xmlLangHash) : fact nilFacts = [] # removed in current draft: stringFactsWithoutXmlLang = [] nonMonetaryNonPureFacts = [] for qname, facts in factsByQname.items(): for f in facts: if modelXbrl.skipDTS: c = f.qname.localName[0] isNumeric = c in ('m', 'p', 'i') isMonetary = c == 'm' isInteger = c == 'i' isPercent = c == 'p' isString = c == 's' else: concept = f.concept if concept is not None: isNumeric = concept.isNumeric isMonetary = concept.isMonetary isInteger = concept.baseXbrliType in integerItemTypes isPercent = concept.typeQname == qnPercentItemType isString = concept.baseXbrliType in ("stringItemType", "normalizedStringItemType") else: isNumeric = isString = False # error situation k = (f.getparent().objectIndex, f.qname, f.context.contextDimAwareHash if f.context is not None else None, f.unit.hash if f.unit is not None else None, hash(f.xmlLang)) if f.qname == qnFIndicators and val.validateEIOPA: pass elif k not in otherFacts: otherFacts[k] = {f} else: matches = [o for o in otherFacts[k] if (f.getparent().objectIndex == o.getparent().objectIndex and f.qname == o.qname and f.context.isEqualTo(o.context) if f.context is not None and o.context is not None else True) and # (f.unit.isEqualTo(o.unit) if f.unit is not None and o.unit is not None else True) and (f.xmlLang == o.xmlLang)] if matches: contexts = [f.contextID] + [o.contextID for o in matches] modelXbrl.error("EBA.2.16", _('Facts are duplicates %(fact)s contexts %(contexts)s.'), modelObject=[f] + matches, fact=f.qname, contexts=', '.join(contexts)) else: otherFacts[k].add(f) if isNumeric: if f.precision: modelXbrl.error("EBA.2.17", _("Numeric fact %(fact)s of context %(contextID)s has a precision attribute '%(precision)s'"), modelObject=f, fact=f.qname, contextID=f.contextID, precision=f.precision) if f.decimals and f.decimals != "INF": try: dec = int(f.decimals) if isMonetary: if dec < -3: modelXbrl.error("EBA.2.17", _("Monetary fact %(fact)s of context %(contextID)s has a decimal attribute < -3: '%(decimals)s'"), modelObject=f, fact=f.qname, contextID=f.contextID, decimals=f.decimals) elif isInteger: if dec != 0: modelXbrl.error("EBA.2.17", _("Integer fact %(fact)s of context %(contextID)s has a decimal attribute \u2260 0: '%(decimals)s'"), modelObject=f, fact=f.qname, contextID=f.contextID, decimals=f.decimals) elif isPercent: if dec < 4: modelXbrl.error("EBA.2.17", _("Percent fact %(fact)s of context %(contextID)s has a decimal attribute < 4: '%(decimals)s'"), modelObject=f, fact=f.qname, contextID=f.contextID, decimals=f.decimals) except ValueError: pass # should have been reported as a schema error by loader '''' (not intended by EBA 2.18, paste here is from EFM) if not f.isNil and getattr(f,"xValid", 0) == 4: try: insignificance = insignificantDigits(f.xValue, decimals=f.decimals) if insignificance: # if not None, returns (truncatedDigits, insiginficantDigits) modelXbrl.error(("EFM.6.05.37", "GFM.1.02.26"), _("Fact %(fact)s of context %(contextID)s decimals %(decimals)s value %(value)s has nonzero digits in insignificant portion %(insignificantDigits)s."), modelObject=f1, fact=f1.qname, contextID=f1.contextID, decimals=f1.decimals, value=f1.xValue, truncatedDigits=insignificance[0], insignificantDigits=insignificance[1]) except (ValueError,TypeError): modelXbrl.error(("EBA.2.18"), _("Fact %(fact)s of context %(contextID)s decimals %(decimals)s value %(value)s causes Value Error exception."), modelObject=f1, fact=f1.qname, contextID=f1.contextID, decimals=f1.decimals, value=f1.value) ''' unit = f.unit if unit is not None: if isMonetary: if unit.measures[0]: val.currenciesUsed[unit.measures[0][0]] = unit elif not unit.isSingleMeasure or unit.measures[0][0] != XbrlConst.qnXbrliPure: nonMonetaryNonPureFacts.append(f) ''' removed in current draft elif isString: if not f.xmlLang: stringFactsWithoutXmlLang.append(f) ''' if f.isNil: nilFacts.append(f) if nilFacts: modelXbrl.error("EBA.2.19", _('Nil facts MUST NOT be present in the instance: %(nilFacts)s.'), modelObject=nilFacts, nilFacts=", ".join(str(f.qname) for f in nilFacts)) ''' removed in current draft if stringFactsWithoutXmlLang: modelXbrl.error("EBA.2.20", _("String facts need to report xml:lang: '%(langLessFacts)s'"), modelObject=stringFactsWithoutXmlLang, langLessFacts=", ".join(set(str(f.qname) for f in stringFactsWithoutXmlLang))) ''' if nonMonetaryNonPureFacts: modelXbrl.error("EBA.3.2", _("Non monetary (numeric) facts MUST use the pure unit: '%(langLessFacts)s'"), modelObject=nonMonetaryNonPureFacts, langLessFacts=", ".join(set(str(f.qname) for f in nonMonetaryNonPureFacts))) val.utrValidator.validateFacts() # validate facts for UTR at logLevel WARNING unitHashes = {} for unit in modelXbrl.units.values(): h = hash(unit) if h in unitHashes and unit.isEqualTo(unitHashes[h]): modelXbrl.warning("EBA.2.22", _("Duplicate units SHOULD NOT be reported, units %(unit1)s and %(unit2)s have same measures.'"), modelObject=(unit, unitHashes[h]), unit1=unit.id, unit2=unitHashes[h].id) else: unitHashes[h] = unit for elt in modelDocument.xmlRootElement.iter(): if isinstance(elt, ModelObject): # skip comments and processing instructions val.namespacePrefixesUsed[elt.qname.namespaceURI].add(elt.qname.prefix) val.prefixesUnused.discard(elt.qname.prefix)
def validateXbrlFinally(val, *args, **kwargs): if not (val.validateESMAplugin): return _xhtmlNs = "{{{}}}".format(xhtml) _xhtmlNsLen = len(_xhtmlNs) modelXbrl = val.modelXbrl modelDocument = modelXbrl.modelDocument _statusMsg = _("validating {0} filing rules").format(val.disclosureSystem.name) modelXbrl.profileActivity() modelXbrl.modelManager.showStatus(_statusMsg) reportXmlLang = None firstRootmostXmlLangDepth = 9999999 if modelDocument.type == ModelDocument.Type.INSTANCE: modelXbrl.error("esma:instanceShallBeInlineXBRL", _("RTS on ESEF requires inline XBRL instances."), modelObject=modelXbrl) checkFilingDimensions(val) # sets up val.primaryItems and val.domainMembers val.hasExtensionSchema = val.hasExtensionPre = val.hasExtensionCal = val.hasExtensionDef = val.hasExtensionLbl = False checkFilingDTS(val, modelXbrl.modelDocument, []) modelXbrl.profileActivity("... filer DTS checks", minTimeToShow=1.0) if not (val.hasExtensionSchema and val.hasExtensionPre and val.hasExtensionCal and val.hasExtensionDef and val.hasExtensionLbl): missingFiles = [] if not val.hasExtensionSchema: missingFiles.append("schema file") if not val.hasExtensionPre: missingFiles.append("presentation linkbase") if not val.hasExtensionCal: missingFiles.append("calculation linkbase") if not val.hasExtensionDef: missingFiles.append("definition linkbase") if not val.hasExtensionLbl: missingFiles.append("label linkbase") modelXbrl.warning("esma:3.1.1.extensionTaxonomyWrongFilesStructure", _("Extension taxonomies MUST consist of at least a schema file and presentation, calculation, definition and label linkbases" ": missing %(missingFiles)s"), modelObject=modelXbrl, missingFiles=", ".join(missingFiles)) if modelDocument.type in (ModelDocument.Type.INLINEXBRL, ModelDocument.Type.INLINEXBRLDOCUMENTSET, ModelDocument.Type.INSTANCE): footnotesRelationshipSet = modelXbrl.relationshipSet("XBRL-footnotes") orphanedFootnotes = set() noLangFootnotes = set() footnoteRoleErrors = set() transformRegistryErrors = set() def checkFootnote(elt, text): if text: # non-empty footnote must be linked to a fact if not empty if not any(isinstance(rel.fromModelObject, ModelFact) for rel in footnotesRelationshipSet.toModelObject(elt)): orphanedFootnotes.add(elt) if not elt.xmlLang: noLangFootnotes.add(elt) if elt.role != XbrlConst.footnote or not all( rel.arcrole == XbrlConst.factFootnote and rel.linkrole == XbrlConst.defaultLinkRole for rel in footnotesRelationshipSet.toModelObject(elt)): footnoteRoleErrors.add(elt) # check file name of each inline document (which might be below a top-level IXDS) for doc in modelXbrl.urlDocs.values(): if doc.type == ModelDocument.Type.INLINEXBRL: _baseName, _baseExt = os.path.splitext(doc.basename) if _baseExt not in (".xhtml",): modelXbrl.warning("esma:TBD.fileNameExtension", _("FileName should have the extension .xhtml: %(fileName)s"), modelObject=doc, fileName=doc.basename) if modelDocument.type in (ModelDocument.Type.INLINEXBRL, ModelDocument.Type.INLINEXBRLDOCUMENTSET): ixNStag = modelXbrl.modelDocument.ixNStag ixTags = set(ixNStag + ln for ln in ("nonNumeric", "nonFraction", "references", "relationship")) ixTextTags = set(ixNStag + ln for ln in ("nonFraction", "continuation", "footnote")) ixExcludeTag = ixNStag + "exclude" ixTupleTag = ixNStag + "tuple" ixFractionTag = ixNStag + "fraction" hiddenEltIds = {} presentedHiddenEltIds = defaultdict(list) eligibleForTransformHiddenFacts = [] requiredToDisplayFacts = [] requiredToDisplayFactIds = {} firstIxdsDoc = True for ixdsHtmlRootElt in modelXbrl.ixdsHtmlElements: # ix root elements for all ix docs in IXDS for elt, depth in etreeIterWithDepth(ixdsHtmlRootElt): eltTag = elt.tag if isinstance(elt, ModelObject) and elt.namespaceURI == xhtml: eltTag = elt.localName if firstIxdsDoc and (not reportXmlLang or depth < firstRootmostXmlLangDepth): xmlLang = elt.get("{http://www.w3.org/XML/1998/namespace}lang") if xmlLang: reportXmlLang = xmlLang firstRootmostXmlLangDepth = depth elif isinstance(elt, (_ElementTree, _Comment, _ProcessingInstruction)): continue # comment or other non-parsed element else: eltTag = elt.tag if eltTag.startswith(_xhtmlNs): eltTag = eltTag[_xhtmlNsLen:] if ((eltTag in ("object", "script")) or (eltTag == "a" and "javascript:" in elt.get("href","")) or (eltTag == "img" and "javascript:" in elt.get("src",""))): modelXbrl.error("esma.2.5.1.executableCodePresent", _("Inline XBRL documents MUST NOT contain executable code: %(element)s"), modelObject=elt, element=eltTag) elif eltTag == "img": src = elt.get("src","").strip() hasParentIxTextTag = False # check if image is in an ix text-bearing element _ancestorElt = elt while (_ancestorElt is not None): if _ancestorElt.tag == ixExcludeTag: # excluded from any parent text-bearing ix element break if _ancestorElt.tag in ixTextTags: hasParentIxTextTag = True break _ancestorElt = _ancestorElt.getparent() if scheme(href) in ("http", "https", "ftp"): modelXbrl.error("esma.3.5.1.inlinXbrlContainsExternalReferences", _("Inline XBRL instance documents MUST NOT contain any reference pointing to resources outside the reporting package: %(element)s"), modelObject=elt, element=eltTag) if not src.startswith("data:image"): if hasParentIxTextTag: modelXbrl.error("esma.2.5.1.imageInIXbrlElementNotEmbedded", _("Images appearing within an inline XBRL element MUST be embedded regardless of their size."), modelObject=elt) else: # presume it to be an image file, check image contents try: base = elt.modelDocument.baseForElement(elt) normalizedUri = elt.modelXbrl.modelManager.cntlr.webCache.normalizeUrl(graphicFile, base) if not elt.modelXbrl.fileSource.isInArchive(normalizedUri): normalizedUri = elt.modelXbrl.modelManager.cntlr.webCache.getfilename(normalizedUri) imglen = 0 with elt.modelXbrl.fileSource.file(normalizedUri,binary=True)[0] as fh: imglen += len(fh.read()) if imglen < browserMaxBase64ImageLength: modelXbrl.error("esma.2.5.1.embeddedImageNotUsingBase64Encoding", _("Images MUST be included in the XHTML document as a base64 encoded string unless their size exceeds support of browsers."), modelObject=elt) except IOError as err: modelXbrl.error("esma.2.5.1.imageFileCannotBeLoaded", _("Image file which isn't openable '%(src)s', error: %(error)s"), modelObject=elt, src=src, error=err) elif not any(src.startswith(m) for m in allowedImgMimeTypes): modelXbrl.error("esma.2.5.1.embeddedImageNotUsingBase64Encoding", _("Images MUST be included in the XHTML document as a base64 encoded string, encoding disallowed: %(src)s."), modelObject=elt, src=attrValue[:128]) elif eltTag == "a": href = elt.get("href","").strip() if scheme(href) in ("http", "https", "ftp"): modelXbrl.error("esma.3.5.1.inlinXbrlContainsExternalReferences", _("Inline XBRL instance documents MUST NOT contain any reference pointing to resources outside the reporting package: %(element)s"), modelObject=elt, element=eltTag) elif eltTag == "base" or elt.tag == "{http://www.w3.org/XML/1998/namespace}base": modelXbrl.error("esma.2.4.2.htmlOrXmlBaseUsed", _("The HTML <base> elements and xml:base attributes MUST NOT be used in the Inline XBRL document."), modelObject=elt, element=eltTag) if eltTag in ixTags and elt.get("target"): modelXbrl.error("esma.2.5.3.targetAttributeUsed", _("Target attribute MUST not be used: element %(localName)s, target attribute %(target)s."), modelObject=elt, localName=elt.elementQname, target=elt.get("target")) if eltTag == ixTupleTag: modelXbrl.error("esma.2.4.1.tupleElementUsed", _("The ix:tuple element MUST not be used in the Inline XBRL document."), modelObject=elt) if eltTag == ixFractionTag: modelXbrl.error("esma.2.4.1.fractionElementUsed", _("The ix:fraction element MUST not be used in the Inline XBRL document."), modelObject=elt) if elt.get("{http://www.w3.org/XML/1998/namespace}base") is not None: modelXbrl.error("esma.2.4.1.xmlBaseUsed", _("xml:base attributes MUST NOT be used in the Inline XBRL document: element %(localName)s, base attribute %(base)s."), modelObject=elt, localName=elt.elementQname, base=elt.get("{http://www.w3.org/XML/1998/namespace}base")) if isinstance(elt, ModelInlineFootnote): checkFootnote(elt, elt.value) elif isinstance(elt, ModelResource) and elt.qname == XbrlConst.qnLinkFootnote: checkFootnote(elt, elt.value) elif isinstance(elt, ModelInlineFact): if elt.format is not None and elt.format.namespaceURI != 'http://www.xbrl.org/inlineXBRL/transformation/2015-02-26': transformRegistryErrors.add(elt) for ixHiddenElt in ixdsHtmlRootElt.iterdescendants(tag=ixNStag + "hidden"): for tag in (ixNStag + "nonNumeric", ixNStag+"nonFraction"): for ixElt in ixHiddenElt.iterdescendants(tag=tag): if (getattr(ixElt, "xValid", 0) >= VALID # may not be validated ): # add future "and" conditions on elements which can be in hidden if (ixElt.concept.baseXsdType not in untransformableTypes and not ixElt.isNil): eligibleForTransformHiddenFacts.append(ixElt) elif ixElt.id is None: requiredToDisplayFacts.append(ixElt) if ixElt.id: hiddenEltIds[ixElt.id] = ixElt firstIxdsDoc = False if eligibleForTransformHiddenFacts: modelXbrl.warning("esma.2.4.1.transformableElementIncludedInHiddenSection", _("The ix:hidden section of Inline XBRL document MUST not include elements eligible for transformation. " "%(countEligible)s fact(s) were eligible for transformation: %(elements)s"), modelObject=eligibleForTransformHiddenFacts, countEligible=len(eligibleForTransformHiddenFacts), elements=", ".join(sorted(set(str(f.qname) for f in eligibleForTransformHiddenFacts)))) for ixdsHtmlRootElt in modelXbrl.ixdsHtmlElements: for ixElt in ixdsHtmlRootElt.getroottree().iterfind("//{http://www.w3.org/1999/xhtml}*[@style]"): hiddenFactRefMatch = styleIxHiddenPattern.match(ixElt.get("style","")) if hiddenFactRefMatch: hiddenFactRef = hiddenFactRefMatch.group(2) if hiddenFactRef not in hiddenEltIds: modelXbrl.error("esma.2.4.1.esefIxHiddenStyleNotLinkingFactInHiddenSection", _("\"-esef-ix-hidden\" style identifies @id, %(id)s of a fact that is not in ix:hidden section."), modelObject=ixElt, id=hiddenFactRef) else: presentedHiddenEltIds[hiddenFactRef].append(ixElt) for hiddenEltId, ixElt in hiddenEltIds.items(): if (hiddenEltId not in presentedHiddenEltIds and getattr(ixElt, "xValid", 0) >= VALID and # may not be validated (ixElt.concept.baseXsdType in untransformableTypes or ixElt.isNil)): requiredToDisplayFacts.append(ixElt) if requiredToDisplayFacts: modelXbrl.warning("esma.2.4.1.factInHiddenSectionNotInReport", _("The ix:hidden section contains %(countUnreferenced)s fact(s) whose @id is not applied on any \"-esef-ix- hidden\" style: %(elements)s"), modelObject=requiredToDisplayFacts, countUnreferenced=len(requiredToDisplayFacts), elements=", ".join(sorted(set(str(f.qname) for f in requiredToDisplayFacts)))) del eligibleForTransformHiddenFacts, hiddenEltIds, presentedHiddenEltIds, requiredToDisplayFacts elif modelDocument.type == ModelDocument.Type.INSTANCE: for elt in modelDocument.xmlRootElement.iter(): if elt.qname == XbrlConst.qnLinkFootnote: # for now assume no private elements extend link:footnote checkFootnote(elt, elt.stringValue) contextsWithDisallowedOCEs = [] contextsWithDisallowedOCEcontent = [] contextsWithPeriodTime = [] contextsWithPeriodTimeZone = [] contextIdentifiers = defaultdict(list) nonStandardTypedDimensions = defaultdict(set) for context in modelXbrl.contexts.values(): if XmlUtil.hasChild(context, XbrlConst.xbrli, "segment"): contextsWithDisallowedOCEs.append(context) for segScenElt in context.iterdescendants("{http://www.xbrl.org/2003/instance}scenario"): if isinstance(segScenElt,ModelObject): if any(True for child in segScenElt.iterchildren() if isinstance(child,ModelObject) and child.tag not in ("{http://xbrl.org/2006/xbrldi}explicitMember", "{http://xbrl.org/2006/xbrldi}typedMember")): contextsWithDisallowedOCEcontent.append(context) # check periods here contextIdentifiers[context.entityIdentifier].append(context) if contextsWithDisallowedOCEs: modelXbrl.error("esma.2.1.3.segmentUsed", _("xbrli:segment container MUST NOT be used in contexts: %(contextIds)s"), modelObject=contextsWithDisallowedOCEs, contextIds=", ".join(c.id for c in contextsWithDisallowedOCEs)) if contextsWithDisallowedOCEcontent: modelXbrl.error("esma.2.1.3.scenarioContainsNonDimensionalContent", _("xbrli:scenario in contexts MUST NOT contain any other content than defined in XBRL Dimensions specification: %(contextIds)s"), modelObject=contextsWithDisallowedOCEcontent, contextIds=", ".join(c.id for c in contextsWithDisallowedOCEcontent)) if len(contextIdentifiers) > 1: modelXbrl.error("esma.2.1.4.multipleIdentifiers", _("All entity identifiers in contexts MUST have identical content: %(contextIdentifiers)s"), modelObject=modelXbrl, contextIds=", ".join(i[1] for i in contextIdentifiers)) for (contextScheme, contextIdentifier), contextElts in contextIdentifiers.items(): if contextScheme != "http://standards.iso.org/iso/17442": modelXbrl.warning("esma.2.1.1.nonLEIContextScheme", _("The scheme attribute of the xbrli:identifier element should have \"http://standards.iso.org/iso/17442\" as its content: %(scheme)s"), modelObject=contextElts, scheme=contextScheme) else: leiValidity = LeiUtil.checkLei(contextIdentifier) if leiValidity == LeiUtil.LEI_INVALID_LEXICAL: modelXbrl.warning("esma.2.1.1.invalidIdentifierFormat", _("The LEI context idenntifier has an invalid format: %(identifier)s"), modelObject=contextElts, identifier=contextIdentifier) elif leiValidity == LeiUtil.LEI_INVALID_CHECKSUM: modelXbrl.warning("esma.2.1.1.invalidIdentifier", _("The LEI context idenntifier has checksum error: %(identifier)s"), modelObject=contextElts, identifier=contextIdentifier) if contextsWithPeriodTime: modelXbrl.warning("esma.2.1.2.periodWithTimeContent", _("Context period startDate, endDate and instant elements should be in whole days without time: %(contextIds)s"), modelObject=contextsWithPeriodTime, contextIds=", ".join(c.id for c in contextsWithPeriodTime)) if contextsWithPeriodTimeZone: modelXbrl.warning("esma.2.1.2.periodWithTimeZone", _("Context period startDate, endDate and instant elements should be in whole days without a timezone: %(contextIds)s"), modelObject=contextsWithPeriodTimeZone, contextIds=", ".join(c.id for c in contextsWithPeriodTimeZone)) # identify unique contexts and units mapContext = {} mapUnit = {} uniqueContextHashes = {} for context in modelXbrl.contexts.values(): h = context.contextDimAwareHash if h in uniqueContextHashes: if context.isEqualTo(uniqueContextHashes[h]): mapContext[context] = uniqueContextHashes[h] else: uniqueContextHashes[h] = context del uniqueContextHashes uniqueUnitHashes = {} for unit in modelXbrl.units.values(): h = unit.hash if h in uniqueUnitHashes: if unit.isEqualTo(uniqueUnitHashes[h]): mapUnit[unit] = uniqueUnitHashes[h] else: uniqueUnitHashes[h] = unit del uniqueUnitHashes reportedMandatory = set() precisionFacts = set() numFactsByConceptContextUnit = defaultdict(list) textFactsByConceptContext = defaultdict(list) footnotesRelationshipSet = modelXbrl.relationshipSet(XbrlConst.factFootnote, XbrlConst.defaultLinkRole) noLangFacts = [] textFactsMissingReportLang = [] conceptsUsed = set() for qn, facts in modelXbrl.factsByQname.items(): if qn in mandatory: reportedMandatory.add(qn) for f in facts: if f.precision is not None: precisionFacts.add(f) if f.isNumeric: numFactsByConceptContextUnit[(f.qname, mapContext.get(f.context,f.context), mapUnit.get(f.unit, f.unit))].append(f) elif f.concept is not None and f.concept.type is not None: if f.concept.type.isOimTextFactType: if not f.xmlLang: noLangFacts.append(f) elif f.context is not None: textFactsByConceptContext[(f.qname, mapContext.get(f.context,f.context))].append(f) conceptsUsed.add(f.concept) if f.context is not None: for dim in f.context.qnameDims.values(): conceptsUsed.add(dim.dimension) if dim.isExplicit: conceptsUsed.add(dim.member) elif dim.isTyped: conceptsUsed.add(dim.typedMember) if noLangFacts: modelXbrl.error("esma.2.5.2.undefinedLanguageForTextFact", _("Each tagged text fact MUST have the 'xml:lang' attribute assigned or inherited."), modelObject=noLangFacts) # missing report lang text facts for fList in textFactsByConceptContext.values(): if not any(f.xmlLang == reportXmlLang for f in fList): modelXbrl.error("esma.2.5.2.taggedTextFactOnlyInLanguagesOtherThanLanguageOfAReport", _("Each tagged text fact MUST have the 'xml:lang' provided in at least the language of the report: %(element)s"), modelObject=fList, element=fList[0].qname) # 2.2.4 test for fList in numFactsByConceptContextUnit.values(): if len(fList) > 1: f0 = fList[0] if any(f.isNil for f in fList): _inConsistent = not all(f.isNil for f in fList) elif all(inferredDecimals(f) == inferredDecimals(f0) for f in fList[1:]): # same decimals v0 = rangeValue(f0.value) _inConsistent = not all(rangeValue(f.value) == v0 for f in fList[1:]) else: # not all have same decimals aMax, bMin = rangeValue(f0.value, inferredDecimals(f0)) for f in fList[1:]: a, b = rangeValue(f.value, inferredDecimals(f)) if a > aMax: aMax = a if b < bMin: bMin = b _inConsistent = (bMin < aMax) if _inConsistent: modelXbrl.error(("esma:2.2.4.inconsistentDuplicateNumericFactInInlineXbrlDocument"), "Inconsistent duplicate numeric facts MUST NOT appear in the content of an inline XBRL document. %(fact)s that was used more than once in contexts equivalent to %(contextID)s: values %(values)s. ", modelObject=fList, fact=f0.qname, contextID=f0.contextID, values=", ".join(strTruncate(f.value, 128) for f in fList)) if precisionFacts: modelXbrl.warning("esma:2.2.1.precisionAttributeUsed", _("The accuracy of numeric facts SHOULD be defined with the 'decimals' attribute rather than the 'precision' attribute: %(elements)s."), modelObject=precisionFacts, elements=", ".join(sorted(str(qn) for qn in precisionFacts))) missingElements = (mandatory - reportedMandatory) if missingElements: modelXbrl.error("esma:???.missingRequiredElements", _("Required elements missing from document: %(elements)s."), modelObject=modelXbrl, elements=", ".join(sorted(str(qn) for qn in missingElements))) if transformRegistryErrors: modelXbrl.warning("esma:2.2.3.transformRegistry", _("ESMA recommends applying the latest available version of the Transformation Rules Registry marked with 'Recommendation' status for these elements: %(elements)s."), modelObject=transformRegistryErrors, elements=", ".join(sorted(str(fact.qname) for fact in transformRegistryErrors))) if orphanedFootnotes: modelXbrl.error("esma.2.3.1.unusedFootnote", _("Non-empty footnotes must be connected to fact(s)."), modelObject=orphanedFootnotes) if noLangFootnotes: modelXbrl.error("esma.2.3.2.undefinedLanguageForFootnote", _("Each footnote MUST have the 'xml:lang' attribute whose value corresponds to the language of the text in the content of the respective footnote."), modelObject=noLangFootnotes) if footnoteRoleErrors: modelXbrl.error("esma.2.3.2.nonStandardRoleForFootnote", _("The xlink:role attribute of a link:footnote and link:footnoteLink element as well as xlink:arcrole attribute of a link:footnoteArc MUST be defined in the XBRL Specification 2.1."), modelObject=footnoteRoleErrors) nonStdFootnoteElts = list() for modelLink in modelXbrl.baseSets[("XBRL-footnotes",None,None,None)]: for elt in ixdsHtmlRootElt.iter(): if isinstance(elt, (_ElementTree, _Comment, _ProcessingInstruction)): continue # comment or other non-parsed element if elt.namespaceURI != link or elt.localName not in ("loc", "link", "footnoteArc"): nonStdFootnoteElts.append(elt) if nonStdFootnoteElts: modelXbrl.error("esma.2.3.2.nonStandardElementInFootnote", _("A link:footnoteLink element MUST have no children other than link:loc, link:footnote, and link:footnoteArc."), modelObject=nonStdFootnoteElts) for qn in modelXbrl.qnameDimensionDefaults.values(): conceptsUsed.add(modelXbrl.qnameConcepts.get(qn)) # unused elements in linkbases for arcroles, err in (((parentChild,), "elementsNotUsedForTaggingAppliedInPresentationLinkbase"), ((summationItem,), "elementsNotUsedForTaggingAppliedInCalculationLinkbase"), ((dimensionDomain,domainMember), "elementsNotUsedForTaggingAppliedInDefinitionLinkbase")): lbElts = set() for arcrole in arcroles: for rel in modelXbrl.relationshipSet(arcrole).modelRelationships: fr = rel.fromModelObject to = rel.toModelObject if arcrole in (parentChild, summationItem): if fr is not None and not fr.isAbstract: lbElts.add(fr) if to is not None and not to.isAbstract: lbElts.add(to) elif arcrole == dimensionDomain: if fr is not None: # dimension, always abstract lbElts.add(fr) if to is not None and rel.isUsable: lbElts.add(to) elif arcrole == domainMember: if to is not None and rel.isUsable: lbElts.add(to) unreportedLbElts = lbElts - conceptsUsed if unreportedLbElts: modelXbrl.error("esma.3.2.6." + err, _("All usable concepts in extension taxonomy relationships MUST be applied by tagged facts: %(elements)s."), modelObject=unreportedLbElts, elements=", ".join(sorted((str(c.qname) for c in unreportedLbElts)))) modelXbrl.profileActivity(_statusMsg, minTimeToShow=0.0) modelXbrl.modelManager.showStatus(None)
def isDivide(self): """(bool) -- True if unit has a divide element""" return XmlUtil.hasChild(self, XbrlConst.xbrli, "divide")
def checkFormulaRules(val, formula, nameVariables): from arelle.ModelFormulaObject import (Aspect) if not (formula.hasRule(Aspect.CONCEPT) or formula.source(Aspect.CONCEPT)): if XmlUtil.hasDescendant(formula.element, XbrlConst.formula, "concept"): val.modelXbrl.error( _("Formula {0} concept rule does not have a nearest source and does not have a child element" ).format(formula.xlinkLabel), "err", "xbrlfe:incompleteConceptRule") else: val.modelXbrl.error( _("Formula {0} omits a rule for the concept aspect").format( formula.xlinkLabel), "err", "xbrlfe:missingConceptRule") if (not (formula.hasRule(Aspect.SCHEME) or formula.source(Aspect.SCHEME)) or not (formula.hasRule(Aspect.VALUE) or formula.source(Aspect.VALUE))): if XmlUtil.hasDescendant(formula.element, XbrlConst.formula, "entityIdentifier"): val.modelXbrl.error( _("Formula {0} entity identifier rule does not have a nearest source and does not have either a @scheme or a @value attribute" ).format(formula.xlinkLabel), "err", "xbrlfe:incompleteEntityIdentifierRule") else: val.modelXbrl.error( _("Formula {0} omits a rule for the entity identifier aspect" ).format(formula.xlinkLabel), "err", "xbrlfe:missingEntityIdentifierRule") if not (formula.hasRule(Aspect.PERIOD_TYPE) or formula.source(Aspect.PERIOD_TYPE)): if XmlUtil.hasDescendant(formula.element, XbrlConst.formula, "period"): val.modelXbrl.error( _("Formula {0} period rule does not have a nearest source and does not have a child element" ).format(formula.xlinkLabel), "err", "xbrlfe:incompletePeriodRule") else: val.modelXbrl.error( _("Formula {0} omits a rule for the period aspect").format( formula.xlinkLabel), "err", "xbrlfe:missingPeriodRule") # for unit need to see if the qname is statically determinable to determine if numeric concept = val.modelXbrl.qnameConcepts.get( formula.evaluateRule(None, Aspect.CONCEPT)) if not concept: # is there a source with a static QName filter sourceFactVar = nameVariables.get(formula.source(Aspect.CONCEPT)) if isinstance(sourceFactVar, ModelFactVariable): for varFilterRels in (formula.groupFilterRelationships, sourceFactVar.filterRelationships): for varFilterRel in varFilterRels: filter = varFilterRel.toModelObject if isinstance( filter, ModelConceptName ): # relationship not constrained to real filters for conceptQname in filter.conceptQnames: concept = val.modelXbrl.qnameConcepts.get( conceptQname) if concept and concept.isNumeric: break if concept: # from concept aspect rule or from source factVariable concept Qname filter if concept.isNumeric: if not (formula.hasRule(Aspect.MULTIPLY_BY) or formula.hasRule( Aspect.DIVIDE_BY) or formula.source(Aspect.UNIT)): if XmlUtil.hasDescendant(formula.element, XbrlConst.formula, "unit"): val.modelXbrl.error( _("Formula {0} unit rule does not have a source and does not have a child element" ).format(formula.xlinkLabel), "err", "xbrlfe:missingSAVForUnitRule") else: val.modelXbrl.error( _("Formula {0} omits a rule for the unit aspect" ).format(formula.xlinkLabel), "err", "xbrlfe:missingUnitRule") elif (formula.hasRule(Aspect.MULTIPLY_BY) or formula.hasRule(Aspect.DIVIDE_BY) or formula.source(Aspect.UNIT, acceptFormulaSource=False)): val.modelXbrl.error( _("Formula {0} has a rule for the unit aspect of a non-numeric concept {1}" ).format(formula.xlinkLabel, str(concept.qname)), "err", "xbrlfe:conflictingAspectRules") aspectPeriodType = formula.evaluateRule(None, Aspect.PERIOD_TYPE) if ((concept.periodType == "duration" and aspectPeriodType == "instant") or (concept.periodType == "instant" and aspectPeriodType in ("duration", "forever"))): val.modelXbrl.error( _("Formula {0} has a rule for the {2} period aspect of a {3} concept {1}" ).format(formula.xlinkLabel, str(concept.qname), aspectPeriodType, concept.periodType), "err", "xbrlfe:conflictingAspectRules") # check dimension elements for eltName, dim, badUsageErr, missingSavErr in ( ("explicitDimension", "explicit", "xbrlfe:badUsageOfExplicitDimensionRule", "xbrlfe:missingSAVForExplicitDimensionRule"), ("typedDimension", "typed", "xbrlfe:badUsageOfTypedDimensionRule", "xbrlfe:missingSAVForTypedDimensionRule")): for dimElt in XmlUtil.descendants(formula.element, XbrlConst.formula, eltName): dimQname = qname(dimElt, dimElt.getAttribute("dimension")) dimConcept = val.modelXbrl.qnameConcepts.get(dimQname) if dimQname and ( not dimConcept or (not dimConcept.isExplicitDimension if dim == "explicit" else not dimConcept.isTypedDimension)): val.modelXbrl.error( _("Formula {0} dimension attribute {1} on the {2} dimension rule contains a QName that does not identify an {2} dimension." ).format(formula.xlinkLabel, dimQname, dim), "err", badUsageErr) elif not XmlUtil.hasChild(dimElt, XbrlConst.formula, "*") and not formula.source( Aspect.DIMENSIONS, dimElt): val.modelXbrl.error( _("Formula {0} {1} dimension rule does not have any child elements and does not have a SAV for the {2} dimension that is identified by its dimension attribute." ).format(formula.xlinkLabel, dim, dimQname), "err", missingSavErr) # check aspect model expectations if formula.aspectModel == "non-dimensional": unexpectedElts = XmlUtil.descendants( formula.element, XbrlConst.formula, ("explicitDimension", "typedDimension")) if unexpectedElts: val.modelXbrl.error( _("Formula {0} aspect model, {1}, includes an rule for aspect not defined in this aspect model: {2}" ).format( formula.xlinkLabel, formula.aspectModel, ", ".join([elt.localName for elt in unexpectedElts])), "err", "xbrlfe:unrecognisedAspectRule") # check source qnames for sourceElt in ([formula.element] + XmlUtil.descendants( formula.element, XbrlConst.formula, "*", "source", "*")): if sourceElt.hasAttribute("source"): qnSource = qname(sourceElt, sourceElt.getAttribute("source"), noPrefixIsNoNamespace=True) if qnSource == XbrlConst.qnFormulaUncovered: if formula.implicitFiltering != "true": val.modelXbrl.error( _("Formula {0}, not implicit filtering element has formulaUncovered source: {1}" ).format(formula.xlinkLabel, sourceElt.localName), "err", "xbrlfe:illegalUseOfUncoveredQName") elif qnSource not in nameVariables: val.modelXbrl.error( _("Variable set {0}, source {1} is not in the variable set" ).format(formula.xlinkLabel, qnSource), "err", "xbrlfe:nonexistentSourceVariable") else: factVariable = nameVariables.get(qnSource) if not isinstance(factVariable, ModelFactVariable): val.modelXbrl.error( _("Variable set {0}, source {1} not a factVariable but is a {2}" ).format(formula.xlinkLabel, qnSource, factVariable.localName), "err", "xbrlfe:nonexistentSourceVariable") elif factVariable.fallbackValue is not None: val.modelXbrl.error( _("Formula {0}: source {1} is a fact variable that has a fallback value" ).format(formula.xlinkLabel, str(qnSource)), "err", "xbrlfe:bindEmptySourceVariable") elif sourceElt.localName == "formula" and factVariable.bindAsSequence == "true": val.modelXbrl.error( _("Formula {0}: formula source {1} is a fact variable that binds as a sequence" ).format(formula.xlinkLabel, str(qnSource)), "err", "xbrlfe:defaultAspectValueConflicts")
def final(val): if not (val.validateEBA or val.validateEIOPA): return modelXbrl = val.modelXbrl modelDocument = modelXbrl.modelDocument _statusMsg = _("validating {0} filing rules").format(val.disclosureSystem.name) modelXbrl.profileActivity() val.showStatus(_statusMsg) if modelDocument.type == ModelDocument.Type.INSTANCE and (val.validateEBA or val.validateEIOPA): if not modelDocument.uri.endswith(".xbrl"): modelXbrl.warning("EBA.1.1", _('XBRL instance documents SHOULD use the extension ".xbrl" but it is "%(extension)s"'), modelObject=modelDocument, xmlEncoding=os.path.splitext(modelDocument.basename)[1]) if modelDocument.documentEncoding.lower() not in ("utf-8", "utf-8-sig"): modelXbrl.error("EBA.1.4", _('XBRL instance documents MUST use "UTF-8" encoding but is "%(xmlEncoding)s"'), modelObject=modelDocument, xmlEncoding=modelDocument.documentEncoding) schemaRefElts = [] schemaRefFileNames = [] for doc, docRef in modelDocument.referencesDocument.items(): if docRef.referenceType == "href": if docRef.referringModelObject.localName == "schemaRef": schemaRefElts.append(docRef.referringModelObject) schemaRefFileNames.append(doc.basename) if not UrlUtil.isAbsolute(doc.uri): modelXbrl.error("EBA.2.2", _('The link:schemaRef element in submitted instances MUST resolve to the full published entry point URL: %(url)s.'), modelObject=docRef.referringModelObject, url=doc.uri) elif docRef.referringModelObject.localName == "linkbaseRef": modelXbrl.error("EBA.2.3", _('The link:linkbaseRef element is not allowed: %(fileName)s.'), modelObject=docRef.referringModelObject, fileName=doc.basename) if len(schemaRefFileNames) > 1: modelXbrl.error("EBA.1.5", _('XBRL instance documents MUST reference only one entry point schema but %(numEntryPoints)s were found: %(entryPointNames)s'), modelObject=modelDocument, numEntryPoints=len(schemaRefFileNames), entryPointNames=', '.join(sorted(schemaRefFileNames))) ### check entry point names appropriate for filing indicator (DPM DB?) if len(schemaRefElts) != 1: modelXbrl.error("EBA.2.3", _('Any reported XBRL instance document MUST contain only one xbrli:xbrl/link:schemaRef node, but %(entryPointCount)s.'), modelObject=schemaRefElts, entryPointCount=len(schemaRefElts)) filingIndicators = {} for fIndicator in modelXbrl.factsByQname[qnFilingIndicator]: _value = (fIndicator.xValue or fIndicator.value) # use validated xValue if DTS else value for skipDTS if _value in filingIndicators: modelXbrl.error("EBA.1.6.1", _('Multiple filing indicators facts for indicator %(filingIndicator)s.'), modelObject=(fIndicator, filingIndicators[_value]), filingIndicator=_value) filingIndicators[_value] = fIndicator if not filingIndicators: modelXbrl.error("EBA.1.6", _('Missing filing indicators. Reported XBRL instances MUST include appropriate filing indicator elements'), modelObject=modelDocument) numFilingIndicatorTuples = len(modelXbrl.factsByQname[qnFIndicators]) if numFilingIndicatorTuples > 1 and not getattr(modelXbrl, "isStreamingMode", False): modelXbrl.info("EBA.1.6.2", _('Multiple filing indicators tuples when not in streaming mode (info).'), modelObject=modelXbrl.factsByQname[qnFIndicators]) # note EBA 2.1 is in ModelDocument.py cntxIDs = set() cntxEntities = set() cntxDates = defaultdict(list) timelessDatePattern = re.compile(r"\s*([0-9]{4})-([0-9]{2})-([0-9]{2})\s*$") for cntx in modelXbrl.contexts.values(): cntxIDs.add(cntx.id) cntxEntities.add(cntx.entityIdentifier) dateElts = XmlUtil.descendants(cntx, XbrlConst.xbrli, ("startDate","endDate","instant")) if any(not timelessDatePattern.match(e.textValue) for e in dateElts): modelXbrl.error("EBA.2.10", _('Period dates must be whole dates without time or timezone: %(dates)s.'), modelObject=cntx, dates=", ".join(e.text for e in dateElts)) if cntx.isForeverPeriod: modelXbrl.error("EBA.2.11", _('Forever context period is not allowed.'), modelObject=cntx) elif cntx.isStartEndPeriod: modelXbrl.error("EBA.2.13", _('Start-End (flow) context period is not allowed.'), modelObject=cntx) elif cntx.isInstantPeriod: cntxDates[cntx.instantDatetime].append(cntx) if XmlUtil.hasChild(cntx, XbrlConst.xbrli, "segment"): modelXbrl.error("EBA.2.14", _("The segment element not allowed in context Id: %(context)s"), modelObject=cntx, context=cntx.contextID) for scenElt in XmlUtil.descendants(cntx, XbrlConst.xbrli, "scenario"): childTags = ", ".join([child.prefixedName for child in scenElt.iterchildren() if isinstance(child,ModelObject) and child.tag != "{http://xbrl.org/2006/xbrldi}explicitMember" and child.tag != "{http://xbrl.org/2006/xbrldi}typedMember"]) if len(childTags) > 0: modelXbrl.error("EBA.2.15", _("Scenario of context Id %(context)s has disallowed content: %(content)s"), modelObject=cntx, context=cntx.id, content=childTags) if len(cntxDates) > 1: modelXbrl.error("EBA.2.13", _('Contexts must have the same date: %(dates)s.'), modelObject=[_cntx for _cntxs in cntxDates.values() for _cntx in _cntxs], dates=', '.join(XmlUtil.dateunionValue(_dt, subtractOneDay=True) for _dt in cntxDates.keys())) unusedCntxIDs = cntxIDs - {fact.contextID for fact in modelXbrl.factsInInstance if fact.contextID} # skip tuples if unusedCntxIDs: modelXbrl.warning("EBA.2.7", _('Unused xbrli:context nodes SHOULD NOT be present in the instance: %(unusedContextIDs)s.'), modelObject=[modelXbrl.contexts[unusedCntxID] for unusedCntxID in unusedCntxIDs], unusedContextIDs=", ".join(sorted(unusedCntxIDs))) if len(cntxEntities) > 1: modelXbrl.warning("EBA.2.9", _('All entity identifiers and schemes must be the same, %(count)s found: %(entities)s.'), modelObject=modelDocument, count=len(cntxEntities), entities=", ".join(sorted(str(cntxEntity) for cntxEntity in cntxEntities))) otherFacts = {} # (contextHash, unitHash, xmlLangHash) : fact nilFacts = [] stringFactsWithoutXmlLang = [] nonMonetaryNonPureFacts = [] unitIDsUsed = set() currenciesUsed = {} for qname, facts in modelXbrl.factsByQname.items(): for f in facts: if modelXbrl.skipDTS: c = f.qname.localName[0] isNumeric = c in ('m', 'p', 'i') isMonetary = c == 'm' isInteger = c == 'i' isPercent = c == 'p' isString = c == 's' else: concept = f.concept if concept is not None: isNumeric = concept.isNumeric isMonetary = concept.isMonetary isInteger = concept.baseXbrliType in integerItemTypes isPercent = concept.typeQname == qnPercentItemType isString = concept.baseXbrliType in ("stringItemType", "normalizedStringItemType") else: isNumeric = isString = False # error situation k = (f.getparent().objectIndex, f.qname, f.context.contextDimAwareHash if f.context is not None else None, f.unit.hash if f.unit is not None else None, hash(f.xmlLang)) if f.qname == qnFIndicators and val.validateEIOPA: pass elif k not in otherFacts: otherFacts[k] = {f} else: matches = [o for o in otherFacts[k] if (f.getparent().objectIndex == o.getparent().objectIndex and f.qname == o.qname and f.context.isEqualTo(o.context) if f.context is not None and o.context is not None else True) and (f.unit.isEqualTo(o.unit) if f.unit is not None and o.unit is not None else True) and (f.xmlLang == o.xmlLang)] if matches: contexts = [f.contextID] + [o.contextID for o in matches] modelXbrl.error("EBA.2.16", _('Facts are duplicates %(fact)s contexts %(contexts)s.'), modelObject=[f] + matches, fact=f.qname, contexts=', '.join(contexts)) else: otherFacts[k].add(f) if isNumeric: if f.precision: modelXbrl.error("EBA.2.17", _("Numeric fact %(fact)s of context %(contextID)s has a precision attribute '%(precision)s'"), modelObject=f, fact=f.qname, contextID=f.contextID, precision=f.precision) if f.decimals and f.decimals != "INF": try: dec = int(f.decimals) if isMonetary: if dec < -3: modelXbrl.error("EBA.2.17", _("Monetary fact %(fact)s of context %(contextID)s has a decimal attribute < -3: '%(decimals)s'"), modelObject=f, fact=f.qname, contextID=f.contextID, decimals=f.decimals) elif isInteger: if dec != 0: modelXbrl.error("EBA.2.17", _("Integer fact %(fact)s of context %(contextID)s has a decimal attribute \u2260 0: '%(decimals)s'"), modelObject=f, fact=f.qname, contextID=f.contextID, decimals=f.decimals) elif isPercent: if dec < 4: modelXbrl.error("EBA.2.17", _("Percent fact %(fact)s of context %(contextID)s has a decimal attribute < 4: '%(decimals)s'"), modelObject=f, fact=f.qname, contextID=f.contextID, decimals=f.decimals) except ValueError: pass # should have been reported as a schema error by loader '''' (not intended by EBA 2.18, paste here is from EFM) if not f.isNil and getattr(f,"xValid", 0) == 4: try: insignificance = insignificantDigits(f.xValue, decimals=f.decimals) if insignificance: # if not None, returns (truncatedDigits, insiginficantDigits) modelXbrl.error(("EFM.6.05.37", "GFM.1.02.26"), _("Fact %(fact)s of context %(contextID)s decimals %(decimals)s value %(value)s has nonzero digits in insignificant portion %(insignificantDigits)s."), modelObject=f1, fact=f1.qname, contextID=f1.contextID, decimals=f1.decimals, value=f1.xValue, truncatedDigits=insignificance[0], insignificantDigits=insignificance[1]) except (ValueError,TypeError): modelXbrl.error(("EBA.2.18"), _("Fact %(fact)s of context %(contextID)s decimals %(decimals)s value %(value)s causes Value Error exception."), modelObject=f1, fact=f1.qname, contextID=f1.contextID, decimals=f1.decimals, value=f1.value) ''' unit = f.unit if unit is not None: if isMonetary: if unit.measures[0]: currenciesUsed[unit.measures[0][0]] = unit elif not unit.isSingleMeasure or unit.measures[0][0] != XbrlConst.qnXbrliPure: nonMonetaryNonPureFacts.append(f) elif isString: if not f.xmlLang: stringFactsWithoutXmlLang.append(f) if f.unitID is not None: unitIDsUsed.add(f.unitID) if f.isNil: nilFacts.append(f) if nilFacts: modelXbrl.error("EBA.2.19", _('Nil facts MUST NOT be present in the instance: %(nilFacts)s.'), modelObject=nilFacts, nilFacts=", ".join(str(f.qname) for f in nilFacts)) if stringFactsWithoutXmlLang: modelXbrl.error("EBA.2.20", _("String facts need to report xml:lang: '%(langLessFacts)s'"), modelObject=stringFactsWithoutXmlLang, langLEssFacts=", ".join(str(f.qname) for f in stringFactsWithoutXmlLang)) if nonMonetaryNonPureFacts: modelXbrl.error("EBA.3.2", _("Non monetary (numeric) facts MUST use the pure unit: '%(langLessFacts)s'"), modelObject=nonMonetaryNonPureFacts, langLessFacts=", ".join(str(f.qname) for f in nonMonetaryNonPureFacts)) unusedUnitIDs = modelXbrl.units.keys() - unitIDsUsed if unusedUnitIDs: modelXbrl.warning("EBA.2.21", _('Unused xbrli:unit nodes SHOULD NOT be present in the instance: %(unusedUnitIDs)s.'), modelObject=[modelXbrl.units[unusedUnitID] for unusedUnitID in unusedUnitIDs], unusedUnitIDs=", ".join(sorted(unusedUnitIDs))) unitHashes = {} for unit in modelXbrl.units.values(): h = hash(unit) if h in unitHashes and unit.isEqualTo(unitHashes[h]): modelXbrl.warning("EBA.2.32", _("Duplicate units SHOULD NOT be reported, units %(unit1)s and %(unit2)s have same measures.'"), modelObject=(unit, unitHashes[h]), unit1=unit.id, unit2=unitHashes[h].id) else: unitHashes[h] = unit if len(currenciesUsed) > 1: modelXbrl.error("EBA.3.1", _("There MUST be only one currency but %(numCurrencies)s were found: %(currencies)s.'"), modelObject=currenciesUsed.values(), numCurrencies=len(currenciesUsed), currencies=", ".join(str(c) for c in currenciesUsed.keys())) namespacePrefixesUsed = defaultdict(set) prefixesUnused = set(modelDocument.xmlRootElement.keys()).copy() for elt in modelDocument.xmlRootElement.iter(): if isinstance(elt, ModelObject): # skip comments and processing instructions namespacePrefixesUsed[elt.qname.namespaceURI].add(elt.qname.prefix) prefixesUnused.discard(elt.qname.prefix) if prefixesUnused: modelXbrl.warning("EBA.3.4", _("There SHOULD be no unused prefixes but these were declared: %(unusedPrefixes)s.'"), modelObject=modelDocument, unusedPrefixes=', '.join(sorted(prefixesUnused))) for ns, prefixes in namespacePrefixesUsed.items(): nsDocs = modelXbrl.namespaceDocs.get(ns) if nsDocs: for nsDoc in nsDocs: nsDocPrefix = XmlUtil.xmlnsprefix(nsDoc.xmlRootElement, ns) if any(prefix != nsDocPrefix for prefix in prefixes if prefix is not None): modelXbrl.warning("EBA.3.5", _("Prefix for namespace %(namespace)s is %(declaredPrefix)s but these were found %(foundPrefixes)s"), modelObject=modelDocument, namespace=ns, declaredPrefix=nsDocPrefix, foundPrefixes=', '.join(sorted(prefixes - {None}))) modelXbrl.profileActivity(_statusMsg, minTimeToShow=0.0) val.showStatus(None) del val.prefixNamespace, val.namespacePrefix, val.idObjects, val.typedDomainElements
def hasSegment(self): """(bool) -- True if a xbrli:segment element is present""" return XmlUtil.hasChild(self.entity, XbrlConst.xbrli, "segment")
def doObject(self, fObj, fromRel, pIndent, visited): if fObj is None: return cIndent = pIndent + " " if isinstance(fObj, ModelAssertionSet): self.xf = "{}assertion-set {} {{".format(pIndent, self.objectId(fObj, "assertionSet")) for modelRel in self.modelXbrl.relationshipSet(XbrlConst.assertionSet).fromModelObject(fObj): self.doObject(modelRel.toModelObject, modelRel, cIndent, visited) self.xf = "{}}};".format(pIndent) elif isinstance(fObj, (ModelValueAssertion, ModelExistenceAssertion, ModelFormula)): varSetType = "formula" if isinstance(fObj, ModelFormula) else "assertion" self.xf = "{}{} {} {{".format(pIndent, varSetType, self.objectId(fObj, varSetType)) for arcrole in (XbrlConst.elementLabel, XbrlConst.assertionSatisfiedMessage, XbrlConst.assertionUnsatisfiedMessage): for modelRel in self.modelXbrl.relationshipSet(arcrole).fromModelObject(fObj): self.doObject(modelRel.toModelObject, modelRel, cIndent, visited) if fObj.aspectModel == "non-dimensional": self.xf = "{}aspect-model-non-dimensional;".format(cIndent) if fObj.implicitFiltering == "false": self.xf = "{}no-implicit-filtering;".format(cIndent) if isinstance(fObj, ModelFormula): for attr in ("decimals", "precision", "value"): if fObj.get(attr): self.xf = "{}{} {{{}}};".format(cIndent, attr, fObj.get(attr)) if fObj.get("source"): self.xf = "{}source {};".format(cIndent, fObj.get("source")) for aspectsElt in XmlUtil.children(fObj, XbrlConst.formula, "aspects"): self.xf = "{}aspect-rules{} {{".format(cIndent, "source {}".format(aspectsElt.get("source")) if aspectsElt.get("source") else "") for ruleElt in XmlUtil.children(aspectsElt, XbrlConst.formula, "*"): self.doObject(ruleElt, None, cIndent + " ", visited) self.xf = "{}}};".format(cIndent) for arcrole in (XbrlConst.variableSetFilter, XbrlConst.variableSet, XbrlConst.variableSetPrecondition): for modelRel in self.modelXbrl.relationshipSet(arcrole).fromModelObject(fObj): self.doObject(modelRel.toModelObject, modelRel, cIndent, visited) if isinstance(fObj, ModelValueAssertion): self.xf = "{}test {{{}}};".format(cIndent, fObj.viewExpression) elif isinstance(fObj, ModelExistenceAssertion): self.xf = "{}evaluation-count {{{}}};".format(cIndent, fObj.viewExpression or ". gt 0") self.xf = "{}}};".format(pIndent) elif isinstance(fObj, ModelConsistencyAssertion): self.xf = "{}consistency-assertion {} {{".format(pIndent, self.objectId(fObj, "consistencyAssertion")) for arcrole in (XbrlConst.elementLabel, XbrlConst.assertionSatisfiedMessage, XbrlConst.assertionUnsatisfiedMessage): for modelRel in self.modelXbrl.relationshipSet(arcrole).fromModelObject(fObj): self.doObject(modelRel.toModelObject, modelRel, cIndent, visited) if fObj.isStrict: self.xf = "{}strict;".format(cIndent) if fObj.get("proportionalAcceptanceRadius"): self.xf = "{}proportional-acceptance-radius {{{}}};".format(cIndent, fObj.get("proportionalAcceptanceRadius")) if fObj.get("absoluteAcceptanceRadius"): self.xf = "{}absolute-acceptance-radius {{{}}};".format(cIndent, fObj.get("absoluteAcceptanceRadius")) for modelRel in self.modelXbrl.relationshipSet(XbrlConst.consistencyAssertionFormula).fromModelObject(fObj): self.doObject(modelRel.toModelObject, modelRel, cIndent, visited) self.xf = "{}}};".format(pIndent) elif isinstance(fObj, ModelFactVariable) and fromRel is not None: self.xf = "{}variable ${} {{".format(pIndent, fromRel.variableQname) if fromRel.variableQname.prefix: self.xmlns[fromRel.variableQname.prefix] = fromRel.variableQname.namespaceURI if fObj.bindAsSequence == "true": self.xf = "{}bind-as-sequence".format(cIndent) if fObj.nils == "true": self.xf = "{}nils".format(cIndent) if fObj.matches == "true": self.xf = "{}matches".format(cIndent) if fObj.fallbackValue: self.xf = "{}fallback {{{}}}".format(cIndent, fObj.fallbackValue) for modelRel in self.modelXbrl.relationshipSet(XbrlConst.variableFilter).fromModelObject(fObj): self.doObject(modelRel.toModelObject, modelRel, cIndent, visited) self.xf = "{}}};".format(pIndent) elif isinstance(fObj, ModelGeneralVariable) and fromRel is not None: self.xf = "{}variable ${} {{".format(pIndent, fromRel.variableQname) if fromRel.variableQname.prefix: self.xmlns[fromRel.variableQname.prefix] = fromRel.variableQname.namespaceURI if fObj.bindAsSequence: self.xf = "{}bind-as-sequence".format(cIndent) self.xf = "{}select {{{}}}".format(cIndent, fObj.select) elif isinstance(fObj, ModelParameter): if fromRel is not None: # parameter is referenced by a different QName on arc if fromRel.variableQname.prefix: self.xmlns[fromRel.variableQname.prefix] = fromRel.variableQname.namespaceURI self.xf = "{}parameter ${} references ${};".format(pIndent, fromRel.variableQname, fObj.parameterQname) else: # root level parameter if fObj.parameterQname.prefix: self.xmlns[fObj.parameterQname.prefix] = fObj.parameterQname.namespaceURI self.xf = "{}parameter {} {{".format(pIndent, fObj.parameterQname) if fObj.isRequired: self.xf = "{}required".format(cIndent) self.xf = "{} select {{{}}}".format(cIndent, fObj.select) if fObj.asType: self.xf = "{} as {{{}}}".format(cIndent, fObj.asType) if fObj.asType.prefix: self.xmlns[fObj.asType.prefix] = fObj.asType.namespaceURI self.xf = "{}}};".format(pIndent) elif isinstance(fObj, ModelFilter): if fromRel.isComplemented: self.xf = "{}complemented".format(pIndent) if not fromRel.isCovered and fromRel.localName == "variableFilterArc": self.xf = "{}non-covering".format(pIndent) if isinstance(fObj, ModelConceptName): if len(fObj.conceptQnames) == 1 and not fObj.qnameExpressions: qn = next(iter(fObj.conceptQnames)) self.xmlns[qn.prefix] = qn.namespaceURI self.xf = "{}concept-name {};".format(pIndent, qn) elif len(fObj.qnameExpressions) == 1 and not fObj.conceptQnames: self.xf = "{}concept-name {{{}}};".format(pIndent, fObj.qnameExpressions[0]) else: self.xf = "{}concept-name".format(pIndent) for qn in fObj.conceptQnames: self.xmlns[qn.prefix] = qn.namespaceURI self.xf = "{} {}".format(pIndent, qn) for qnExpr in fObj.qnameExpressions: self.xf = "{} {}".format(pIndent, qnExpr) self.xf = "{} ;".format(pIndent) elif isinstance(fObj, ModelConceptPeriodType): self.xf = "{}concept-period {};".format(pIndent, fObj.periodType) elif isinstance(fObj, ModelConceptBalance): self.xf = "{}concept-balance {};".format(pIndent, fObj.balance) elif isinstance(fObj, (ModelConceptDataType, ModelConceptSubstitutionGroup)): self.xf = "{}{} {} {};".format(pIndent, kebabCase(fObj.localName), "strict" if fObj.strict == "true" else "non-strict", fObj.filterQname if fObj.filterQname else "{{{}}}".format(fObj.qnameExpression)) elif isinstance(fObj, ModelExplicitDimension): members = [] for memberElt in XmlUtil.children(fObj, XbrlConst.df, "member"): members.append("member") member = XmlUtil.childText(memberElt, XbrlConst.df, "qname") if member: member = str(member) # qname, must coerce to string else: member = XmlUtil.childText(memberElt, XbrlConst.df, "qnameExpression") if member: member = "{{{}}}".format(member) else: member = "$" + XmlUtil.childText(memberElt, XbrlConst.df, "variable") members.append(member) linkrole = XmlUtil.childText(memberElt, XbrlConst.df, "linkrole") if linkrole: members.append("linkrole") members.append("\"{}\"".format(linkrole)) arcrole = XmlUtil.childText(memberElt, XbrlConst.df, "arcrole") if arcrole: members.append("arcrole") members.append("\"{}\"".format(arcrole)) axis = XmlUtil.childText(memberElt, XbrlConst.df, "axis") if axis: members.append("axis") members.append(axis) self.xf = "{}explicit-dimension {}{};".format(pIndent, fObj.dimQname or ("{{{}}}".format(fObj.dimQnameExpression) if fObj.dimQnameExpression else ""), " ".join(members)) elif isinstance(fObj, ModelTypedDimension): # this is a ModelTestFilter not same as genera/unit/period self.xf = "{}typed-dimension {}{};".format(pIndent, fObj.dimQname or ("{{{}}}".format(fObj.dimQnameExpression) if fObj.dimQnameExpression else ""), " {{{}}}".format(fObj.test) if fObj.test else "") elif isinstance(fObj, ModelTestFilter): self.xf = "{}{} {{{}}};".format(pIndent, "general" if isinstance(fObj, ModelGeneral) else "unit-general-measures" if isinstance(fObj, ModelGeneralMeasures) else "period" if isinstance(fObj, ModelPeriod) else "entity-identifier" if isinstance(fObj, ModelIdentifier) else None, fObj.test) elif isinstance(fObj, ModelDateTimeFilter): self.xf = "{}{} {{{}}}{};".format(pIndent, kebabCase(fObj.localName), fObj.date, " {{{}}}".format(fObj.time) if fObj.time else "") elif isinstance(fObj, ModelInstantDuration): self.xf = "{}instant-duration {} {};".format(pIndent, fObj.boundary, fObj.variable) elif isinstance(fObj, ModelSingleMeasure): self.xf = "{}unit-single-measure {};".format(pIndent, fObj.measureQname or ("{{{}}}".format(fObj.qnameExpression) if fObj.qnameExpression else "")) elif isinstance(fObj, ModelEntitySpecificIdentifier): self.xf = "{}entity scheme {{{}}} value {{{}}};".format(pIndent, fObj.scheme, fObj.value) elif isinstance(fObj, ModelEntityScheme): self.xf = "{}entity-scheme {{{}}};".format(pIndent, fObj.scheme) elif isinstance(fObj, ModelEntityRegexpScheme): self.xf = "{}entity-scheme-pattern \"{}\";".format(pIndent, fObj.pattern) elif isinstance(fObj, ModelEntityRegexpIdentifier): self.xf = "{}entity-identifier-pattern \"{}\";".format(pIndent, fObj.pattern) elif isinstance(fObj, ModelMatchFilter): self.xf = "{}{} ${} {}{};".format(pIndent, kebabCase(fObj.localName), fObj.variable, " dimension {}".format(fObj.dimension) if fObj.get("dimension") else "", " match-any" if fObj.matchAny else "") elif isinstance(fObj, ModelRelativeFilter): self.xf = "{}relative ${};".format(pIndent, fObj.variable) elif isinstance(fObj, ModelAncestorFilter): self.xf = "{}ancestor {};".format(pIndent, fObj.ancestorQname or ("{{{}}}".format(fObj.qnameExpression) if fObj.qnameExpression else "")) elif isinstance(fObj, ModelParentFilter): self.xf = "{}parent {};".format(pIndent, fObj.parentQname or ("{{{}}}".format(fObj.qnameExpression) if fObj.qnameExpression else "")) elif isinstance(fObj, ModelSiblingFilter): self.xf = "{}sibling ${};".format(pIndent, fObj.variable) elif isinstance(fObj, ModelNilFilter): self.xf = "{}nilled;".format(pIndent) elif isinstance(fObj, ModelAspectCover): aspects = [] for aspectElt in XmlUtil.children(fObj, XbrlConst.acf, "aspect"): aspects.append(XmlUtil.text(aspectElt)) for dimElt in XmlUtil.descendants(fObj, XbrlConst.acf, ("qname", "qnameExpression")): dimAspect = qname( dimElt, XmlUtil.text(dimElt) ) aspects.append("exclude-dimension" if dimElt.getparent().localName == "excludeDimension" else "dimension") if dimElt.localName == "qname": aspects.append(str(qname( dimElt, XmlUtil.text(dimElt) ))) else: aspects.append("{{{}}}".format(XmlUtil.text(dimElt))) self.xf = "{}aspect-cover {};".format(pIndent, " ".join(aspects)) elif isinstance(fObj, ModelConceptRelation): conceptRelationTerms = [] if fObj.sourceQname: conceptRelationTerms.append(fObj.sourceQname) elif fObj.variable: conceptRelationTerms.append("$" + fObj.variable) else: conceptRelationTerms.append("{{{}}}".format(fObj.sourceQnameExpression)) if fObj.linkrole: conceptRelationTerms.append("linkrole") conceptRelationTerms.append(fObj.linkrole) elif fObj.linkroleExpression: conceptRelationTerms.append("linkrole") conceptRelationTerms.append("{{{}}}".format(fObj.linkroleExpression)) if fObj.arcrole: conceptRelationTerms.append("arcrole") conceptRelationTerms.append(fObj.arcrole) elif fObj.arcroleExpression: conceptRelationTerms.append("arcrole") conceptRelationTerms.append("{{{}}}".format(fObj.arcroleExpression)) if fObj.axis: conceptRelationTerms.append("axis") conceptRelationTerms.append(fObj.axis) if fObj.generations is not None: conceptRelationTerms.append("generations {}".format(fObj.generations)) if fObj.test: conceptRelationTerms.append("test") conceptRelationTerms.append("{{{}}}".format(fObj.test)) self.xf = "{}concept-relation {};".format(pIndent, " ".join(conceptRelationTerms)) elif isinstance(fObj, (ModelAndFilter, ModelOrFilter)): self.xf = "{}{} {{".format(pIndent, "and" if isinstance(fObj, ModelAndFilter)else "or") if fObj not in visited: visited.add(fObj) for modelRel in self.modelXbrl.relationshipSet(XbrlConst.booleanFilter).fromModelObject(fObj): self.doObject(modelRel.toModelObject, modelRel, cIndent, visited) visited.remove(fObj) self.xf = "{}}};".format(pIndent) elif isinstance(fObj, ModelMessage): self.xf = "{}{}{} \"{}\";".format( pIndent, "satisfied-message" if fromRel.arcrole == XbrlConst.assertionSatisfiedMessage else "unsatisfied-message", " ({})".format(fObj.xmlLang) if fObj.xmlLang else "", fObj.text.replace('"', '""')) elif isinstance(fObj, ModelCustomFunctionSignature): hasImplememntation = False if fObj not in visited: visited.add(fObj) for modelRel in self.modelXbrl.relationshipSet(XbrlConst.functionImplementation).fromModelObject(fObj): self.doObject(modelRel.toModelObject, modelRel, pIndent, visited) # note: use pIndent as parent doesn't show hasImplementation = True visited.remove(fObj) if not hasImplementation: self.xmlns[fObj.functionQname.prefix] = fObj.functionQname.namespaceURI self.xf = "{}abstract-function {}({}) as {};".format(pIndent, fObj.name, ", ".join(str(t) for t in fObj.inputTypes), fObj.outputType) elif isinstance(fObj, ModelCustomFunctionImplementation): sigObj = fromRel.fromModelObject self.xmlns[sigObj.functionQname.prefix] = sigObj.functionQname.namespaceURI self.xf = "{}function {}({}) as {} {{".format(pIndent, sigObj.name, ", ".join("{} as {}".format(inputName, sigObj.inputTypes[i]) for i, inputName in enumerate(fObj.inputNames)), sigObj.outputType) for name, stepExpr in fObj.stepExpressions: if "\n" not in stepExpr: self.xf = "{}step ${} {{{}}};".format(cIndent, name, stepExpr) else: self.xf = "{}step ${} {{".format(cIndent, name) for exprLine in stepExpr.split("\n"): self.xf = "{} {}".format(cIndent, exprLine.lstrip()) self.xf = "{}}};".format(cIndent) self.xf = "{}return {{{}}};".format(cIndent, fObj.outputExpression) self.xf = "{}}};".format(pIndent) elif fObj.getparent().tag == "{http://xbrl.org/2008/formula}aspects": # aspect rules arg = "" if fObj.localName == "concept": if XmlUtil.hasChild(fObj, None, "qname"): arg += " " + XmlUtil.childText(fObj, None, "qname") elif XmlUtil.hasChild(fObj, None, "qnameExpression"): arg += " {" + XmlUtil.childText(fObj, None, "qnameExpression") + "}" elif fObj.localName == "entityIdentifier": if fObj.get("scheme"): arg += " scheme {" + fObj.get("scheme") + "}" if fObj.get("identifier"): arg += " identifier {" + fObj.get("identifier") + "}" elif fObj.localName == "period": if XmlUtil.hasChild(fObj, None, "forever"): arg += " forever" if XmlUtil.hasChild(fObj, None, "instant"): arg += " instant" attr = XmlUtil.childAttr(fObj, None, "instant", "value") if attr: arg += "{" + attr + "}" if XmlUtil.hasChild(fObj, None, "duration"): arg += " duration" attr = XmlUtil.childAttr(fObj, None, "duration", "start") if attr: arg += " start {" + attr + "}" attr = XmlUtil.childAttr(fObj, None, "duration", "end") if attr: arg += " end {" + attr + "}" elif fObj.localName == "unit": if fObj.get("augment") == "true": arg += " augment" if fObj.localName in ("explicitDimension", "typedDimension"): arg += " dimension " + fObj.get("dimension") if fObj.localName in ("concept", "entityIdentifier", "period"): arg += ";" else: arg += " {" self.xf = "{}{}{}".format(pIndent, kebabCase(fObj.localName), arg) if fObj.localName == "unit": for elt in fObj.iterchildren(): arg = "" if elt.get("source"): arg += " source " + elt.get("source") if elt.get("measure"): arg += " measure {" + elt.get("measure") + "}" self.xf = "{}{}{};".format(cIndent, kebabCase(elt.localName), arg) elif fObj.localName == "explicitDimension": for elt in fObj.iterchildren(): arg = "" if XmlUtil.hasChild(elt, None, "qname"): arg += " " + XmlUtil.childText(elt, None, "qname") elif XmlUtil.hasChild(elt, None, "qnameExpression"): arg += " {" + XmlUtil.childText(elt, None, "qnameExpression") + "}" self.xf = "{}{}{};".format(cIndent, kebabCase(elt.localName), arg) elif fObj.localName == "typedDimension": for elt in fObj.iterchildren(): arg = "" if XmlUtil.hasChild(elt, None, "xpath"): arg += " xpath {" + ModelXbrl.childText(elt, None, "xpath") + "}" elif XmlUtil.hasChild(elt, None, "value"): arg += " value " + strQoute(XmlUtil.xmlstring(XmlUtil.child(elt, None, "value"), stripXmlns=True, contentsOnly=False)) self.xf = "{}{}{};".format(cIndent, kebabCase(elt.localName), arg) if fObj.localName not in ("concept", "entityIdentifier", "period"): self.xf = "{}}};".format(pIndent) # check for prefixes in AST of programs of fObj if hasattr(fObj, "compile") and type(fObj.compile).__name__ == "method": fObj.compile() for _prog, _ast in fObj.__dict__.items(): if _prog.endswith("Prog") and isinstance(_ast, list): XPathParser.prefixDeclarations(_ast, self.xmlns, fObj)
def checkDTSdocument(val, modelDocument): modelXbrl = val.modelXbrl if modelDocument.type == ModelDocument.Type.INSTANCE: if not modelDocument.uri.endswith(".xbrl"): modelXbrl.warning("EBA.1.1", _('XBRL instance documents SHOULD use the extension ".xbrl" encoding but it is "%(extension)s"'), modelObject=modelDocument, xmlEncoding=os.path.splitext(modelDocument.basename)[1]) if modelDocument.documentEncoding.lower() not in ("utf-8", "utf-8-sig"): modelXbrl.error("EBA.1.4", _('XBRL instance documents MUST use "UTF-8" encoding but is "%(xmlEncoding)s"'), modelObject=modelDocument, xmlEncoding=modelDocument.documentEncoding) schemaRefElts = [] schemaRefFileNames = [] for doc, docRef in modelDocument.referencesDocument.items(): if docRef.referenceType == "href": if docRef.referringModelObject.localName == "schemaRef": schemaRefElts.append(docRef.referringModelObject) schemaRefFileNames.append(doc.basename) if not UrlUtil.isAbsolute(doc.uri): modelXbrl.error("EBA.2.2", _('The link:schemaRef element in submitted instances MUST resolve to the full published entry point URL: %(url)s.'), modelObject=docRef.referringModelObject, url=doc.uri) elif docRef.referringModelObject.localName == "linkbaseRef": modelXbrl.error("EBA.2.3", _('The link:linkbaseRef element is not allowed: %(fileName)s.'), modelObject=docRef.referringModelObject, fileName=doc.basename) if len(schemaRefFileNames) > 1: modelXbrl.error("EBA.1.5", _('XBRL instance documents MUST reference only one entry point schema but %(numEntryPoints)s were found: %(entryPointNames)s'), modelObject=modelDocument, numEntryPoints=len(schemaRefFileNames), entryPointNames=', '.join(sorted(schemaRefFileNames))) ### check entry point names appropriate for filing indicator (DPM DB?) if len(schemaRefElts) != 1: modelXbrl.error("EBA.2.3", _('Any reported XBRL instance document MUST contain only one xbrli:xbrl/link:schemaRef node, but %(entryPointCount)s.'), modelObject=schemaRefElts, entryPointCount=len(schemaRefElts)) filingIndicators = {} for fIndicator in modelXbrl.factsByQname[qnFilingIndicator]: if fIndicator.xValue in filingIndicators: modelXbrl.error("EBA.1.6.1", _('Multiple filing indicators facts for indicator %(filingIndicator)s.'), modelObject=(fIndicator, filingIndicators[filingIndicators]), filingIndicator=fIndicator.xValue) filingIndicators[fIndicator.xValue] = fIndicator if not filingIndicators: modelXbrl.error("EBA.1.6", _('Missing filing indicators. Reported XBRL instances MUST include appropriate filing indicator elements'), modelObject=modelDocument) numFilingIndicatorTuples = len(modelXbrl.factsByQname[qnFilingIndicators]) if numFilingIndicatorTuples > 1 and not getattr(modelXbrl, "isStreamingMode", False): modelXbrl.info("EBA.1.6.2", _('Multiple filing indicators tuples when not in streaming mode (info).'), modelObject=modelXbrl.factsByQname[qnFilingIndicators]) # note EBA 2.1 is in ModelDocument.py cntxIDs = set() cntxEntities = set() timelessDatePattern = re.compile(r"\s*([0-9]{4})-([0-9]{2})-([0-9]{2})\s*$") for cntx in modelXbrl.contexts.values(): cntxIDs.add(cntx.id) cntxEntities.add(cntx.entityIdentifier) dateElts = XmlUtil.descendants(cntx, XbrlConst.xbrli, ("startDate","endDate","instant")) if any(not timelessDatePattern.match(e.textValue) for e in dateElts): modelXbrl.error("EBA.2.10", _('Period dates must be whole dates without time or timezone: %(dates)s.'), modelObject=cntx, dates=", ".join(e.text for e in dateElts)) if cntx.isForeverPeriod: modelXbrl.error("EBA.2.11", _('Forever context period is not allowed.'), modelObject=cntx) if XmlUtil.hasChild(cntx, XbrlConst.xbrli, "segment"): modelXbrl.error("EBA.2.14", _("The segment element not allowed in context Id: %(context)s"), modelObject=cntx, context=cntx.contextID) for scenElt in XmlUtil.descendants(cntx, XbrlConst.xbrli, "scenario"): childTags = ", ".join([child.prefixedName for child in scenElt.iterchildren() if isinstance(child,ModelObject) and child.tag != "{http://xbrl.org/2006/xbrldi}explicitMember" and child.tag != "{http://xbrl.org/2006/xbrldi}typedMember"]) if len(childTags) > 0: modelXbrl.error("EBA.2.15", _("Scenario of context Id %(context)s has disallowed content: %(content)s"), modelObject=cntx, context=cntx.id, content=childTags) unusedCntxIDs = cntxIDs - {fact.contextID for fact in modelXbrl.facts} if unusedCntxIDs: modelXbrl.warning("EBA.2.7", _('Unused xbrli:context nodes SHOULD NOT be present in the instance: %(unusedContextIDs)s.'), modelObject=[modelXbrl.contexts[unusedCntxID] for unusedCntxID in unusedCntxIDs], unusedContextIDs=", ".join(sorted(unusedCntxIDs))) if len(cntxEntities) > 1: modelXbrl.warning("EBA.2.9", _('All entity identifiers and schemes must be the same, %(count)s found: %(entities)s.'), modelObject=modelDocument, count=len(cntxEntities), entities=", ".join(sorted(str(cntxEntity) for cntxEntity in cntxEntities))) otherFacts = {} # (contextHash, unitHash, xmlLangHash) : fact nilFacts = [] unitIDsUsed = set() currenciesUsed = {} for qname, facts in modelXbrl.factsByQname.items(): for f in facts: concept = f.concept k = (f.context.contextDimAwareHash if f.context is not None else None, f.unit.hash if f.unit is not None else None, hash(f.xmlLang)) if k not in otherFacts: otherFacts[k] = {f} else: matches = [o for o in otherFacts[k] if (f.context.isEqualTo(o.context) if f.context is not None and o.context is not None else True) and (f.unit.isEqualTo(o.unit) if f.unit is not None and o.unit is not None else True) and (f.xmlLang == o.xmlLang)] if matches: modelXbrl.error("EBA.2.16", _('Facts are duplicates %(fact)s and %(otherFacts)s.'), modelObject=[f] + matches, fact=f.qname, otherFacts=', '.join(str(f.qname) for f in matches)) else: otherFacts[k].add(f) if concept is not None and concept.isNumeric: if f.precision: modelXbrl.error("EBA.2.17", _("Numeric fact %(fact)s of context %(contextID)s has a precision attribute '%(precision)s'"), modelObject=f, fact=f.qname, contextID=f.contextID, precision=f.precision) '''' (not intended by EBA 2.18) if f.decimals and f.decimals != "INF" and not f.isNil and getattr(f,"xValid", 0) == 4: try: insignificance = insignificantDigits(f.xValue, decimals=f.decimals) if insignificance: # if not None, returns (truncatedDigits, insiginficantDigits) modelXbrl.error(("EFM.6.05.37", "GFM.1.02.26"), _("Fact %(fact)s of context %(contextID)s decimals %(decimals)s value %(value)s has nonzero digits in insignificant portion %(insignificantDigits)s."), modelObject=f1, fact=f1.qname, contextID=f1.contextID, decimals=f1.decimals, value=f1.xValue, truncatedDigits=insignificance[0], insignificantDigits=insignificance[1]) except (ValueError,TypeError): modelXbrl.error(("EFM.6.05.37", "GFM.1.02.26"), _("Fact %(fact)s of context %(contextID)s decimals %(decimals)s value %(value)s causes Value Error exception."), modelObject=f1, fact=f1.qname, contextID=f1.contextID, decimals=f1.decimals, value=f1.value) ''' unit = f.unit if concept.isMonetary and unit is not None and unit.measures[0]: currenciesUsed[unit.measures[0][0]] = unit if f.unitID is not None: unitIDsUsed.add(f.unitID) if f.isNil: nilFacts.append(f) if nilFacts: modelXbrl.warning("EBA.2.19", _('Nil facts SHOULD NOT be present in the instance: %(nilFacts)s.'), modelObject=nilFacts, nilFacts=", ".join(str(f.qname) for f in nilFacts)) unusedUnitIDs = modelXbrl.units.keys() - unitIDsUsed if unusedUnitIDs: modelXbrl.warning("EBA.2.21", _('Unused xbrli:unit nodes SHOULD NOT be present in the instance: %(unusedUnitIDs)s.'), modelObject=[modelXbrl.units[unusedUnitID] for unusedUnitID in unusedUnitIDs], unusedUnitIDs=", ".join(sorted(unusedUnitIDs))) unitHashes = {} for unit in modelXbrl.units.values(): h = hash(unit) if h in unitHashes and unit.isEqualTo(unitHashes[h]): modelXbrl.error("EBA.2.32", _("Units %(unit1)s and %(unit2)s have same measures.'"), modelObject=(unit, unitHashes[h]), unit1=unit.id, unit2=unitHashes[h].id) else: unitHashes[h] = unit if len(currenciesUsed) > 1: modelXbrl.error("EBA.3.1", _("There MUST be only one currency but %(numCurrencies)s were found: %(currencies)s.'"), modelObject=currenciesUsed.values(), numCurrencies=len(currenciesUsed), currencies=", ".join(str(c) for c in currenciesUsed.keys())) namespacePrefixesUsed = defaultdict(set) prefixesUnused = set(modelDocument.xmlRootElement.keys()).copy() for elt in modelDocument.xmlRootElement.iter(): if isinstance(elt, ModelObject): # skip comments and processing instructions namespacePrefixesUsed[elt.qname.namespaceURI].add(elt.qname.prefix) prefixesUnused.discard(elt.qname.prefix) if prefixesUnused: modelXbrl.warning("EBA.3.4", _("There SHOULD be no unused prefixes but these were declared: %(unusedPrefixes)s.'"), modelObject=modelDocument, unusedPrefixes=', '.join(sorted(prefixesUnused))) for ns, prefixes in namespacePrefixesUsed.items(): nsDocs = modelXbrl.namespaceDocs.get(ns) if nsDocs: for nsDoc in nsDocs: nsDocPrefix = XmlUtil.xmlnsprefix(nsDoc.xmlRootElement, ns) if any(prefix != nsDocPrefix for prefix in prefixes if prefix is not None): modelXbrl.warning("EBA.3.5", _("Prefix for namespace %(namespace)s is %(declaredPrefix)s but these were found %(foundPrefixes)s"), modelObject=modelDocument, namespace=ns, declaredPrefix=nsDocPrefix, foundPrefixes=', '.join(sorted(prefixes - {None})))