def checkHierarchyConstraints(elt): constraints = ixHierarchyConstraints.get(elt.localName) if constraints: for _rel, names in constraints: reqt = _rel[0] rel = _rel[1:] if reqt in ('&', '^', '1'): nameFilter = ('*', ) else: nameFilter = names if nameFilter == ('*', ): namespaceFilter = namespacePrefix = '*' elif len(nameFilter) == 1 and "}" in nameFilter[ 0] and nameFilter[0][0] == "{": namespaceFilter, _sep, nameFilter = nameFilter[0][ 1:].partition("}") namespacePrefix = XmlUtil.xmlnsprefix(elt, namespaceFilter) else: namespaceFilter = elt.namespaceURI namespacePrefix = elt.prefix relations = { "ancestor": XmlUtil.ancestor, "parent": XmlUtil.parent, "child-choice": XmlUtil.children, "child-sequence": XmlUtil.children, "child-or-text": XmlUtil.children, "descendant": XmlUtil.descendants }[rel](elt, namespaceFilter, nameFilter) if rel in ("ancestor", "parent"): if relations is None: relations = [] else: relations = [relations] if rel == "child-or-text": relations += XmlUtil.innerTextNodes(elt, ixExclude=True, ixEscape=False, ixContinuation=False) issue = '' if reqt in ('^', ): if not any(r.localName in names and r.namespaceURI == elt.namespaceURI for r in relations): issue = " and is missing one of " + ', '.join(names) if reqt in ('1', ) and not elt.isNil: if sum(r.localName in names and r.namespaceURI == elt.namespaceURI for r in relations) != 1: issue = " and must have exactly one of " + ', '.join( names) if reqt in ('&', '^'): disallowed = [ str(r.elementQname) for r in relations if not (r.tag in names or (r.localName in names and r.namespaceURI == elt.namespaceURI)) ] if disallowed: issue += " and may not have " + ", ".join(disallowed) elif rel == "child-sequence": sequencePosition = 0 for i, r in enumerate(relations): rPos = names.index(str(r.localName)) if rPos < sequencePosition: issue += " and is out of sequence: " + str( r.elementQname) else: sequencePosition = rPos if reqt == '?' and len(relations) > 1: issue = " may only have 0 or 1 but {0} present ".format( len(relations)) if reqt == '+' and len(relations) == 0: issue = " must have at least 1 but none present " disallowedChildText = bool( reqt == '&' and rel in ("child-sequence", "child-choice") and elt.textValue.strip()) if ((reqt == '+' and not relations) or (reqt == '-' and relations) or (issue) or disallowedChildText): code = "{}:{}".format( ixSect[elt.namespaceURI].get(elt.localName, "other")["constraint"], { 'ancestor': "ancestorNode", 'parent': "parentNode", 'child-choice': "childNodes", 'child-sequence': "childNodes", 'child-or-text': "childNodesOrText", 'descendant': "descendantNodes" }[rel] + { '+': "Required", '-': "Disallowed", '&': "Allowed", '^': "Specified", '1': "Specified" }.get(reqt, "Specified")) msg = _("Inline XBRL ix:{0} {1} {2} {3} {4} element{5}" ).format( elt.localName, { '+': "must", '-': "may not", '&': "may only", '?': "may", '+': "must", '^': "must", '1': "must" }[reqt], { 'ancestor': "be nested in", 'parent': "have parent", 'child-choice': "have child", 'child-sequence': "have child", 'child-or-text': "have child or text,", 'descendant': "have as descendant" }[rel], '' if rel == 'child-or-text' else ', '.join( str(r.elementQname) for r in relations) if names == ('*', ) and relations else ", ".join( "{}:{}".format(namespacePrefix, n) for n in names), issue, " and no child text (\"{}\")".format( elt.textValue.strip()[:32]) if disallowedChildText else "") modelXbrl.error( code, msg, modelObject=[elt] + relations, requirement=reqt, messageCodes= ("ix{ver.sect}:ancestorNode{Required|Disallowed}", "ix{ver.sect}:childNodesOrTextRequired", "ix{ver.sect}:childNodes{Required|Disallowed|Allowed}", "ix{ver.sect}:descendantNodesDisallowed", "ix{ver.sect}:parentNodeRequired")) # other static element checks (that don't require a complete object model, context, units, etc if elt.localName == "nonFraction": childElts = XmlUtil.children(elt, '*', '*') hasText = (elt.text or "") or any( (childElt.tail or "") for childElt in childElts) if elt.isNil: ancestorNonFractions = XmlUtil.ancestors( elt, _ixNS, elt.localName) if ancestorNonFractions: modelXbrl.error( ixMsgCode("nonFractionAncestors", elt), _("Fact %(fact)s is a nil nonFraction and MUST not have an ancestor ix:nonFraction" ), modelObject=[elt] + ancestorNonFractions, fact=elt.qname) if childElts or hasText: modelXbrl.error( ixMsgCode("nonFractionTextAndElementChildren", elt), _("Fact %(fact)s is a nil nonFraction and MUST not have an child elements or text" ), modelObject=[elt] + childElts, fact=elt.qname) elt.setInvalid( ) # prevent further validation or cascading errors else: if ((childElts and (len(childElts) != 1 or childElts[0].namespaceURI != _ixNS or childElts[0].localName != "nonFraction")) or (childElts and hasText)): modelXbrl.error( ixMsgCode("nonFractionTextAndElementChildren", elt), _("Fact %(fact)s is a non-nil nonFraction and MUST have exactly one ix:nonFraction child element or text." ), modelObject=[elt] + childElts, fact=elt.qname) elt.setInvalid() if elt.localName == "fraction": if elt.isNil: ancestorFractions = XmlUtil.ancestors(elt, _ixNS, elt.localName) if ancestorFractions: modelXbrl.error( ixMsgCode("fractionAncestors", elt), _("Fact %(fact)s is a nil fraction and MUST not have an ancestor ix:fraction" ), modelObject=[elt] + ancestorFractions, fact=elt.qname) else: nonFrChildren = [ e for e in XmlUtil.children(elt, _ixNS, '*') if e.localName not in ("fraction", "numerator", "denominator") ] if nonFrChildren: modelXbrl.error( ixMsgCode("fractionElementChildren", elt), _("Fact %(fact)s is a non-nil fraction and not have any child elements except ix:fraction, ix:numerator and ix:denominator: %(children)s" ), modelObject=[elt] + nonFrChildren, fact=elt.qname, children=", ".join(e.localName for e in nonFrChildren)) for ancestorFraction in XmlUtil.ancestors( elt, XbrlConst.ixbrl11, "fraction"): # only ix 1.1 if normalizeSpace(elt.get("unitRef")) != normalizeSpace( ancestorFraction.get("unitRef")): modelXbrl.error( ixMsgCode("fractionNestedUnitRef", elt), _("Fact %(fact)s fraction and ancestor fractions must have matching unitRefs: %(unitRef)s, %(unitRef2)s" ), modelObject=[elt] + nonFrChildren, fact=elt.qname, unitRef=elt.get("unitRef"), unitRef2=ancestorFraction.get("unitRef")) if elt.localName in ("nonFraction", "numerator", "denominator", "nonNumeric"): fmt = elt.format if fmt: if fmt in _customTransforms: pass elif fmt.namespaceURI not in FunctionIxt.ixtNamespaceFunctions: modelXbrl.error( ixMsgCode("invalidTransformation", elt, sect="validation"), _("Fact %(fact)s has unrecognized transformation namespace %(namespace)s" ), modelObject=elt, fact=elt.qname, transform=fmt, namespace=fmt.namespaceURI) elt.setInvalid() elif fmt.localName not in FunctionIxt.ixtNamespaceFunctions[ fmt.namespaceURI]: modelXbrl.error( ixMsgCode("invalidTransformation", elt, sect="validation"), _("Fact %(fact)s has unrecognized transformation name %(name)s" ), modelObject=elt, fact=elt.qname, transform=fmt, name=fmt.localName) elt.setInvalid()
def checkHierarchyConstraints(elt): constraints = ixHierarchyConstraints.get(elt.localName) if constraints: for _rel, names in constraints: reqt = _rel[0] rel = _rel[1:] if reqt in ('&', '^', '1'): nameFilter = ('*',) else: nameFilter = names if nameFilter == ('*',): namespaceFilter = namespacePrefix = '*' elif len(nameFilter) == 1 and "}" in nameFilter[0] and nameFilter[0][0] == "{": namespaceFilter, _sep, nameFilter = nameFilter[0][1:].partition("}") namespacePrefix = XmlUtil.xmlnsprefix(elt,namespaceFilter) else: namespaceFilter = elt.namespaceURI namespacePrefix = elt.prefix relations = {"ancestor": XmlUtil.ancestor, "parent": XmlUtil.parent, "child-choice": XmlUtil.children, "child-sequence": XmlUtil.children, "child-or-text": XmlUtil.children, "descendant": XmlUtil.descendants}[rel]( elt, namespaceFilter, nameFilter) if rel in ("ancestor", "parent"): if relations is None: relations = [] else: relations = [relations] if rel == "child-or-text": relations += XmlUtil.innerTextNodes(elt, ixExclude=True, ixEscape=False, ixContinuation=False, ixResolveUris=False) issue = '' if reqt in ('^',): if not any(r.localName in names and r.namespaceURI == elt.namespaceURI for r in relations): issue = " and is missing one of " + ', '.join(names) if reqt in ('1',) and not elt.isNil: if sum(r.localName in names and r.namespaceURI == elt.namespaceURI for r in relations) != 1: issue = " and must have exactly one of " + ', '.join(names) if reqt in ('&', '^'): disallowed = [str(r.elementQname) for r in relations if not (r.tag in names or (r.localName in names and r.namespaceURI == elt.namespaceURI))] if disallowed: issue += " and may not have " + ", ".join(disallowed) elif rel == "child-sequence": sequencePosition = 0 for i, r in enumerate(relations): rPos = names.index(str(r.localName)) if rPos < sequencePosition: issue += " and is out of sequence: " + str(r.elementQname) else: sequencePosition = rPos if reqt == '?' and len(relations) > 1: issue = " may only have 0 or 1 but {0} present ".format(len(relations)) if reqt == '+' and len(relations) == 0: issue = " must have at least 1 but none present " disallowedChildText = bool(reqt == '&' and rel in ("child-sequence", "child-choice") and elt.textValue.strip()) if ((reqt == '+' and not relations) or (reqt == '-' and relations) or (issue) or disallowedChildText): code = "{}:{}".format(ixSect[elt.namespaceURI].get(elt.localName,"other")["constraint"], { 'ancestor': "ancestorNode", 'parent': "parentNode", 'child-choice': "childNodes", 'child-sequence': "childNodes", 'child-or-text': "childNodesOrText", 'descendant': "descendantNodes"}[rel] + { '+': "Required", '-': "Disallowed", '&': "Allowed", '^': "Specified", '1': "Specified"}.get(reqt, "Specified")) msg = _("Inline XBRL ix:{0} {1} {2} {3} {4} element{5}").format( elt.localName, {'+': "must", '-': "may not", '&': "may only", '?': "may", '+': "must", '^': "must", '1': "must"}[reqt], {'ancestor': "be nested in", 'parent': "have parent", 'child-choice': "have child", 'child-sequence': "have child", 'child-or-text': "have child or text,", 'descendant': "have as descendant"}[rel], '' if rel == 'child-or-text' else ', '.join(str(r.elementQname) for r in relations) if names == ('*',) and relations else ", ".join("{}:{}".format(namespacePrefix, n) for n in names), issue, " and no child text (\"{}\")".format(elt.textValue.strip()[:32]) if disallowedChildText else "") modelXbrl.error(code, msg, modelObject=[elt] + relations, requirement=reqt, messageCodes=("ix{ver.sect}:ancestorNode{Required|Disallowed}", "ix{ver.sect}:childNodesOrTextRequired", "ix{ver.sect}:childNodes{Required|Disallowed|Allowed}", "ix{ver.sect}:descendantNodesDisallowed", "ix{ver.sect}:parentNodeRequired")) # other static element checks (that don't require a complete object model, context, units, etc if elt.localName == "nonFraction": childElts = XmlUtil.children(elt, '*', '*') hasText = (elt.text or "") or any((childElt.tail or "") for childElt in childElts) if elt.isNil: ancestorNonFractions = XmlUtil.ancestors(elt, _ixNS, elt.localName) if ancestorNonFractions: modelXbrl.error(ixMsgCode("nonFractionAncestors", elt), _("Fact %(fact)s is a nil nonFraction and MUST not have an ancestor ix:nonFraction"), modelObject=[elt] + ancestorNonFractions, fact=elt.qname) if childElts or hasText: modelXbrl.error(ixMsgCode("nonFractionTextAndElementChildren", elt), _("Fact %(fact)s is a nil nonFraction and MUST not have an child elements or text"), modelObject=[elt] + childElts, fact=elt.qname) elt.setInvalid() # prevent further validation or cascading errors else: if ((childElts and (len(childElts) != 1 or childElts[0].namespaceURI != _ixNS or childElts[0].localName != "nonFraction")) or (childElts and hasText)): modelXbrl.error(ixMsgCode("nonFractionTextAndElementChildren", elt), _("Fact %(fact)s is a non-nil nonFraction and MUST have exactly one ix:nonFraction child element or text."), modelObject=[elt] + childElts, fact=elt.qname) elt.setInvalid() if elt.localName == "fraction": if elt.isNil: ancestorFractions = XmlUtil.ancestors(elt, _ixNS, elt.localName) if ancestorFractions: modelXbrl.error(ixMsgCode("fractionAncestors", elt), _("Fact %(fact)s is a nil fraction and MUST not have an ancestor ix:fraction"), modelObject=[elt] + ancestorFractions, fact=elt.qname) else: nonFrChildren = [e for e in XmlUtil.children(elt, _ixNS, '*') if e.localName not in ("fraction", "numerator", "denominator")] if nonFrChildren: modelXbrl.error(ixMsgCode("fractionElementChildren", elt), _("Fact %(fact)s is a non-nil fraction and not have any child elements except ix:fraction, ix:numerator and ix:denominator: %(children)s"), modelObject=[elt] + nonFrChildren, fact=elt.qname, children=", ".join(e.localName for e in nonFrChildren)) for ancestorFraction in XmlUtil.ancestors(elt, XbrlConst.ixbrl11, "fraction"): # only ix 1.1 if normalizeSpace(elt.get("unitRef")) != normalizeSpace(ancestorFraction.get("unitRef")): modelXbrl.error(ixMsgCode("fractionNestedUnitRef", elt), _("Fact %(fact)s fraction and ancestor fractions must have matching unitRefs: %(unitRef)s, %(unitRef2)s"), modelObject=[elt] + nonFrChildren, fact=elt.qname, unitRef=elt.get("unitRef"), unitRef2=ancestorFraction.get("unitRef")) if elt.localName in ("nonFraction", "numerator", "denominator", "nonNumeric"): fmt = elt.format if fmt: if fmt in _customTransforms: pass elif fmt.namespaceURI not in FunctionIxt.ixtNamespaceFunctions: modelXbrl.error(ixMsgCode("invalidTransformation", elt, sect="validation"), _("Fact %(fact)s has unrecognized transformation namespace %(namespace)s"), modelObject=elt, fact=elt.qname, transform=fmt, namespace=fmt.namespaceURI) elt.setInvalid() elif fmt.localName not in FunctionIxt.ixtNamespaceFunctions[fmt.namespaceURI]: modelXbrl.error(ixMsgCode("invalidTransformation", elt, sect="validation"), _("Fact %(fact)s has unrecognized transformation name %(name)s"), modelObject=elt, fact=elt.qname, transform=fmt, name=fmt.localName) elt.setInvalid()