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
Beispiel #4
0
 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
Beispiel #5
0
 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
Beispiel #6
0
 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
Beispiel #7
0
 def isInstantPeriod(self):
     try:
         return self._isInstantPeriod
     except AttributeError:
         self._isInstantPeriod = XmlUtil.hasChild(self.period,
                                                  XbrlConst.xbrli,
                                                  "instant")
         return self._isInstantPeriod
Beispiel #8
0
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")
Beispiel #9
0
 def isForeverPeriod(self):
     try:
         return self._isForeverPeriod
     except AttributeError:
         self._isForeverPeriod = XmlUtil.hasChild(self.period,
                                                  XbrlConst.xbrli,
                                                  "forever")
         return self._isForeverPeriod
Beispiel #10
0
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")
Beispiel #11
0
 def isStartEndPeriod(self):
     try:
         return self._isStartEndPeriod
     except AttributeError:
         self._isStartEndPeriod = XmlUtil.hasChild(self.period,
                                                   XbrlConst.xbrli,
                                                   ("startDate", "endDate"))
         return self._isStartEndPeriod
Beispiel #12
0
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)
Beispiel #13
0
 def isDivide(self):
     return XmlUtil.hasChild(self, XbrlConst.xbrli, "divide")
Beispiel #14
0
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)
Beispiel #15
0
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")
Beispiel #17
0
 def hasDrsNetwork(self):
     return XmlUtil.hasChild(self, XbrlConst.verdim, "drsNetwork")
Beispiel #18
0
 def isDivide(self):
     return XmlUtil.hasChild(self, XbrlConst.xbrli, "divide")
Beispiel #19
0
 def hasScenario(self):
     return XmlUtil.hasChild(self, XbrlConst.xbrli, "scenario")
Beispiel #20
0
 def hasSegment(self):
     return XmlUtil.hasChild(self.entity, XbrlConst.xbrli, "segment")
Beispiel #21
0
 def isForeverPeriod(self):
     try:
         return self._isForeverPeriod
     except AttributeError:
         self._isForeverPeriod = XmlUtil.hasChild(self.period, XbrlConst.xbrli, "forever")
         return self._isForeverPeriod
Beispiel #22
0
 def isInstantPeriod(self):
     try:
         return self._isInstantPeriod
     except AttributeError:
         self._isInstantPeriod = XmlUtil.hasChild(self.period, XbrlConst.xbrli, "instant")
         return self._isInstantPeriod
Beispiel #23
0
 def isStartEndPeriod(self):
     try:
         return self._isStartEndPeriod
     except AttributeError:
         self._isStartEndPeriod = XmlUtil.hasChild(self.period, XbrlConst.xbrli, ("startDate","endDate"))
         return self._isStartEndPeriod
Beispiel #24
0
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)
Beispiel #25
0
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)
Beispiel #26
0
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)
Beispiel #28
0
 def hasScenario(self):
     """(bool) -- True if a xbrli:scenario element is present"""
     return XmlUtil.hasChild(self, XbrlConst.xbrli, "scenario")
Beispiel #29
0
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)
Beispiel #30
0
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")
Beispiel #31
0
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)
Beispiel #32
0
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)
Beispiel #33
0
 def isDivide(self):
     """(bool) -- True if unit has a divide element"""
     return XmlUtil.hasChild(self, XbrlConst.xbrli, "divide")
Beispiel #34
0
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")
Beispiel #35
0
 def hasScenario(self):
     return XmlUtil.hasChild(self, XbrlConst.xbrli, "scenario")
Beispiel #36
0
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
Beispiel #37
0
 def hasSegment(self):
     """(bool) -- True if a xbrli:segment element is present"""
     return XmlUtil.hasChild(self.entity, XbrlConst.xbrli, "segment")
Beispiel #38
0
 def hasSegment(self):
     return XmlUtil.hasChild(self.entity, XbrlConst.xbrli, "segment")
Beispiel #39
0
 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)
Beispiel #40
0
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})))