Beispiel #1
0
def validateValue(modelXbrl,
                  elt,
                  attrTag,
                  baseXsdType,
                  value,
                  isNillable=False,
                  isNil=False,
                  facets=None):
    if baseXsdType:
        try:
            '''
            if (len(value) == 0 and attrTag is None and not isNillable and 
                baseXsdType not in ("anyType", "string", "normalizedString", "token", "NMTOKEN", "anyURI", "noContent")):
                raise ValueError("missing value for not nillable element")
            '''
            xValid = VALID
            whitespaceReplace = (baseXsdType == "normalizedString")
            whitespaceCollapse = (not whitespaceReplace
                                  and baseXsdType != "string")
            isList = baseXsdType in {"IDREFS", "ENTITIES", "NMTOKENS"}
            if isList:
                baseXsdType = baseXsdType[:-1]  # remove plural
                if facets:
                    if "minLength" not in facets:
                        facets = facets.copy()
                        facets["minLength"] = 1
                else:
                    facets = {"minLength": 1}
            pattern = baseXsdTypePatterns.get(baseXsdType)
            if facets:
                if "pattern" in facets:
                    pattern = facets["pattern"]
                    # note multiple patterns are or'ed togetner, which isn't yet implemented!
                if "whiteSpace" in facets:
                    whitespaceReplace, whitespaceCollapse = {
                        "preserve": (False, False),
                        "replace": (True, False),
                        "collapse": (False, True)
                    }[facets["whiteSpace"]]
            if whitespaceReplace:
                value = normalizeWhitespacePattern.sub(
                    ' ', value)  # replace tab, line feed, return with space
            elif whitespaceCollapse:
                value = collapseWhitespacePattern.sub(' ', value).strip(
                    ' '
                )  # collapse multiple spaces, tabs, line feeds and returns to single space
            if baseXsdType == "noContent":
                if len(value) > 0 and not entirelyWhitespacePattern.match(
                        value):  # only xml schema pattern whitespaces removed
                    raise ValueError("value content not permitted")
                # note that sValue and xValue are not innerText but only text elements on specific element (or attribute)
                xValue = sValue = None
                xValid = VALID_NO_CONTENT  # notify others that element may contain subelements (for stringValue needs)
            elif not value and isNil and isNillable:  # rest of types get None if nil/empty value
                xValue = sValue = None
            else:
                if pattern is not None:
                    if ((isList and any(
                            pattern.match(v) is None for v in value.split()))
                            or (not isList and pattern.match(value) is None)):
                        raise ValueError(
                            "pattern facet " +
                            facets["pattern"].pattern if facets
                            and "pattern" in facets else "pattern mismatch")
                if facets:
                    if "enumeration" in facets and value not in facets[
                            "enumeration"]:
                        raise ValueError("{0} is not in {1}".format(
                            value, facets["enumeration"].keys()))
                    if "length" in facets and len(value) != facets["length"]:
                        raise ValueError("length {0}, expected {1}".format(
                            len(value), facets["length"]))
                    if "minLength" in facets and len(
                            value) < facets["minLength"]:
                        raise ValueError("length {0}, minLength {1}".format(
                            len(value), facets["minLength"]))
                    if "maxLength" in facets and len(
                            value) > facets["maxLength"]:
                        raise ValueError("length {0}, maxLength {1}".format(
                            len(value), facets["maxLength"]))
                if baseXsdType in {
                        "string", "normalizedString", "language",
                        "languageOrEmpty", "token", "NMTOKEN", "Name",
                        "NCName", "IDREF", "ENTITY"
                }:
                    xValue = sValue = value
                elif baseXsdType == "ID":
                    xValue = sValue = value
                    xValid = VALID_ID
                elif baseXsdType == "anyURI":
                    if value:  # allow empty strings to be valid anyURIs
                        if UrlUtil.relativeUrlPattern.match(value) is None:
                            raise ValueError("IETF RFC 2396 4.3 syntax")
                    # encode PSVI xValue similarly to Xerces and other implementations
                    xValue = anyURI(UrlUtil.anyUriQuoteForPSVI(value))
                    sValue = value
                elif baseXsdType in ("decimal", "float", "double",
                                     "XBRLI_NONZERODECIMAL"):
                    if baseXsdType in ("decimal", "XBRLI_NONZERODECIMAL"):
                        if decimalPattern.match(value) is None:
                            raise ValueError("lexical pattern mismatch")
                        xValue = Decimal(value)
                        sValue = float(
                            value
                        )  # s-value uses Number (float) representation
                        if sValue == 0 and baseXsdType == "XBRLI_NONZERODECIMAL":
                            raise ValueError("zero is not allowed")
                    else:
                        if floatPattern.match(value) is None:
                            raise ValueError("lexical pattern mismatch")
                        xValue = sValue = float(value)
                    if facets:
                        if "totalDigits" in facets and len(
                                value.replace(".",
                                              "")) > facets["totalDigits"]:
                            raise ValueError("totalDigits facet {0}".format(
                                facets["totalDigits"]))
                        if "fractionDigits" in facets and (
                                '.' in value
                                and len(value[value.index('.') + 1:]) >
                                facets["fractionDigits"]):
                            raise ValueError(
                                "fraction digits facet {0}".format(
                                    facets["fractionDigits"]))
                        if "maxInclusive" in facets and xValue > facets[
                                "maxInclusive"]:
                            raise ValueError(" > maxInclusive {0}".format(
                                facets["maxInclusive"]))
                        if "maxExclusive" in facets and xValue >= facets[
                                "maxExclusive"]:
                            raise ValueError(" >= maxInclusive {0}".format(
                                facets["maxExclusive"]))
                        if "minInclusive" in facets and xValue < facets[
                                "minInclusive"]:
                            raise ValueError(" < minInclusive {0}".format(
                                facets["minInclusive"]))
                        if "minExclusive" in facets and xValue <= facets[
                                "minExclusive"]:
                            raise ValueError(" <= minExclusive {0}".format(
                                facets["minExclusive"]))
                elif baseXsdType in {
                        "integer", "nonPositiveInteger", "negativeInteger",
                        "nonNegativeInteger", "positiveInteger", "long",
                        "unsignedLong", "int", "unsignedInt", "short",
                        "unsignedShort", "byte", "unsignedByte"
                }:
                    xValue = sValue = _INT(value)
                    if ((baseXsdType in {
                            "nonNegativeInteger", "unsignedLong", "unsignedInt"
                    } and xValue < 0) or
                        (baseXsdType == "nonPositiveInteger" and xValue > 0) or
                        (baseXsdType == "positiveInteger" and xValue <= 0) or
                        (baseXsdType == "byte" and not -128 <= xValue < 127)
                            or (baseXsdType == "unsignedByte"
                                and not 0 <= xValue < 255)
                            or (baseXsdType == "short"
                                and not -32768 <= xValue < 32767)
                            or (baseXsdType == "unsignedShort"
                                and not 0 <= xValue < 65535) or
                        (baseXsdType == "positiveInteger" and xValue <= 0)):
                        raise ValueError("{0} is not {1}".format(
                            value, baseXsdType))
                    if facets:
                        if "totalDigits" in facets and len(
                                value.replace(".",
                                              "")) > facets["totalDigits"]:
                            raise ValueError("totalDigits facet {0}".format(
                                facets["totalDigits"]))
                        if "fractionDigits" in facets and (
                                '.' in value
                                and len(value[value.index('.') + 1:]) >
                                facets["fractionDigits"]):
                            raise ValueError(
                                "fraction digits facet {0}".format(
                                    facets["fractionDigits"]))
                        if "maxInclusive" in facets and xValue > facets[
                                "maxInclusive"]:
                            raise ValueError(" > maxInclusive {0}".format(
                                facets["maxInclusive"]))
                        if "maxExclusive" in facets and xValue >= facets[
                                "maxExclusive"]:
                            raise ValueError(" >= maxInclusive {0}".format(
                                facets["maxExclusive"]))
                        if "minInclusive" in facets and xValue < facets[
                                "minInclusive"]:
                            raise ValueError(" < minInclusive {0}".format(
                                facets["minInclusive"]))
                        if "minExclusive" in facets and xValue <= facets[
                                "minExclusive"]:
                            raise ValueError(" <= minExclusive {0}".format(
                                facets["minExclusive"]))
                elif baseXsdType == "boolean":
                    if value in ("true", "1"):
                        xValue = sValue = True
                    elif value in ("false", "0"):
                        xValue = sValue = False
                    else:
                        raise ValueError
                elif baseXsdType == "QName":
                    xValue = qnameEltPfxName(elt,
                                             value,
                                             prefixException=ValueError)
                    #xValue = qname(elt, value, castException=ValueError, prefixException=ValueError)
                    sValue = value
                    ''' not sure here, how are explicitDimensions validated, but bad units not?
                    if xValue.namespaceURI in modelXbrl.namespaceDocs:
                        if (xValue not in modelXbrl.qnameConcepts and 
                            xValue not in modelXbrl.qnameTypes and
                            xValue not in modelXbrl.qnameAttributes and
                            xValue not in modelXbrl.qnameAttributeGroups):
                            raise ValueError("qname not defined " + str(xValue))
                    '''
                elif baseXsdType == "enumerationHrefs":
                    xValue = [qnameHref(href) for href in value.split()]
                    sValue = value
                elif baseXsdType == "enumerationQNames":
                    xValue = [
                        qnameEltPfxName(elt, qn, prefixException=ValueError)
                        for qn in value.split()
                    ]
                    sValue = value
                elif baseXsdType in ("XBRLI_DECIMALSUNION",
                                     "XBRLI_PRECISIONUNION"):
                    xValue = sValue = value if value == "INF" else _INT(value)
                elif baseXsdType in ("XBRLI_NONZERODECIMAL"):
                    xValue = sValue = _INT(value)
                    if xValue == 0:
                        raise ValueError("invalid value")
                elif baseXsdType == "regex-pattern":
                    # for facet compiling
                    try:
                        sValue = value
                        if value in xmlSchemaPatterns:
                            xValue = xmlSchemaPatterns[value]
                        else:
                            if r"\i" in value or r"\c" in value:
                                value = value.replace(r"[\i-[:]]", iNameChar).replace(r"\i", iNameChar) \
                                              .replace(r"[\c-[:]]", cMinusCNameChar).replace(r"\c", cNameChar)
                            xValue = re_compile(value +
                                                "$")  # must match whole string
                    except Exception as err:
                        raise ValueError(err)
                elif baseXsdType == "fraction":
                    numeratorStr, denominatorStr = elt.fractionValue
                    if numeratorStr == INVALIDixVALUE or denominatorStr == INVALIDixVALUE:
                        sValue = xValue = INVALIDixVALUE
                        xValid = INVALID
                    else:
                        sValue = value
                        numeratorNum = float(numeratorStr)
                        denominatorNum = float(denominatorStr)
                        if numeratorNum.is_integer(
                        ) and denominatorNum.is_integer():
                            xValue = Fraction(int(numeratorNum),
                                              int(denominatorNum))
                        else:
                            xValue = Fraction(numeratorNum / denominatorNum)
                else:
                    if baseXsdType in lexicalPatterns:
                        match = lexicalPatterns[baseXsdType].match(value)
                        if match is None:
                            raise ValueError("lexical pattern mismatch")
                        if baseXsdType == "XBRLI_DATEUNION":
                            xValue = dateTime(value,
                                              type=DATEUNION,
                                              castException=ValueError)
                            sValue = value
                        elif baseXsdType == "dateTime":
                            xValue = dateTime(value,
                                              type=DATETIME,
                                              castException=ValueError)
                            sValue = value
                        elif baseXsdType == "date":
                            xValue = dateTime(value,
                                              type=DATE,
                                              castException=ValueError)
                            sValue = value
                        elif baseXsdType == "gMonthDay":
                            month, day, zSign, zHrMin, zHr, zMin = match.groups(
                            )
                            if int(day) > {
                                    2: 29,
                                    4: 30,
                                    6: 30,
                                    9: 30,
                                    11: 30,
                                    1: 31,
                                    3: 31,
                                    5: 31,
                                    7: 31,
                                    8: 31,
                                    10: 31,
                                    12: 31
                            }[int(month)]:
                                raise ValueError(
                                    "invalid day {0} for month {1}".format(
                                        day, month))
                            xValue = gMonthDay(month, day)
                        elif baseXsdType == "gYearMonth":
                            year, month, zSign, zHrMin, zHr, zMin = match.groups(
                            )
                            xValue = gYearMonth(year, month)
                        elif baseXsdType == "gYear":
                            year, zSign, zHrMin, zHr, zMin = match.groups()
                            xValue = gYear(year)
                        elif baseXsdType == "gMonth":
                            month, zSign, zHrMin, zHr, zMin = match.groups()
                            xValue = gMonth(month)
                        elif baseXsdType == "gDay":
                            day, zSign, zHrMin, zHr, zMin = match.groups()
                            xValue = gDay(day)
                        elif baseXsdType == "duration":
                            xValue = isoDuration(value)
                        else:
                            xValue = value
                    else:  # no lexical pattern, forget compiling value
                        xValue = value
                    sValue = value
        except (ValueError, InvalidOperation) as err:
            if ModelInlineValueObject is not None and isinstance(
                    elt, ModelInlineValueObject):
                errElt = "{0} fact {1}".format(elt.elementQname, elt.qname)
            else:
                errElt = elt.elementQname
            if attrTag:
                modelXbrl.error(
                    "xmlSchema:valueError",
                    _("Element %(element)s attribute %(attribute)s type %(typeName)s value error: %(value)s, %(error)s"
                      ),
                    modelObject=elt,
                    element=errElt,
                    attribute=XmlUtil.clarkNotationToPrefixedName(
                        elt, attrTag, isAttribute=True),
                    typeName=baseXsdType,
                    value=strTruncate(value, 30),
                    error=err)
            else:
                modelXbrl.error(
                    "xmlSchema:valueError",
                    _("Element %(element)s type %(typeName)s value error: %(value)s, %(error)s"
                      ),
                    modelObject=elt,
                    element=errElt,
                    typeName=baseXsdType,
                    value=strTruncate(value, 30),
                    error=err)
            xValue = None
            sValue = value
            xValid = INVALID
    else:
        xValue = sValue = None
        xValid = UNKNOWN
    if attrTag:
        try:  # dynamically allocate attributes (otherwise given shared empty set)
            xAttributes = elt.xAttributes
        except AttributeError:
            elt.xAttributes = xAttributes = {}
        xAttributes[attrTag] = ModelAttribute(elt, attrTag, xValid, xValue,
                                              sValue, value)
    else:
        elt.xValid = xValid
        elt.xValue = xValue
        elt.sValue = sValue
Beispiel #2
0
def validateValue(modelXbrl, elt, attrTag, baseXsdType, value, isNillable=False, isNil=False, facets=None):
    if baseXsdType:
        try:
            '''
            if (len(value) == 0 and attrTag is None and not isNillable and 
                baseXsdType not in ("anyType", "string", "normalizedString", "token", "NMTOKEN", "anyURI", "noContent")):
                raise ValueError("missing value for not nillable element")
            '''
            xValid = VALID
            whitespaceReplace = (baseXsdType == "normalizedString")
            whitespaceCollapse = (not whitespaceReplace and baseXsdType != "string")
            isList = baseXsdType in {"IDREFS", "ENTITIES", "NMTOKENS"}
            if isList:
                baseXsdType = baseXsdType[:-1] # remove plural
            pattern = baseXsdTypePatterns.get(baseXsdType)
            if facets:
                if "pattern" in facets:
                    pattern = facets["pattern"]
                    # note multiple patterns are or'ed togetner, which isn't yet implemented!
                if "whiteSpace" in facets:
                    whitespaceReplace, whitespaceCollapse = {"preserve":(False,False), "replace":(True,False), "collapse":(False,True)}[facets["whiteSpace"]]
            if whitespaceReplace:
                value = normalizeWhitespacePattern.sub(' ', value)
            elif whitespaceCollapse:
                value = collapseWhitespacePattern.sub(' ', value.strip())
            if baseXsdType == "noContent":
                if len(value) > 0 and not value.isspace():
                    raise ValueError("value content not permitted")
                # note that sValue and xValue are not innerText but only text elements on specific element (or attribute)
                xValue = sValue = None
                xValid = VALID_NO_CONTENT # notify others that element may contain subelements (for stringValue needs)
            elif not value and isNil and isNillable: # rest of types get None if nil/empty value
                xValue = sValue = None
            else:
                if pattern is not None:
                    if ((isList and any(pattern.match(v) is None for v in value.split())) or
                        (not isList and pattern.match(value) is None)):
                        raise ValueError("pattern facet " + facets["pattern"].pattern if facets and "pattern" in facets else "pattern mismatch")
                if facets:
                    if "enumeration" in facets and value not in facets["enumeration"]:
                        raise ValueError("{0} is not in {1}".format(value, facets["enumeration"].keys()))
                    if "length" in facets and len(value) != facets["length"]:
                        raise ValueError("length {0}, expected {1}".format(len(value), facets["length"]))
                    if "minLength" in facets and len(value) < facets["minLength"]:
                        raise ValueError("length {0}, minLength {1}".format(len(value), facets["minLength"]))
                    if "maxLength" in facets and len(value) > facets["maxLength"]:
                        raise ValueError("length {0}, maxLength {1}".format(len(value), facets["maxLength"]))
                if baseXsdType in {"string", "normalizedString", "language", "token", "NMTOKEN","Name","NCName","IDREF","ENTITY"}:
                    xValue = sValue = value
                elif baseXsdType == "ID":
                    xValue = sValue = value
                    xValid = VALID_ID
                elif baseXsdType == "anyURI":
                    if value:  # allow empty strings to be valid anyURIs
                        if UrlUtil.relativeUrlPattern.match(value) is None:
                            raise ValueError("IETF RFC 2396 4.3 syntax")
                    # encode PSVI xValue similarly to Xerces and other implementations
                    xValue = anyURI(UrlUtil.anyUriQuoteForPSVI(value))
                    sValue = value
                elif baseXsdType in ("decimal", "float", "double"):
                    if baseXsdType == "decimal":
                        if decimalPattern.match(value) is None:
                            raise ValueError("lexical pattern mismatch")
                        xValue = Decimal(value)
                        sValue = float(value) # s-value uses Number (float) representation
                    else:
                        if floatPattern.match(value) is None:
                            raise ValueError("lexical pattern mismatch")
                        xValue = sValue = float(value)
                    if facets:
                        if "totalDigits" in facets and len(value.replace(".","")) > facets["totalDigits"]:
                            raise ValueError("totalDigits facet {0}".format(facets["totalDigits"]))
                        if "fractionDigits" in facets and ( '.' in value and
                            len(value[value.index('.') + 1:]) > facets["fractionDigits"]):
                            raise ValueError("fraction digits facet {0}".format(facets["fractionDigits"]))
                        if "maxInclusive" in facets and xValue > facets["maxInclusive"]:
                            raise ValueError(" > maxInclusive {0}".format(facets["maxInclusive"]))
                        if "maxExclusive" in facets and xValue >= facets["maxExclusive"]:
                            raise ValueError(" >= maxInclusive {0}".format(facets["maxExclusive"]))
                        if "minInclusive" in facets and xValue < facets["minInclusive"]:
                            raise ValueError(" < minInclusive {0}".format(facets["minInclusive"]))
                        if "minExclusive" in facets and xValue <= facets["minExclusive"]:
                            raise ValueError(" <= minExclusive {0}".format(facets["minExclusive"]))
                elif baseXsdType in {"integer",
                                     "nonPositiveInteger","negativeInteger","nonNegativeInteger","positiveInteger",
                                     "long","unsignedLong",
                                     "int","unsignedInt",
                                     "short","unsignedShort",
                                     "byte","unsignedByte"}:
                    xValue = sValue = _INT(value)
                    if ((baseXsdType in {"nonNegativeInteger","unsignedLong","unsignedInt"} 
                         and xValue < 0) or
                        (baseXsdType == "nonPositiveInteger" and xValue > 0) or
                        (baseXsdType == "positiveInteger" and xValue <= 0) or
                        (baseXsdType == "byte" and not -128 <= xValue < 127) or
                        (baseXsdType == "unsignedByte" and not 0 <= xValue < 255) or
                        (baseXsdType == "short" and not -32768 <= xValue < 32767) or
                        (baseXsdType == "unsignedShort" and not 0 <= xValue < 65535) or
                        (baseXsdType == "positiveInteger" and xValue <= 0)):
                        raise ValueError("{0} is not {1}".format(value, baseXsdType))
                    if facets:
                        if "totalDigits" in facets and len(value.replace(".","")) > facets["totalDigits"]:
                            raise ValueError("totalDigits facet {0}".format(facets["totalDigits"]))
                        if "fractionDigits" in facets and ( '.' in value and
                            len(value[value.index('.') + 1:]) > facets["fractionDigits"]):
                            raise ValueError("fraction digits facet {0}".format(facets["fractionDigits"]))
                        if "maxInclusive" in facets and xValue > facets["maxInclusive"]:
                            raise ValueError(" > maxInclusive {0}".format(facets["maxInclusive"]))
                        if "maxExclusive" in facets and xValue >= facets["maxExclusive"]:
                            raise ValueError(" >= maxInclusive {0}".format(facets["maxExclusive"]))
                        if "minInclusive" in facets and xValue < facets["minInclusive"]:
                            raise ValueError(" < minInclusive {0}".format(facets["minInclusive"]))
                        if "minExclusive" in facets and xValue <= facets["minExclusive"]:
                            raise ValueError(" <= minExclusive {0}".format(facets["minExclusive"]))
                elif baseXsdType == "boolean":
                    if value in ("true", "1"):  
                        xValue = sValue = True
                    elif value in ("false", "0"): 
                        xValue = sValue = False
                    else: raise ValueError
                elif baseXsdType == "QName":
                    xValue = qnameEltPfxName(elt, value, prefixException=ValueError)
                    #xValue = qname(elt, value, castException=ValueError, prefixException=ValueError)
                    sValue = value
                    ''' not sure here, how are explicitDimensions validated, but bad units not?
                    if xValue.namespaceURI in modelXbrl.namespaceDocs:
                        if (xValue not in modelXbrl.qnameConcepts and 
                            xValue not in modelXbrl.qnameTypes and
                            xValue not in modelXbrl.qnameAttributes and
                            xValue not in modelXbrl.qnameAttributeGroups):
                            raise ValueError("qname not defined " + str(xValue))
                    '''
                elif baseXsdType in ("XBRLI_DECIMALSUNION", "XBRLI_PRECISIONUNION"):
                    xValue = sValue = value if value == "INF" else _INT(value)
                elif baseXsdType in ("XBRLI_NONZERODECIMAL"):
                    xValue = sValue = _INT(value)
                    if xValue == 0:
                        raise ValueError("invalid value")
                elif baseXsdType == "XBRLI_DATEUNION":
                    xValue = dateTime(value, type=DATEUNION, castException=ValueError)
                    sValue = value
                elif baseXsdType == "dateTime":
                    xValue = dateTime(value, type=DATETIME, castException=ValueError)
                    sValue = value
                elif baseXsdType == "date":
                    xValue = dateTime(value, type=DATE, castException=ValueError)
                    sValue = value
                elif baseXsdType == "regex-pattern":
                    # for facet compiling
                    try:
                        sValue = value
                        if value in xmlSchemaPatterns:
                            xValue = xmlSchemaPatterns[value]
                        else:
                            if r"\i" in value or r"\c" in value:
                                value = value.replace(r"\i", iNameChar).replace(r"\c", cNameChar)
                            xValue = re_compile(value + "$") # must match whole string
                    except Exception as err:
                        raise ValueError(err)
                elif baseXsdType == "fraction":
                    sValue = value
                    xValue = Fraction("/".join(elt.fractionValue))
                else:
                    if baseXsdType in lexicalPatterns:
                        match = lexicalPatterns[baseXsdType].match(value)
                        if match is None:
                            raise ValueError("lexical pattern mismatch")
                        if baseXsdType == "gMonthDay":
                            month, day, zSign, zHrMin, zHr, zMin = match.groups()
                            if int(day) > {2:29, 4:30, 6:30, 9:30, 11:30, 1:31, 3:31, 5:31, 7:31, 8:31, 10:31, 12:31}[int(month)]:
                                raise ValueError("invalid day {0} for month {1}".format(day, month))
                            xValue = gMonthDay(month, day)
                        elif baseXsdType == "gYearMonth":
                            year, month, zSign, zHrMin, zHr, zMin = match.groups()
                            xValue = gYearMonth(year, month)
                        elif baseXsdType == "gYear":
                            year, zSign, zHrMin, zHr, zMin = match.groups()
                            xValue = gYear(year)
                        elif baseXsdType == "gMonth":
                            month, zSign, zHrMin, zHr, zMin = match.groups()
                            xValue = gMonth(month)
                        elif baseXsdType == "gDay":
                            day, zSign, zHrMin, zHr, zMin = match.groups()
                            xValue = gDay(day)
                        else:
                            xValue = value
                    else: # no lexical pattern, forget compiling value
                        xValue = value
                    sValue = value
        except (ValueError, InvalidOperation) as err:
            if ModelInlineValueObject is not None and isinstance(elt, ModelInlineValueObject):
                errElt = "{0} fact {1}".format(elt.elementQname, elt.qname)
            else:
                errElt = elt.elementQname
            if attrTag:
                modelXbrl.error("xmlSchema:valueError",
                    _("Element %(element)s attribute %(attribute)s type %(typeName)s value error: %(value)s, %(error)s"),
                    modelObject=elt,
                    element=errElt,
                    attribute=XmlUtil.clarkNotationToPrefixedName(elt,attrTag,isAttribute=True),
                    typeName=baseXsdType,
                    value=strTruncate(value, 30),
                    error=err)
            else:
                modelXbrl.error("xmlSchema:valueError",
                    _("Element %(element)s type %(typeName)s value error: %(value)s, %(error)s"),
                    modelObject=elt,
                    element=errElt,
                    typeName=baseXsdType,
                    value=strTruncate(value, 30),
                    error=err)
            xValue = None
            sValue = value
            xValid = INVALID
    else:
        xValue = sValue = None
        xValid = UNKNOWN
    if attrTag:
        try:  # dynamically allocate attributes (otherwise given shared empty set)
            xAttributes = elt.xAttributes
        except AttributeError:
            elt.xAttributes = xAttributes = {}
        xAttributes[attrTag] = ModelAttribute(elt, attrTag, xValid, xValue, sValue, value)
    else:
        elt.xValid = xValid
        elt.xValue = xValue
        elt.sValue = sValue
Beispiel #3
0
def validateXbrlFinally(val, *args, **kwargs):
    if not (val.validateROSplugin):
        return

    _xhtmlNs = "{{{}}}".format(xhtml)
    _xhtmlNsLen = len(_xhtmlNs)
    modelXbrl = val.modelXbrl
    modelDocument = modelXbrl.modelDocument
    if not modelDocument:
        return  # never loaded properly

    _statusMsg = _("validating {0} filing rules").format(
        val.disclosureSystem.name)
    modelXbrl.profileActivity()
    modelXbrl.modelManager.showStatus(_statusMsg)

    if modelDocument.type == ModelDocument.Type.INSTANCE:
        modelXbrl.error("ROS:instanceMustBeInlineXBRL",
                        _("ROS expects inline XBRL instances."),
                        modelObject=modelXbrl)
    if modelDocument.type in (ModelDocument.Type.INLINEXBRL,
                              ModelDocument.Type.INLINEXBRLDOCUMENTSET):
        for ixdsHtmlRootElt in modelXbrl.ixdsHtmlElements:  # ix root elements for all ix docs in IXDS
            ixNStag = ixdsHtmlRootElt.modelDocument.ixNStag
            ixTags = set(ixNStag + ln for ln in ("nonNumeric", "nonFraction",
                                                 "references", "relationship"))

        transformRegistryErrors = set()
        ixTargets = set()
        for ixdsHtmlRootElt in modelXbrl.ixdsHtmlElements:
            for elt in ixdsHtmlRootElt.iter():
                if isinstance(
                        elt, (_ElementTree, _Comment, _ProcessingInstruction)):
                    continue  # comment or other non-parsed element
                if isinstance(elt, ModelInlineFact):
                    if elt.format is not None and elt.format.namespaceURI not in TRnamespaces:
                        transformRegistryErrors.add(elt)
                    if elt.get("escape") in ("true", "1"):
                        modelXbrl.error(
                            "ROS.escapedHTML",
                            _("Escaped (x)html fact content is not supported: %(element)s"
                              ),
                            modelObject=elt,
                            element=eltTag)
                eltTag = elt.tag
                if eltTag in ixTags:
                    ixTargets.add(elt.get("target"))
                else:
                    if eltTag.startswith(_xhtmlNs):
                        eltTag = eltTag[_xhtmlNsLen:]
                        if eltTag == "link" and elt.get("type") == "text/css":
                            modelXbrl.error(
                                "ROS.externalCssStyle",
                                _("CSS must be embedded in the inline XBRL document: %(element)s"
                                  ),
                                modelObject=elt,
                                element=eltTag)
                        elif ((eltTag in ("object", "script"))
                              or (eltTag == "a"
                                  and "javascript:" in elt.get("href", ""))
                              or (eltTag == "img"
                                  and "javascript:" in elt.get("src", ""))):
                            modelXbrl.error(
                                "ROS.embeddedCode",
                                _("Inline XBRL documents MUST NOT contain embedded code: %(element)s"
                                  ),
                                modelObject=elt,
                                element=eltTag)
                        elif eltTag == "img":
                            src = elt.get("src", "").strip()
                            if not src.startswith("data:image"):
                                modelXbrl.warning(
                                    "ROS.embeddedCode",
                                    _("Images should be inlined as a base64-encoded string: %(element)s"
                                      ),
                                    modelObject=elt,
                                    element=eltTag)

        if len(ixTargets) > 1:
            modelXbrl.error(
                "ROS:singleOutputDocument",
                _("Multiple target instance documents are not supported: %(targets)s."
                  ),
                modelObject=modelXbrl,
                targets=", ".join((t or "(default)") for t in ixTargets))

        filingTypes = set()
        unexpectedTaxonomyReferences = set()
        numIxDocs = 0
        for doc in modelXbrl.urlDocs.values():
            if doc.type == ModelDocument.Type.INLINEXBRL:
                # base file extension
                _baseName, _baseExt = os.path.splitext(doc.basename)
                if _baseExt not in (".xhtml", ".html", "htm", "ixbrl", "xml",
                                    "xhtml"):
                    modelXbrl.error(
                        "ROS.fileNameExtension",
                        _("The list of acceptable file extensions for upload is: html, htm, ixbrl, xml, xhtml: %(fileName)s"
                          ),
                        modelObject=doc,
                        fileName=doc.basename)

                # document encoding
                if doc.documentEncoding.lower() != "utf-8":
                    modelXbrl.error(
                        "ROS.documentEncoding",
                        _("iXBRL documents submitted to Revenue should be UTF-8 encoded: %(encoding)s"
                          ),
                        modelObject=doc,
                        encoding=doc.documentEncoding)

                # identify type of filing
                for referencedDoc in doc.referencesDocument.keys():
                    if referencedDoc.type == ModelDocument.Type.SCHEMA:
                        if referencedDoc.uri in taxonomyReferences:
                            filingTypes.add(referencedDoc.uri)
                        else:
                            unexpectedTaxonomyReferences.add(referencedDoc.uri)
                # count of inline docs in IXDS
                numIxDocs += 1

        if len(filingTypes) != 1:
            modelXbrl.error(
                "ROS:multipleFilingTypes",
                _("Multiple filing types detected: %(filingTypes)s."),
                modelObject=modelXbrl,
                filingTypes=", ".join(sorted(filingTypes)))
        if unexpectedTaxonomyReferences:
            modelXbrl.error(
                "ROS:unexpectedTaxonomyReferences",
                _("Referenced schema(s) does not map to a taxonomy supported by Revenue (schemaRef): %(unexpectedReferences)s."
                  ),
                modelObject=modelXbrl,
                unexpectedReferences=", ".join(
                    sorted(unexpectedTaxonomyReferences)))

        # single document IXDS
        if numIxDocs > 1:
            modelXbrl.warning(
                "ROS:multipleInlineDocuments",
                _("A single inline document should be submitted but %(numberDocs)s were found."
                  ),
                modelObject=modelXbrl,
                numberDocs=numIxDocs)

        # build namespace maps
        nsMap = {}
        for prefix in ("ie-common", "bus", "uk-bus", "ie-dpl", "core"):
            if prefix in modelXbrl.prefixedNamespaces:
                nsMap[prefix] = modelXbrl.prefixedNamespaces[prefix]

        # build mandatory table by ns qname in use
        mandatory = set()
        for prefix in mandatoryElements:
            if prefix in nsMap:
                ns = nsMap[prefix]
                for localName in mandatoryElements[prefix]:
                    mandatory.add(qname(ns, prefix + ":" + localName))

        equivalentManatoryQNames = [[
            qname(q, nsMap) for q in equivElts
        ] for equivElts in equivalentMandatoryElements]

        # document creator requirement
        if "bus" in nsMap:
            if qname("bus:NameProductionSoftware",
                     nsMap) not in modelXbrl.factsByQname or qname(
                         "bus:VersionProductionSoftware",
                         nsMap) not in modelXbrl.factsByQname:
                modelXbrl.warning(
                    "ROS:documentCreatorProductInformation",
                    _("Please use the NameProductionSoftware tag to identify the software package and the VersionProductionSoftware tag to identify the version of the software package."
                      ),
                    modelObject=modelXbrl)
        elif "uk-bus" in nsMap:
            if qname("uk-bus:NameAuthor",
                     nsMap) not in modelXbrl.factsByQname or qname(
                         "uk-bus:DescriptionOrTitleAuthor",
                         nsMap) not in modelXbrl.factsByQname:
                modelXbrl.warning(
                    "ROS:documentCreatorProductInformation",
                    _("Revenue request that vendor, product and version information is embedded in the generated inline XBRL document using a single XBRLDocumentAuthorGrouping tuple. The NameAuthor tag should be used to identify the name and version of the software package."
                      ),
                    modelObject=modelXbrl)

        schemeEntityIds = set()
        mapContext = {}  # identify unique contexts and units
        mapUnit = {}
        uniqueContextHashes = {}
        hasCRO = False
        unsupportedSchemeContexts = []
        mismatchIdentifierContexts = []
        for context in modelXbrl.contexts.values():
            schemeEntityIds.add(context.entityIdentifier)
            scheme, entityId = context.entityIdentifier
            if scheme not in schemePatterns:
                unsupportedSchemeContexts.append(context)
            elif not schemePatterns[scheme].match(entityId):
                mismatchIdentifierContexts.append(context)
            if scheme == "http://www.cro.ie/":
                hasCRO = True
            h = context.contextDimAwareHash
            if h in uniqueContextHashes:
                if context.isEqualTo(uniqueContextHashes[h]):
                    mapContext[context] = uniqueContextHashes[h]
            else:
                uniqueContextHashes[h] = context
        del uniqueContextHashes
        if len(schemeEntityIds) > 1:
            modelXbrl.error(
                "ROS:differentContextEntityIdentifiers",
                _("Context entity identifier not all the same: %(schemeEntityIds)s."
                  ),
                modelObject=modelXbrl,
                schemeEntityIds=", ".join(
                    sorted(str(s) for s in schemeEntityIds)))
        if unsupportedSchemeContexts:
            modelXbrl.error(
                "ROS:unsupportedContextEntityIdentifierScheme",
                _("Context identifier scheme(s) is not supported: %(schemes)s."
                  ),
                modelObject=unsupportedSchemeContexts,
                schemes=", ".join(
                    sorted(
                        set(c.entityIdentifier[0]
                            for c in unsupportedSchemeContexts))))
        if mismatchIdentifierContexts:
            modelXbrl.error(
                "ROS:invalidContextEntityIdentifier",
                _("Context entity identifier(s) lexically invalid: %(identifiers)s."
                  ),
                modelObject=mismatchIdentifierContexts,
                identifiers=", ".join(
                    sorted(
                        set(c.entityIdentifier[1]
                            for c in mismatchIdentifierContexts))))

        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

        if hasCRO and "ie-common" in nsMap:
            mandatory.add(
                qname("ie-common:CompaniesRegistrationOfficeNumber", nsMap))

        reportedMandatory = set()
        numFactsByConceptContextUnit = defaultdict(list)

        for qn, facts in modelXbrl.factsByQname.items():
            if qn in mandatory:
                reportedMandatory.add(qn)
            for f in facts:
                if f.isNumeric and f.parentElement.qname == qnXbrliXbrl:
                    numFactsByConceptContextUnit[(f.qname,
                                                  mapContext.get(
                                                      f.context, f.context),
                                                  mapUnit.get(
                                                      f.unit,
                                                      f.unit))].append(f)

        missingElements = (
            mandatory - reportedMandatory
        )  # | (reportedFootnoteIfNil - reportedFootnoteIfNil)

        for qnames in equivalentManatoryQNames:  # remove missing elements for which an or-match was reported
            if any(qn in modelXbrl.factsByQname for qn in qnames):
                for qn in qnames:
                    missingElements.discard(qn)

        if missingElements:
            modelXbrl.error(
                "ROS:missingRequiredElements",
                _("Required elements missing from document: %(elements)s."),
                modelObject=modelXbrl,
                elements=", ".join(sorted(str(qn) for qn in missingElements)))

        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(
                        ("ROS.inconsistentDuplicateFacts"),
                        "Inconsistent duplicate numeric facts: %(fact)s were 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))

    modelXbrl.profileActivity(_statusMsg, minTimeToShow=0.0)
    modelXbrl.modelManager.showStatus(None)
Beispiel #4
0
def validateXbrlFinally(val, *args, **kwargs):
    if not (val.validateESEFplugin):
        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

    _ifrsNs = None
    for targetNs in modelXbrl.namespaceDocs.keys():
        if ifrsNsPattern.match(targetNs):
            _ifrsNs = targetNs
    if not _ifrsNs:
        modelXbrl.error("ESEF.RTS.ifrsRequired",
                        _("RTS on ESEF requires IFRS taxonomy."),
                        modelObject=modelXbrl)
        return

    esefPrimaryStatementPlaceholders = set(
        qname(_ifrsNs, n) for n in esefPrimaryStatementPlaceholderNames)
    esefMandatoryElements2020 = set(
        qname(_ifrsNs, n) for n in esefMandatoryElementNames2020)

    if modelDocument.type == ModelDocument.Type.INSTANCE:
        modelXbrl.error("ESEF.I.1.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(
            "ESEF.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 == ModelDocument.Type.INLINEXBRLDOCUMENTSET:
    #    # reports only under reports, none elsewhere
    #    modelXbrl.fileSource.dir

    if modelDocument.type in (ModelDocument.Type.INLINEXBRL,
                              ModelDocument.Type.INLINEXBRLDOCUMENTSET,
                              ModelDocument.Type.INSTANCE):
        footnotesRelationshipSet = modelXbrl.relationshipSet("XBRL-footnotes")
        orphanedFootnotes = set()
        noLangFootnotes = set()
        factLangFootnotes = defaultdict(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)
            lang = elt.xmlLang
            if not lang:
                noLangFootnotes.add(elt)
            else:
                for rel in footnotesRelationshipSet.toModelObject(elt):
                    if rel.fromModelObject is not None:
                        factLangFootnotes[rel.fromModelObject].add(lang)
            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", ".html"):
                    modelXbrl.warning(
                        "ESEF.RTS.Art.3.fileNameExtension",
                        _("FileName SHOULD have the extension .xhtml or .html: %(fileName)s"
                          ),
                        modelObject=doc,
                        fileName=doc.basename)
                docinfo = doc.xmlRootElement.getroottree().docinfo
                if " html" in docinfo.doctype:
                    modelXbrl.warning(
                        "ESEF.RTS.Art.3.htmlDoctype",
                        _("Doctype SHOULD NOT be html: %(fileName)s"),
                        modelObject=doc,
                        fileName=doc.basename)

        if modelDocument.type in (ModelDocument.Type.INLINEXBRL,
                                  ModelDocument.Type.INLINEXBRLDOCUMENTSET):
            hiddenEltIds = {}
            presentedHiddenEltIds = defaultdict(list)
            eligibleForTransformHiddenFacts = []
            requiredToDisplayFacts = []
            requiredToDisplayFactIds = {}
            firstIxdsDoc = True
            for ixdsHtmlRootElt in modelXbrl.ixdsHtmlElements:  # ix root elements for all ix docs in IXDS
                ixNStag = ixdsHtmlRootElt.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"
                for elt, depth in etreeIterWithDepth(ixdsHtmlRootElt):
                    eltTag = elt.tag
                    if isinstance(
                            elt,
                        (_ElementTree, _Comment, _ProcessingInstruction)):
                        continue  # comment or other non-parsed element
                    else:
                        eltTag = elt.tag
                        if eltTag.startswith(_xhtmlNs):
                            eltTag = eltTag[_xhtmlNsLen:]
                            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
                        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(
                                "ESEF.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(src) in ("http", "https", "ftp"):
                                modelXbrl.error(
                                    "ESEF.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 not src.startswith("data:image"):
                                if hasParentIxTextTag:
                                    modelXbrl.error(
                                        "ESEF.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(
                                            src, 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(
                                                "ESEF.2.5.1.embeddedImageNotUsingBase64Encoding",
                                                _("Images MUST be included in the XHTML document as a base64 encoded string unless their size exceeds support of browsers (%(maxImageSize)s): %(file)s."
                                                  ),
                                                modelObject=elt,
                                                maxImageSize=
                                                browserMaxBase64ImageLength,
                                                file=os.path.basename(
                                                    normalizedUri))
                                    except IOError as err:
                                        modelXbrl.error(
                                            "ESEF.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(
                                    "ESEF.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(
                                    "ESEF.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(
                                "ESEF.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)
                        elif eltTag == "link" and elt.get(
                                "type") == "text/css":
                            if len(modelXbrl.ixdsHtmlElements) > 1:
                                f = elt.get("href")
                                if not f or isHttpUrl(f) or os.path.isabs(f):
                                    modelXbrl.warning(
                                        "ESEF.2.5.4.externalCssReportPackage",
                                        _("The CSS file should be physically stored within the report package: %{file}s."
                                          ),
                                        modelObject=elt,
                                        file=f)
                            else:
                                modelXbrl.error(
                                    "ESEF.2.5.4.externalCssFileForSingleIXbrlDocument",
                                    _("Where an Inline XBRL document set contains a single document, the CSS MUST be embedded within the document."
                                      ),
                                    modelObject=elt,
                                    element=eltTag)
                        elif eltTag == "style" and elt.get(
                                "type") == "text/css":
                            if len(modelXbrl.ixdsHtmlElements) > 1:
                                modelXbrl.warning(
                                    "ESEF.2.5.4.embeddedCssForMultiHtmlIXbrlDocumentSets",
                                    _("Where an Inline XBRL document set contains multiple documents, the CSS SHOULD be defined in a separate file."
                                      ),
                                    modelObject=elt,
                                    element=eltTag)

                    if eltTag in ixTags and elt.get("target"):
                        modelXbrl.error(
                            "ESEF.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(
                            "ESEF.2.4.1.tupleElementUsed",
                            _("The ix:tuple element MUST not be used in the Inline XBRL document: %(qname)s."
                              ),
                            modelObject=elt,
                            qname=elt.qname)
                    if eltTag == ixFractionTag:
                        modelXbrl.error(
                            "ESEF.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(
                            "ESEF.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 not in IXT_NAMESPACES:
                            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(
                    "ESEF.2.4.1.transformableElementIncludedInHiddenSection",
                    _("The ix:hidden section of Inline XBRL document MUST not include elements eligible for transformation. "
                      "%(countEligible)s fact(s) were eligible for transformation: %(elements)s"
                      ),
                    modelObject=eligibleForTransformHiddenFacts,
                    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(
                                "ESEF.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(
                    "ESEF.2.4.1.factInHiddenSectionNotInReport",
                    _("The ix:hidden section contains %(countUnreferenced)s fact(s) whose @id is not applied on any \"-esef-ix- hidden\" style: %(elements)s"
                      ),
                    modelObject=requiredToDisplayFacts,
                    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():
            for elt in context.iterdescendants(
                    "{http://www.xbrl.org/2003/instance}startDate",
                    "{http://www.xbrl.org/2003/instance}endDate",
                    "{http://www.xbrl.org/2003/instance}instant"):
                m = datetimePattern.match(elt.stringValue)
                if m:
                    if m.group(1):
                        contextsWithPeriodTime.append(context)
                    if m.group(3):
                        contextsWithPeriodTimeZone.append(context)
            for elt in context.iterdescendants(
                    "{http://www.xbrl.org/2003/instance}segment"):
                contextsWithDisallowedOCEs.append(context)
                break
            for elt in context.iterdescendants(
                    "{http://www.xbrl.org/2003/instance}scenario"):
                if isinstance(elt, ModelObject):
                    if any(True for child in elt.iterchildren()
                           if isinstance(child, ModelObject)
                           and child.tag not in (
                               "{http://xbrl.org/2006/xbrldi}explicitMember",
                               "{http://xbrl.org/2006/xbrldi}typedMember")):
                        contextsWithDisallowedOCEcontent.append(context)
            # check periods here
            contextIdentifiers[context.entityIdentifier].append(context)

        if contextsWithDisallowedOCEs:
            modelXbrl.error(
                "ESEF.2.1.3.segmentUsed",
                _("xbrli:segment container MUST NOT be used in contexts: %(contextIds)s"
                  ),
                modelObject=contextsWithDisallowedOCEs,
                contextIds=", ".join(c.id for c in contextsWithDisallowedOCEs))
        if contextsWithDisallowedOCEcontent:
            modelXbrl.error(
                "ESEF.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(
                "ESEF.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 != iso17442:
                modelXbrl.warning(
                    "ESEF.2.1.1.nonLEIContextScheme",
                    _("The scheme attribute of the xbrli:identifier element should have \"%(leiScheme)s\" as its content: %(contextScheme)s"
                      ),
                    modelObject=contextElts,
                    contextScheme=contextScheme,
                    leiScheme=iso17442)
            else:
                leiValidity = LeiUtil.checkLei(contextIdentifier)
                if leiValidity == LeiUtil.LEI_INVALID_LEXICAL:
                    modelXbrl.warning(
                        "ESEF.2.1.1.invalidIdentifierFormat",
                        _("The LEI context identifier has an invalid format: %(identifier)s"
                          ),
                        modelObject=contextElts,
                        identifier=contextIdentifier)
                elif leiValidity == LeiUtil.LEI_INVALID_CHECKSUM:
                    modelXbrl.warning(
                        "ESEF.2.1.1.invalidIdentifier",
                        _("The LEI context identifier has checksum error: %(identifier)s"
                          ),
                        modelObject=contextElts,
                        identifier=contextIdentifier)
        if contextsWithPeriodTime:
            modelXbrl.warning(
                "ESEF.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(
                "ESEF.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 = {}
        utrValidator = ValidateUtr(modelXbrl)
        utrUnitIds = set(
            u.unitId
            for unitItemType in utrValidator.utrItemTypeEntries.values()
            for u in unitItemType.values())
        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
            # check if any custom measure is in UTR
            for measureTerm in unit.measures:
                for measure in measureTerm:
                    ns = measure.namespaceURI
                    if ns != XbrlConst.iso4217 and not ns.startswith(
                            "http://www.xbrl.org/"):
                        if measure.localName in utrUnitIds:
                            modelXbrl.error(
                                "ESEF.RTS.III.1.G1-7-1.customUnitInUtr",
                                _("Custom measure SHOULD NOT duplicate a UnitID of UTR: %(measure)s"
                                  ),
                                modelObject=unit,
                                measure=measure)
        del uniqueUnitHashes

        reportedMandatory = set()
        precisionFacts = set()
        numFactsByConceptContextUnit = defaultdict(list)
        textFactsByConceptContext = defaultdict(list)
        footnotesRelationshipSet = modelXbrl.relationshipSet(
            XbrlConst.factFootnote, XbrlConst.defaultLinkRole)
        noLangFacts = []
        textFactsMissingReportLang = []
        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)
                    if f.concept is not None and not f.isNil and f.xValid >= VALID and f.xValue > 1 and f.concept.type is not None and (
                            f.concept.type.qname == PERCENT_TYPE
                            or f.concept.type.isDerivedFrom(PERCENT_TYPE)):
                        modelXbrl.warning(
                            "ESEF.2.2.2.percentGreaterThan100",
                            _("A percent fact should have value <= 100: %(element)s in context %(context)s value %(value)s"
                              ),
                            modelObject=f,
                            element=f.qname,
                            context=f.context.id,
                            value=f.xValue)
                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(
                "ESEF.2.5.2.undefinedLanguageForTextFact",
                _("Each tagged text fact MUST have the 'xml:lang' attribute assigned or inherited."
                  ),
                modelObject=noLangFacts)

        # missing report lang text facts
        if reportXmlLang:
            for fList in textFactsByConceptContext.values():
                if not any(f.xmlLang == reportXmlLang for f in fList):
                    modelXbrl.error(
                        "ESEF.2.5.2.taggedTextFactOnlyInLanguagesOtherThanLanguageOfAReport",
                        _("Each tagged text fact MUST have the 'xml:lang' provided in at least the language of the report: %(element)s"
                          ),
                        modelObject=fList,
                        element=fList[0].qname)

        # 2.2.4 test
        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(
                        ("ESEF.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(
                "ESEF.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(e.qname) for e in precisionFacts)))

        missingElements = (mandatory - reportedMandatory)
        if missingElements:
            modelXbrl.error(
                "ESEF.???.missingRequiredElements",
                _("Required elements missing from document: %(elements)s."),
                modelObject=modelXbrl,
                elements=", ".join(sorted(str(qn) for qn in missingElements)))

        if transformRegistryErrors:
            modelXbrl.warning(
                "ESEF.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(
                "ESEF.2.3.1.unusedFootnote",
                _("Non-empty footnotes must be connected to fact(s)."),
                modelObject=orphanedFootnotes)

        if noLangFootnotes:
            modelXbrl.error(
                "ESEF.2.3.1.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)
        nonDefLangFtFacts = set(f for f, langs in factLangFootnotes.items()
                                if reportXmlLang not in langs)
        if nonDefLangFtFacts:
            modelXbrl.error(
                "ESEF.2.3.1.footnoteOnlyInLanguagesOtherThanLanguageOfAReport",
                _("Each fact MUST have at least one footnote with 'xml:lang' attribute whose value corresponds to the language of the text in the content of the respective footnote: %(qnames)s."
                  ),
                modelObject=nonDefLangFtFacts,
                qnames=", ".join(
                    sorted(str(f.qname) for f in nonDefLangFtFacts)))
        del nonDefLangFtFacts
        if footnoteRoleErrors:
            modelXbrl.error(
                "ESEF.2.3.1.nonStandardRoleForFootnote",
                _("The xlink:role attribute of a link:footnote and link:footnoteLink element as well as xlink:arcrole attribute of a link:footnoteArc MUST be defined in the XBRL Specification 2.1."
                  ),
                modelObject=footnoteRoleErrors)

        nonStdFootnoteElts = list()
        for modelLink in modelXbrl.baseSets[("XBRL-footnotes", None, None,
                                             None)]:
            for elt in modelLink.iterchildren():
                if isinstance(
                        elt, (_ElementTree, _Comment, _ProcessingInstruction)):
                    continue  # comment or other non-parsed element
                if elt.qname not in FOOTNOTE_LINK_CHILDREN:
                    nonStdFootnoteElts.append(elt)

        if nonStdFootnoteElts:
            modelXbrl.error(
                "ESEF.2.3.2.nonStandardElementInFootnote",
                _("A link:footnoteLink element MUST have no children other than link:loc, link:footnote, and link:footnoteArc."
                  ),
                modelObject=nonStdFootnoteElts)

        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")):
            unreportedLbElts = 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 and fr not in conceptsUsed and isExtension(
                                val, rel):
                            unreportedLbElts.add(fr)
                        if to is not None and not to.isAbstract and to not in conceptsUsed and isExtension(
                                val, rel):
                            unreportedLbElts.add(to)
                    elif arcrole == dimensionDomain:  # dimension, always abstract
                        if fr is not None and fr not in conceptsUsed and isExtension(
                                val, rel):
                            unreportedLbElts.add(fr)
                        if to is not None and rel.isUsable and to not in conceptsUsed and isExtension(
                                val, rel):
                            unreportedLbElts.add(to)
                    elif arcrole == domainMember:
                        if to is not None and not fr.isAbstract and rel.isUsable and to not in conceptsUsed and isExtension(
                                val, rel):
                            unreportedLbElts.add(to)
            if unreportedLbElts:
                modelXbrl.error(
                    "ESEF.3.4.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))))

        # 3.4.4 check for presentation preferred labels
        missingConceptLabels = defaultdict(set)  # by role
        pfsConceptsRootInPreLB = set()

        def checkLabels(parent, relSet, labelrole, visited):
            if not parent.label(
                    labelrole, lang=reportXmlLang, fallbackToQname=False):
                if parent.name != "NotesAccountingPoliciesAndMandatoryTags":  # TEMPORARY TBD remove
                    missingConceptLabels[labelrole].add(parent)
            visited.add(parent)
            conceptRels = defaultdict(
                list)  # counts for concepts without preferred label role
            for rel in relSet.fromModelObject(parent):
                child = rel.toModelObject
                if child is not None:
                    labelrole = rel.preferredLabel
                    if not labelrole:
                        conceptRels[child].append(rel)
                    if child not in visited:
                        checkLabels(child, relSet, labelrole, visited)
            for concept, rels in conceptRels.items():
                if len(rels) > 1:
                    modelXbrl.warning(
                        "ESEF.3.4.4.missingPreferredLabelRole",
                        _("Preferred label role SHOULD be used when concept is duplicated in same presentation tree location: %(qname)s."
                          ),
                        modelObject=rels + [concept],
                        qname=concept.qname)
            visited.remove(parent)

        for ELR in modelXbrl.relationshipSet(parentChild).linkRoleUris:
            relSet = modelXbrl.relationshipSet(parentChild, ELR)
            for rootConcept in relSet.rootConcepts:
                checkLabels(rootConcept, relSet, None, set())
                # check for PFS element which isn't an orphan
                if rootConcept.qname in esefPrimaryStatementPlaceholders and relSet.fromModelObject(
                        rootConcept):
                    pfsConceptsRootInPreLB.add(rootConcept)
        for labelrole, concepts in missingConceptLabels.items():
            modelXbrl.warning(
                "ESEF.3.4.5.missingLabelForRoleInReportLanguage",
                _("Label for %(role)s role SHOULD be available in report language for concepts: %(qnames)s."
                  ),
                modelObject=concepts,
                qnames=", ".join(str(c.qname) for c in concepts),
                role=os.path.basename(labelrole) if labelrole else "standard")
        if not pfsConceptsRootInPreLB:
            # no PFS statements were recognized
            modelXbrl.error(
                "ESEF.RTS.Annex.II.Par.1.Par.7.missingPrimaryFinancialStatement",
                _("A primary financial statement placeholder element MUST be a root of a presentation linkbase tree."
                  ),
                modelObject=modelXbrl)
        # dereference
        del missingConceptLabels, pfsConceptsRootInPreLB

        # mandatory factc RTS Annex II
        missingMandatoryElements = esefMandatoryElements2020 - modelXbrl.factsByQname.keys(
        )
        if missingMandatoryElements:
            modelXbrl.error(
                "ESEF.RTS.Annex.II.Par.2.missingMandatoryMarkups",
                _("Mandatory elements to be marked up are missing: %(qnames)s."
                  ),
                modelObject=missingMandatoryElements,
                qnames=", ".join(
                    sorted(str(qn) for qn in missingMandatoryElements)))

        # duplicated core taxonomy elements
        for name, concepts in modelXbrl.nameConcepts.items():
            if len(concepts) > 1:
                i = None  # ifrs Concept
                for c in concepts:
                    if c.qname.namespaceURI == _ifrsNs:
                        i = c
                        break
                if i is not None:
                    for c in concepts:
                        if c != i and c.balance == i.balance and c.periodType == i.periodType:
                            modelXbrl.error(
                                "ESEF.RTS.Annex.IV.Par.4.2.extensionElementDuplicatesCoreElement",
                                _("Extension elements must not duplicate the existing elements from the core taxonomy and be identifiable %(qname)s."
                                  ),
                                modelObject=(c, i),
                                qname=c.qname)

    modelXbrl.profileActivity(_statusMsg, minTimeToShow=0.0)
    modelXbrl.modelManager.showStatus(None)
Beispiel #5
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)