Esempio n. 1
0
 def prefixedNameQname(self, prefixedName):
     """Returns ModelValue.QName of prefixedName using this element and its ancestors' xmlns.
     
     :param prefixedName: A prefixed name string
     :type prefixedName: str
     :returns: QName -- the resolved prefixed name, or None if no prefixed name was provided
     """
     if prefixedName:    # passing None would return element qname, not prefixedName None Qname
         return qnameEltPfxName(self, prefixedName)
     else:
         return None
Esempio n. 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
                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
Esempio n. 3
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"]))
                    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=value if len(value) < 31 else 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=value if len(value) < 31 else 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
Esempio n. 4
0
def validateFacts(val, factsToCheck):
    # may be called in streaming batches or all at end (final) if not streaming
    
    modelXbrl = val.modelXbrl
    modelDocument = modelXbrl.modelDocument
    
    # note EBA 2.1 is in ModelDocument.py
    
    timelessDatePattern = re.compile(r"\s*([0-9]{4})-([0-9]{2})-([0-9]{2})\s*$")
    for cntx in modelXbrl.contexts.values():
        if getattr(cntx, "_batchChecked", False):
            continue # prior streaming batch already checked
        cntx._batchChecked = True
        val.cntxEntities.add(cntx.entityIdentifier)
        dateElts = XmlUtil.descendants(cntx, XbrlConst.xbrli, ("startDate","endDate","instant"))
        if any(not timelessDatePattern.match(e.textValue) for e in dateElts):
            modelXbrl.error(("EBA.2.10","EIOPA.2.10"),
                    _('Period dates must be whole dates without time or timezone: %(dates)s.'),
                    modelObject=cntx, dates=", ".join(e.text for e in dateElts))
        if cntx.isForeverPeriod:
            modelXbrl.error(("EBA.2.11","EIOPA.N.2.11"),
                    _('Forever context period is not allowed.'),
                    modelObject=cntx)
        elif cntx.isStartEndPeriod:
            modelXbrl.error(("EBA.2.13","EIOPA.N.2.11"),
                    _('Start-End (flow) context period is not allowed.'),
                    modelObject=cntx)
        elif cntx.isInstantPeriod:
            # cannot pass context object to final() below, for error logging, if streaming mode
            val.cntxDates[cntx.instantDatetime].add(modelXbrl if getattr(val.modelXbrl, "isStreamingMode", False)
                                                    else cntx)
        if cntx.hasSegment:
            modelXbrl.error(("EBA.2.14","EIOPA.N.2.14"),
                _("Contexts MUST NOT contain xbrli:segment values: %(cntx)s.'"),
                modelObject=cntx, cntx=cntx.id)
        if cntx.nonDimValues("scenario"):
            modelXbrl.error(("EBA.2.15","EIOPA.S.2.15" if val.isEIOPAfullVersion else "EIOPA.N.2.15"),
                _("Contexts MUST NOT contain non-dimensional xbrli:scenario values: %(cntx)s.'"),
                modelObject=cntx, cntx=cntx.id, 
                messageCodes=("EBA.2.15","EIOPA.N.2.15","EIOPA.S.2.15"))
        val.unusedCntxIDs.add(cntx.id)

    for unit in modelXbrl.units.values():
        if getattr(unit, "_batchChecked", False):
            continue # prior streaming batch already checked
        unit._batchChecked = True
        val.unusedUnitIDs.add(unit.id)
        
    factsByQname = defaultdict(set) # top level for this
    for f in factsToCheck: 
        factsByQname[f.qname].add(f)
        val.unusedCntxIDs.discard(f.contextID)
        val.unusedUnitIDs.discard(f.unitID)
        if f.objectIndex < val.firstFactObjectIndex:
            val.firstFactObjectIndex = f.objectIndex
            val.firstFact = f
        

    for fIndicators in factsByQname[qnFIndicators]:
        val.numFilingIndicatorTuples += 1
        for fIndicator in fIndicators.modelTupleFacts:
            _value = (getattr(fIndicator, "xValue", None) or fIndicator.value) # use validated xValue if DTS else value for skipDTS 
            if _value in val.filingIndicators:
                modelXbrl.error(("EBA.1.6.1", "EIOPA.1.6.1"),
                        _('Multiple filing indicators facts for indicator %(filingIndicator)s.'),
                        modelObject=(fIndicator, val.filingIndicators[_value]), filingIndicator=_value)
            val.filingIndicators[_value] = fIndicator.get("{http://www.eurofiling.info/xbrl/ext/filing-indicators}filed", "true") in ("true", "1")
            val.unusedCntxIDs.discard(fIndicator.contextID)
            cntx = fIndicator.context
            if cntx is not None and (cntx.hasSegment or cntx.hasScenario):
                modelXbrl.error("EIOPA.N.1.6.d" if val.isEIOPAfullVersion else "EIOPA.S.1.6.d",
                        _('Filing indicators must not contain segment or scenario elements %(filingIndicator)s.'),
                        modelObject=fIndicator, filingIndicator=_value)
        if fIndicators.objectIndex > val.firstFactObjectIndex:
            modelXbrl.warning("EIOPA.1.6.2",
                    _('Filing indicators should precede first fact %(firstFact)s.'),
                    modelObject=(fIndicators, val.firstFact), firstFact=val.firstFact.qname)
    
    if val.isEIOPAfullVersion:
        for fIndicator in factsByQname[qnFilingIndicator]:
            if fIndicator.getparent().qname == XbrlConst.qnXbrliXbrl:
                _isPos = fIndicator.get("{http://www.eurofiling.info/xbrl/ext/filing-indicators}filed", "true") in ("true", "1")
                _value = (getattr(fIndicator, "xValue", None) or fIndicator.value) # use validated xValue if DTS else value for skipDTS 
                modelXbrl.error("EIOPA.1.6.a" if _isPos else "EIOPA.1.6.b",
                        _('Filing indicators must be in a tuple %(filingIndicator)s.'),
                        modelObject=fIndicator, filingIndicator=_value,
                        messageCodes=("EIOPA.1.6.a", "EIOPA.1.6.b"))
                
    otherFacts = {} # (contextHash, unitHash, xmlLangHash) : fact
    nilFacts = []
    # removed in current draft: stringFactsWithoutXmlLang = []
    nonMonetaryNonPureFacts = []
    for qname, facts in factsByQname.items():
        for f in facts:
            if modelXbrl.skipDTS:
                c = f.qname.localName[0]
                isNumeric = c in ('m', 'p', 'r', 'i')
                isMonetary = c == 'm'
                isInteger = c == 'i'
                isPercent = c == 'p'
                isString = c == 's'
                isEnum = c == 'e'
            else:
                concept = f.concept
                if concept is not None:
                    isNumeric = concept.isNumeric
                    isMonetary = concept.isMonetary
                    isInteger = concept.baseXbrliType in integerItemTypes
                    isPercent = concept.typeQname in (qnPercentItemType, qnPureItemType)
                    isString = concept.baseXbrliType in ("stringItemType", "normalizedStringItemType")
                    isEnum = concept.typeQname == qnEnumerationItemType
                else:
                    isNumeric = isString = isEnum = False # error situation
            k = (f.getparent().objectIndex,
                 f.qname,
                 f.context.contextDimAwareHash if f.context is not None else None,
                 f.unit.hash if f.unit is not None else None,
                 hash(f.xmlLang))
            if f.qname == qnFIndicators and val.validateEIOPA:
                pass
            elif k not in otherFacts:
                otherFacts[k] = {f}
            else:
                matches = [o
                           for o in otherFacts[k]
                           if (f.getparent().objectIndex == o.getparent().objectIndex and
                               f.qname == o.qname and
                               f.context.isEqualTo(o.context) if f.context is not None and o.context is not None else True) and
                               # (f.unit.isEqualTo(o.unit) if f.unit is not None and o.unit is not None else True) and
                              (f.xmlLang == o.xmlLang)]
                if matches:
                    contexts = [f.contextID] + [o.contextID for o in matches]
                    modelXbrl.error(("EBA.2.16", "EIOPA.S.2.16" if val.isEIOPAfullVersion else "EIOPA.S.2.16.a"),
                                    _('Facts are duplicates %(fact)s contexts %(contexts)s.'),
                                    modelObject=[f] + matches, fact=f.qname, contexts=', '.join(contexts),
                                    messageCodes=("EBA.2.16", "EIOPA.S.2.16", "EIOPA.S.2.16.a"))
                else:
                    otherFacts[k].add(f)
            if isNumeric:
                if f.precision:
                    modelXbrl.error(("EBA.2.17", "EIOPA.2.18.a"),
                        _("Numeric fact %(fact)s of context %(contextID)s has a precision attribute '%(precision)s'"),
                        modelObject=f, fact=f.qname, contextID=f.contextID, precision=f.precision)
                if f.decimals and not f.isNil:
                    if f.decimals == "INF":
                        if not val.isEIOPAfullVersion:
                            modelXbrl.error("EIOPA.S.2.18.f",
                                _("Monetary fact %(fact)s of context %(contextID)s has a decimal attribute INF: '%(decimals)s'"),
                                modelObject=f, fact=f.qname, contextID=f.contextID, decimals=f.decimals)
                    else:
                        try:
                            xValue = f.xValue
                            dec = int(f.decimals)
                            if isMonetary:
                                if dec < -3:
                                    modelXbrl.error(("EBA.2.18","EIOPA.S.2.18.c"),
                                        _("Monetary fact %(fact)s of context %(contextID)s has a decimals attribute < -3: '%(decimals)s'"),
                                        modelObject=f, fact=f.qname, contextID=f.contextID, decimals=f.decimals)
                                else: # apply dynamic decimals check
                                    if  -.1 < xValue < .1: dMin = 2
                                    elif -1 < xValue < 1: dMin = 1
                                    elif -10 < xValue < 10: dMin = 0
                                    elif -100 < xValue < 100: dMin = -1
                                    elif -1000 < xValue < 1000: dMin = -2
                                    else: dMin = -3
                                    if dMin > dec:
                                        modelXbrl.warning("EIOPA:factDecimalsWarning",
                                            _("Monetary fact %(fact)s of context %(contextID)s value %(value)s has an imprecise decimals attribute: %(decimals)s, minimum is %(mindec)s"),
                                            modelObject=f, fact=f.qname, contextID=f.contextID, value=xValue, decimals=f.decimals, mindec=dMin)
                            elif isInteger:
                                if dec != 0:
                                    modelXbrl.error(("EBA.2.18","EIOPA.S.2.18.d"),
                                        _("Integer fact %(fact)s of context %(contextID)s has a decimals attribute \u2260 0: '%(decimals)s'"),
                                        modelObject=f, fact=f.qname, contextID=f.contextID, decimals=f.decimals)
                            elif isPercent:
                                if dec < 4:
                                    modelXbrl.error(("EBA.2.18","EIOPA.S.2.18.e"),
                                        _("Percent fact %(fact)s of context %(contextID)s has a decimals attribute < 4: '%(decimals)s'"),
                                        modelObject=f, fact=f.qname, contextID=f.contextID, decimals=f.decimals)
                            else:
                                if -.001 < xValue < .001: dMin = 4
                                elif -.01 < xValue < .01: dMin = 3
                                elif -.1 < xValue < .1: dMin = 2
                                elif  -1 < xValue < 1: dMin = 1
                                else: dMin = 0
                                if dMin > dec:
                                    modelXbrl.warning("EIOPA:factDecimalsWarning",
                                        _("Numeric fact %(fact)s of context %(contextID)s value %(value)s has an imprecise decimals attribute: %(decimals)s, minimum is %(mindec)s"),
                                        modelObject=f, fact=f.qname, contextID=f.contextID, value=xValue, decimals=f.decimals, mindec=dMin)
                        except (AttributeError, ValueError):
                            pass # should have been reported as a schema error by loader
                        '''' (not intended by EBA 2.18, paste here is from EFM)
                        if not f.isNil and getattr(f,"xValid", 0) == 4:
                            try:
                                insignificance = insignificantDigits(f.xValue, decimals=f.decimals)
                                if insignificance: # if not None, returns (truncatedDigits, insiginficantDigits)
                                    modelXbrl.error(("EFM.6.05.37", "GFM.1.02.26"),
                                        _("Fact %(fact)s of context %(contextID)s decimals %(decimals)s value %(value)s has nonzero digits in insignificant portion %(insignificantDigits)s."),
                                        modelObject=f1, fact=f1.qname, contextID=f1.contextID, decimals=f1.decimals, 
                                        value=f1.xValue, truncatedDigits=insignificance[0], insignificantDigits=insignificance[1])
                            except (ValueError,TypeError):
                                modelXbrl.error(("EBA.2.18"),
                                    _("Fact %(fact)s of context %(contextID)s decimals %(decimals)s value %(value)s causes Value Error exception."),
                                    modelObject=f1, fact=f1.qname, contextID=f1.contextID, decimals=f1.decimals, value=f1.value)
                        '''
                unit = f.unit
                if unit is not None:
                    if isMonetary:
                        if unit.measures[0]:
                            val.currenciesUsed[unit.measures[0][0]] = unit
                    elif not unit.isSingleMeasure or unit.measures[0][0] != XbrlConst.qnXbrliPure:
                        nonMonetaryNonPureFacts.append(f)
            if isEnum:
                _eQn = getattr(f,"xValue", None) or qnameEltPfxName(f, f.value)
                if _eQn:
                    val.namespacePrefixesUsed[_eQn.namespaceURI].add(_eQn.prefix)
                    val.prefixesUnused.discard(_eQn.prefix)
            ''' removed in current draft
            elif isString: 
                if not f.xmlLang:
                    stringFactsWithoutXmlLang.append(f)
            '''
                        
            if f.isNil:
                nilFacts.append(f)
                
            if val.footnotesRelationshipSet.fromModelObject(f):
                modelXbrl.warning("EIOPA.S.19",
                    _("Fact %(fact)s of context %(contextID)s has footnotes.'"),
                    modelObject=f, fact=f.qname, contextID=f.contextID)
                
    if nilFacts:
        modelXbrl.error(("EBA.2.19", "EIOPA.S.2.19"),
                _('Nil facts MUST NOT be present in the instance: %(nilFacts)s.'),
                modelObject=nilFacts, nilFacts=", ".join(str(f.qname) for f in nilFacts))
    ''' removed in current draft
    if stringFactsWithoutXmlLang:
        modelXbrl.error("EBA.2.20",
                        _("String facts need to report xml:lang: '%(langLessFacts)s'"),
                        modelObject=stringFactsWithoutXmlLang, langLessFacts=", ".join(set(str(f.qname) for f in stringFactsWithoutXmlLang)))
    '''
    if nonMonetaryNonPureFacts:
        modelXbrl.error(("EBA.3.2","EIOPA.3.2.a"),
                        _("Non monetary (numeric) facts MUST use the pure unit: '%(langLessFacts)s'"),
                        modelObject=nonMonetaryNonPureFacts, langLessFacts=", ".join(set(str(f.qname) for f in nonMonetaryNonPureFacts)))

    val.utrValidator.validateFacts() # validate facts for UTR at logLevel WARNING
    
    unitHashes = {}
    for unit in modelXbrl.units.values():
        h = unit.hash
        if h in unitHashes and unit.isEqualTo(unitHashes[h]):
            modelXbrl.warning("EBA.2.21",
                _("Duplicate units SHOULD NOT be reported, units %(unit1)s and %(unit2)s have same measures.'"),
                modelObject=(unit, unitHashes[h]), unit1=unit.id, unit2=unitHashes[h].id)
            if not getattr(modelXbrl, "isStreamingMode", False):
                modelXbrl.error("EIOPA.2.21",
                    _("Duplicate units MUST NOT be reported, units %(unit1)s and %(unit2)s have same measures.'"),
                    modelObject=(unit, unitHashes[h]), unit1=unit.id, unit2=unitHashes[h].id)
        else:
            unitHashes[h] = unit
        for _measures in unit.measures:
            for _measure in _measures:
                val.namespacePrefixesUsed[_measure.namespaceURI].add(_measure.prefix)
                val.prefixesUnused.discard(_measure.prefix)
                
    del unitHashes
    
    cntxHashes = {}
    for cntx in modelXbrl.contexts.values():
        h = cntx.contextDimAwareHash
        if h in cntxHashes and cntx.isEqualTo(cntxHashes[h]):
            if not getattr(modelXbrl, "isStreamingMode", False):
                modelXbrl.log("WARNING" if val.isEIOPAfullVersion else "ERROR",
                    "EIOPA.S.2.7.b",
                    _("Duplicate contexts MUST NOT be reported, contexts %(cntx1)s and %(cntx2)s are equivalent.'"),
                    modelObject=(cntx, cntxHashes[h]), cntx1=cntx.id, cntx2=cntxHashes[h].id)
        else:
            cntxHashes[h] = cntx
        for _dim in cntx.qnameDims.values():
            _dimQn = _dim.dimensionQname
            val.namespacePrefixesUsed[_dimQn.namespaceURI].add(_dimQn.prefix)
            val.prefixesUnused.discard(_dimQn.prefix)
            if _dim.isExplicit:
                _memQn = _dim.memberQname
            else:
                _memQn = _dim.typedMember.qname
            if _memQn:
                val.namespacePrefixesUsed[_memQn.namespaceURI].add(_memQn.prefix)
                val.prefixesUnused.discard(_memQn.prefix)

    for elt in modelDocument.xmlRootElement.iter():
        if isinstance(elt, ModelObject): # skip comments and processing instructions
            val.namespacePrefixesUsed[elt.qname.namespaceURI].add(elt.qname.prefix)
            val.prefixesUnused.discard(elt.qname.prefix)
            for attrTag in elt.keys():
                if attrTag.startswith("{"):
                    _prefix, _NS, _localName = XmlUtil.clarkNotationToPrefixNsLocalname(elt, attrTag, isAttribute=True)
                    if _prefix:
                        val.namespacePrefixesUsed[_NS].add(_prefix)
                        val.prefixesUnused.discard(_prefix)
Esempio n. 5
0
def validateFacts(val, factsToCheck):
    # may be called in streaming batches or all at end (final) if not streaming
    
    modelXbrl = val.modelXbrl
    modelDocument = modelXbrl.modelDocument
    
    # note EBA 2.1 is in ModelDocument.py
    
    timelessDatePattern = re.compile(r"\s*([0-9]{4})-([0-9]{2})-([0-9]{2})\s*$")
    for cntx in modelXbrl.contexts.values():
        if getattr(cntx, "_batchChecked", False):
            continue # prior streaming batch already checked
        cntx._batchChecked = True
        val.cntxEntities.add(cntx.entityIdentifier)
        dateElts = XmlUtil.descendants(cntx, XbrlConst.xbrli, ("startDate","endDate","instant"))
        if any(not timelessDatePattern.match(e.textValue) for e in dateElts):
            modelXbrl.error(("EBA.2.10","EIOPA.2.10"),
                    _('Period dates must be whole dates without time or timezone: %(dates)s.'),
                    modelObject=cntx, dates=", ".join(e.text for e in dateElts))
        if cntx.isForeverPeriod:
            modelXbrl.error(("EBA.2.11","EIOPA.N.2.11"),
                    _('Forever context period is not allowed.'),
                    modelObject=cntx)
        elif cntx.isStartEndPeriod:
            modelXbrl.error(("EBA.2.13","EIOPA.N.2.11"),
                    _('Start-End (flow) context period is not allowed.'),
                    modelObject=cntx)
        elif cntx.isInstantPeriod:
            # cannot pass context object to final() below, for error logging, if streaming mode
            val.cntxDates[cntx.instantDatetime].add(modelXbrl if getattr(val.modelXbrl, "isStreamingMode", False)
                                                    else cntx)
        if cntx.hasSegment:
            modelXbrl.error(("EBA.2.14","EIOPA.N.2.14"),
                _("Contexts MUST NOT contain xbrli:segment values: %(cntx)s.'"),
                modelObject=cntx, cntx=cntx.id)
        if cntx.nonDimValues("scenario"):
            modelXbrl.error(("EBA.2.15","EIOPA.S.2.15" if val.isEIOPAfullVersion else "EIOPA.N.2.15"),
                _("Contexts MUST NOT contain non-dimensional xbrli:scenario values: %(cntx)s.'"),
                modelObject=cntx, cntx=cntx.id, 
                messageCodes=("EBA.2.15","EIOPA.N.2.15","EIOPA.S.2.15"))
        val.unusedCntxIDs.add(cntx.id)
        if val.isEIOPA_2_0_1 and len(cntx.id) > 128:
            modelXbrl.warning("EIOPA.S.2.6",
                _("Contexts IDs SHOULD be short: %(cntx)s.'"),
                modelObject=cntx, cntx=cntx.id)

    for unit in modelXbrl.units.values():
        if getattr(unit, "_batchChecked", False):
            continue # prior streaming batch already checked
        unit._batchChecked = True
        val.unusedUnitIDs.add(unit.id)
        
    factsByQname = defaultdict(set) # top level for this
    for f in factsToCheck: 
        factsByQname[f.qname].add(f)
        val.unusedCntxIDs.discard(f.contextID)
        val.unusedUnitIDs.discard(f.unitID)
        if f.objectIndex < val.firstFactObjectIndex:
            val.firstFactObjectIndex = f.objectIndex
            val.firstFact = f
        

    for fIndicators in factsByQname[qnFIndicators]:
        val.numFilingIndicatorTuples += 1
        for fIndicator in fIndicators.modelTupleFacts:
            _value = (getattr(fIndicator, "xValue", None) or fIndicator.value) # use validated xValue if DTS else value for skipDTS 
            _filed = fIndicator.get("{http://www.eurofiling.info/xbrl/ext/filing-indicators}filed", "true") in ("true", "1")
            if _value in val.filingIndicators:
                modelXbrl.error(("EBA.1.6.1", "EIOPA.1.6.1"),
                        _('Multiple filing indicators facts for indicator %(filingIndicator)s.'),
                        modelObject=(fIndicator, val.filingIndicators[_value]), filingIndicator=_value)
                if _filed and not val.filingIndicators[_value]:
                    val.filingIndicators[_value] = _filed #set to filed if any of the multiple indicators are filed=true
            else: # not a duplicate filing indicator
                val.filingIndicators[_value] = _filed
            val.unusedCntxIDs.discard(fIndicator.contextID)
            cntx = fIndicator.context
            if cntx is not None and (cntx.hasSegment or cntx.hasScenario):
                modelXbrl.error("EIOPA.N.1.6.d" if val.isEIOPAfullVersion else "EIOPA.S.1.6.d",
                        _('Filing indicators must not contain segment or scenario elements %(filingIndicator)s.'),
                        modelObject=fIndicator, filingIndicator=_value)
        if fIndicators.objectIndex > val.firstFactObjectIndex:
            modelXbrl.warning("EIOPA.1.6.2",
                    _('Filing indicators should precede first fact %(firstFact)s.'),
                    modelObject=(fIndicators, val.firstFact), firstFact=val.firstFact.qname)
    
    if val.isEIOPAfullVersion:
        for fIndicator in factsByQname[qnFilingIndicator]:
            if fIndicator.getparent().qname == XbrlConst.qnXbrliXbrl:
                _isPos = fIndicator.get("{http://www.eurofiling.info/xbrl/ext/filing-indicators}filed", "true") in ("true", "1")
                _value = (getattr(fIndicator, "xValue", None) or fIndicator.value) # use validated xValue if DTS else value for skipDTS 
                modelXbrl.error("EIOPA.1.6.a" if _isPos else "EIOPA.1.6.b",
                        _('Filing indicators must be in a tuple %(filingIndicator)s.'),
                        modelObject=fIndicator, filingIndicator=_value,
                        messageCodes=("EIOPA.1.6.a", "EIOPA.1.6.b"))
                
    otherFacts = {} # (contextHash, unitHash, xmlLangHash) : fact
    nilFacts = []
    stringFactsWithXmlLang = []
    nonMonetaryNonPureFacts = []
    for qname, facts in factsByQname.items():
        for f in facts:
            if f.qname == qnFilingIndicator:
                continue # skip erroneous root-level filing indicators
            if modelXbrl.skipDTS:
                c = f.qname.localName[0]
                isNumeric = c in ('m', 'p', 'r', 'i')
                isMonetary = c == 'm'
                isInteger = c == 'i'
                isPercent = c == 'p'
                isString = c == 's'
                isEnum = c == 'e'
            else:
                concept = f.concept
                if concept is not None:
                    isNumeric = concept.isNumeric
                    isMonetary = concept.isMonetary
                    isInteger = concept.baseXbrliType in integerItemTypes
                    isPercent = concept.typeQname in (qnPercentItemType, qnPureItemType)
                    isString = concept.baseXbrliType in ("stringItemType", "normalizedStringItemType")
                    isEnum = concept.typeQname == qnEnumerationItemType
                else:
                    isNumeric = isString = isEnum = False # error situation
            k = (f.getparent().objectIndex,
                 f.qname,
                 f.context.contextDimAwareHash if f.context is not None else None,
                 f.unit.hash if f.unit is not None else None,
                 hash(f.xmlLang))
            if f.qname == qnFIndicators and val.validateEIOPA:
                pass
            elif k not in otherFacts:
                otherFacts[k] = {f}
            else:
                matches = [o
                           for o in otherFacts[k]
                           if (f.getparent().objectIndex == o.getparent().objectIndex and
                               f.qname == o.qname and
                               f.context.isEqualTo(o.context) if f.context is not None and o.context is not None else True) and
                               # (f.unit.isEqualTo(o.unit) if f.unit is not None and o.unit is not None else True) and
                              (f.xmlLang == o.xmlLang)]
                if matches:
                    contexts = [f.contextID] + [o.contextID for o in matches]
                    modelXbrl.error(("EBA.2.16", "EIOPA.S.2.16" if val.isEIOPAfullVersion else "EIOPA.S.2.16.a"),
                                    _('Facts are duplicates %(fact)s contexts %(contexts)s.'),
                                    modelObject=[f] + matches, fact=f.qname, contexts=', '.join(contexts),
                                    messageCodes=("EBA.2.16", "EIOPA.S.2.16", "EIOPA.S.2.16.a"))
                else:
                    otherFacts[k].add(f)
            if isNumeric:
                if f.precision:
                    modelXbrl.error(("EBA.2.17", "EIOPA.2.18.a"),
                        _("Numeric fact %(fact)s of context %(contextID)s has a precision attribute '%(precision)s'"),
                        modelObject=f, fact=f.qname, contextID=f.contextID, precision=f.precision)
                if f.decimals and not f.isNil: # in XbrlDpmSqlDB for 2_0_1
                    if f.decimals == "INF":
                        if not val.isEIOPAfullVersion:
                            modelXbrl.error("EIOPA.S.2.18.f",
                                _("Monetary fact %(fact)s of context %(contextID)s has a decimal attribute INF: '%(decimals)s'"),
                                modelObject=f, fact=f.qname, contextID=f.contextID, decimals=f.decimals)
                    else:
                        try:
                            xValue = f.xValue
                            dec = int(f.decimals)
                            if isMonetary:
                                if val.isEIOPA_2_0_1:
                                    _absXvalue = abs(xValue)
                                    if str(f.qname) in s_2_18_c_a_met:
                                        dMin = 2
                                    elif _absXvalue >= 100000000:
                                        dMin = -4
                                    elif 100000000 > _absXvalue >= 1000000:
                                        dMin = -3
                                    elif 1000000 > _absXvalue >= 1000:
                                        dMin = -2
                                    else:
                                        dMin = -1
                                    if dMin > dec:
                                        modelXbrl.error("EIOPA.S.2.18.c",
                                            _("Monetary fact %(fact)s of context %(contextID)s has a decimals attribute less than minimum %(minimumDecimals)s: '%(decimals)s'"),
                                            modelObject=f, fact=f.qname, contextID=f.contextID, minimumDecimals=dMin, decimals=f.decimals)
                                elif dec < -3:
                                    modelXbrl.error(("EBA.2.18","EIOPA.S.2.18.c"),
                                        _("Monetary fact %(fact)s of context %(contextID)s has a decimals attribute < -3: '%(decimals)s'"),
                                        modelObject=f, fact=f.qname, contextID=f.contextID, decimals=f.decimals)
                                else: # apply dynamic decimals check
                                    if  -.1 < xValue < .1: dMin = 2
                                    elif -1 < xValue < 1: dMin = 1
                                    elif -10 < xValue < 10: dMin = 0
                                    elif -100 < xValue < 100: dMin = -1
                                    elif -1000 < xValue < 1000: dMin = -2
                                    else: dMin = -3
                                    if dMin > dec:
                                        modelXbrl.warning("EIOPA:factDecimalsWarning",
                                            _("Monetary fact %(fact)s of context %(contextID)s value %(value)s has an imprecise decimals attribute: %(decimals)s, minimum is %(mindec)s"),
                                            modelObject=f, fact=f.qname, contextID=f.contextID, value=xValue, decimals=f.decimals, mindec=dMin)
                            elif isInteger:
                                if dec != 0:
                                    modelXbrl.error(("EBA.2.18","EIOPA.S.2.18.d"),
                                        _("Integer fact %(fact)s of context %(contextID)s has a decimals attribute \u2260 0: '%(decimals)s'"),
                                        modelObject=f, fact=f.qname, contextID=f.contextID, decimals=f.decimals)
                            elif isPercent:
                                if dec < 4:
                                    modelXbrl.error(("EBA.2.18","EIOPA.S.2.18.e"),
                                        _("Percent fact %(fact)s of context %(contextID)s has a decimals attribute < 4: '%(decimals)s'"),
                                        modelObject=f, fact=f.qname, contextID=f.contextID, decimals=f.decimals)
                                if val.isEIOPA_2_0_1 and xValue > 1:
                                    modelXbrl.warning(("EIOPA.3.2.b"),
                                        _("Percent fact %(fact)s of context %(contextID)s appears to be over 100% = 1.0: '%(value)s'"),
                                        modelObject=f, fact=f.qname, contextID=f.contextID, value=xValue)
                            else:
                                if -.001 < xValue < .001: dMin = 4
                                elif -.01 < xValue < .01: dMin = 3
                                elif -.1 < xValue < .1: dMin = 2
                                elif  -1 < xValue < 1: dMin = 1
                                else: dMin = 0
                                if dMin > dec:
                                    modelXbrl.warning("EIOPA:factDecimalsWarning",
                                        _("Numeric fact %(fact)s of context %(contextID)s value %(value)s has an imprecise decimals attribute: %(decimals)s, minimum is %(mindec)s"),
                                        modelObject=f, fact=f.qname, contextID=f.contextID, value=xValue, decimals=f.decimals, mindec=dMin)
                        except (AttributeError, ValueError):
                            pass # should have been reported as a schema error by loader
                        '''' (not intended by EBA 2.18, paste here is from EFM)
                        if not f.isNil and getattr(f,"xValid", 0) == 4:
                            try:
                                insignificance = insignificantDigits(f.xValue, decimals=f.decimals)
                                if insignificance: # if not None, returns (truncatedDigits, insiginficantDigits)
                                    modelXbrl.error(("EFM.6.05.37", "GFM.1.02.26"),
                                        _("Fact %(fact)s of context %(contextID)s decimals %(decimals)s value %(value)s has nonzero digits in insignificant portion %(insignificantDigits)s."),
                                        modelObject=f1, fact=f1.qname, contextID=f1.contextID, decimals=f1.decimals, 
                                        value=f1.xValue, truncatedDigits=insignificance[0], insignificantDigits=insignificance[1])
                            except (ValueError,TypeError):
                                modelXbrl.error(("EBA.2.18"),
                                    _("Fact %(fact)s of context %(contextID)s decimals %(decimals)s value %(value)s causes Value Error exception."),
                                    modelObject=f1, fact=f1.qname, contextID=f1.contextID, decimals=f1.decimals, value=f1.value)
                        '''
                unit = f.unit
                if unit is not None:
                    if isMonetary:
                        if unit.measures[0]:
                            _currencyMeasure = unit.measures[0][0]
                            if val.isEIOPA_2_0_1 and f.context is not None:
                                if f.context.dimMemberQname(val.qnDimAF) == val.qnCAx1 and val.qnDimOC in f.context.qnameDims:
                                    _ocCurrency = f.context.dimMemberQname(val.qnDimOC).localName
                                    if _currencyMeasure.localName != _ocCurrency:
                                        modelXbrl.error("EIOPA.3.1",
                                            _("There MUST be only one currency but metric %(metric)s reported OC dimension currency %(ocCurrency)s differs from unit currency: %(unitCurrency)s."),
                                            modelObject=f, metric=f.qname, ocCurrency=_ocCurrency, unitCurrency=_currencyMeasure.localName)
                                else:
                                    val.currenciesUsed[_currencyMeasure] = unit
                            else:
                                val.currenciesUsed[_currencyMeasure] = unit
                    elif not unit.isSingleMeasure or unit.measures[0][0] != XbrlConst.qnXbrliPure:
                        nonMonetaryNonPureFacts.append(f)
            if isEnum:
                _eQn = getattr(f,"xValue", None) or qnameEltPfxName(f, f.value)
                if _eQn:
                    prefixUsed(val, _eQn.namespaceURI, _eQn.prefix)
                    if val.isEIOPA_2_0_1 and f.qname.localName == "ei1930":
                        val.reportingCurrency = _eQn.localName
            elif isString: 
                if f.xmlLang: # requires disclosureSystem to NOT specify default language
                    stringFactsWithXmlLang.append(f)
                        
            if f.isNil:
                nilFacts.append(f)
                
            if val.footnotesRelationshipSet.fromModelObject(f):
                modelXbrl.warning("EIOPA.S.19",
                    _("Fact %(fact)s of context %(contextID)s has footnotes.'"),
                    modelObject=f, fact=f.qname, contextID=f.contextID)
                
    if nilFacts:
        modelXbrl.error(("EBA.2.19", "EIOPA.S.2.19"),
                _('Nil facts MUST NOT be present in the instance: %(nilFacts)s.'),
                modelObject=nilFacts, nilFacts=", ".join(str(f.qname) for f in nilFacts))
    if stringFactsWithXmlLang:
        modelXbrl.warning("EIOPA.2.20", # not reported for EBA
                          _("String facts reporting xml:lang (not saved by T4U, not round-tripped): '%(factsWithLang)s'"),
                          modelObject=stringFactsWithXmlLang, factsWithLang=", ".join(set(str(f.qname) for f in stringFactsWithXmlLang)))
    if nonMonetaryNonPureFacts:
        modelXbrl.error(("EBA.3.2","EIOPA.3.2.a"),
                        _("Non monetary (numeric) facts MUST use the pure unit: '%(langLessFacts)s'"),
                        modelObject=nonMonetaryNonPureFacts, langLessFacts=", ".join(set(str(f.qname) for f in nonMonetaryNonPureFacts)))

    val.utrValidator.validateFacts() # validate facts for UTR at logLevel WARNING
    
    unitHashes = {}
    for unit in modelXbrl.units.values():
        h = unit.hash
        if h in unitHashes and unit.isEqualTo(unitHashes[h]):
            modelXbrl.warning("EBA.2.21",
                _("Duplicate units SHOULD NOT be reported, units %(unit1)s and %(unit2)s have same measures.'"),
                modelObject=(unit, unitHashes[h]), unit1=unit.id, unit2=unitHashes[h].id)
            if not getattr(modelXbrl, "isStreamingMode", False):
                modelXbrl.error("EIOPA.2.21",
                    _("Duplicate units MUST NOT be reported, units %(unit1)s and %(unit2)s have same measures.'"),
                    modelObject=(unit, unitHashes[h]), unit1=unit.id, unit2=unitHashes[h].id)
        else:
            unitHashes[h] = unit
        for _measures in unit.measures:
            for _measure in _measures:
                prefixUsed(val, _measure.namespaceURI, _measure.prefix)
                
    del unitHashes
    
    cntxHashes = {}
    for cntx in modelXbrl.contexts.values():
        h = cntx.contextDimAwareHash
        if h in cntxHashes and cntx.isEqualTo(cntxHashes[h]):
            if not getattr(modelXbrl, "isStreamingMode", False):
                modelXbrl.log("WARNING" if val.isEIOPAfullVersion else "ERROR",
                    "EIOPA.S.2.7.b",
                    _("Duplicate contexts MUST NOT be reported, contexts %(cntx1)s and %(cntx2)s are equivalent.'"),
                    modelObject=(cntx, cntxHashes[h]), cntx1=cntx.id, cntx2=cntxHashes[h].id)
        else:
            cntxHashes[h] = cntx
        for _dim in cntx.qnameDims.values():
            _dimQn = _dim.dimensionQname
            prefixUsed(val, _dimQn.namespaceURI, _dimQn.prefix)
            if _dim.isExplicit:
                _memQn = _dim.memberQname
            else:
                _memQn = _dim.typedMember.qname
            if _memQn:
                prefixUsed(val, _memQn.namespaceURI, _memQn.prefix)

    for elt in modelDocument.xmlRootElement.iter():
        if isinstance(elt, ModelObject): # skip comments and processing instructions
            prefixUsed(val, elt.qname.namespaceURI, elt.qname.prefix)
            for attrTag in elt.keys():
                if attrTag.startswith("{"):
                    _prefix, _NS, _localName = XmlUtil.clarkNotationToPrefixNsLocalname(elt, attrTag, isAttribute=True)
                    if _prefix:
                        prefixUsed(val, _NS, _prefix)
        elif val.isEIOPA_2_0_1:
            if elt.tag in ("{http://www.w3.org/2001/XMLSchema}documentation", "{http://www.w3.org/2001/XMLSchema}annotation"):
                modelXbrl.error("EIOPA.2.5",
                    _("xs:documentation element found, all relevant business data MUST only be contained in contexts, units, schemaRef and facts."),
                    modelObject=modelDocument)
            elif isinstance(elt, etree._Comment):
                modelXbrl.error("EIOPA.2.5",
                    _("XML comment found, all relevant business data MUST only be contained in contexts, units, schemaRef and facts: %(comment)s"),
                    modelObject=modelDocument, comment=elt.text)
Esempio n. 6
0
def validateFacts(val, factsToCheck):
    # may be called in streaming batches or all at end (final) if not streaming
    
    modelXbrl = val.modelXbrl
    modelDocument = modelXbrl.modelDocument
    isStreamingMode = getattr(modelXbrl, "isStreamingMode", False)
    
    # note EBA 2.1 is in ModelDocument.py
    
    timelessDatePattern = re.compile(r"\s*([0-9]{4})-([0-9]{2})-([0-9]{2})\s*$")
    for cntx in modelXbrl.contexts.values():
        if cntx is not None:
            if getattr(cntx, "_batchChecked", False) and isStreamingMode:
                continue # prior streaming batch already checked
            cntx._batchChecked = True
            val.cntxEntities.add(cntx.entityIdentifier)
            dateElts = XmlUtil.descendants(cntx, XbrlConst.xbrli, ("startDate","endDate","instant"))
            if any(not timelessDatePattern.match(e.textValue) for e in dateElts):
                modelXbrl.error(("EBA.2.10","EIOPA.2.10"),
                        _('Period dates must be whole dates without time or timezone: %(dates)s.'),
                        modelObject=cntx, dates=", ".join(e.text for e in dateElts))
            if cntx.isForeverPeriod:
                modelXbrl.error(("EBA.2.11","EIOPA.N.2.11"),
                        _('Forever context period is not allowed.'),
                        modelObject=cntx)
            elif cntx.isStartEndPeriod:
                modelXbrl.error(("EBA.2.13","EIOPA.N.2.11"),
                        _('Start-End (flow) context period is not allowed.'),
                        modelObject=cntx)
            elif cntx.isInstantPeriod:
                # cannot pass context object to final() below, for error logging, if streaming mode
                val.cntxDates[cntx.instantDatetime].add(modelXbrl if getattr(val.modelXbrl, "isStreamingMode", False)
                                                        else cntx)
            if cntx.hasSegment:
                modelXbrl.error(("EBA.2.14","EIOPA.N.2.14"),
                    _("Contexts MUST NOT contain xbrli:segment values: %(cntx)s.'"),
                    modelObject=cntx, cntx=cntx.id)
            if cntx.nonDimValues("scenario"):
                modelXbrl.error(("EBA.2.15","EIOPA.N.2.15"),
                    _("Contexts MUST NOT contain non-dimensional xbrli:scenario values: %(cntx)s.'"),
                    modelObject=cntx, cntx=cntx.id)
            val.unusedCntxIDs.add(cntx.id)

    for unit in modelXbrl.units.values():
        if unit is None or (getattr(unit, "_batchChecked", False) and isStreamingMode):
            continue # prior streaming batch already checked
        unit._batchChecked = True
        val.unusedUnitIDs.add(unit.id)
        
    factsByQname = defaultdict(set) # top level for this
    for f in factsToCheck: 
        factsByQname[f.qname].add(f)
        val.unusedCntxIDs.discard(f.contextID)
        val.unusedUnitIDs.discard(f.unitID)
        if f.objectIndex < val.firstFactObjectIndex:
            val.firstFactObjectIndex = f.objectIndex
            val.firstFact = f
        

    for fIndicators in factsByQname[qnFIndicators]:
        val.numFilingIndicatorTuples += 1
        for fIndicator in fIndicators.modelTupleFacts:
            _value = (getattr(fIndicator, "xValue", None) or fIndicator.value) # use validated xValue if DTS else value for skipDTS 
            if _value in val.filingIndicators:
                modelXbrl.error(("EBA.1.6.1", "EIOPA.1.6.1"),
                        _('Multiple filing indicators facts for indicator %(filingIndicator)s.'),
                        modelObject=(fIndicator, val.filingIndicators[_value]), filingIndicator=_value)
            val.filingIndicators[_value] = fIndicator.get("{http://www.eurofiling.info/xbrl/ext/filing-indicators}filed", "true") in ("true", "1")
            val.unusedCntxIDs.discard(fIndicator.contextID)
            cntx = fIndicator.context
            if cntx is not None and (cntx.hasSegment or cntx.hasScenario):
                modelXbrl.error("EIOPA.S.1.6.d",
                        _('Filing indicators must not contain segment or scenario elements %(filingIndicator)s.'),
                        modelObject=fIndicator, filingIndicator=_value)
        if fIndicators.objectIndex > val.firstFactObjectIndex:
            modelXbrl.warning("EIOPA.1.6.2",
                    _('Filing indicators should precede first fact %(firstFact)s.'),
                    modelObject=(fIndicators, val.firstFact), firstFact=val.firstFact.qname)
                
    otherFacts = {} # (contextHash, unitHash, xmlLangHash) : fact
    nilFacts = []
    # removed in current draft: stringFactsWithoutXmlLang = []
    nonMonetaryNonPureFacts = []
    for qname, facts in factsByQname.items():
        for f in facts:
            if modelXbrl.skipDTS:
                c = f.qname.localName[0]
                isNumeric = c in ('m', 'p', 'i')
                isMonetary = c == 'm'
                isInteger = c == 'i'
                isPercent = c == 'p'
                isString = c == 's'
                isEnum = c == 'e'
            else:
                concept = f.concept
                if concept is not None:
                    isNumeric = concept.isNumeric
                    isMonetary = concept.isMonetary
                    isInteger = concept.baseXbrliType in integerItemTypes
                    isPercent = concept.typeQname in (qnPercentItemType, qnPureItemType)
                    isString = concept.baseXbrliType in ("stringItemType", "normalizedStringItemType")
                    isEnum = concept.typeQname == qnEnumerationItemType
                else:
                    isNumeric = isString = isEnum = False # error situation
            k = (f.getparent().objectIndex,
                 f.qname,
                 f.context.contextDimAwareHash if f.context is not None else None,
                 f.unit.hash if f.unit is not None else None,
                 hash(f.xmlLang))
            if f.qname == qnFIndicators and val.validateEIOPA:
                pass
            elif k not in otherFacts:
                otherFacts[k] = {f}
            else:
                matches = [o
                           for o in otherFacts[k]
                           if (f.getparent().objectIndex == o.getparent().objectIndex and
                               f.qname == o.qname and
                               f.context.isEqualTo(o.context) if f.context is not None and o.context is not None else True) and
                               # (f.unit.isEqualTo(o.unit) if f.unit is not None and o.unit is not None else True) and
                              (f.xmlLang == o.xmlLang)]
                if matches:
                    contexts = [f.contextID] + [o.contextID for o in matches]
                    modelXbrl.error(("EBA.2.16", "EIOPA.S.2.16.a"),
                                    _('Facts are duplicates %(fact)s contexts %(contexts)s.'),
                                    modelObject=[f] + matches, fact=f.qname, contexts=', '.join(contexts))
                else:
                    otherFacts[k].add(f)
            if isNumeric:
                if f.precision:
                    modelXbrl.error(("EBA.2.17", "EIOPA.2.18.a"),
                        _("Numeric fact %(fact)s of context %(contextID)s has a precision attribute '%(precision)s'"),
                        modelObject=f, fact=f.qname, contextID=f.contextID, precision=f.precision)
                if f.decimals and not f.isNil:
                    if f.decimals == "INF":
                        modelXbrl.error("EIOPA.S.2.18.f",
                            _("Monetary fact %(fact)s of context %(contextID)s has a decimal attribute INF: '%(decimals)s'"),
                            modelObject=f, fact=f.qname, contextID=f.contextID, decimals=f.decimals)
                    else:
                        try:
                            xValue = f.xValue
                            dec = int(f.decimals)
                            # will only work if getattr(f,"xValid", XmlValidate.UNVALIDATED) >= XmlValidate.VALID:
                            if isMonetary:
                                if dec < -3:
                                    modelXbrl.error(("EBA.2.18","EIOPA.S.2.18.c"),
                                        _("Monetary fact %(fact)s of context %(contextID)s has a decimals attribute < -3: '%(decimals)s'"),
                                        modelObject=f, fact=f.qname, contextID=f.contextID, decimals=f.decimals)
                                else: # apply dynamic decimals check
                                    if  -.1 < xValue < .1: dMin = 2
                                    elif -1 < xValue < 1: dMin = 1
                                    elif -10 < xValue < 10: dMin = 0
                                    elif -100 < xValue < 100: dMin = -1
                                    elif -1000 < xValue < 1000: dMin = -2
                                    else: dMin = -3
                                    if dMin > dec:
                                        modelXbrl.warning("EIOPA:factDecimalsWarning",
                                            _("Monetary fact %(fact)s of context %(contextID)s value %(value)s has an imprecise decimals attribute: %(decimals)s, minimum is %(mindec)s"),
                                            modelObject=f, fact=f.qname, contextID=f.contextID, value=xValue, decimals=f.decimals, mindec=dMin)
                            elif isInteger:
                                if dec != 0:
                                    modelXbrl.error(("EBA.2.18","EIOPA.S.2.18.d"),
                                        _("Integer fact %(fact)s of context %(contextID)s has a decimals attribute \u2260 0: '%(decimals)s'"),
                                        modelObject=f, fact=f.qname, contextID=f.contextID, decimals=f.decimals)
                            elif isPercent:
                                if dec < 4:
                                    modelXbrl.error(("EBA.2.18","EIOPA.S.2.18.e"),
                                        _("Percent fact %(fact)s of context %(contextID)s has a decimals attribute < 4: '%(decimals)s'"),
                                        modelObject=f, fact=f.qname, contextID=f.contextID, decimals=f.decimals)
                            else:
                                if -.001 < xValue < .001: dMin = 4
                                elif -.01 < xValue < .01: dMin = 3
                                elif -.1 < xValue < .1: dMin = 2
                                elif  -1 < xValue < 1: dMin = 1
                                else: dMin = 0
                                if dMin > dec:
                                    modelXbrl.warning("EIOPA:factDecimalsWarning",
                                        _("Numeric fact %(fact)s of context %(contextID)s value %(value)s has an imprecise decimals attribute: %(decimals)s, minimum is %(mindec)s"),
                                        modelObject=f, fact=f.qname, contextID=f.contextID, value=xValue, decimals=f.decimals, mindec=dMin)
                        except (AttributeError, ValueError, TypeError):
                            pass # should have been reported as a schema error by loader
                        '''' (not intended by EBA 2.18, paste here is from EFM)
                        if not f.isNil and getattr(f,"xValid", 0) == 4:
                            try:
                                insignificance = insignificantDigits(f.xValue, decimals=f.decimals)
                                if insignificance: # if not None, returns (truncatedDigits, insiginficantDigits)
                                    modelXbrl.error(("EFM.6.05.37", "GFM.1.02.26"),
                                        _("Fact %(fact)s of context %(contextID)s decimals %(decimals)s value %(value)s has nonzero digits in insignificant portion %(insignificantDigits)s."),
                                        modelObject=f1, fact=f1.qname, contextID=f1.contextID, decimals=f1.decimals, 
                                        value=f1.xValue, truncatedDigits=insignificance[0], insignificantDigits=insignificance[1])
                            except (ValueError,TypeError):
                                modelXbrl.error(("EBA.2.18"),
                                    _("Fact %(fact)s of context %(contextID)s decimals %(decimals)s value %(value)s causes Value Error exception."),
                                    modelObject=f1, fact=f1.qname, contextID=f1.contextID, decimals=f1.decimals, value=f1.value)
                        '''
                unit = f.unit
                if unit is not None:
                    if isMonetary:
                        if unit.measures[0]:
                            val.currenciesUsed[unit.measures[0][0]] = unit
                    elif not unit.isSingleMeasure or unit.measures[0][0] != XbrlConst.qnXbrliPure:
                        nonMonetaryNonPureFacts.append(f)
            if isEnum:
                _eQn = getattr(f,"xValue", None) or qnameEltPfxName(f, f.value)
                if _eQn:
                    val.namespacePrefixesUsed[_eQn.namespaceURI].add(_eQn.prefix)
                    val.prefixesUnused.discard(_eQn.prefix)
            ''' removed in current draft
            elif isString: 
                if not f.xmlLang:
                    stringFactsWithoutXmlLang.append(f)
            '''
                        
            if f.isNil:
                nilFacts.append(f)
                
            if val.footnotesRelationshipSet.fromModelObject(f):
                modelXbrl.warning("EIOPA.S.19",
                    _("Fact %(fact)s of context %(contextID)s has footnotes.'"),
                    modelObject=f, fact=f.qname, contextID=f.contextID)
                
    if nilFacts:
        nilFactNames = [str(f.qname) for f in nilFacts]
        nilFactNames = sorted(nilFactNames)
        modelXbrl.error(("EBA.2.19", "EIOPA.S.2.19"),
                _('Nil facts MUST NOT be present in the instance: %(nilFacts)s.'),
                modelObject=nilFacts, nilFacts=", ".join(fname for fname in nilFactNames))
    ''' removed in current draft
    if stringFactsWithoutXmlLang:
        modelXbrl.error("EBA.2.20",
                        _("String facts need to report xml:lang: '%(langLessFacts)s'"),
                        modelObject=stringFactsWithoutXmlLang, langLessFacts=", ".join(set(str(f.qname) for f in stringFactsWithoutXmlLang)))
    '''
    if nonMonetaryNonPureFacts:
        modelXbrl.error(("EBA.3.2","EIOPA.3.2.a"),
                        _("Non monetary (numeric) facts MUST use the pure unit: '%(langLessFacts)s'"),
                        modelObject=nonMonetaryNonPureFacts, langLessFacts=", ".join(set(str(f.qname) for f in nonMonetaryNonPureFacts)))

    val.utrValidator.validateFacts() # validate facts for UTR at logLevel WARNING
    
    unitHashes = {}
    for unit in modelXbrl.units.values():
        if unit is not None:
            h = unit.hash
            if h in unitHashes and unit.isEqualTo(unitHashes[h]):
                modelXbrl.warning("EBA.2.21",
                    _("Duplicate units SHOULD NOT be reported, units %(unit1)s and %(unit2)s have same measures.'"),
                    modelObject=(unit, unitHashes[h]), unit1=unit.id, unit2=unitHashes[h].id)
                if not getattr(modelXbrl, "isStreamingMode", False):
                    modelXbrl.error("EIOPA.2.21",
                        _("Duplicate units MUST NOT be reported, units %(unit1)s and %(unit2)s have same measures.'"),
                        modelObject=(unit, unitHashes[h]), unit1=unit.id, unit2=unitHashes[h].id)
            else:
                unitHashes[h] = unit
            for _measures in unit.measures:
                for _measure in _measures:
                    val.namespacePrefixesUsed[_measure.namespaceURI].add(_measure.prefix)
                    val.prefixesUnused.discard(_measure.prefix)
                
    del unitHashes
    
    cntxHashes = {}
    for cntx in modelXbrl.contexts.values():
        if cntx is not None:
            h = cntx.contextDimAwareHash
            if h in cntxHashes and cntx.isEqualTo(cntxHashes[h]):
                if not getattr(modelXbrl, "isStreamingMode", False):
                    modelXbrl.error("EIOPA.S.2.7.b",
                        _("Duplicate contexts MUST NOT be reported, contexts %(cntx1)s and %(cntx2)s are equivalent.'"),
                        modelObject=(cntx, cntxHashes[h]), cntx1=cntx.id, cntx2=cntxHashes[h].id)
            else:
                cntxHashes[h] = cntx
            for _dim in cntx.qnameDims.values():
                _dimQn = _dim.dimensionQname
                val.namespacePrefixesUsed[_dimQn.namespaceURI].add(_dimQn.prefix)
                val.prefixesUnused.discard(_dimQn.prefix)
                if _dim.isExplicit:
                    _memQn = _dim.memberQname
                else:
                    _memQn = _dim.typedMember.qname
                if _memQn:
                    val.namespacePrefixesUsed[_memQn.namespaceURI].add(_memQn.prefix)
                    val.prefixesUnused.discard(_memQn.prefix)

    for elt in modelDocument.xmlRootElement.iter():
        if isinstance(elt, ModelObject): # skip comments and processing instructions
            val.namespacePrefixesUsed[elt.qname.namespaceURI].add(elt.qname.prefix)
            val.prefixesUnused.discard(elt.qname.prefix)
            for attrTag in elt.keys():
                if attrTag.startswith("{"):
                    _prefix, _NS, _localName = XmlUtil.clarkNotationToPrefixNsLocalname(elt, attrTag, isAttribute=True)
                    if _prefix:
                        val.namespacePrefixesUsed[_NS].add(_prefix)
                        val.prefixesUnused.discard(_prefix)
Esempio n. 7
0
def validateValue(modelXbrl, elt, attrTag, baseXsdType, value, isNillable=False, isNil=False, facets=None):
    if baseXsdType:
        try:
            u'''
            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 == u"normalizedString")
            whitespaceCollapse = (not whitespaceReplace and baseXsdType != u"string")
            isList = baseXsdType in set([u"IDREFS", u"ENTITIES", u"NMTOKENS"])
            if isList:
                baseXsdType = baseXsdType[:-1] # remove plural
            pattern = baseXsdTypePatterns.get(baseXsdType)
            if facets:
                if u"pattern" in facets:
                    pattern = facets[u"pattern"]
                    # note multiple patterns are or'ed togetner, which isn't yet implemented!
                if u"whiteSpace" in facets:
                    whitespaceReplace, whitespaceCollapse = {u"preserve":(False,False), u"replace":(True,False), u"collapse":(False,True)}[facets[u"whiteSpace"]]
            if whitespaceReplace:
                value = normalizeWhitespacePattern.sub(u' ', value)
            elif whitespaceCollapse:
                value = collapseWhitespacePattern.sub(u' ', value.strip())
            if baseXsdType == u"noContent":
                if len(value) > 0 and not value.isspace():
                    raise ValueError(u"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(u"pattern facet " + facets[u"pattern"].pattern if facets and u"pattern" in facets else u"pattern mismatch")
                if facets:
                    if u"enumeration" in facets and value not in facets[u"enumeration"]:
                        raise ValueError(u"{0} is not in {1}".format(value, facets[u"enumeration"]))
                    if u"length" in facets and len(value) != facets[u"length"]:
                        raise ValueError(u"length {0}, expected {1}".format(len(value), facets[u"length"]))
                    if u"minLength" in facets and len(value) < facets[u"minLength"]:
                        raise ValueError(u"length {0}, minLength {1}".format(len(value), facets[u"minLength"]))
                    if u"maxLength" in facets and len(value) > facets[u"maxLength"]:
                        raise ValueError(u"length {0}, maxLength {1}".format(len(value), facets[u"maxLength"]))
                if baseXsdType in set([u"string", u"normalizedString", u"language", u"token", u"NMTOKEN",u"Name",u"NCName",u"IDREF",u"ENTITY"]):
                    xValue = sValue = value
                elif baseXsdType == u"ID":
                    xValue = sValue = value
                    xValid = VALID_ID
                elif baseXsdType == u"anyURI":
                    if value:  # allow empty strings to be valid anyURIs
                        if UrlUtil.relativeUrlPattern.match(value) is None:
                            raise ValueError(u"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 (u"decimal", u"float", u"double"):
                    if baseXsdType == u"decimal":
                        if decimalPattern.match(value) is None:
                            raise ValueError(u"lexical pattern mismatch")
                        xValue = Decimal(value)
                        sValue = float(value) # s-value uses Number (float) representation
                    else:
                        if floatPattern.match(value) is None:
                            raise ValueError(u"lexical pattern mismatch")
                        xValue = sValue = float(value)
                    if facets:
                        if u"totalDigits" in facets and len(value.replace(u".",u"")) > facets[u"totalDigits"]:
                            raise ValueError(u"totalDigits facet {0}".format(facets[u"totalDigits"]))
                        if u"fractionDigits" in facets and ( u'.' in value and
                            len(value[value.index(u'.') + 1:]) > facets[u"fractionDigits"]):
                            raise ValueError(u"fraction digits facet {0}".format(facets[u"fractionDigits"]))
                        if u"maxInclusive" in facets and xValue > facets[u"maxInclusive"]:
                            raise ValueError(u" > maxInclusive {0}".format(facets[u"maxInclusive"]))
                        if u"maxExclusive" in facets and xValue >= facets[u"maxExclusive"]:
                            raise ValueError(u" >= maxInclusive {0}".format(facets[u"maxExclusive"]))
                        if u"minInclusive" in facets and xValue < facets[u"minInclusive"]:
                            raise ValueError(u" < minInclusive {0}".format(facets[u"minInclusive"]))
                        if u"minExclusive" in facets and xValue <= facets[u"minExclusive"]:
                            raise ValueError(u" <= minExclusive {0}".format(facets[u"minExclusive"]))
                elif baseXsdType in set([u"integer",
                                     u"nonPositiveInteger",u"negativeInteger",u"nonNegativeInteger",u"positiveInteger",
                                     u"long",u"unsignedLong",
                                     u"int",u"unsignedInt",
                                     u"short",u"unsignedShort",
                                     u"byte",u"unsignedByte"]):
                    xValue = sValue = _INT(value)
                    if ((baseXsdType in set([u"nonNegativeInteger",u"unsignedLong",u"unsignedInt"]) 
                         and xValue < 0) or
                        (baseXsdType == u"nonPositiveInteger" and xValue > 0) or
                        (baseXsdType == u"positiveInteger" and xValue <= 0) or
                        (baseXsdType == u"byte" and not -128 <= xValue < 127) or
                        (baseXsdType == u"unsignedByte" and not 0 <= xValue < 255) or
                        (baseXsdType == u"short" and not -32768 <= xValue < 32767) or
                        (baseXsdType == u"unsignedShort" and not 0 <= xValue < 65535) or
                        (baseXsdType == u"positiveInteger" and xValue <= 0)):
                        raise ValueError(u"{0} is not {1}".format(value, baseXsdType))
                    if facets:
                        if u"totalDigits" in facets and len(value.replace(u".",u"")) > facets[u"totalDigits"]:
                            raise ValueError(u"totalDigits facet {0}".format(facets[u"totalDigits"]))
                        if u"fractionDigits" in facets and ( u'.' in value and
                            len(value[value.index(u'.') + 1:]) > facets[u"fractionDigits"]):
                            raise ValueError(u"fraction digits facet {0}".format(facets[u"fractionDigits"]))
                        if u"maxInclusive" in facets and xValue > facets[u"maxInclusive"]:
                            raise ValueError(u" > maxInclusive {0}".format(facets[u"maxInclusive"]))
                        if u"maxExclusive" in facets and xValue >= facets[u"maxExclusive"]:
                            raise ValueError(u" >= maxInclusive {0}".format(facets[u"maxExclusive"]))
                        if u"minInclusive" in facets and xValue < facets[u"minInclusive"]:
                            raise ValueError(u" < minInclusive {0}".format(facets[u"minInclusive"]))
                        if u"minExclusive" in facets and xValue <= facets[u"minExclusive"]:
                            raise ValueError(u" <= minExclusive {0}".format(facets[u"minExclusive"]))
                elif baseXsdType == u"boolean":
                    if value in (u"true", u"1"):  
                        xValue = sValue = True
                    elif value in (u"false", u"0"): 
                        xValue = sValue = False
                    else: raise ValueError
                elif baseXsdType == u"QName":
                    xValue = qnameEltPfxName(elt, value, prefixException=ValueError)
                    #xValue = qname(elt, value, castException=ValueError, prefixException=ValueError)
                    sValue = value
                    u''' 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 (u"XBRLI_DECIMALSUNION", u"XBRLI_PRECISIONUNION"):
                    xValue = sValue = value if value == u"INF" else _INT(value)
                elif baseXsdType in (u"XBRLI_NONZERODECIMAL"):
                    xValue = sValue = _INT(value)
                    if xValue == 0:
                        raise ValueError(u"invalid value")
                elif baseXsdType == u"XBRLI_DATEUNION":
                    xValue = dateTime(value, type=DATEUNION, castException=ValueError)
                    sValue = value
                elif baseXsdType == u"dateTime":
                    xValue = dateTime(value, type=DATETIME, castException=ValueError)
                    sValue = value
                elif baseXsdType == u"date":
                    xValue = dateTime(value, type=DATE, castException=ValueError)
                    sValue = value
                elif baseXsdType == u"regex-pattern":
                    # for facet compiling
                    try:
                        sValue = value
                        if value in xmlSchemaPatterns:
                            xValue = xmlSchemaPatterns[value]
                        else:
                            if ur"\i" in value or ur"\c" in value:
                                value = value.replace(ur"\i", iNameChar).replace(ur"\c", cNameChar)
                            xValue = re_compile(value + u"$") # must match whole string
                    except Exception, err:
                        raise ValueError(err)
                else:
                    if baseXsdType in lexicalPatterns:
                        match = lexicalPatterns[baseXsdType].match(value)
                        if match is None:
                            raise ValueError(u"lexical pattern mismatch")
                        if baseXsdType == u"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(u"invalid day {0} for month {1}".format(day, month))
                            xValue = gMonthDay(month, day)
                        elif baseXsdType == u"gYearMonth":
                            year, month, zSign, zHrMin, zHr, zMin = match.groups()
                            xValue = gYearMonth(year, month)
                        elif baseXsdType == u"gYear":
                            year, zSign, zHrMin, zHr, zMin = match.groups()
                            xValue = gYear(year)
                        elif baseXsdType == u"gMonth":
                            month, zSign, zHrMin, zHr, zMin = match.groups()
                            xValue = gMonth(month)
                        elif baseXsdType == u"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