def expected(self): for pluginXbrlMethod in pluginClassMethods( "ModelTestcaseVariation.ExpectedResult"): expected = pluginXbrlMethod(self) if expected: return expected # default behavior without plugins if self.localName == "testcase": return self.document.basename[:4] #starts with PASS or FAIL elif self.localName == "testGroup": #w3c testcase instanceTestElement = XmlUtil.descendant(self, None, "instanceTest") if instanceTestElement is not None: # take instance first return XmlUtil.descendantAttr(instanceTestElement, None, "expected", "validity") else: schemaTestElement = XmlUtil.descendant(self, None, "schemaTest") if schemaTestElement is not None: return XmlUtil.descendantAttr(schemaTestElement, None, "expected", "validity") resultElement = XmlUtil.descendant(self, None, "result") if resultElement is not None: expected = resultElement.get("expected") if expected and resultElement.get( "nonStandardErrorCodes") == "true": # if @expected and @nonStandardErrorCodes then use expected instead of error codes return expected errorElement = XmlUtil.descendant(self, None, "error") if errorElement is not None: _errorText = XmlUtil.text(errorElement) if ' ' in _errorText: # list of tokens return _errorText return ModelValue.qname(errorElement, _errorText) # turn into a QName if resultElement is not None: if expected: return expected for assertElement in XmlUtil.children(resultElement, None, "assert"): num = assertElement.get("num") if num == "99999": # inline test, use name as expected return assertElement.get("name") if len(num) == 5: return "EFM.{0}.{1}.{2}".format(num[0], num[1:3], num[3:6]) asserTests = {} for atElt in XmlUtil.children(resultElement, None, "assertionTests"): try: asserTests[atElt.get("assertionID")] = ( _INT(atElt.get("countSatisfied")), _INT(atElt.get("countNotSatisfied"))) except ValueError: pass if asserTests: return asserTests elif self.get("result"): return self.get("result") return None
def expected(self): if self.localName == "testcase": return self.document.basename[:4] #starts with PASS or FAIL errorElement = XmlUtil.descendant(self, None, "error") if errorElement is not None: return ModelValue.qname(errorElement, XmlUtil.text(errorElement)) resultElement = XmlUtil.descendant(self, None, "result") if resultElement is not None: expected = resultElement.get("expected") if expected: return expected for assertElement in XmlUtil.children(resultElement, None, "assert"): num = assertElement.get("num") if len(num) == 5: return "EFM.{0}.{1}.{2}".format(num[0], num[1:3], num[3:6]) asserTests = {} for atElt in XmlUtil.children(resultElement, None, "assertionTests"): try: asserTests[atElt.get("assertionID")] = ( _INT(atElt.get("countSatisfied")), _INT(atElt.get("countNotSatisfied"))) except ValueError: pass if asserTests: return asserTests elif self.get("result"): return self.get("result") return None
def expected(self): if self.localName == "testcase": return self.document.basename[:4] #starts with PASS or FAIL errorElement = XmlUtil.descendant(self, None, "error") if errorElement is not None: return ModelValue.qname(errorElement, XmlUtil.text(errorElement)) resultElement = XmlUtil.descendant(self, None, "result") if resultElement is not None: expected = resultElement.get("expected") if expected: return expected for assertElement in XmlUtil.children(resultElement, None, "assert"): num = assertElement.get("num") if len(num) == 5: return "EFM.{0}.{1}.{2}".format(num[0],num[1:3],num[3:6]) asserTests = {} for atElt in XmlUtil.children(resultElement, None, "assertionTests"): try: asserTests[atElt.get("assertionID")] = (_INT(atElt.get("countSatisfied")),_INT(atElt.get("countNotSatisfied"))) except ValueError: pass if asserTests: return asserTests elif self.get("result"): return self.get("result") return None
def explicitDims(self): return { (self.prefixedNameQname(e.get("dimension")), self.prefixedNameQname(XmlUtil.text(qn))) for e in XmlUtil.children(self, XbrlConst.formula, "explicitDimension") for m in XmlUtil.children(e, XbrlConst.formula, "member") for qn in XmlUtil.children(m, XbrlConst.formula, "qname") }
def aspectValue(self, xpCtx, aspect, inherit=False): if aspect == Aspect.DIMENSIONS: dims = set(self.prefixedNameQname(e.get("dimension")) for e in XmlUtil.children(self, XbrlConst.euRend, "explicitDimCoord")) if inherit and self.parentDefinitionNode is not None: dims |= self.parentDefinitionNode.aspectValue(None, aspect, inherit) return dims if inherit and not self.hasAspect(None, aspect): if self.parentDefinitionNode is not None: return self.parentDefinitionNode.aspectValue(None, aspect, inherit) return None if aspect == Aspect.CONCEPT: priItem = XmlUtil.childAttr(self, XbrlConst.euRend, "primaryItem", "name") if priItem is not None: return self.prefixedNameQname(priItem) return None elif aspect == Aspect.PERIOD_TYPE: if XmlUtil.hasChild(self, XbrlConst.euRend, "timeReference"): return "instant" elif aspect == Aspect.INSTANT: return XmlUtil.datetimeValue(XmlUtil.childAttr(self, XbrlConst.euRend, "timeReference", "instant"), addOneDay=True) elif isinstance(aspect, QName): for e in XmlUtil.children(self, XbrlConst.euRend, "explicitDimCoord"): if self.prefixedNameQname(e.get("dimension")) == aspect: return self.prefixedNameQname(e.get("value")) return None
def nonDimValues(self, contextElement): if contextElement in ("segment", Aspect.NON_XDT_SEGMENT): return self.segNonDimValues elif contextElement in ("scenario", Aspect.NON_XDT_SCENARIO): return self.scenNonDimValues elif contextElement == Aspect.COMPLETE_SEGMENT and self.hasSegment: return XmlUtil.children(self.segment, None, "*") elif contextElement == Aspect.COMPLETE_SCENARIO and self.hasScenario: return XmlUtil.children(self.scenario, None, "*") return []
def stepAxis(self, op, p, sourceSequence): targetSequence = [] for node in sourceSequence: if not isinstance(node,(ModelObject, etree._ElementTree, ModelAttribute)): raise XPathException(self.progHeader, 'err:XPTY0020', _('Axis step {0} context item is not a node: {1}').format(op, node)) targetNodes = [] if isinstance(p,QNameDef): ns = p.namespaceURI; localname = p.localName if p.isAttribute: if isinstance(node,ModelObject): attrTag = p.localName if p.unprefixed else p.clarkNotation modelAttribute = None try: modelAttribute = node.xAttributes[attrTag] except (AttributeError, TypeError, IndexError, KeyError): # may be lax or deferred validated try: validate(node.modelXbrl, node, p) modelAttribute = node.xAttributes[attrTag] except (AttributeError, TypeError, IndexError, KeyError): pass if modelAttribute is None: value = node.get(attrTag) if value is not None: targetNodes.append(ModelAttribute(node,p.clarkNotation,UNKNOWN,value,value,value)) elif modelAttribute.xValid >= VALID: targetNodes.append(modelAttribute) elif op == '/' or op is None: if isinstance(node,(ModelObject, etree._ElementTree)): targetNodes = XmlUtil.children(node, ns, localname) elif op == '//': if isinstance(node,(ModelObject, etree._ElementTree)): targetNodes = XmlUtil.descendants(node, ns, localname) elif op == '..': if isinstance(node,ModelAttribute): targetNodes = [ node.modelElement ] else: targetNodes = [ XmlUtil.parent(node) ] elif isinstance(p, OperationDef) and isinstance(p.name,QNameDef): if isinstance(node,ModelObject): if p.name.localName == "text": targetNodes = [XmlUtil.text(node)] # todo: add element, attribute, node, etc... elif p == '*': # wildcard if op == '/' or op is None: if isinstance(node,(ModelObject, etree._ElementTree)): targetNodes = XmlUtil.children(node, '*', '*') elif op == '//': if isinstance(node,(ModelObject, etree._ElementTree)): targetNodes = XmlUtil.descendants(node, '*', '*') targetSequence.extend(targetNodes) return targetSequence
def expected(self): for pluginXbrlMethod in pluginClassMethods("ModelTestcaseVariation.ExpectedResult"): expected = pluginXbrlMethod(self) if expected: return expected # default behavior without plugins if self.localName == "testcase": return self.document.basename[:4] #starts with PASS or FAIL elif self.localName == "testGroup": #w3c testcase instanceTestElement = XmlUtil.descendant(self, None, "instanceTest") if instanceTestElement is not None: # take instance first return XmlUtil.descendantAttr(instanceTestElement, None, "expected", "validity") else: schemaTestElement = XmlUtil.descendant(self, None, "schemaTest") if schemaTestElement is not None: return XmlUtil.descendantAttr(schemaTestElement, None, "expected", "validity") resultElement = XmlUtil.descendant(self, None, "result") if resultElement is not None: expected = resultElement.get("expected") if expected and resultElement.get("nonStandardErrorCodes") == "true": # if @expected and @nonStandardErrorCodes then use expected instead of error codes return expected errorElement = XmlUtil.descendant(self, None, "error") resultElement = XmlUtil.descendant(self, None, "result") if errorElement is not None and not errorElement.get("nonStandardErrorCodes"): _errorText = XmlUtil.text(errorElement) if ' ' in _errorText: # list of tokens return _errorText return ModelValue.qname(errorElement, _errorText) # turn into a QName if resultElement is not None: if expected: return expected for assertElement in XmlUtil.children(resultElement, None, "assert"): num = assertElement.get("num") if num == "99999": # inline test, use name as expected return assertElement.get("name") if len(num) == 5: return "EFM.{0}.{1}.{2}".format(num[0],num[1:3],num[3:6]) asserTests = {} for atElt in XmlUtil.children(resultElement, None, "assertionTests"): try: asserTests[atElt.get("assertionID")] = (_INT(atElt.get("countSatisfied")),_INT(atElt.get("countNotSatisfied"))) except ValueError: pass if asserTests: return asserTests elif self.get("result"): return self.get("result") return None
def reference(self): efmNameElts = XmlUtil.children(self.getparent(), None, "name") for efmNameElt in efmNameElts: if efmNameElt is not None and efmNameElt.text.startswith("EDGAR"): return efmNameElt.text referenceElement = XmlUtil.descendant(self, None, "reference") if referenceElement is not None: # formula test suite return "{0}#{1}".format( referenceElement.get("specification"), referenceElement.get("id")) referenceElement = XmlUtil.descendant(self, None, "documentationReference") if referenceElement is not None: # w3c test suite return referenceElement.get("{http://www.w3.org/1999/xlink}href") descriptionElement = XmlUtil.descendant(self, None, "description") if descriptionElement is not None and descriptionElement.get( "reference"): return descriptionElement.get("reference") # xdt test suite if self.getparent().get("description"): return self.getparent().get( "description") # base spec 2.1 test suite functRegistryRefElt = XmlUtil.descendant(self.getparent(), None, "reference") if functRegistryRefElt is not None: # function registry return functRegistryRefElt.get( "{http://www.w3.org/1999/xlink}href") return None
def stepAxis(self, op, p, sourceSequence): targetSequence = [] for node in sourceSequence: if not isinstance(node,(ModelObject.ModelObject, xml.dom.Node)): raise XPathException(p, 'err:XPTY0020', _('Axis step {0} context item is not a node: {1}').format(op, node)) targetNodes = [] if isinstance(node, ModelObject.ModelObject): node = node.element if isinstance(p,QNameDef): ns = p.namespaceURI; localname = p.localName if p.isAttribute: if p.unprefixed: if node.hasAttribute(localname): targetNodes.append(node.getAttribute(localname)) else: if node.hasAttributeNS(ns,localname): targetNodes.append(node.getAttributeNS(ns,localname)) elif op == '/' or op is None: targetNodes = XmlUtil.children(node, ns, localname) elif op == '//': targetNodes = XmlUtil.descendants(node, ns, localname) elif op == '..': targetNodes = [ XmlUtil.parent(node) ] elif isinstance(p, OperationDef) and isinstance(p.name,QNameDef): if p.name.localName == "text": targetNodes = [XmlUtil.text(node)] # todo: add element, attribute, node, etc... targetSequence.extend(targetNodes) return targetSequence
def reference(self): efmNameElts = XmlUtil.children(self.getparent(), None, "name") for efmNameElt in efmNameElts: if efmNameElt is not None and efmNameElt.text.startswith("EDGAR"): return efmNameElt.text referenceElement = XmlUtil.descendant(self, None, "reference") if referenceElement is not None: # formula test suite return "{0}#{1}".format(referenceElement.get("specification"), referenceElement.get("id")) referenceElement = XmlUtil.descendant(self, None, "documentationReference") if referenceElement is not None: # w3c test suite return referenceElement.get("{http://www.w3.org/1999/xlink}href") descriptionElement = XmlUtil.descendant(self, None, "description") if descriptionElement is not None and descriptionElement.get( "reference"): return descriptionElement.get("reference") # xdt test suite if self.getparent().get("description"): return self.getparent().get( "description") # base spec 2.1 test suite functRegistryRefElt = XmlUtil.descendant(self.getparent(), None, "reference") if functRegistryRefElt is not None: # function registry return functRegistryRefElt.get( "{http://www.w3.org/1999/xlink}href") return None
def nonDimValues(self, contextElement): """([ModelObject]) -- ContextElement is either string or Aspect code for segment or scenario, returns nonXDT ModelObject children of context element. :param contextElement: one of 'segment', 'scenario', Aspect.NON_XDT_SEGMENT, Aspect.NON_XDT_SCENARIO, Aspect.COMPLETE_SEGMENT, Aspect.COMPLETE_SCENARIO :type contextElement: str or Aspect type :returns: list of ModelObjects """ if contextElement in ("segment", Aspect.NON_XDT_SEGMENT): return self.segNonDimValues elif contextElement in ("scenario", Aspect.NON_XDT_SCENARIO): return self.scenNonDimValues elif contextElement == Aspect.COMPLETE_SEGMENT and self.hasSegment: return XmlUtil.children(self.segment, None, "*") elif contextElement == Aspect.COMPLETE_SCENARIO and self.hasScenario: return XmlUtil.children(self.scenario, None, "*") return []
def stepAxis(self, op, p, sourceSequence): targetSequence = [] for node in sourceSequence: if not isinstance(node, (ModelObject.ModelObject, xml.dom.Node)): raise XPathException( p, 'err:XPTY0020', _('Axis step {0} context item is not a node: {1}').format( op, node)) targetNodes = [] if isinstance(node, ModelObject.ModelObject): node = node.element if isinstance(p, QNameDef): ns = p.namespaceURI localname = p.localName if p.isAttribute: if p.unprefixed: if node.hasAttribute(localname): targetNodes.append(node.getAttribute(localname)) else: if node.hasAttributeNS(ns, localname): targetNodes.append( node.getAttributeNS(ns, localname)) elif op == '/' or op is None: targetNodes = XmlUtil.children(node, ns, localname) elif op == '//': targetNodes = XmlUtil.descendants(node, ns, localname) elif op == '..': targetNodes = [XmlUtil.parent(node)] elif isinstance(p, OperationDef) and isinstance(p.name, QNameDef): if p.name.localName == "text": targetNodes = [XmlUtil.text(node)] # todo: add element, attribute, node, etc... targetSequence.extend(targetNodes) return targetSequence
def aspectsCovered(self): aspectsCovered = set() if XmlUtil.hasChild(self, XbrlConst.euRend, "primaryItem"): aspectsCovered.add(Aspect.CONCEPT) if XmlUtil.hasChild(self, XbrlConst.euRend, "timeReference"): aspectsCovered.add(Aspect.INSTANT) for e in XmlUtil.children(self, XbrlConst.euRend, "explicitDimCoord"): aspectsCovered.add(self.prefixedNameQname(e.get("dimension"))) return aspectsCovered
def hasAspect(self, structuralNode, aspect): if aspect == Aspect.CONCEPT: return XmlUtil.hasChild(self, XbrlConst.euRend, "primaryItem") elif aspect == Aspect.DIMENSIONS: return XmlUtil.hasChild(self, XbrlConst.euRend, "explicitDimCoord") elif aspect in (Aspect.PERIOD_TYPE, Aspect.INSTANT): return XmlUtil.hasChild(self, XbrlConst.euRend, "timeReference") elif isinstance(aspect, QName): for e in XmlUtil.children(self, XbrlConst.euRend, "explicitDimCoord"): if self.prefixedNameQname(e.get("dimension")) == aspect: return True return False
def expected(self): for pluginXbrlMethod in pluginClassMethods(u"ModelTestcaseVariation.ExpectedResult"): expected = pluginXbrlMethod(self) if expected: return expected # default behavior without plugins if self.localName == u"testcase": return self.document.basename[:4] #starts with PASS or FAIL elif self.localName == u"testGroup": #w3c testcase instanceTestElement = XmlUtil.descendant(self, None, u"instanceTest") if instanceTestElement is not None: # take instance first return XmlUtil.descendantAttr(instanceTestElement, None, u"expected", u"validity") else: schemaTestElement = XmlUtil.descendant(self, None, u"schemaTest") if schemaTestElement is not None: return XmlUtil.descendantAttr(schemaTestElement, None, u"expected", u"validity") errorElement = XmlUtil.descendant(self, None, u"error") if errorElement is not None: return ModelValue.qname(errorElement, XmlUtil.text(errorElement)) resultElement = XmlUtil.descendant(self, None, u"result") if resultElement is not None: expected = resultElement.get(u"expected") if expected: return expected for assertElement in XmlUtil.children(resultElement, None, u"assert"): num = assertElement.get(u"num") if len(num) == 5: return u"EFM.{0}.{1}.{2}".format(num[0],num[1:3],num[3:6]) asserTests = {} for atElt in XmlUtil.children(resultElement, None, u"assertionTests"): try: asserTests[atElt.get(u"assertionID")] = (_INT(atElt.get(u"countSatisfied")),_INT(atElt.get(u"countNotSatisfied"))) except ValueError: pass if asserTests: return asserTests elif self.get(u"result"): return self.get(u"result") return None
def checkBreakdownDefinitionNode(modelXbrl, modelTable, tblAxisRel, tblAxisDisposition, uncoverableAspects, aspectsCovered): definitionNode = tblAxisRel.toModelObject hasCoveredAspect = False if isinstance(definitionNode, (ModelDefinitionNode, ModelEuAxisCoord)): for aspect in definitionNode.aspectsCovered(): aspectsCovered.add(aspect) if (aspect in uncoverableAspects or (isinstance(aspect, QName) and modelTable.aspectModel == 'non-dimensional')): modelXbrl.error( "xbrlte:axisAspectModelMismatch", _("%(definitionNode)s %(xlinkLabel)s, aspect model %(aspectModel)s, aspect %(aspect)s not allowed" ), modelObject=modelTable, definitionNode=definitionNode.localName, xlinkLabel=definitionNode.xlinkLabel, aspectModel=modelTable.aspectModel, aspect=str(aspect) if isinstance(aspect, QName) else Aspect.label[aspect]) hasCoveredAspect = True if aspect in modelTable.priorAspectAxisDisposition: otherAxisDisposition, otherDefinitionNode = modelTable.priorAspectAxisDisposition[ aspect] if tblAxisDisposition != otherAxisDisposition and aspect != Aspect.DIMENSIONS: modelXbrl.error( "xbrlte:aspectClashBetweenBreakdowns", _("%(definitionNode)s %(xlinkLabel)s, aspect %(aspect)s defined on axes of disposition %(axisDisposition)s and %(axisDisposition2)s" ), modelObject=(modelTable, definitionNode, otherDefinitionNode), definitionNode=definitionNode.localName, xlinkLabel=definitionNode.xlinkLabel, axisDisposition=tblAxisDisposition, axisDisposition2=otherAxisDisposition, aspect=str(aspect) if isinstance(aspect, QName) else Aspect.label[aspect]) else: modelTable.priorAspectAxisDisposition[aspect] = ( tblAxisDisposition, definitionNode) ruleSetChildren = XmlUtil.children(definitionNode, definitionNode.namespaceURI, "ruleSet") if definitionNode.isMerged: if ruleSetChildren: modelXbrl.error( "xbrlte:mergedRuleNodeWithTaggedRuleSet", _("Merged %(definitionNode)s %(xlinkLabel)s has tagged rule set(s)" ), modelObject=[modelTable, definitionNode] + ruleSetChildren, definitionNode=definitionNode.localName, xlinkLabel=definitionNode.xlinkLabel) labelRels = modelXbrl.relationshipSet( XbrlConst.elementLabel).fromModelObject(definitionNode) if labelRels: modelXbrl.error( "xbrlte:invalidUseOfLabel", _("Merged %(definitionNode)s %(xlinkLabel)s has label(s)"), modelObject=[modelTable, definitionNode] + [r.toModelObject for r in labelRels], definitionNode=definitionNode.localName, xlinkLabel=definitionNode.xlinkLabel) if not definitionNode.isAbstract: modelXbrl.error( "xbrlte:nonAbstractMergedRuleNode", _("Merged %(definitionNode)s %(xlinkLabel)s is not abstract" ), modelObject=(modelTable, definitionNode), definitionNode=definitionNode.localName, xlinkLabel=definitionNode.xlinkLabel) if isinstance(definitionNode, ModelRuleDefinitionNode): tagConstraintSets = {} otherConstraintSet = None # must look at xml constructs for duplicates for ruleSet in XmlUtil.children(definitionNode, definitionNode.namespaceURI, "ruleSet"): tag = ruleSet.tagName if tag is not None: # named constraint sets only for aspect in ruleSet.aspectsCovered(): if aspect != Aspect.DIMENSIONS: modelTable.aspectsInTaggedConstraintSets.add(aspect) if tag in tagConstraintSets: modelXbrl.error( "xbrlte:duplicateTag", _("%(definitionNode)s %(xlinkLabel)s duplicate rule set tags %(tag)s" ), modelObject=(modelTable, definitionNode, tagConstraintSets[tag], ruleSet), definitionNode=definitionNode.localName, xlinkLabel=definitionNode.xlinkLabel, tag=tag) else: tagConstraintSets[tag] = ruleSet for tag, constraintSet in definitionNode.constraintSets.items(): if otherConstraintSet is None: otherConstraintSet = constraintSet elif otherConstraintSet.aspectsCovered( ) != constraintSet.aspectsCovered(): modelXbrl.error( "xbrlte:constraintSetAspectMismatch", _("%(definitionNode)s %(xlinkLabel)s constraint set mismatches between %(tag1)s and %(tag2)s in constraints %(aspects)s" ), modelObject=(modelTable, definitionNode, otherConstraintSet, constraintSet), definitionNode=definitionNode.localName, xlinkLabel=definitionNode.xlinkLabel, tag1=otherConstraintSet.tagName, tag2=constraintSet.tagName, aspects=", ".join( aspectStr(aspect) for aspect in otherConstraintSet.aspectsCovered() ^ constraintSet.aspectsCovered() if aspect != Aspect.DIMENSIONS)) if isinstance(definitionNode, ModelDimensionRelationshipDefinitionNode): hasCoveredAspect = True if modelTable.aspectModel == 'non-dimensional': modelXbrl.error( "xbrlte:axisAspectModelMismatch", _("DimensionRelationship axis %(xlinkLabel)s can't be used in non-dimensional aspect model" ), modelObject=(modelTable, definitionNode), xlinkLabel=definitionNode.xlinkLabel) definitionNodeHasChild = False for axisSubtreeRel in modelXbrl.relationshipSet( (XbrlConst.tableBreakdownTree, XbrlConst.tableBreakdownTreeMMDD, XbrlConst.tableBreakdownTree201305, XbrlConst.tableDefinitionNodeSubtree, XbrlConst.tableDefinitionNodeSubtreeMMDD, XbrlConst.tableDefinitionNodeSubtree201305, XbrlConst.tableDefinitionNodeSubtree201301, XbrlConst.tableAxisSubtree2011)).fromModelObject(definitionNode): if checkBreakdownDefinitionNode(modelXbrl, modelTable, axisSubtreeRel, tblAxisDisposition, uncoverableAspects, aspectsCovered): hasCoveredAspect = True # something below was covering definitionNodeHasChild = True if isinstance(definitionNode, ModelFilterDefinitionNode): for aspect in definitionNode.aspectsCovered(): if isinstance(aspect, QName): # dimension aspect concept = modelXbrl.qnameConcepts.get(aspect) if concept is None or not concept.isDimensionItem: modelXbrl.error( "xbrlte:invalidDimensionQNameOnAspectNode", _("Aspect node %(xlinkLabel)s dimensional aspect %(dimension)s is not a dimension" ), modelObject=(modelTable, definitionNode), xlinkLabel=definitionNode.xlinkLabel, dimension=aspect) if not definitionNodeHasChild: if (definitionNode.namespaceURI in ("http://www.eurofiling.info/2010/rendering", "http://xbrl.org/2011/table") and not hasCoveredAspect): modelXbrl.error( "xbrlte:aspectValueNotDefinedByOrdinate", _("%(definitionNode)s %(xlinkLabel)s does not define an aspect" ), modelObject=(modelTable, definitionNode), xlinkLabel=definitionNode.xlinkLabel, definitionNode=definitionNode.localName) if (isinstance(definitionNode, ModelClosedDefinitionNode) and definitionNode.isAbstract): modelXbrl.error( "xbrlte:abstractRuleNodeNoChildren", _("Abstract %(definitionNode)s %(xlinkLabel)s has no children" ), modelObject=(modelTable, definitionNode), xlinkLabel=definitionNode.xlinkLabel, definitionNode=definitionNode.localName) return hasCoveredAspect
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 checkBreakdownDefinitionNode(modelXbrl, modelTable, tblAxisRel, tblAxisDisposition, uncoverableAspects, aspectsCovered): definitionNode = tblAxisRel.toModelObject hasCoveredAspect = False if isinstance(definitionNode, (ModelDefinitionNode, ModelEuAxisCoord)): for aspect in definitionNode.aspectsCovered(): aspectsCovered.add(aspect) if (aspect in uncoverableAspects or (isinstance(aspect, QName) and modelTable.aspectModel == 'non-dimensional')): modelXbrl.error("xbrlte:axisAspectModelMismatch", _("%(definitionNode)s %(xlinkLabel)s, aspect model %(aspectModel)s, aspect %(aspect)s not allowed"), modelObject=modelTable, definitionNode=definitionNode.localName, xlinkLabel=definitionNode.xlinkLabel, aspectModel=modelTable.aspectModel, aspect=str(aspect) if isinstance(aspect,QName) else Aspect.label[aspect]) hasCoveredAspect = True if aspect in modelTable.priorAspectAxisDisposition: otherAxisDisposition, otherDefinitionNode = modelTable.priorAspectAxisDisposition[aspect] if tblAxisDisposition != otherAxisDisposition and aspect != Aspect.DIMENSIONS: modelXbrl.error("xbrlte:aspectClashBetweenBreakdowns", _("%(definitionNode)s %(xlinkLabel)s, aspect %(aspect)s defined on axes of disposition %(axisDisposition)s and %(axisDisposition2)s"), modelObject=(modelTable, definitionNode, otherDefinitionNode), definitionNode=definitionNode.localName, xlinkLabel=definitionNode.xlinkLabel, axisDisposition=tblAxisDisposition, axisDisposition2=otherAxisDisposition, aspect=str(aspect) if isinstance(aspect,QName) else Aspect.label[aspect]) else: modelTable.priorAspectAxisDisposition[aspect] = (tblAxisDisposition, definitionNode) ruleSetChildren = XmlUtil.children(definitionNode, definitionNode.namespaceURI, "ruleSet") if definitionNode.isMerged: if ruleSetChildren: modelXbrl.error("xbrlte:mergedRuleNodeWithTaggedRuleSet", _("Merged %(definitionNode)s %(xlinkLabel)s has tagged rule set(s)"), modelObject=[modelTable, definitionNode] + ruleSetChildren, definitionNode=definitionNode.localName, xlinkLabel=definitionNode.xlinkLabel) labelRels = modelXbrl.relationshipSet(XbrlConst.elementLabel).fromModelObject(definitionNode) if labelRels: modelXbrl.error("xbrlte:mergedRuleNodeWithLabel", _("Merged %(definitionNode)s %(xlinkLabel)s has label(s)"), modelObject=[modelTable, definitionNode] + [r.toModelObject for r in labelRels], definitionNode=definitionNode.localName, xlinkLabel=definitionNode.xlinkLabel) if not definitionNode.isAbstract: modelXbrl.error("xbrlte:nonAbstractMergedRuleNode", _("Merged %(definitionNode)s %(xlinkLabel)s is not abstract"), modelObject=(modelTable, definitionNode), definitionNode=definitionNode.localName, xlinkLabel=definitionNode.xlinkLabel) if isinstance(definitionNode, ModelRuleDefinitionNode): tagConstraintSets = {} otherConstraintSet = None # must look at xml constructs for duplicates for ruleSet in XmlUtil.children(definitionNode, definitionNode.namespaceURI, "ruleSet"): tag = ruleSet.tagName if tag is not None: # named constraint sets only for aspect in ruleSet.aspectsCovered(): if aspect != Aspect.DIMENSIONS: modelTable.aspectsInTaggedConstraintSets.add(aspect) if tag in tagConstraintSets: modelXbrl.error("xbrlte:duplicateTag", _("%(definitionNode)s %(xlinkLabel)s duplicate rule set tags %(tag)s"), modelObject=(modelTable, definitionNode, tagConstraintSets[tag], ruleSet), definitionNode=definitionNode.localName, xlinkLabel=definitionNode.xlinkLabel, tag=tag) else: tagConstraintSets[tag] = ruleSet for tag, constraintSet in definitionNode.constraintSets.items(): if otherConstraintSet is None: otherConstraintSet = constraintSet elif otherConstraintSet.aspectsCovered() != constraintSet.aspectsCovered(): modelXbrl.error("xbrlte:constraintSetAspectMismatch", _("%(definitionNode)s %(xlinkLabel)s constraint set mismatches between %(tag1)s and %(tag2)s in constraints %(aspects)s"), modelObject=(modelTable, definitionNode, otherConstraintSet, constraintSet), definitionNode=definitionNode.localName, xlinkLabel=definitionNode.xlinkLabel, tag1=otherConstraintSet.tagName, tag2=constraintSet.tagName, aspects=", ".join(aspectStr(aspect) for aspect in otherConstraintSet.aspectsCovered() ^ constraintSet.aspectsCovered() if aspect != Aspect.DIMENSIONS)) if isinstance(definitionNode, ModelDimensionRelationshipDefinitionNode): hasCoveredAspect = True if modelTable.aspectModel == 'non-dimensional': modelXbrl.error("xbrlte:axisAspectModelMismatch", _("DimensionRelationship axis %(xlinkLabel)s can't be used in non-dimensional aspect model"), modelObject=(modelTable,definitionNode), xlinkLabel=definitionNode.xlinkLabel) definitionNodeHasChild = False for axisSubtreeRel in modelXbrl.relationshipSet((XbrlConst.tableBreakdownTree, XbrlConst.tableBreakdownTreeMMDD, XbrlConst.tableBreakdownTree201305, XbrlConst.tableDefinitionNodeSubtree, XbrlConst.tableDefinitionNodeSubtreeMMDD, XbrlConst.tableDefinitionNodeSubtree201305, XbrlConst.tableDefinitionNodeSubtree201301, XbrlConst.tableAxisSubtree2011)).fromModelObject(definitionNode): if checkBreakdownDefinitionNode(modelXbrl, modelTable, axisSubtreeRel, tblAxisDisposition, uncoverableAspects, aspectsCovered): hasCoveredAspect = True # something below was covering definitionNodeHasChild = True if isinstance(definitionNode, ModelFilterDefinitionNode): for aspect in definitionNode.aspectsCovered(): if isinstance(aspect, QName): # dimension aspect concept = modelXbrl.qnameConcepts.get(aspect) if concept is None or not concept.isDimensionItem: modelXbrl.error("xbrlte:invalidDimensionQNameOnAspectNode", _("Aspect node %(xlinkLabel)s dimensional aspect %(dimension)s is not a dimension"), modelObject=(modelTable,definitionNode), xlinkLabel=definitionNode.xlinkLabel, dimension=aspect) if not definitionNodeHasChild: if (definitionNode.namespaceURI in ("http://www.eurofiling.info/2010/rendering", "http://xbrl.org/2011/table") and not hasCoveredAspect): modelXbrl.error("xbrlte:aspectValueNotDefinedByOrdinate", _("%(definitionNode)s %(xlinkLabel)s does not define an aspect"), modelObject=(modelTable,definitionNode), xlinkLabel=definitionNode.xlinkLabel, definitionNode=definitionNode.localName) if (isinstance(definitionNode, ModelClosedDefinitionNode) and definitionNode.isAbstract): modelXbrl.error("xbrlte:abstractRuleNodeNoChildren", _("Abstract %(definitionNode)s %(xlinkLabel)s has no children"), modelObject=(modelTable,definitionNode), xlinkLabel=definitionNode.xlinkLabel, definitionNode=definitionNode.localName) return hasCoveredAspect
def checkFilingDTS(val, modelDocument, isEFM, isGFM, visited): global targetNamespaceDatePattern, efmFilenamePattern, htmlFileNamePattern, roleTypePattern, arcroleTypePattern, \ arcroleDefinitionPattern, namePattern, linkroleDefinitionBalanceIncomeSheet, \ namespacesConflictPattern if targetNamespaceDatePattern is None: targetNamespaceDatePattern = re.compile( r"/([12][0-9]{3})-([01][0-9])-([0-3][0-9])|" r"/([12][0-9]{3})([01][0-9])([0-3][0-9])|") efmFilenamePattern = re.compile( r"^[a-z0-9][a-zA-Z0-9_\.\-]*(\.xsd|\.xml|\.htm)$") htmlFileNamePattern = re.compile( r"^[a-zA-Z0-9][._a-zA-Z0-9-]*(\.htm)$") roleTypePattern = re.compile(r"^.*/role/[^/\s]+$") arcroleTypePattern = re.compile(r"^.*/arcrole/[^/\s]+$") arcroleDefinitionPattern = re.compile( r"^.*[^\\s]+.*$") # at least one non-whitespace character namePattern = re.compile( "[][()*+?\\\\/^{}|@#%^=~`\"';:,<>&$\u00a3\u20ac]" ) # u20ac=Euro, u00a3=pound sterling linkroleDefinitionBalanceIncomeSheet = re.compile( r"[^-]+-\s+Statement\s+-\s+.*(income|balance|financial\W+position)", re.IGNORECASE) namespacesConflictPattern = re.compile( r"http://(xbrl\.us|fasb\.org|xbrl\.sec\.gov)/(dei|us-types|us-roles|rr)/([0-9]{4}-[0-9]{2}-[0-9]{2})$" ) nonDomainItemNameProblemPattern = re.compile( r"({0})|(FirstQuarter|SecondQuarter|ThirdQuarter|FourthQuarter|[1-4]Qtr|Qtr[1-4]|ytd|YTD|HalfYear)(?:$|[A-Z\W])" .format(re.sub(r"\W", "", (val.entityRegistrantName or "").title()))) visited.append(modelDocument) for referencedDocument, modelDocumentReference in modelDocument.referencesDocument.items( ): #6.07.01 no includes if modelDocumentReference.referenceType == "include": val.modelXbrl.error( ("EFM.6.07.01", "GFM.1.03.01"), _("Taxonomy schema %(schema)s includes %(include)s, only import is allowed" ), modelObject=modelDocumentReference.referringModelObject, schema=os.path.basename(modelDocument.uri), include=os.path.basename(referencedDocument.uri)) if referencedDocument not in visited and referencedDocument.inDTS: # ignore EdgarRenderer added non-DTS documents checkFilingDTS(val, referencedDocument, isEFM, isGFM, visited) if val.disclosureSystem.standardTaxonomiesDict is None: pass if isEFM: if modelDocument.uri in val.disclosureSystem.standardTaxonomiesDict: if modelDocument.targetNamespace: # check for duplicates of us-types, dei, and rr taxonomies match = namespacesConflictPattern.match( modelDocument.targetNamespace) if match is not None: val.standardNamespaceConflicts[match.group(2)].add( modelDocument) else: if len(modelDocument.basename) > 32: val.modelXbrl.error( "EFM.5.01.01.tooManyCharacters", _("Document file name %(filename)s must not exceed 32 characters." ), modelObject=modelDocument, filename=modelDocument.basename) if modelDocument.type == ModelDocument.Type.INLINEXBRL: if not htmlFileNamePattern.match(modelDocument.basename): val.modelXbrl.error( "EFM.5.01.01", _("Document file name %(filename)s must start with a-z or 0-9, contain upper or lower case letters, ., -, _, and end with .htm." ), modelObject=modelDocument, filename=modelDocument.basename) elif not efmFilenamePattern.match(modelDocument.basename): val.modelXbrl.error( "EFM.5.01.01", _("Document file name %(filename)s must start with a-z or 0-9, contain upper or lower case letters, ., -, _, and end with .xsd or .xml." ), modelObject=modelDocument, filename=modelDocument.basename) if (modelDocument.type == ModelDocument.Type.SCHEMA and modelDocument.targetNamespace not in val.disclosureSystem.baseTaxonomyNamespaces and modelDocument.uri.startswith(val.modelXbrl.uriDir)): val.hasExtensionSchema = True # check schema contents types # 6.7.3 check namespace for standard authority targetNamespaceAuthority = UrlUtil.authority( modelDocument.targetNamespace) if targetNamespaceAuthority in val.disclosureSystem.standardAuthorities: val.modelXbrl.error( ("EFM.6.07.03", "GFM.1.03.03"), _("Taxonomy schema %(schema)s namespace %(targetNamespace)s is a disallowed authority" ), modelObject=modelDocument, schema=os.path.basename(modelDocument.uri), targetNamespace=modelDocument.targetNamespace, targetNamespaceAuthority=UrlUtil.authority( modelDocument.targetNamespace, includeScheme=False)) # 6.7.4 check namespace format if modelDocument.targetNamespace is None or not modelDocument.targetNamespace.startswith( "http://"): match = None else: targetNamespaceDate = modelDocument.targetNamespace[ len(targetNamespaceAuthority):] match = targetNamespaceDatePattern.match(targetNamespaceDate) if match is not None: try: if match.lastindex == 3: date = datetime.date(int(match.group(1)), int(match.group(2)), int(match.group(3))) elif match.lastindex == 6: date = datetime.date(int(match.group(4)), int(match.group(5)), int(match.group(6))) else: match = None except ValueError: match = None if match is None: val.modelXbrl.error( ("EFM.6.07.04", "GFM.1.03.04"), _("Taxonomy schema %(schema)s namespace %(targetNamespace)s must have format http://{authority}/{versionDate}" ), modelObject=modelDocument, schema=os.path.basename(modelDocument.uri), targetNamespace=modelDocument.targetNamespace) elif val.fileNameDate and date > val.fileNameDate: val.modelXbrl.info( ("EFM.6.07.06", "GFM.1.03.06"), _("Warning: Taxonomy schema %(schema)s namespace %(targetNamespace)s has date later than document name date %(docNameDate)s" ), modelObject=modelDocument, schema=os.path.basename(modelDocument.uri), targetNamespace=modelDocument.targetNamespace, docNameDate=val.fileNameDate) if modelDocument.targetNamespace is not None: # 6.7.5 check prefix for _ authority = UrlUtil.authority(modelDocument.targetNamespace) if not re.match(r"(http://|https://|ftp://|urn:)\w+", authority): val.modelXbrl.error( ("EFM.6.07.05", "GFM.1.03.05"), _("Taxonomy schema %(schema)s namespace %(targetNamespace)s must be a valid URL with a valid authority for the namespace." ), modelObject=modelDocument, schema=os.path.basename(modelDocument.uri), targetNamespace=modelDocument.targetNamespace) prefix = XmlUtil.xmlnsprefix(modelDocument.xmlRootElement, modelDocument.targetNamespace) if not prefix: val.modelXbrl.error( ("EFM.6.07.07", "GFM.1.03.07"), _("Taxonomy schema %(schema)s namespace %(targetNamespace)s missing prefix for the namespace." ), modelObject=modelDocument, schema=os.path.basename(modelDocument.uri), targetNamespace=modelDocument.targetNamespace) elif "_" in prefix: val.modelXbrl.error( ("EFM.6.07.07", "GFM.1.03.07"), _("Taxonomy schema %(schema)s namespace %(targetNamespace)s prefix %(prefix)s must not have an '_'" ), modelObject=modelDocument, schema=os.path.basename(modelDocument.uri), targetNamespace=modelDocument.targetNamespace, prefix=prefix) for modelConcept in modelDocument.xmlRootElement.iterdescendants( tag="{http://www.w3.org/2001/XMLSchema}element"): if isinstance(modelConcept, ModelConcept): # 6.7.16 name not duplicated in standard taxonomies name = modelConcept.get("name") if name is None: name = "" if modelConcept.get("ref") is not None: continue # don't validate ref's here for c in val.modelXbrl.nameConcepts.get(name, []): if c.modelDocument != modelDocument: if not c.modelDocument.uri.startswith( val.modelXbrl.uriDir): val.modelXbrl.error( ("EFM.6.07.16", "GFM.1.03.18"), _("Concept %(concept)s is also defined in standard taxonomy schema schema %(standardSchema)s" ), modelObject=(modelConcept, c), concept=modelConcept.qname, standardSchema=os.path.basename( c.modelDocument.uri), standardConcept=c.qname) # 6.7.17 id properly formed _id = modelConcept.id requiredId = (prefix if prefix is not None else "") + "_" + name if _id != requiredId: val.modelXbrl.error( ("EFM.6.07.17", "GFM.1.03.19"), _("Concept %(concept)s id %(id)s should be %(requiredId)s" ), modelObject=modelConcept, concept=modelConcept.qname, id=_id, requiredId=requiredId) # 6.7.18 nillable is true nillable = modelConcept.get("nillable") if nillable != "true" and modelConcept.isItem: val.modelXbrl.error( ("EFM.6.07.18", "GFM.1.03.20"), _("Taxonomy schema %(schema)s element %(concept)s nillable %(nillable)s should be 'true'" ), modelObject=modelConcept, schema=os.path.basename(modelDocument.uri), concept=name, nillable=nillable) # 6.7.19 not tuple if modelConcept.isTuple: val.modelXbrl.error( ("EFM.6.07.19", "GFM.1.03.21"), _("Concept %(concept)s is a tuple"), modelObject=modelConcept, concept=modelConcept.qname) # 6.7.20 no typed domain ref if modelConcept.isTypedDimension: val.modelXbrl.error( ("EFM.6.07.20", "GFM.1.03.22"), _("Concept %(concept)s has typedDomainRef %(typedDomainRef)s" ), modelObject=modelConcept, concept=modelConcept.qname, typedDomainRef=modelConcept.typedDomainElement. qname if modelConcept.typedDomainElement is not None else modelConcept.typedDomainRef) # 6.7.21 abstract must be duration isDuration = modelConcept.periodType == "duration" if modelConcept.isAbstract and not isDuration: val.modelXbrl.error( ("EFM.6.07.21", "GFM.1.03.23"), _("Taxonomy schema %(schema)s element %(concept)s is abstract but period type is not duration" ), modelObject=modelConcept, schema=os.path.basename(modelDocument.uri), concept=modelConcept.qname) # 6.7.22 abstract must be stringItemType ''' removed SEC EFM v.17, Edgar release 10.4, and GFM 2011-04-08 if modelConcept.abstract == "true" and modelConcept.typeQname != XbrlConst. qnXbrliStringItemType: val.modelXbrl.error(("EFM.6.07.22", "GFM.1.03.24"), _("Concept %(concept)s is abstract but type is not xbrli:stringItemType"), modelObject=modelConcept, concept=modelConcept.qname) ''' substitutionGroupQname = modelConcept.substitutionGroupQname # 6.7.23 Axis must be subs group dimension if name.endswith("Axis") ^ ( substitutionGroupQname == XbrlConst.qnXbrldtDimensionItem): val.modelXbrl.error( ("EFM.6.07.23", "GFM.1.03.25"), _("Concept %(concept)s must end in Axis to be in xbrldt:dimensionItem substitution group" ), modelObject=modelConcept, concept=modelConcept.qname) # 6.7.24 Table must be subs group hypercube if name.endswith("Table") ^ ( substitutionGroupQname == XbrlConst.qnXbrldtHypercubeItem): val.modelXbrl.error( ("EFM.6.07.24", "GFM.1.03.26"), _("Concept %(concept)s must end in Table to be in xbrldt:hypercubeItem substitution group" ), modelObject=modelConcept, schema=os.path.basename(modelDocument.uri), concept=modelConcept.qname) # 6.7.25 if neither hypercube or dimension, substitution group must be item if substitutionGroupQname not in ( None, XbrlConst.qnXbrldtDimensionItem, XbrlConst.qnXbrldtHypercubeItem, XbrlConst.qnXbrliItem): val.modelXbrl.error( ("EFM.6.07.25", "GFM.1.03.27"), _("Concept %(concept)s has disallowed substitution group %(substitutionGroup)s" ), modelObject=modelConcept, concept=modelConcept.qname, substitutionGroup=modelConcept. substitutionGroupQname) # 6.7.26 Table must be subs group hypercube if name.endswith( "LineItems") and modelConcept.abstract != "true": val.modelXbrl.error( ("EFM.6.07.26", "GFM.1.03.28"), _("Concept %(concept)s is a LineItems but not abstract" ), modelObject=modelConcept, concept=modelConcept.qname) # 6.7.27 type domainMember must end with Domain or Member conceptType = modelConcept.type isDomainItemType = conceptType is not None and conceptType.isDomainItemType endsWithDomainOrMember = name.endswith( "Domain") or name.endswith("Member") if isDomainItemType != endsWithDomainOrMember: val.modelXbrl.error( ("EFM.6.07.27", "GFM.1.03.29"), _("Concept %(concept)s must end with Domain or Member for type of domainItemType" ), modelObject=modelConcept, concept=modelConcept.qname) # 6.7.28 domainItemType must be duration if isDomainItemType and not isDuration: val.modelXbrl.error( ("EFM.6.07.28", "GFM.1.03.30"), _("Concept %(concept)s is a domainItemType and must be periodType duration" ), modelObject=modelConcept, concept=modelConcept.qname) #6.7.31 (version 27) fractions if modelConcept.isFraction: val.modelXbrl.error( "EFM.6.07.31", _("Concept %(concept)s is a fraction"), modelObject=modelConcept, concept=modelConcept.qname) #6.7.32 (version 27) instant non numeric if modelConcept.isItem and (not modelConcept.isNumeric and not isDuration and not modelConcept.isAbstract and not isDomainItemType): val.modelXbrl.error( "EFM.6.07.32", _("Taxonomy schema %(schema)s element %(concept)s is non-numeric but period type is not duration" ), modelObject=modelConcept, schema=os.path.basename(modelDocument.uri), concept=modelConcept.qname) # 6.8.5 semantic check, check LC3 name if name: if not name[0].isupper(): val.modelXbrl.log( "ERROR-SEMANTIC", ("EFM.6.08.05.firstLetter", "GFM.2.03.05.firstLetter"), _("Concept %(concept)s name must start with a capital letter" ), modelObject=modelConcept, concept=modelConcept.qname) if namePattern.search(name): val.modelXbrl.log( "ERROR-SEMANTIC", ("EFM.6.08.05.disallowedCharacter", "GFM.2.03.05.disallowedCharacter"), _("Concept %(concept)s has disallowed name character" ), modelObject=modelConcept, concept=modelConcept.qname) if len(name) > 200: val.modelXbrl.log( "ERROR-SEMANTIC", "EFM.6.08.05.nameLength", _("Concept %(concept)s name length %(namelength)s exceeds 200 characters" ), modelObject=modelConcept, concept=modelConcept.qname, namelength=len(name)) if isEFM: label = modelConcept.label(lang="en-US", fallbackToQname=False) if label: # allow Joe's Bar, N.A. to be JoesBarNA -- remove ', allow A. as not article "a" lc3name = ''.join( re.sub(r"['.-]", "", ( w[0] or w[2] or w[3] or w[4])).title() for w in re.findall( r"((\w+')+\w+)|(A[.-])|([.-]A(?=\W|$))|(\w+)", label ) # EFM implies this should allow - and . re.findall(r"[\w\-\.]+", label) if w[4].lower() not in ("the", "a", "an")) if not (name == lc3name or (name and lc3name and lc3name[0].isdigit() and name[1:] == lc3name and (name[0].isalpha() or name[0] == '_'))): val.modelXbrl.log( "WARNING-SEMANTIC", "EFM.6.08.05.LC3", _("Concept %(concept)s should match expected LC3 composition %(lc3name)s" ), modelObject=modelConcept, concept=modelConcept.qname, lc3name=lc3name) if conceptType is not None: # 6.8.6 semantic check if not isDomainItemType and conceptType.qname != XbrlConst.qnXbrliDurationItemType: nameProblems = nonDomainItemNameProblemPattern.findall( name) if any( any(t) for t in nameProblems ): # list of tuples with possibly nonempty strings val.modelXbrl.log( "WARNING-SEMANTIC", ("EFM.6.08.06", "GFM.2.03.06"), _("Concept %(concept)s should not contain company or period information, found: %(matches)s" ), modelObject=modelConcept, concept=modelConcept.qname, matches=", ".join(''.join(t) for t in nameProblems)) if conceptType.qname == XbrlConst.qnXbrliMonetaryItemType: if not modelConcept.balance: # 6.8.11 may not appear on a income or balance statement if any( linkroleDefinitionBalanceIncomeSheet. match(roleType.definition) for rel in val.modelXbrl. relationshipSet(XbrlConst.parentChild). toModelObject(modelConcept) for roleType in val.modelXbrl. roleTypes.get(rel.linkrole, ())): val.modelXbrl.log( "ERROR-SEMANTIC", ("EFM.6.08.11", "GFM.2.03.11"), _("Concept %(concept)s must have a balance because it appears in a statement of income or balance sheet" ), modelObject=modelConcept, concept=modelConcept.qname) # 6.11.5 semantic check, must have a documentation label stdLabel = modelConcept.label( lang="en-US", fallbackToQname=False) defLabel = modelConcept.label( preferredLabel=XbrlConst. documentationLabel, lang="en-US", fallbackToQname=False) if not defLabel or ( # want different words than std label stdLabel and re.findall(r"\w+", stdLabel) == re.findall(r"\w+", defLabel)): val.modelXbrl.log( "ERROR-SEMANTIC", ("EFM.6.11.05", "GFM.2.04.04"), _("Concept %(concept)s is monetary without a balance and must have a documentation label that disambiguates its sign" ), modelObject=modelConcept, concept=modelConcept.qname) # 6.8.16 semantic check if conceptType.qname == XbrlConst.qnXbrliDateItemType and modelConcept.periodType != "duration": val.modelXbrl.log( "ERROR-SEMANTIC", ("EFM.6.08.16", "GFM.2.03.16"), _("Concept %(concept)s of type xbrli:dateItemType must have periodType duration" ), modelObject=modelConcept, concept=modelConcept.qname) # 6.8.17 semantic check if conceptType.qname == XbrlConst.qnXbrliStringItemType and modelConcept.periodType != "duration": val.modelXbrl.log( "ERROR-SEMANTIC", ("EFM.6.08.17", "GFM.2.03.17"), _("Concept %(concept)s of type xbrli:stringItemType must have periodType duration" ), modelObject=modelConcept, concept=modelConcept.qname) # 6.7.8 check for embedded linkbase for e in modelDocument.xmlRootElement.iterdescendants( tag="{http://www.xbrl.org/2003/linkbase}linkbase"): if isinstance(e, ModelObject): val.modelXbrl.error( ("EFM.6.07.08", "GFM.1.03.08"), _("Taxonomy schema %(schema)s contains an embedded linkbase" ), modelObject=e, schema=modelDocument.basename) break requiredUsedOns = { XbrlConst.qnLinkPresentationLink, XbrlConst.qnLinkCalculationLink, XbrlConst.qnLinkDefinitionLink } standardUsedOns = { XbrlConst.qnLinkLabel, XbrlConst.qnLinkReference, XbrlConst.qnLinkDefinitionArc, XbrlConst.qnLinkCalculationArc, XbrlConst.qnLinkPresentationArc, XbrlConst.qnLinkLabelArc, XbrlConst.qnLinkReferenceArc, # per WH, private footnote arc and footnore resource roles are not allowed XbrlConst.qnLinkFootnoteArc, XbrlConst.qnLinkFootnote, } # 6.7.9 role types authority for e in modelDocument.xmlRootElement.iterdescendants( tag="{http://www.xbrl.org/2003/linkbase}roleType"): if isinstance(e, ModelObject): roleURI = e.get("roleURI") if targetNamespaceAuthority != UrlUtil.authority(roleURI): val.modelXbrl.error( ("EFM.6.07.09", "GFM.1.03.09"), _("RoleType %(roleType)s does not match authority %(targetNamespaceAuthority)s" ), modelObject=e, roleType=roleURI, targetNamespaceAuthority=targetNamespaceAuthority, targetNamespace=modelDocument.targetNamespace) # 6.7.9 end with .../role/lc3 name if not roleTypePattern.match(roleURI): val.modelXbrl.warning( ("EFM.6.07.09.roleEnding", "GFM.1.03.09"), "RoleType %(roleType)s should end with /role/{LC3name}", modelObject=e, roleType=roleURI) # 6.7.10 only one role type declaration in DTS modelRoleTypes = val.modelXbrl.roleTypes.get(roleURI) if modelRoleTypes is not None: modelRoleType = modelRoleTypes[0] definition = modelRoleType.definitionNotStripped usedOns = modelRoleType.usedOns if len(modelRoleTypes) == 1: # 6.7.11 used on's for pre, cal, def if any has a used on if not usedOns.isdisjoint(requiredUsedOns) and len( requiredUsedOns - usedOns) > 0: val.modelXbrl.error( ("EFM.6.07.11", "GFM.1.03.11"), _("RoleType %(roleType)s missing used on %(usedOn)s" ), modelObject=e, roleType=roleURI, usedOn=requiredUsedOns - usedOns) # 6.7.12 definition match pattern if (val.disclosureSystem.roleDefinitionPattern is not None and (definition is None or not val.disclosureSystem. roleDefinitionPattern.match(definition))): val.modelXbrl.error( ("EFM.6.07.12", "GFM.1.03.12-14"), _("RoleType %(roleType)s definition \"%(definition)s\" must match {Sortcode} - {Type} - {Title}" ), modelObject=e, roleType=roleURI, definition=(definition or "")) if usedOns & standardUsedOns: # semantics check val.modelXbrl.log( "ERROR-SEMANTIC", ("EFM.6.08.03", "GFM.2.03.03"), _("RoleType %(roleuri)s is defined using role types already defined by standard roles for: %(qnames)s" ), modelObject=e, roleuri=roleURI, qnames=', '.join( str(qn) for qn in usedOns & standardUsedOns)) # 6.7.13 arcrole types authority for e in modelDocument.xmlRootElement.iterdescendants( tag="{http://www.xbrl.org/2003/linkbase}arcroleType"): if isinstance(e, ModelObject): arcroleURI = e.get("arcroleURI") if targetNamespaceAuthority != UrlUtil.authority(arcroleURI): val.modelXbrl.error( ("EFM.6.07.13", "GFM.1.03.15"), _("ArcroleType %(arcroleType)s does not match authority %(targetNamespaceAuthority)s" ), modelObject=e, arcroleType=arcroleURI, targetNamespaceAuthority=targetNamespaceAuthority, targetNamespace=modelDocument.targetNamespace) # 6.7.13 end with .../arcrole/lc3 name if not arcroleTypePattern.match(arcroleURI): val.modelXbrl.warning( ("EFM.6.07.13.arcroleEnding", "GFM.1.03.15"), _("ArcroleType %(arcroleType)s should end with /arcrole/{LC3name}" ), modelObject=e, arcroleType=arcroleURI) # 6.7.15 definition match pattern modelRoleTypes = val.modelXbrl.arcroleTypes[arcroleURI] definition = modelRoleTypes[0].definition if definition is None or not arcroleDefinitionPattern.match( definition): val.modelXbrl.error( ("EFM.6.07.15", "GFM.1.03.17"), _("ArcroleType %(arcroleType)s definition must be non-empty" ), modelObject=e, arcroleType=arcroleURI) # semantic checks usedOns = modelRoleTypes[0].usedOns if usedOns & standardUsedOns: # semantics check val.modelXbrl.log( "ERROR-SEMANTIC", ("EFM.6.08.03", "GFM.2.03.03"), _("ArcroleType %(arcroleuri)s is defined using role types already defined by standard arcroles for: %(qnames)s" ), modelObject=e, arcroleuri=arcroleURI, qnames=', '.join( str(qn) for qn in usedOns & standardUsedOns)) #6.3.3 filename check m = re.match(r"^\w+-([12][0-9]{3}[01][0-9][0-3][0-9]).xsd$", modelDocument.basename) if m: try: # check date value datetime.datetime.strptime(m.group(1), "%Y%m%d").date() # date and format are ok, check "should" part of 6.3.3 if val.fileNameBasePart: expectedFilename = "{0}-{1}.xsd".format( val.fileNameBasePart, val.fileNameDatePart) if modelDocument.basename != expectedFilename: val.modelXbrl.log( "WARNING-SEMANTIC", ("EFM.6.03.03.matchInstance", "GFM.1.01.01.matchInstance"), _('Schema file name warning: %(filename)s, should match %(expectedFilename)s' ), modelObject=modelDocument, filename=modelDocument.basename, expectedFilename=expectedFilename) except ValueError: val.modelXbrl.error( (val.EFM60303, "GFM.1.01.01"), _('Invalid schema file base name part (date) in "{base}-{yyyymmdd}.xsd": %(filename)s' ), modelObject=modelDocument, filename=modelDocument.basename, messageCodes=("EFM.6.03.03", "EFM.6.23.01", "GFM.1.01.01")) else: val.modelXbrl.error( (val.EFM60303, "GFM.1.01.01"), _('Invalid schema file name, must match "{base}-{yyyymmdd}.xsd": %(filename)s' ), modelObject=modelDocument, filename=modelDocument.basename, messageCodes=("EFM.6.03.03", "EFM.6.23.01", "GFM.1.01.01")) elif modelDocument.type == ModelDocument.Type.LINKBASE: # if it is part of the submission (in same directory) check name labelRels = None if modelDocument.filepath.startswith( val.modelXbrl.modelDocument.filepathdir): #6.3.3 filename check extLinkElt = XmlUtil.descendant( modelDocument.xmlRootElement, XbrlConst.link, "*", "{http://www.w3.org/1999/xlink}type", "extended") if extLinkElt is None: # no ext link element val.modelXbrl.error( (val.EFM60303 + ".noLinkElement", "GFM.1.01.01.noLinkElement"), _('Invalid linkbase file name: %(filename)s, has no extended link element, cannot determine link type.' ), modelObject=modelDocument, filename=modelDocument.basename, messageCodes=("EFM.6.03.03.noLinkElement", "EFM.6.23.01.noLinkElement", "GFM.1.01.01.noLinkElement")) elif extLinkElt.localName not in extLinkEltFileNameEnding: val.modelXbrl.error( "EFM.6.03.02", _('Invalid linkbase link element %(linkElement)s in %(filename)s' ), modelObject=modelDocument, linkElement=extLinkElt.localName, filename=modelDocument.basename) else: m = re.match( r"^\w+-([12][0-9]{3}[01][0-9][0-3][0-9])(_[a-z]{3}).xml$", modelDocument.basename) expectedSuffix = extLinkEltFileNameEnding[extLinkElt.localName] if m and m.group(2) == expectedSuffix: try: # check date value datetime.datetime.strptime(m.group(1), "%Y%m%d").date() # date and format are ok, check "should" part of 6.3.3 if val.fileNameBasePart: expectedFilename = "{0}-{1}{2}.xml".format( val.fileNameBasePart, val.fileNameDatePart, expectedSuffix) if modelDocument.basename != expectedFilename: val.modelXbrl.log( "WARNING-SEMANTIC", ("EFM.6.03.03.matchInstance", "GFM.1.01.01.matchInstance"), _('Linkbase name warning: %(filename)s should match %(expectedFilename)s' ), modelObject=modelDocument, filename=modelDocument.basename, expectedFilename=expectedFilename) except ValueError: val.modelXbrl.error( (val.EFM60303, "GFM.1.01.01"), _('Invalid linkbase base file name part (date) in "{base}-{yyyymmdd}_{suffix}.xml": %(filename)s' ), modelObject=modelDocument, filename=modelDocument.basename, messageCodes=("EFM.6.03.03", "EFM.6.23.01", "GFM.1.01.01")) else: val.modelXbrl.error( (val.EFM60303, "GFM.1.01.01"), _('Invalid linkbase name, must match "{base}-{yyyymmdd}%(expectedSuffix)s.xml": %(filename)s' ), modelObject=modelDocument, filename=modelDocument.basename, expectedSuffix=expectedSuffix, messageCodes=("EFM.6.03.03", "EFM.6.23.01", "GFM.1.01.01")) if extLinkElt.localName == "labelLink": if labelRels is None: labelRels = val.modelXbrl.relationshipSet( XbrlConst.conceptLabel) for labelElt in XmlUtil.children(extLinkElt, XbrlConst.link, "label"): # 6.10.9 if XbrlConst.isNumericRole(labelElt.role): for rel in labelRels.toModelObject(labelElt): if rel.fromModelObject is not None and not rel.fromModelObject.isNumeric: val.modelXbrl.error( "EFM.6.10.09", _("Label of non-numeric concept %(concept)s has a numeric role: %(role)s" ), modelObject=(labelElt, rel.fromModelObject), concept=rel.fromModelObject.qname, role=labelElt.role)
def stepAxis(self, op, p, sourceSequence): targetSequence = [] for node in sourceSequence: if not isinstance(node,(ModelObject, etree._ElementTree, ModelAttribute)): raise XPathException(self.progHeader, 'err:XPTY0020', _('Axis step {0} context item is not a node: {1}').format(op, node)) targetNodes = [] if isinstance(p,QNameDef): ns = p.namespaceURI; localname = p.localName; axis = p.axis if p.isAttribute: if isinstance(node,ModelObject): attrTag = p.localName if p.unprefixed else p.clarkNotation modelAttribute = None try: modelAttribute = node.xAttributes[attrTag] except (AttributeError, TypeError, IndexError, KeyError): # may be lax or deferred validated try: xmlValidate(node.modelXbrl, node, p) modelAttribute = node.xAttributes[attrTag] except (AttributeError, TypeError, IndexError, KeyError): pass if modelAttribute is None: value = node.get(attrTag) if value is not None: targetNodes.append(ModelAttribute(node,p.clarkNotation,UNKNOWN,value,value,value)) elif modelAttribute.xValid >= VALID: targetNodes.append(modelAttribute) elif op == '/' or op is None: if axis is None or axis == "child": if isinstance(node,(ModelObject, etree._ElementTree)): targetNodes = XmlUtil.children(node, ns, localname) elif axis == "parent": if isinstance(node,ModelAttribute): parentNode = [ node.modelElement ] else: parentNode = [ XmlUtil.parent(node) ] if (isinstance(node,ModelObject) and (not ns or ns == parentNode.namespaceURI or ns == "*") and (localname == parentNode.localName or localname == "*")): targetNodes = [ parentNode ] elif axis == "self": if (isinstance(node,ModelObject) and (not ns or ns == node.namespaceURI or ns == "*") and (localname == node.localName or localname == "*")): targetNodes = [ node ] elif axis.startswith("descendant"): if isinstance(node,(ModelObject, etree._ElementTree)): targetNodes = XmlUtil.descendants(node, ns, localname) if (axis.endswith("-or-self") and isinstance(node,ModelObject) and (not ns or ns == node.namespaceURI or ns == "*") and (localname == node.localName or localname == "*")): targetNodes.append(node) elif axis.startswith("ancestor"): if isinstance(node,ModelObject): targetNodes = [ancestor for ancestor in XmlUtil.ancestors(node) if ((not ns or ns == ancestor.namespaceURI or ns == "*") and (localname == ancestor.localName or localname == "*"))] if (axis.endswith("-or-self") and isinstance(node,ModelObject) and (not ns or ns == node.namespaceURI or ns == "*") and (localname == node.localName or localname == "*")): targetNodes.insert(0, node) elif axis.endswith("-sibling"): if isinstance(node,ModelObject): targetNodes = [sibling for sibling in node.itersiblings(preceding=axis.startswith("preceding")) if ((not ns or ns == sibling.namespaceURI or ns == "*") and (localname == sibling.localName or localname == "*"))] elif axis == "preceding": if isinstance(node,ModelObject): for preceding in node.getroottree().iter(): if preceding == node: break elif ((not ns or ns == preceding.namespaceURI or ns == "*") and (localname == preceding.localName or localname == "*")): targetNodes.append(preceding) elif axis == "following": if isinstance(node,ModelObject): foundNode = False for following in node.getroottree().iter(): if following == node: foundNode = True elif (foundNode and (not ns or ns == following.namespaceURI or ns == "*") and (localname == following.localName or localname == "*")): targetNodes.append(following) elif op == '//': if isinstance(node,(ModelObject, etree. _ElementTree)): targetNodes = XmlUtil.descendants(node, ns, localname) elif op == '..': if isinstance(node,ModelAttribute): targetNodes = [ node.modelElement ] else: targetNodes = [ XmlUtil.parent(node) ] elif isinstance(p, OperationDef) and isinstance(p.name,QNameDef): if isinstance(node,ModelObject): if p.name.localName == "text": # note this is not string value, just child text targetNodes = [node.textValue] # todo: add element, attribute, node, etc... elif p == '*': # wildcard if op == '/' or op is None: if isinstance(node,(ModelObject, etree._ElementTree)): targetNodes = XmlUtil.children(node, '*', '*') elif op == '//': if isinstance(node,(ModelObject, etree._ElementTree)): targetNodes = XmlUtil.descendants(node, '*', '*') targetSequence.extend(targetNodes) return targetSequence
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()
def checkDTS(val, modelDocument, visited): global targetNamespaceDatePattern, efmFilenamePattern, roleTypePattern, arcroleTypePattern, \ arcroleDefinitionPattern, namePattern, linkroleDefinitionBalanceIncomeSheet, \ namespacesConflictPattern if targetNamespaceDatePattern is None: targetNamespaceDatePattern = re.compile(r"/([12][0-9]{3})-([01][0-9])-([0-3][0-9])|" r"/([12][0-9]{3})([01][0-9])([0-3][0-9])|") efmFilenamePattern = re.compile(r"^[a-z0-9][a-zA-Z0-9_\.\-]*(\.xsd|\.xml)$") roleTypePattern = re.compile(r"^.*/role/[^/\s]+$") arcroleTypePattern = re.compile(r"^.*/arcrole/[^/\s]+$") arcroleDefinitionPattern = re.compile(r"^.*[^\\s]+.*$") # at least one non-whitespace character namePattern = re.compile("[][()*+?\\\\/^{}|@#%^=~`\"';:,<>&$\u00a3\u20ac]") # u20ac=Euro, u00a3=pound sterling linkroleDefinitionBalanceIncomeSheet = re.compile(r"[^-]+-\s+Statement\s+-\s+.*(income|balance|financial\W+position)", re.IGNORECASE) namespacesConflictPattern = re.compile(r"http://(xbrl\.us|fasb\.org|xbrl\.sec\.gov)/(dei|us-types|us-roles|rr)/([0-9]{4}-[0-9]{2}-[0-9]{2})$") nonDomainItemNameProblemPattern = re.compile( r"({0})|(FirstQuarter|SecondQuarter|ThirdQuarter|FourthQuarter|[1-4]Qtr|Qtr[1-4]|ytd|YTD|HalfYear)(?:$|[A-Z\W])" .format(re.sub(r"\W", "", (val.entityRegistrantName or "").title()))) visited.append(modelDocument) definesLabelLinkbase = False for referencedDocument, modelDocumentReference in modelDocument.referencesDocument.items(): #6.07.01 no includes if modelDocumentReference.referenceType == "include": val.modelXbrl.error(("EFM.6.07.01", "GFM.1.03.01", "SBR.NL.2.2.0.18"), _("Taxonomy schema %(schema)s includes %(include)s, only import is allowed"), modelObject=modelDocumentReference.referringModelObject, schema=os.path.basename(modelDocument.uri), include=os.path.basename(referencedDocument.uri)) if referencedDocument not in visited: checkDTS(val, referencedDocument, visited) if val.disclosureSystem.standardTaxonomiesDict is None: pass if val.validateEFM: if modelDocument.uri in val.disclosureSystem.standardTaxonomiesDict: if modelDocument.targetNamespace: # check for duplicates of us-types, dei, and rr taxonomies match = namespacesConflictPattern.match(modelDocument.targetNamespace) if match is not None: val.standardNamespaceConflicts[match.group(2)].add(modelDocument) else: if len(modelDocument.basename) > 32: val.modelXbrl.error("EFM.5.01.01.tooManyCharacters", _("Document file name %(filename)s must not exceed 32 characters."), modelObject=modelDocument, filename=modelDocument.basename) if not efmFilenamePattern.match(modelDocument.basename): val.modelXbrl.error("EFM.5.01.01", _("Document file name %(filename)s must start with a-z or 0-9, contain upper or lower case letters, ., -, _, and end with .xsd or .xml."), modelObject=modelDocument, filename=modelDocument.basename) if (modelDocument.type == ModelDocument.Type.SCHEMA and modelDocument.targetNamespace not in val.disclosureSystem.baseTaxonomyNamespaces and modelDocument.uri.startswith(val.modelXbrl.uriDir)): # check schema contents types if val.validateSBRNL: definesLinkroles = False definesArcroles = False definesLinkParts = False definesAbstractItems = False definesNonabstractItems = False definesConcepts = False definesTuples = False definesPresentationTuples = False definesSpecificationTuples = False definesTypes = False definesEnumerations = False definesDimensions = False definesDomains = False definesHypercubes = False # 6.7.3 check namespace for standard authority targetNamespaceAuthority = UrlUtil.authority(modelDocument.targetNamespace) if targetNamespaceAuthority in val.disclosureSystem.standardAuthorities: val.modelXbrl.error(("EFM.6.07.03", "GFM.1.03.03"), _("Taxonomy schema %(schema)s namespace %(targetNamespace)s is a disallowed authority"), modelObject=modelDocument, schema=os.path.basename(modelDocument.uri), targetNamespace=modelDocument.targetNamespace, targetNamespaceAuthority=UrlUtil.authority(modelDocument.targetNamespace, includeScheme=False)) # 6.7.4 check namespace format if modelDocument.targetNamespace is None or not modelDocument.targetNamespace.startswith("http://"): match = None elif val.validateEFMorGFM: targetNamespaceDate = modelDocument.targetNamespace[len(targetNamespaceAuthority):] match = targetNamespaceDatePattern.match(targetNamespaceDate) else: match = None if match is not None: try: if match.lastindex == 3: date = datetime.date(int(match.group(1)),int(match.group(2)),int(match.group(3))) elif match.lastindex == 6: date = datetime.date(int(match.group(4)),int(match.group(5)),int(match.group(6))) else: match = None except ValueError: match = None if match is None: val.modelXbrl.error(("EFM.6.07.04", "GFM.1.03.04"), _("Taxonomy schema %(schema)s namespace %(targetNamespace)s must have format http://{authority}/{versionDate}"), modelObject=modelDocument, schema=os.path.basename(modelDocument.uri), targetNamespace=modelDocument.targetNamespace) elif val.fileNameDate and date > val.fileNameDate: val.modelXbrl.info(("EFM.6.07.06", "GFM.1.03.06"), _("Warning: Taxonomy schema %(schema)s namespace %(targetNamespace)s has date later than document name date %(docNameDate)s"), modelObject=modelDocument, schema=os.path.basename(modelDocument.uri), targetNamespace=modelDocument.targetNamespace, docNameDate=val.fileNameDate) if modelDocument.targetNamespace is not None: # 6.7.5 check prefix for _ authority = UrlUtil.authority(modelDocument.targetNamespace) if not re.match(r"(http://|https://|ftp://|urn:)\w+",authority): val.modelXbrl.error(("EFM.6.07.05", "GFM.1.03.05"), _("Taxonomy schema %(schema)s namespace %(targetNamespace)s must be a valid URL with a valid authority for the namespace."), modelObject=modelDocument, schema=os.path.basename(modelDocument.uri), targetNamespace=modelDocument.targetNamespace) prefix = XmlUtil.xmlnsprefix(modelDocument.xmlRootElement,modelDocument.targetNamespace) if not prefix: val.modelXbrl.error(("EFM.6.07.07", "GFM.1.03.07"), _("Taxonomy schema %(schema)s namespace %(targetNamespace)s missing prefix for the namespace."), modelObject=modelDocument, schema=os.path.basename(modelDocument.uri), targetNamespace=modelDocument.targetNamespace) elif "_" in prefix: val.modelXbrl.error(("EFM.6.07.07", "GFM.1.03.07"), _("Taxonomy schema %(schema)s namespace %(targetNamespace)s prefix %(prefix)s must not have an '_'"), modelObject=modelDocument, schema=os.path.basename(modelDocument.uri), targetNamespace=modelDocument.targetNamespace, prefix=prefix) if val.validateSBRNL: genrlSpeclRelSet = val.modelXbrl.relationshipSet(XbrlConst.generalSpecial) for modelConcept in modelDocument.xmlRootElement.iterdescendants(tag="{http://www.w3.org/2001/XMLSchema}element"): if isinstance(modelConcept,ModelConcept): # 6.7.16 name not duplicated in standard taxonomies name = modelConcept.get("name") if name is None: name = "" if modelConcept.get("ref") is not None: continue # don't validate ref's here for c in val.modelXbrl.nameConcepts.get(name, []): if c.modelDocument != modelDocument: if (val.validateEFMorGFM and not c.modelDocument.uri.startswith(val.modelXbrl.uriDir)): val.modelXbrl.error(("EFM.6.07.16", "GFM.1.03.18"), _("Concept %(concept)s is also defined in standard taxonomy schema schema %(standardSchema)s"), modelObject=(modelConcept,c), concept=modelConcept.qname, standardSchema=os.path.basename(c.modelDocument.uri), standardConcept=c.qname) elif val.validateSBRNL: if not (genrlSpeclRelSet.isRelated(modelConcept, "child", c) or genrlSpeclRelSet.isRelated(c, "child", modelConcept)): val.modelXbrl.error("SBR.NL.2.2.2.02", _("Concept %(concept)s is also defined in standard taxonomy schema %(standardSchema)s without a general-special relationship"), modelObject=c, concept=modelConcept.qname, standardSchema=os.path.basename(c.modelDocument.uri)) ''' removed RH 2011-12-23 corresponding set up of table in ValidateFiling if val.validateSBRNL and name in val.nameWordsTable: if not any( any( genrlSpeclRelSet.isRelated(c, "child", modelConcept) for c in val.modelXbrl.nameConcepts.get(partialWordName, [])) for partialWordName in val.nameWordsTable[name]): val.modelXbrl.error("SBR.NL.2.3.2.01", _("Concept %(specialName)s is appears to be missing a general-special relationship to %(generalNames)s"), modelObject=c, specialName=modelConcept.qname, generalNames=', or to '.join(val.nameWordsTable[name])) ''' # 6.7.17 id properly formed id = modelConcept.id requiredId = (prefix if prefix is not None else "") + "_" + name if val.validateEFMorGFM and id != requiredId: val.modelXbrl.error(("EFM.6.07.17", "GFM.1.03.19"), _("Concept %(concept)s id %(id)s should be %(requiredId)s"), modelObject=modelConcept, concept=modelConcept.qname, id=id, requiredId=requiredId) # 6.7.18 nillable is true nillable = modelConcept.get("nillable") if nillable != "true" and modelConcept.isItem: val.modelXbrl.error(("EFM.6.07.18", "GFM.1.03.20"), _("Taxonomy schema %(schema)s element %(concept)s nillable %(nillable)s should be 'true'"), modelObject=modelConcept, schema=os.path.basename(modelDocument.uri), concept=name, nillable=nillable) # 6.7.19 not tuple if modelConcept.isTuple: if val.validateEFMorGFM: val.modelXbrl.error(("EFM.6.07.19", "GFM.1.03.21"), _("Concept %(concept)s is a tuple"), modelObject=modelConcept, concept=modelConcept.qname) # 6.7.20 no typed domain ref if modelConcept.isTypedDimension: val.modelXbrl.error(("EFM.6.07.20", "GFM.1.03.22"), _("Concept %(concept)s has typedDomainRef %(typedDomainRef)s"), modelObject=modelConcept, concept=modelConcept.qname, typedDomainRef=modelConcept.typedDomainElement.qname if modelConcept.typedDomainElement is not None else modelConcept.typedDomainRef) # 6.7.21 abstract must be duration isDuration = modelConcept.periodType == "duration" if modelConcept.isAbstract and not isDuration: val.modelXbrl.error(("EFM.6.07.21", "GFM.1.03.23"), _("Taxonomy schema %(schema)s element %(concept)s is abstract but period type is not duration"), modelObject=modelConcept, schema=os.path.basename(modelDocument.uri), concept=modelConcept.qname) # 6.7.22 abstract must be stringItemType ''' removed SEC EFM v.17, Edgar release 10.4, and GFM 2011-04-08 if modelConcept.abstract == "true" and modelConcept.typeQname != XbrlConst. qnXbrliStringItemType: val.modelXbrl.error(("EFM.6.07.22", "GFM.1.03.24"), _("Concept %(concept)s is abstract but type is not xbrli:stringItemType"), modelObject=modelConcept, concept=modelConcept.qname) ''' substititutionGroupQname = modelConcept.substitutionGroupQname # 6.7.23 Axis must be subs group dimension if name.endswith("Axis") ^ (substititutionGroupQname == XbrlConst.qnXbrldtDimensionItem): val.modelXbrl.error(("EFM.6.07.23", "GFM.1.03.25"), _("Concept %(concept)s must end in Axis to be in xbrldt:dimensionItem substitution group"), modelObject=modelConcept, concept=modelConcept.qname) # 6.7.24 Table must be subs group hypercube if name.endswith("Table") ^ (substititutionGroupQname == XbrlConst.qnXbrldtHypercubeItem): val.modelXbrl.error(("EFM.6.07.24", "GFM.1.03.26"), _("Concept %(concept)s must end in Table to be in xbrldt:hypercubeItem substitution group"), modelObject=modelConcept, schema=os.path.basename(modelDocument.uri), concept=modelConcept.qname) # 6.7.25 if neither hypercube or dimension, substitution group must be item if substititutionGroupQname not in (None, XbrlConst.qnXbrldtDimensionItem, XbrlConst.qnXbrldtHypercubeItem, XbrlConst.qnXbrliItem): val.modelXbrl.error(("EFM.6.07.25", "GFM.1.03.27"), _("Concept %(concept)s has disallowed substitution group %(substitutionGroup)s"), modelObject=modelConcept, concept=modelConcept.qname, substitutionGroup=modelConcept.substitutionGroupQname) # 6.7.26 Table must be subs group hypercube if name.endswith("LineItems") and modelConcept.abstract != "true": val.modelXbrl.error(("EFM.6.07.26", "GFM.1.03.28"), _("Concept %(concept)s is a LineItems but not abstract"), modelObject=modelConcept, concept=modelConcept.qname) # 6.7.27 type domainMember must end with Domain or Member conceptType = modelConcept.type isDomainItemType = conceptType is not None and conceptType.isDomainItemType endsWithDomainOrMember = name.endswith("Domain") or name.endswith("Member") if isDomainItemType != endsWithDomainOrMember: val.modelXbrl.error(("EFM.6.07.27", "GFM.1.03.29"), _("Concept %(concept)s must end with Domain or Member for type of domainItemType"), modelObject=modelConcept, concept=modelConcept.qname) # 6.7.28 domainItemType must be duration if isDomainItemType and not isDuration: val.modelXbrl.error(("EFM.6.07.28", "GFM.1.03.30"), _("Concept %(concept)s is a domainItemType and must be periodType duration"), modelObject=modelConcept, concept=modelConcept.qname) #6.7.31 (version 27) fractions if modelConcept.isFraction: val.modelXbrl.error("EFM.6.07.31", _("Concept %(concept)s is a fraction"), modelObject=modelConcept, concept=modelConcept.qname) #6.7.32 (version 27) instant non numeric if modelConcept.isItem and (not modelConcept.isNumeric and not isDuration and not modelConcept.isAbstract and not isDomainItemType): val.modelXbrl.error("EFM.6.07.32", _("Taxonomy schema %(schema)s element %(concept)s is non-numeric but period type is not duration"), modelObject=modelConcept, schema=os.path.basename(modelDocument.uri), concept=modelConcept.qname) # 6.8.5 semantic check, check LC3 name if name: if not name[0].isupper(): val.modelXbrl.log("ERROR-SEMANTIC", ("EFM.6.08.05.firstLetter", "GFM.2.03.05.firstLetter"), _("Concept %(concept)s name must start with a capital letter"), modelObject=modelConcept, concept=modelConcept.qname) if namePattern.search(name): val.modelXbrl.log("ERROR-SEMANTIC", ("EFM.6.08.05.disallowedCharacter", "GFM.2.03.05.disallowedCharacter"), _("Concept %(concept)s has disallowed name character"), modelObject=modelConcept, concept=modelConcept.qname) if len(name) > 200: val.modelXbrl.log("ERROR-SEMANTIC", "EFM.6.08.05.nameLength", _("Concept %(concept)s name length %(namelength)s exceeds 200 characters"), modelObject=modelConcept, concept=modelConcept.qname, namelength=len(name)) if val.validateEFM: label = modelConcept.label(lang="en-US", fallbackToQname=False) if label: # allow Joe's Bar, N.A. to be JoesBarNA -- remove ', allow A. as not article "a" lc3name = ''.join(re.sub(r"['.-]", "", (w[0] or w[2] or w[3] or w[4])).title() for w in re.findall(r"((\w+')+\w+)|(A[.-])|([.-]A(?=\W|$))|(\w+)", label) # EFM implies this should allow - and . re.findall(r"[\w\-\.]+", label) if w[4].lower() not in ("the", "a", "an")) if not(name == lc3name or (name and lc3name and lc3name[0].isdigit() and name[1:] == lc3name and (name[0].isalpha() or name[0] == '_'))): val.modelXbrl.log("WARNING-SEMANTIC", "EFM.6.08.05.LC3", _("Concept %(concept)s should match expected LC3 composition %(lc3name)s"), modelObject=modelConcept, concept=modelConcept.qname, lc3name=lc3name) if conceptType is not None: # 6.8.6 semantic check if not isDomainItemType and conceptType.qname != XbrlConst.qnXbrliDurationItemType: nameProblems = nonDomainItemNameProblemPattern.findall(name) if any(any(t) for t in nameProblems): # list of tuples with possibly nonempty strings val.modelXbrl.log("WARNING-SEMANTIC", ("EFM.6.08.06", "GFM.2.03.06"), _("Concept %(concept)s should not contain company or period information, found: %(matches)s"), modelObject=modelConcept, concept=modelConcept.qname, matches=", ".join(''.join(t) for t in nameProblems)) if conceptType.qname == XbrlConst.qnXbrliMonetaryItemType: if not modelConcept.balance: # 6.8.11 may not appear on a income or balance statement if any(linkroleDefinitionBalanceIncomeSheet.match(roleType.definition) for rel in val.modelXbrl.relationshipSet(XbrlConst.parentChild).toModelObject(modelConcept) for roleType in val.modelXbrl.roleTypes.get(rel.linkrole,())): val.modelXbrl.log("ERROR-SEMANTIC", ("EFM.6.08.11", "GFM.2.03.11"), _("Concept %(concept)s must have a balance because it appears in a statement of income or balance sheet"), modelObject=modelConcept, concept=modelConcept.qname) # 6.11.5 semantic check, must have a documentation label stdLabel = modelConcept.label(lang="en-US", fallbackToQname=False) defLabel = modelConcept.label(preferredLabel=XbrlConst.documentationLabel, lang="en-US", fallbackToQname=False) if not defLabel or ( # want different words than std label stdLabel and re.findall(r"\w+", stdLabel) == re.findall(r"\w+", defLabel)): val.modelXbrl.log("ERROR-SEMANTIC", ("EFM.6.11.05", "GFM.2.04.04"), _("Concept %(concept)s is monetary without a balance and must have a documentation label that disambiguates its sign"), modelObject=modelConcept, concept=modelConcept.qname) # 6.8.16 semantic check if conceptType.qname == XbrlConst.qnXbrliDateItemType and modelConcept.periodType != "duration": val.modelXbrl.log("ERROR-SEMANTIC", ("EFM.6.08.16", "GFM.2.03.16"), _("Concept %(concept)s of type xbrli:dateItemType must have periodType duration"), modelObject=modelConcept, concept=modelConcept.qname) # 6.8.17 semantic check if conceptType.qname == XbrlConst.qnXbrliStringItemType and modelConcept.periodType != "duration": val.modelXbrl.log("ERROR-SEMANTIC", ("EFM.6.08.17", "GFM.2.03.17"), _("Concept %(concept)s of type xbrli:stringItemType must have periodType duration"), modelObject=modelConcept, concept=modelConcept.qname) if val.validateSBRNL: if modelConcept.isTuple: if modelConcept.substitutionGroupQname.localName == "presentationTuple" and modelConcept.substitutionGroupQname.namespaceURI.endswith("/basis/sbr/xbrl/xbrl-syntax-extension"): # namespace may change each year definesPresentationTuples = True elif modelConcept.substitutionGroupQname.localName == "specificationTuple" and modelConcept.substitutionGroupQname.namespaceURI.endswith("/basis/sbr/xbrl/xbrl-syntax-extension"): # namespace may change each year definesSpecificationTuples = True else: definesTuples = True definesConcepts = True if modelConcept.isAbstract: val.modelXbrl.error("SBR.NL.2.2.2.03", _("Concept %(concept)s is an abstract tuple"), modelObject=modelConcept, concept=modelConcept.qname) if tupleCycle(val,modelConcept): val.modelXbrl.error("SBR.NL.2.2.2.07", _("Tuple %(concept)s has a tuple cycle"), modelObject=modelConcept, concept=modelConcept.qname) if modelConcept.get("nillable") != "false" and modelConcept.isRoot: val.modelXbrl.error("SBR.NL.2.2.2.17", #don't want default, just what was really there _("Tuple %(concept)s must have nillable='false'"), modelObject=modelConcept, concept=modelConcept.qname) elif modelConcept.isItem: definesConcepts = True if modelConcept.abstract == "true": if modelConcept.isRoot: if modelConcept.get("nillable") != "false": #don't want default, just what was really there val.modelXbrl.error("SBR.NL.2.2.2.16", _("Abstract root concept %(concept)s must have nillable='false'"), modelObject=modelConcept, concept=modelConcept.qname) if modelConcept.typeQname != XbrlConst.qnXbrliStringItemType: val.modelXbrl.error("SBR.NL.2.2.2.21", _("Abstract root concept %(concept)s must have type='xbrli:stringItemType'"), modelObject=modelConcept, concept=modelConcept.qname) if modelConcept.balance: val.modelXbrl.error("SBR.NL.2.2.2.22", _("Abstract concept %(concept)s must not have a balance attribute"), modelObject=modelConcept, concept=modelConcept.qname) if modelConcept.isHypercubeItem: definesHypercubes = True elif modelConcept.isDimensionItem: definesDimensions = True elif substititutionGroupQname and substititutionGroupQname.localName in ("domainItem","domainMemberItem"): definesDomains = True elif modelConcept.isItem: definesAbstractItems = True else: # not abstract if modelConcept.isItem: definesNonabstractItems = True if not (modelConcept.label(preferredLabel=XbrlConst.documentationLabel,fallbackToQname=False,lang="nl") or val.modelXbrl.relationshipSet(XbrlConst.conceptReference).fromModelObject(c) or modelConcept.genLabel(role=XbrlConst.genDocumentationLabel,lang="nl") or val.modelXbrl.relationshipSet(XbrlConst.elementReference).fromModelObject(c)): val.modelXbrl.error("SBR.NL.2.2.2.28", _("Concept %(concept)s must have a documentation label or reference"), modelObject=modelConcept, concept=modelConcept.qname) if modelConcept.balance and not modelConcept.instanceOfType(XbrlConst.qnXbrliMonetaryItemType): val.modelXbrl.error("SBR.NL.2.2.2.24", _("Non-monetary concept %(concept)s must not have a balance attribute"), modelObject=modelConcept, concept=modelConcept.qname) if modelConcept.isLinkPart: definesLinkParts = True val.modelXbrl.error("SBR.NL.2.2.5.01", _("Link:part concept %(concept)s is not allowed"), modelObject=modelConcept, concept=modelConcept.qname) if not modelConcept.genLabel(fallbackToQname=False,lang="nl"): val.modelXbrl.error("SBR.NL.2.2.5.02", _("Link part definition %(concept)s must have a generic label in language 'nl'"), modelObject=modelConcept, concept=modelConcept.qname) # 6.7.8 check for embedded linkbase for e in modelDocument.xmlRootElement.iterdescendants(tag="{http://www.xbrl.org/2003/linkbase}linkbase"): if isinstance(e,ModelObject): val.modelXbrl.error(("EFM.6.07.08", "GFM.1.03.08"), _("Taxonomy schema %(schema)s contains an embedded linkbase"), modelObject=e, schema=modelDocument.basename) break requiredUsedOns = {XbrlConst.qnLinkPresentationLink, XbrlConst.qnLinkCalculationLink, XbrlConst.qnLinkDefinitionLink} standardUsedOns = {XbrlConst.qnLinkLabel, XbrlConst.qnLinkReference, XbrlConst.qnLinkDefinitionArc, XbrlConst.qnLinkCalculationArc, XbrlConst.qnLinkPresentationArc, XbrlConst.qnLinkLabelArc, XbrlConst.qnLinkReferenceArc, # per WH, private footnote arc and footnore resource roles are not allowed XbrlConst.qnLinkFootnoteArc, XbrlConst.qnLinkFootnote, } # 6.7.9 role types authority for e in modelDocument.xmlRootElement.iterdescendants(tag="{http://www.xbrl.org/2003/linkbase}roleType"): if isinstance(e,ModelObject): roleURI = e.get("roleURI") if targetNamespaceAuthority != UrlUtil.authority(roleURI): val.modelXbrl.error(("EFM.6.07.09", "GFM.1.03.09"), _("RoleType %(roleType)s does not match authority %(targetNamespaceAuthority)s"), modelObject=e, roleType=roleURI, targetNamespaceAuthority=targetNamespaceAuthority, targetNamespace=modelDocument.targetNamespace) # 6.7.9 end with .../role/lc3 name if not roleTypePattern.match(roleURI): val.modelXbrl.warning(("EFM.6.07.09.roleEnding", "GFM.1.03.09"), "RoleType %(roleType)s should end with /role/{LC3name}", modelObject=e, roleType=roleURI) # 6.7.10 only one role type declaration in DTS modelRoleTypes = val.modelXbrl.roleTypes.get(roleURI) if modelRoleTypes is not None: modelRoleType = modelRoleTypes[0] definition = modelRoleType.definitionNotStripped usedOns = modelRoleType.usedOns if len(modelRoleTypes) == 1: # 6.7.11 used on's for pre, cal, def if any has a used on if not usedOns.isdisjoint(requiredUsedOns) and len(requiredUsedOns - usedOns) > 0: val.modelXbrl.error(("EFM.6.07.11", "GFM.1.03.11"), _("RoleType %(roleType)s missing used on %(usedOn)s"), modelObject=e, roleType=roleURI, usedOn=requiredUsedOns - usedOns) # 6.7.12 definition match pattern if (val.disclosureSystem.roleDefinitionPattern is not None and (definition is None or not val.disclosureSystem.roleDefinitionPattern.match(definition))): val.modelXbrl.error(("EFM.6.07.12", "GFM.1.03.12-14"), _("RoleType %(roleType)s definition \"%(definition)s\" must match {Sortcode} - {Type} - {Title}"), modelObject=e, roleType=roleURI, definition=(definition or "")) if usedOns & standardUsedOns: # semantics check val.modelXbrl.log("ERROR-SEMANTIC", ("EFM.6.08.03", "GFM.2.03.03"), _("RoleType %(roleuri)s is defined using role types already defined by standard roles for: %(qnames)s"), modelObject=e, roleuri=roleURI, qnames=', '.join(str(qn) for qn in usedOns & standardUsedOns)) if val.validateSBRNL: if usedOns & XbrlConst.standardExtLinkQnames or XbrlConst.qnGenLink in usedOns: definesLinkroles = True if not e.genLabel(): val.modelXbrl.error("SBR.NL.2.2.3.03", _("Link RoleType %(roleType)s missing a generic standard label"), modelObject=e, roleType=roleURI) nlLabel = e.genLabel(lang="nl") if definition != nlLabel: val.modelXbrl.error("SBR.NL.2.2.3.04", _("Link RoleType %(roleType)s definition does not match NL standard generic label, \ndefinition: %(definition)s \nNL label: %(label)s"), modelObject=e, roleType=roleURI, definition=definition, label=nlLabel) if definition and (definition[0].isspace() or definition[-1].isspace()): val.modelXbrl.error("SBR.NL.2.2.3.07", _('Link RoleType %(roleType)s definition has leading or trailing spaces: "%(definition)s"'), modelObject=e, roleType=roleURI, definition=definition) # 6.7.13 arcrole types authority for e in modelDocument.xmlRootElement.iterdescendants(tag="{http://www.xbrl.org/2003/linkbase}arcroleType"): if isinstance(e,ModelObject): arcroleURI = e.get("arcroleURI") if targetNamespaceAuthority != UrlUtil.authority(arcroleURI): val.modelXbrl.error(("EFM.6.07.13", "GFM.1.03.15"), _("ArcroleType %(arcroleType)s does not match authority %(targetNamespaceAuthority)s"), modelObject=e, arcroleType=arcroleURI, targetNamespaceAuthority=targetNamespaceAuthority, targetNamespace=modelDocument.targetNamespace) # 6.7.13 end with .../arcrole/lc3 name if not arcroleTypePattern.match(arcroleURI): val.modelXbrl.warning(("EFM.6.07.13.arcroleEnding", "GFM.1.03.15"), _("ArcroleType %(arcroleType)s should end with /arcrole/{LC3name}"), modelObject=e, arcroleType=arcroleURI) # 6.7.15 definition match pattern modelRoleTypes = val.modelXbrl.arcroleTypes[arcroleURI] definition = modelRoleTypes[0].definition if definition is None or not arcroleDefinitionPattern.match(definition): val.modelXbrl.error(("EFM.6.07.15", "GFM.1.03.17"), _("ArcroleType %(arcroleType)s definition must be non-empty"), modelObject=e, arcroleType=arcroleURI) # semantic checks usedOns = modelRoleTypes[0].usedOns if usedOns & standardUsedOns: # semantics check val.modelXbrl.log("ERROR-SEMANTIC", ("EFM.6.08.03", "GFM.2.03.03"), _("ArcroleType %(arcroleuri)s is defined using role types already defined by standard arcroles for: %(qnames)s"), modelObject=e, arcroleuri=arcroleURI, qnames=', '.join(str(qn) for qn in usedOns & standardUsedOns)) if val.validateSBRNL: definesArcroles = True val.modelXbrl.error("SBR.NL.2.2.4.01", _("Arcrole type definition is not allowed: %(arcroleURI)s"), modelObject=e, arcroleURI=arcroleURI) if val.validateSBRNL: for appinfoElt in modelDocument.xmlRootElement.iter(tag="{http://www.w3.org/2001/XMLSchema}appinfo"): for nonLinkElt in appinfoElt.iterdescendants(): if isinstance(nonLinkElt, ModelObject) and nonLinkElt.namespaceURI != XbrlConst.link: val.modelXbrl.error("SBR.NL.2.2.11.05", _("Appinfo contains disallowed non-link element %(element)s"), modelObject=nonLinkElt, element=nonLinkElt.qname) for cplxTypeElt in modelDocument.xmlRootElement.iter(tag="{http://www.w3.org/2001/XMLSchema}complexType"): choiceElt = cplxTypeElt.find("{http://www.w3.org/2001/XMLSchema}choice") if choiceElt is not None: val.modelXbrl.error("SBR.NL.2.2.11.09", _("ComplexType contains disallowed xs:choice element"), modelObject=choiceElt) for cplxContentElt in modelDocument.xmlRootElement.iter(tag="{http://www.w3.org/2001/XMLSchema}complexContent"): if XmlUtil.descendantAttr(cplxContentElt, "http://www.w3.org/2001/XMLSchema", ("extension","restriction"), "base") != "sbr:placeholder": val.modelXbrl.error("SBR.NL.2.2.11.10", _("ComplexContent is disallowed"), modelObject=cplxContentElt) definesTypes = (modelDocument.xmlRootElement.find("{http://www.w3.org/2001/XMLSchema}complexType") is not None or modelDocument.xmlRootElement.find("{http://www.w3.org/2001/XMLSchema}simpleType") is not None) for enumElt in modelDocument.xmlRootElement.iter(tag="{http://www.w3.org/2001/XMLSchema}enumeration"): definesEnumerations = True if any(not valueElt.genLabel(lang="nl") for valueElt in enumElt.iter(tag="{http://www.w3.org/2001/XMLSchema}value")): val.modelXbrl.error("SBR.NL.2.2.7.05", _("Enumeration element has value(s) without generic label."), modelObject=enumElt) if (definesLinkroles + definesArcroles + definesLinkParts + definesAbstractItems + definesNonabstractItems + definesTuples + definesPresentationTuples + definesSpecificationTuples + definesTypes + definesEnumerations + definesDimensions + definesDomains + definesHypercubes) != 1: schemaContents = [] if definesLinkroles: schemaContents.append(_("linkroles")) if definesArcroles: schemaContents.append(_("arcroles")) if definesLinkParts: schemaContents.append(_("link parts")) if definesAbstractItems: schemaContents.append(_("abstract items")) if definesNonabstractItems: schemaContents.append(_("nonabstract items")) if definesTuples: schemaContents.append(_("tuples")) if definesPresentationTuples: schemaContents.append(_("sbrPresentationTuples")) if definesSpecificationTuples: schemaContents.append(_("sbrSpecificationTuples")) if definesTypes: schemaContents.append(_("types")) if definesEnumerations: schemaContents.append(_("enumerations")) if definesDimensions: schemaContents.append(_("dimensions")) if definesDomains: schemaContents.append(_("domains")) if definesHypercubes: schemaContents.append(_("hypercubes")) if schemaContents: if not ((definesTuples or definesPresentationTuples or definesSpecificationTuples) and not (definesLinkroles or definesArcroles or definesLinkParts or definesAbstractItems or definesTypes or definesDimensions or definesDomains or definesHypercubes)): val.modelXbrl.error("SBR.NL.2.2.1.01", _("Taxonomy schema may only define one of these: %(contents)s"), modelObject=modelDocument, contents=', '.join(schemaContents)) elif not any(refDoc.inDTS and refDoc.targetNamespace not in val.disclosureSystem.baseTaxonomyNamespaces for refDoc in modelDocument.referencesDocument.keys()): # no linkbase ref or includes val.modelXbrl.error("SBR.NL.2.2.1.01", _("Taxonomy schema must be a DTS entrypoint OR define linkroles OR arcroles OR link:parts OR context fragments OR abstract items OR tuples OR non-abstract elements OR types OR enumerations OR dimensions OR domains OR hypercubes"), modelObject=modelDocument) if definesConcepts ^ any( # xor so either concepts and no label LB or no concepts and has label LB (refDoc.type == ModelDocument.Type.LINKBASE and XmlUtil.descendant(refDoc.xmlRootElement, XbrlConst.link, "labelLink") is not None) for refDoc in modelDocument.referencesDocument.keys()): # no label linkbase val.modelXbrl.error("SBR.NL.2.2.1.02", _("A schema that defines concepts MUST have a linked 2.1 label linkbase"), modelObject=modelDocument) if (definesNonabstractItems or definesTuples) and not any( # was xor but changed to and not per RH 1/11/12 (refDoc.type == ModelDocument.Type.LINKBASE and (XmlUtil.descendant(refDoc.xmlRootElement, XbrlConst.link, "referenceLink") is not None or XmlUtil.descendant(refDoc.xmlRootElement, XbrlConst.link, "label", "{http://www.w3.org/1999/xlink}role", "http://www.xbrl.org/2003/role/documentation" ) is not None)) for refDoc in modelDocument.referencesDocument.keys()): val.modelXbrl.error("SBR.NL.2.2.1.03", _("A schema that defines non-abstract items MUST have a linked (2.1) reference linkbase AND/OR a label linkbase with @xlink:role=documentation"), modelObject=modelDocument) #6.3.3 filename check m = re.match(r"^\w+-([12][0-9]{3}[01][0-9][0-3][0-9]).xsd$", modelDocument.basename) if m: try: # check date value datetime.datetime.strptime(m.group(1),"%Y%m%d").date() # date and format are ok, check "should" part of 6.3.3 if val.fileNameBasePart: expectedFilename = "{0}-{1}.xsd".format(val.fileNameBasePart, val.fileNameDatePart) if modelDocument.basename != expectedFilename: val.modelXbrl.log("WARNING-SEMANTIC", ("EFM.6.03.03.matchInstance", "GFM.1.01.01.matchInstance"), _('Schema file name warning: %(filename)s, should match %(expectedFilename)s'), modelObject=modelDocument, filename=modelDocument.basename, expectedFilename=expectedFilename) except ValueError: val.modelXbrl.error((val.EFM60303, "GFM.1.01.01"), _('Invalid schema file base name part (date) in "{base}-{yyyymmdd}.xsd": %(filename)s'), modelObject=modelDocument, filename=modelDocument.basename, messageCodes=("EFM.6.03.03", "EFM.6.23.01", "GFM.1.01.01")) else: val.modelXbrl.error((val.EFM60303, "GFM.1.01.01"), _('Invalid schema file name, must match "{base}-{yyyymmdd}.xsd": %(filename)s'), modelObject=modelDocument, filename=modelDocument.basename, messageCodes=("EFM.6.03.03", "EFM.6.23.01", "GFM.1.01.01")) elif modelDocument.type == ModelDocument.Type.LINKBASE: # if it is part of the submission (in same directory) check name labelRels = None if modelDocument.filepath.startswith(val.modelXbrl.modelDocument.filepathdir): #6.3.3 filename check extLinkElt = XmlUtil.descendant(modelDocument.xmlRootElement, XbrlConst.link, "*", "{http://www.w3.org/1999/xlink}type", "extended") if extLinkElt is None:# no ext link element val.modelXbrl.error((val.EFM60303 + ".noLinkElement", "GFM.1.01.01.noLinkElement"), _('Invalid linkbase file name: %(filename)s, has no extended link element, cannot determine link type.'), modelObject=modelDocument, filename=modelDocument.basename, messageCodes=("EFM.6.03.03.noLinkElement", "EFM.6.23.01.noLinkElement", "GFM.1.01.01.noLinkElement")) elif extLinkElt.localName not in extLinkEltFileNameEnding: val.modelXbrl.error("EFM.6.03.02", _('Invalid linkbase link element %(linkElement)s in %(filename)s'), modelObject=modelDocument, linkElement=extLinkElt.localName, filename=modelDocument.basename) else: m = re.match(r"^\w+-([12][0-9]{3}[01][0-9][0-3][0-9])(_[a-z]{3}).xml$", modelDocument.basename) expectedSuffix = extLinkEltFileNameEnding[extLinkElt.localName] if m and m.group(2) == expectedSuffix: try: # check date value datetime.datetime.strptime(m.group(1),"%Y%m%d").date() # date and format are ok, check "should" part of 6.3.3 if val.fileNameBasePart: expectedFilename = "{0}-{1}{2}.xml".format(val.fileNameBasePart, val.fileNameDatePart, expectedSuffix) if modelDocument.basename != expectedFilename: val.modelXbrl.log("WARNING-SEMANTIC", ("EFM.6.03.03.matchInstance", "GFM.1.01.01.matchInstance"), _('Linkbase name warning: %(filename)s should match %(expectedFilename)s'), modelObject=modelDocument, filename=modelDocument.basename, expectedFilename=expectedFilename) except ValueError: val.modelXbrl.error((val.EFM60303, "GFM.1.01.01"), _('Invalid linkbase base file name part (date) in "{base}-{yyyymmdd}_{suffix}.xml": %(filename)s'), modelObject=modelDocument, filename=modelDocument.basename, messageCodes=("EFM.6.03.03", "EFM.6.23.01", "GFM.1.01.01")) else: val.modelXbrl.error((val.EFM60303, "GFM.1.01.01"), _('Invalid linkbase name, must match "{base}-{yyyymmdd}%(expectedSuffix)s.xml": %(filename)s'), modelObject=modelDocument, filename=modelDocument.basename, expectedSuffix=expectedSuffix, messageCodes=("EFM.6.03.03", "EFM.6.23.01", "GFM.1.01.01")) if extLinkElt.localName == "labelLink": if labelRels is None: labelRels = val.modelXbrl.relationshipSet(XbrlConst.conceptLabel) for labelElt in XmlUtil.children(extLinkElt, XbrlConst.link, "label"): # 6.10.9 if XbrlConst.isNumericRole(labelElt.role): for rel in labelRels.toModelObject(labelElt): if rel.fromModelObject is not None and not rel.fromModelObject.isNumeric: val.modelXbrl.error("EFM.6.10.09", _("Label of non-numeric concept %(concept)s has a numeric role: %(role)s"), modelObject=(labelElt, rel.fromModelObject), concept=rel.fromModelObject.qname, role=labelElt.role) visited.remove(modelDocument)
def checkFilingDTS(val, modelDocument, isEFM, isGFM, visited): global targetNamespaceDatePattern, efmFilenamePattern, htmlFileNamePattern, roleTypePattern, arcroleTypePattern, \ arcroleDefinitionPattern, namePattern, linkroleDefinitionBalanceIncomeSheet if targetNamespaceDatePattern is None: targetNamespaceDatePattern = re.compile(r"/([12][0-9]{3})-([01][0-9])-([0-3][0-9])|" r"/([12][0-9]{3})([01][0-9])([0-3][0-9])|") efmFilenamePattern = re.compile(r"^[a-z0-9][a-zA-Z0-9_\.\-]*(\.xsd|\.xml|\.htm)$") htmlFileNamePattern = re.compile(r"^[a-zA-Z0-9][._a-zA-Z0-9-]*(\.htm)$") roleTypePattern = re.compile(r"^.*/role/[^/\s]+$") arcroleTypePattern = re.compile(r"^.*/arcrole/[^/\s]+$") arcroleDefinitionPattern = re.compile(r"^.*[^\\s]+.*$") # at least one non-whitespace character namePattern = re.compile("[][()*+?\\\\/^{}|@#%^=~`\"';:,<>&$\u00a3\u20ac]") # u20ac=Euro, u00a3=pound sterling linkroleDefinitionBalanceIncomeSheet = re.compile(r"[^-]+-\s+Statement\s+-\s+.*(income|balance|financial\W+position)", re.IGNORECASE) nonDomainItemNameProblemPattern = re.compile( r"({0})|(FirstQuarter|SecondQuarter|ThirdQuarter|FourthQuarter|[1-4]Qtr|Qtr[1-4]|ytd|YTD|HalfYear)(?:$|[A-Z\W])" .format(re.sub(r"\W", "", (val.entityRegistrantName or "").title()))) visited.append(modelDocument) for referencedDocument, modelDocumentReference in modelDocument.referencesDocument.items(): #6.07.01 no includes if modelDocumentReference.referenceType == "include": val.modelXbrl.error(("EFM.6.07.01", "GFM.1.03.01"), _("Taxonomy schema %(schema)s includes %(include)s, only import is allowed"), modelObject=modelDocumentReference.referringModelObject, schema=modelDocument.basename, include=referencedDocument.basename) if referencedDocument not in visited and (referencedDocument.inDTS or referencedDocument.type == ModelDocument.Type.INLINEXBRLDOCUMENTSET): # ignore EdgarRenderer added non-DTS documents checkFilingDTS(val, referencedDocument, isEFM, isGFM, visited) if modelDocument.type == ModelDocument.Type.INLINEXBRLDOCUMENTSET: return # nothing to check in inline document set surrogate parent if val.disclosureSystem.standardTaxonomiesDict is None: pass if isEFM: if modelDocument.uri in val.disclosureSystem.standardTaxonomiesDict: if modelDocument.targetNamespace: # check for duplicates of us-types, dei, and rr taxonomies match = standardNamespacesPattern.match(modelDocument.targetNamespace) if match is not None: conflictClass = match.group(2) or match.group(5) if (conflictClass == 'us-gaap' and match.group(3) < '2018') or conflictClass == 'srt': val.standardNamespaceConflicts['srt+us-gaap'].add(modelDocument) # ignore non-srt multi-usgaap in Filing.py if conflictClass == 'us-gaap' or conflictClass == 'ifrs-full': val.standardNamespaceConflicts['ifrs+us-gaap'].add(modelDocument) if conflictClass not in ('us-gaap', 'srt'): val.standardNamespaceConflicts[conflictClass].add(modelDocument) else: if len(modelDocument.basename) > 32: val.modelXbrl.error("EFM.5.01.01.tooManyCharacters", _("Document file name %(filename)s must not exceed 32 characters."), edgarCode="cp-0501-File-Name-Length", modelObject=modelDocument, filename=modelDocument.basename) if modelDocument.type != ModelDocument.Type.INLINEXBRLDOCUMENTSET: if modelDocument.type == ModelDocument.Type.INLINEXBRL: _pattern = htmlFileNamePattern _suffix = ".htm" else: _pattern = efmFilenamePattern _suffix = ".xsd or .xml" if not _pattern.match(modelDocument.basename): val.modelXbrl.error("EFM.5.01.01", _("Document file name %(filename)s must start with a-z or 0-9, contain upper or lower case letters, ., -, _, and end with %(suffix)s."), edgarCode="cp-0501-File-Name", modelObject=modelDocument, filename=modelDocument.basename, suffix=_suffix) if (modelDocument.type == ModelDocument.Type.SCHEMA and modelDocument.targetNamespace not in val.disclosureSystem.baseTaxonomyNamespaces and modelDocument.uri.startswith(val.modelXbrl.uriDir)): val.hasExtensionSchema = True # check schema contents types # 6.7.3 check namespace for standard authority targetNamespaceAuthority = UrlUtil.authority(modelDocument.targetNamespace) if targetNamespaceAuthority in val.disclosureSystem.standardAuthorities: val.modelXbrl.error(("EFM.6.07.03", "GFM.1.03.03"), _("The target namespace, %(targetNamespace)s cannot have the same authority (%(targetNamespaceAuthority)s) as a standard " "taxonomy, in %(schema)s. Please change your target namespace."), edgarCode="fs-0703-Extension-Has-Standard-Namespace-Authority", modelObject=modelDocument, schema=modelDocument.basename, targetNamespace=modelDocument.targetNamespace, targetNamespaceAuthority=UrlUtil.authority(modelDocument.targetNamespace, includeScheme=False)) # 6.7.4 check namespace format if modelDocument.targetNamespace is None or not modelDocument.targetNamespace.startswith("http://"): match = None else: targetNamespaceDate = modelDocument.targetNamespace[len(targetNamespaceAuthority):] match = targetNamespaceDatePattern.match(targetNamespaceDate) if match is not None: try: if match.lastindex == 3: date = datetime.date(int(match.group(1)),int(match.group(2)),int(match.group(3))) elif match.lastindex == 6: date = datetime.date(int(match.group(4)),int(match.group(5)),int(match.group(6))) else: match = None except ValueError: match = None if match is None: val.modelXbrl.error(("EFM.6.07.04", "GFM.1.03.04"), _("You did not adhere to the requirements for target namespace, for %(targetNamespace)s in %(schema)s. " "Please recheck your submission and adhere to the target namespace requirements."), edgarCode="cp-0704-Taxonomy-Valid-Target-Namespace", modelObject=modelDocument, schema=modelDocument.basename, targetNamespace=modelDocument.targetNamespace) elif val.fileNameDate and date > val.fileNameDate: val.modelXbrl.info(("EFM.6.07.06", "GFM.1.03.06"), _("Warning: Taxonomy schema %(schema)s namespace %(targetNamespace)s has date later than document name date %(docNameDate)s"), modelObject=modelDocument, schema=modelDocument.basename, targetNamespace=modelDocument.targetNamespace, docNameDate=val.fileNameDate) if modelDocument.targetNamespace is not None: # 6.7.5 check prefix for _ authority = UrlUtil.authority(modelDocument.targetNamespace) if not re.match(r"(http://|https://|ftp://|urn:)\w+",authority): val.modelXbrl.error(("EFM.6.07.05", "GFM.1.03.05"), _("Taxonomy schema %(schema)s namespace %(targetNamespace)s must be a valid URI with a valid authority for the namespace."), edgarCode="du-0705-Namespace-Authority", modelObject=modelDocument, schema=modelDocument.basename, targetNamespace=modelDocument.targetNamespace) # may be multiple prefixes for namespace prefixes = [(prefix if prefix is not None else "") for prefix, NS in modelDocument.xmlRootElement.nsmap.items() if NS == modelDocument.targetNamespace] if not prefixes: prefix = None val.modelXbrl.error(("EFM.6.07.07", "GFM.1.03.07"), _("The schema does not supply a prefix for %(targetNamespace)s without an underscore character, in file %(schema)s. " "Please provide or change a prefix"), edgarCode="du-0707-Recommended-Prefix-Disallowed", modelObject=modelDocument, schema=modelDocument.basename, targetNamespace=modelDocument.targetNamespace) else: prefix = prefixes[0] if len(prefixes) > 1: val.modelXbrl.error(("EFM.6.07.07", "GFM.1.03.07"), _("The schema does not supply a prefix for %(targetNamespace)s without an underscore character, in file %(schema)s. " "Please provide or change a prefix"), edgarCode="du-0707-Recommended-Prefix-Disallowed", modelObject=modelDocument, schema=modelDocument.basename, targetNamespace=modelDocument.targetNamespace, prefix=", ".join(prefixes)) elif "_" in prefix: val.modelXbrl.error(("EFM.6.07.07", "GFM.1.03.07"), _("The schema does not supply a prefix for %(targetNamespace)s without an underscore character, in file %(schema)s. " "Please provide or change a prefix"), edgarCode="du-0707-Recommended-Prefix-Disallowed", modelObject=modelDocument, schema=modelDocument.basename, targetNamespace=modelDocument.targetNamespace, prefix=prefix) for modelConcept in modelDocument.xmlRootElement.iterdescendants(tag="{http://www.w3.org/2001/XMLSchema}element"): if isinstance(modelConcept,ModelConcept): # 6.7.16 name not duplicated in standard taxonomies name = modelConcept.get("name") if name is None: name = "" if modelConcept.get("ref") is not None: continue # don't validate ref's here for c in val.modelXbrl.nameConcepts.get(name, []): if c.modelDocument != modelDocument: if not c.modelDocument.uri.startswith(val.modelXbrl.uriDir): val.modelXbrl.error(("EFM.6.07.16", "GFM.1.03.18"), _("Your extension taxonomy contains an element, %(concept)s, which has the same name as an element in the base taxonomy, " "%(standardConcept)s. Please ensure that this extension is appropriate and if so, please change the extension concept name."), edgarCode="cp-0716-Element-Name-Same-As-Base", modelObject=(modelConcept,c), concept=modelConcept.qname, standardSchema=c.modelDocument.basename, standardConcept=c.qname) # 6.7.17 id properly formed _id = modelConcept.id requiredId = (prefix if prefix is not None else "") + "_" + name if _id != requiredId: val.modelXbrl.error(("EFM.6.07.17", "GFM.1.03.19"), _("You did not adhere to the declarations for concepts by containing an 'id' attribute whose value begins with the recommended " "namespace prefix of the taxonomy, followed by an underscore, followed by an element name, for the concept %(concept)s. " "Please recheck your submission."), edgarCode="cp-0717-Element-Id", modelObject=modelConcept, concept=modelConcept.qname, id=_id, requiredId=requiredId) # 6.7.18 nillable is true nillable = modelConcept.get("nillable") if nillable != "true" and modelConcept.isItem: val.modelXbrl.error(("EFM.6.07.18", "GFM.1.03.20"), _("Element %(concept)s is declared without a 'true' value for the nillable attribute. Please set the value to 'true'."), edgarCode="du-0718-Nillable-Not-True", modelObject=modelConcept, schema=modelDocument.basename, concept=name, nillable=nillable) # 6.7.19 not tuple if modelConcept.isTuple: val.modelXbrl.error(("EFM.6.07.19", "GFM.1.03.21"), _("You provided an extension concept which is a tuple, %(concept)s. Please remove tuples and check your submission."), edgarCode="cp-0719-No-Tuple-Element", modelObject=modelConcept, concept=modelConcept.qname) # 6.7.20 no typed domain ref if modelConcept.isTypedDimension: val.modelXbrl.error(("EFM.6.07.20", "GFM.1.03.22"), _("There is an xbrldt:typedDomainRef attribute on %(concept)s (%(typedDomainRef)s). Please remove it."), edgarCode="du-0720-Typed-Domain-Ref-Disallowed", modelObject=modelConcept, concept=modelConcept.qname, typedDomainRef=modelConcept.typedDomainElement.qname if modelConcept.typedDomainElement is not None else modelConcept.typedDomainRef) # 6.7.21 abstract must be duration isDuration = modelConcept.periodType == "duration" if modelConcept.isAbstract and not isDuration: val.modelXbrl.error(("EFM.6.07.21", "GFM.1.03.23"), _("Element %(concept)s is declared as an abstract item with period type 'instant'. Please change it to 'duration' or " "make the element not abstract."), edgarCode="du-0721-Abstract-Is-Instant", modelObject=modelConcept, schema=modelDocument.basename, concept=modelConcept.qname) # 6.7.22 abstract must be stringItemType ''' removed SEC EFM v.17, Edgar release 10.4, and GFM 2011-04-08 if modelConcept.abstract == "true" and modelConcept.typeQname != XbrlConst. qnXbrliStringItemType: val.modelXbrl.error(("EFM.6.07.22", "GFM.1.03.24"), _("Concept %(concept)s is abstract but type is not xbrli:stringItemType"), modelObject=modelConcept, concept=modelConcept.qname) ''' substitutionGroupQname = modelConcept.substitutionGroupQname # 6.7.23 Axis must be subs group dimension if name.endswith("Axis") ^ (substitutionGroupQname == XbrlConst.qnXbrldtDimensionItem): val.modelXbrl.error(("EFM.6.07.23", "GFM.1.03.25"), _("The substitution group 'xbrldt:dimensionItem' is only consistent with an element name that ends with 'Axis'. " "Please change %(conceptLocalName)s or change the substitutionGroup."), edgarCode="du-0723-Axis-Dimension-Name-Mismatch", modelObject=modelConcept, concept=modelConcept.qname, conceptLocalName=modelConcept.qname.localName) # 6.7.24 Table must be subs group hypercube if name.endswith("Table") ^ (substitutionGroupQname == XbrlConst.qnXbrldtHypercubeItem): val.modelXbrl.error(("EFM.6.07.24", "GFM.1.03.26"), _("The substitution group 'xbrldt:hypercubeItem' is only allowed with an element name that ends with 'Table'. " "Please change %(conceptLocalName)s or change the substitutionGroup."), edgarCode="du-0724-Table-Hypercube-Name-Mismatch", modelObject=modelConcept, schema=modelDocument.basename, concept=modelConcept.qname, conceptLocalName=modelConcept.qname.localName) # 6.7.25 if neither hypercube or dimension, substitution group must be item if substitutionGroupQname not in (None, XbrlConst.qnXbrldtDimensionItem, XbrlConst.qnXbrldtHypercubeItem, XbrlConst.qnXbrliItem): val.modelXbrl.error(("EFM.6.07.25", "GFM.1.03.27"), _("The substitution group attribute value %(substitutionGroup)s of element %(conceptLocalName)s is not allowed. " "Please change it to one of 'xbrli:item', 'xbrldt:dimensionItem' or 'xbrldt:hypercubeItem'."), edgarCode="du-0725-Substitution-Group-Custom", modelObject=modelConcept, concept=modelConcept.qname, conceptLocalName=modelConcept.qname.localName, substitutionGroup=modelConcept.substitutionGroupQname) # 6.7.26 Table must be subs group hypercube if name.endswith("LineItems") and modelConcept.abstract != "true": val.modelXbrl.error(("EFM.6.07.26", "GFM.1.03.28"), _("The element %(conceptLocalName)s ends with 'LineItems' but is not abstract. Please change %(conceptLocalName)s or " "the value of the 'abstract' attribute."), edgarCode="du-0726-LineItems-Abstract-Name-Mismatch", modelObject=modelConcept, concept=modelConcept.qname, conceptLocalName=modelConcept.qname.localName) # 6.7.27 type domainMember must end with Domain or Member conceptType = modelConcept.type isDomainItemType = conceptType is not None and conceptType.isDomainItemType endsWithDomainOrMember = name.endswith("Domain") or name.endswith("Member") if isDomainItemType != endsWithDomainOrMember: val.modelXbrl.error(("EFM.6.07.27", "GFM.1.03.29"), _("The type 'us-types:domainItemType' is only allowed with an element name that ends with 'Domain' or 'Member'. " "Please change %(conceptLocalName)s or change the type."), edgarCode="du-0727-Domain-Type-Name-Mismatch", modelObject=modelConcept, concept=modelConcept.qname, conceptLocalName=modelConcept.qname.localName) # 6.7.28 domainItemType must be duration if isDomainItemType and not isDuration: val.modelXbrl.error(("EFM.6.07.28", "GFM.1.03.30"), _("Element %(conceptLocalName)s is declared as a us-types:domainItemType with period type 'instant'. " "Please change it to 'duration' or change the item type."), edgarCode="du-0728-Domain-Member-Is-Instant", modelObject=modelConcept, concept=modelConcept.qname, conceptLocalName=modelConcept.qname.localName) #6.7.31 (version 27) fractions if modelConcept.isFraction: val.modelXbrl.error("EFM.6.07.31", _("Element %(concept)s is declared as a fraction item type. Change or remove the declaration."), edgarCode="du-0731-Fraction-Item-Type", modelObject=modelConcept, concept=modelConcept.qname) #6.7.32 (version 27) instant non numeric if modelConcept.isItem and (not modelConcept.isNumeric and not isDuration and not modelConcept.isAbstract and not isDomainItemType): val.modelXbrl.error("EFM.6.07.32", _("Declaration of element %(concept)s in %(schema)s must have xbrli:periodType of 'duration' because its base type is not numeric."), edgarCode="rq-0732-Nonnnumeric-Must-Be-Duration", modelObject=modelConcept, schema=modelDocument.basename, concept=modelConcept.qname) # 6.8.5 semantic check, check LC3 name if name: if not name[0].isupper(): val.modelXbrl.log("ERROR-SEMANTIC", ("EFM.6.08.05.firstLetter", "GFM.2.03.05.firstLetter"), _("Concept %(concept)s name must start with a capital letter"), modelObject=modelConcept, concept=modelConcept.qname) if namePattern.search(name): val.modelXbrl.log("ERROR-SEMANTIC", ("EFM.6.08.05.disallowedCharacter", "GFM.2.03.05.disallowedCharacter"), _("Concept %(concept)s has disallowed name character"), modelObject=modelConcept, concept=modelConcept.qname) if len(name) > 200: val.modelXbrl.log("ERROR-SEMANTIC", "EFM.6.08.05.nameLength", _("Concept %(concept)s name length %(namelength)s exceeds 200 characters"), modelObject=modelConcept, concept=modelConcept.qname, namelength=len(name)) if isEFM: label = modelConcept.label(lang="en-US", fallbackToQname=False) if label: # allow Joe's Bar, N.A. to be JoesBarNA -- remove ', allow A. as not article "a" lc3name = ''.join(re.sub(r"['.-]", "", (w[0] or w[2] or w[3] or w[4])).title() for w in re.findall(r"((\w+')+\w+)|(A[.-])|([.-]A(?=\W|$))|(\w+)", label) # EFM implies this should allow - and . re.findall(r"[\w\-\.]+", label) if w[4].lower() not in ("the", "a", "an")) if not(name == lc3name or (name and lc3name and lc3name[0].isdigit() and name[1:] == lc3name and (name[0].isalpha() or name[0] == '_'))): val.modelXbrl.log("WARNING-SEMANTIC", "EFM.6.08.05.LC3", _("Concept %(concept)s should match expected LC3 composition %(lc3name)s"), modelObject=modelConcept, concept=modelConcept.qname, lc3name=lc3name) if conceptType is not None: # 6.8.6 semantic check if not isDomainItemType and conceptType.qname != XbrlConst.qnXbrliDurationItemType: nameProblems = nonDomainItemNameProblemPattern.findall(name) if any(any(t) for t in nameProblems): # list of tuples with possibly nonempty strings val.modelXbrl.log("WARNING-SEMANTIC", ("EFM.6.08.06", "GFM.2.03.06"), _("Concept %(concept)s should not contain company or period information, found: %(matches)s"), modelObject=modelConcept, concept=modelConcept.qname, matches=", ".join(''.join(t) for t in nameProblems)) if conceptType.qname == XbrlConst.qnXbrliMonetaryItemType: if not modelConcept.balance: # 6.8.11 may not appear on a income or balance statement if any(linkroleDefinitionBalanceIncomeSheet.match(roleType.definition) for rel in val.modelXbrl.relationshipSet(XbrlConst.parentChild).toModelObject(modelConcept) for roleType in val.modelXbrl.roleTypes.get(rel.linkrole,())): val.modelXbrl.log("ERROR-SEMANTIC", ("EFM.6.08.11", "GFM.2.03.11"), _("Concept %(concept)s must have a balance because it appears in a statement of income or balance sheet"), modelObject=modelConcept, concept=modelConcept.qname) # 6.11.5 semantic check, must have a documentation label stdLabel = modelConcept.label(lang="en-US", fallbackToQname=False) defLabel = modelConcept.label(preferredLabel=XbrlConst.documentationLabel, lang="en-US", fallbackToQname=False) if not defLabel or ( # want different words than std label stdLabel and re.findall(r"\w+", stdLabel) == re.findall(r"\w+", defLabel)): val.modelXbrl.log("ERROR-SEMANTIC", ("EFM.6.11.05", "GFM.2.04.04"), _("Concept %(concept)s is monetary without a balance and must have a documentation label that disambiguates its sign"), modelObject=modelConcept, concept=modelConcept.qname) # 6.8.16 semantic check if conceptType.qname == XbrlConst.qnXbrliDateItemType and modelConcept.periodType != "duration": val.modelXbrl.log("ERROR-SEMANTIC", ("EFM.6.08.16", "GFM.2.03.16"), _("Concept %(concept)s of type xbrli:dateItemType must have periodType duration"), modelObject=modelConcept, concept=modelConcept.qname) # 6.8.17 semantic check if conceptType.qname == XbrlConst.qnXbrliStringItemType and modelConcept.periodType != "duration": val.modelXbrl.log("ERROR-SEMANTIC", ("EFM.6.08.17", "GFM.2.03.17"), _("Concept %(concept)s of type xbrli:stringItemType must have periodType duration"), modelObject=modelConcept, concept=modelConcept.qname) # 6.7.8 check for embedded linkbase for e in modelDocument.xmlRootElement.iterdescendants(tag="{http://www.xbrl.org/2003/linkbase}linkbase"): if isinstance(e,ModelObject): val.modelXbrl.error(("EFM.6.07.08", "GFM.1.03.08"), _("Your filing contained embedded linkbases in %(schema)s. Please recheck your submission and remove all embedded linkbases."), edgarCode="cp-0708-No-Embedded-Linkbases", modelObject=e, schema=modelDocument.basename) break requiredUsedOns = {XbrlConst.qnLinkPresentationLink, XbrlConst.qnLinkCalculationLink, XbrlConst.qnLinkDefinitionLink} standardUsedOns = {XbrlConst.qnLinkLabel, XbrlConst.qnLinkReference, XbrlConst.qnLinkDefinitionArc, XbrlConst.qnLinkCalculationArc, XbrlConst.qnLinkPresentationArc, XbrlConst.qnLinkLabelArc, XbrlConst.qnLinkReferenceArc, # per WH, private footnote arc and footnore resource roles are not allowed XbrlConst.qnLinkFootnoteArc, XbrlConst.qnLinkFootnote, } # 6.7.9 role types authority for e in modelDocument.xmlRootElement.iterdescendants(tag="{http://www.xbrl.org/2003/linkbase}roleType"): if isinstance(e,ModelObject): roleURI = e.get("roleURI") if targetNamespaceAuthority != UrlUtil.authority(roleURI): val.modelXbrl.error(("EFM.6.07.09", "GFM.1.03.09"), _("Role %(roleType)s does not begin with %(targetNamespace)s's scheme and authority. " "Please change the role URI or target namespace URI."), edgarCode="du-0709-Role-Namespace-Mismatch", modelObject=e, roleType=roleURI, targetNamespaceAuthority=targetNamespaceAuthority, targetNamespace=modelDocument.targetNamespace) # 6.7.9 end with .../role/lc3 name if not roleTypePattern.match(roleURI): val.modelXbrl.warning(("EFM.6.07.09.roleEnding", "GFM.1.03.09"), "RoleType %(roleType)s should end with /role/{LC3name}", modelObject=e, roleType=roleURI) # 6.7.10 only one role type declaration in DTS modelRoleTypes = val.modelXbrl.roleTypes.get(roleURI) if modelRoleTypes is not None: modelRoleType = modelRoleTypes[0] definition = modelRoleType.definitionNotStripped usedOns = modelRoleType.usedOns if len(modelRoleTypes) == 1: # 6.7.11 used on's for pre, cal, def if any has a used on if not usedOns.isdisjoint(requiredUsedOns) and len(requiredUsedOns - usedOns) > 0: val.modelXbrl.error(("EFM.6.07.11", "GFM.1.03.11"), _("The role %(roleType)s did not provide a usedOn element for all three link types (presentation, " "calculation and definition), missing %(usedOn)s. Change the declaration to be for all three types of link, and resubmit."), edgarCode="du-0711-Role-Type-Declaration-Incomplete", modelObject=e, roleType=roleURI, usedOn=requiredUsedOns - usedOns) # 6.7.12 definition match pattern if (val.disclosureSystem.roleDefinitionPattern is not None and (definition is None or not val.disclosureSystem.roleDefinitionPattern.match(definition))): val.modelXbrl.error(("EFM.6.07.12", "GFM.1.03.12-14"), _("The definition '%(definition)s' of role %(roleType)s does not match the expected format. " "Please check that the definition matches {number} - {type} - {text}."), edgarCode="rq-0712-Role-Definition-Mismatch", modelObject=e, roleType=roleURI, definition=(definition or "")) if usedOns & standardUsedOns: # semantics check val.modelXbrl.log("ERROR-SEMANTIC", ("EFM.6.08.03", "GFM.2.03.03"), _("RoleType %(roleuri)s is defined using role types already defined by standard roles for: %(qnames)s"), modelObject=e, roleuri=roleURI, qnames=', '.join(str(qn) for qn in usedOns & standardUsedOns)) # 6.7.13 arcrole types authority for e in modelDocument.xmlRootElement.iterdescendants(tag="{http://www.xbrl.org/2003/linkbase}arcroleType"): if isinstance(e,ModelObject): arcroleURI = e.get("arcroleURI") if targetNamespaceAuthority != UrlUtil.authority(arcroleURI): val.modelXbrl.error(("EFM.6.07.13", "GFM.1.03.15"), _("Relationship role %(arcroleType)s does not begin with %(targetNamespace)s's scheme and authority. " "Please change the relationship role URI or target namespace URI."), edgarCode="du-0713-Arcrole-Namespace-Mismatch", modelObject=e, arcroleType=arcroleURI, targetNamespaceAuthority=targetNamespaceAuthority, targetNamespace=modelDocument.targetNamespace) # 6.7.13 end with .../arcrole/lc3 name if not arcroleTypePattern.match(arcroleURI): val.modelXbrl.warning(("EFM.6.07.13.arcroleEnding", "GFM.1.03.15"), _("ArcroleType %(arcroleType)s should end with /arcrole/{LC3name}"), modelObject=e, arcroleType=arcroleURI) # 6.7.15 definition match pattern modelRoleTypes = val.modelXbrl.arcroleTypes[arcroleURI] definition = modelRoleTypes[0].definition if definition is None or not arcroleDefinitionPattern.match(definition): val.modelXbrl.error(("EFM.6.07.15", "GFM.1.03.17"), _("Relationship role declaration %(arcroleType)s is missing a definition. Please provide a definition."), edgarCode="du-0715-Arcrole-Definition-Missing", modelObject=e, arcroleType=arcroleURI) # semantic checks usedOns = modelRoleTypes[0].usedOns if usedOns & standardUsedOns: # semantics check val.modelXbrl.log("ERROR-SEMANTIC", ("EFM.6.08.03", "GFM.2.03.03"), _("ArcroleType %(arcroleuri)s is defined using role types already defined by standard arcroles for: %(qnames)s"), modelObject=e, arcroleuri=arcroleURI, qnames=', '.join(str(qn) for qn in usedOns & standardUsedOns)) #6.3.3 filename check m = re.match(r"^\w+-([12][0-9]{3}[01][0-9][0-3][0-9]).xsd$", modelDocument.basename) if m: try: # check date value datetime.datetime.strptime(m.group(1),"%Y%m%d").date() # date and format are ok, check "should" part of 6.3.3 if val.fileNameBasePart: expectedFilename = "{0}-{1}.xsd".format(val.fileNameBasePart, val.fileNameDatePart) if modelDocument.basename != expectedFilename: val.modelXbrl.log("WARNING-SEMANTIC", ("EFM.6.03.03.matchInstance", "GFM.1.01.01.matchInstance"), _('Schema file name warning: %(filename)s, should match %(expectedFilename)s'), modelObject=modelDocument, filename=modelDocument.basename, expectedFilename=expectedFilename) except ValueError: val.modelXbrl.error((val.EFM60303, "GFM.1.01.01"), _('Invalid schema file base name part (date) in "{base}-{yyyymmdd}.xsd": %(filename)s'), modelObject=modelDocument, filename=modelDocument.basename, messageCodes=("EFM.6.03.03", "EFM.6.23.01", "GFM.1.01.01")) else: val.modelXbrl.error((val.EFM60303, "GFM.1.01.01"), _('Invalid schema file name, must match "{base}-{yyyymmdd}.xsd": %(filename)s'), modelObject=modelDocument, filename=modelDocument.basename, messageCodes=("EFM.6.03.03", "EFM.6.23.01", "GFM.1.01.01")) elif modelDocument.type == ModelDocument.Type.LINKBASE: # if it is part of the submission (in same directory) check name labelRels = None if modelDocument.filepath.startswith(val.modelXbrl.modelDocument.filepathdir): #6.3.3 filename check extLinkElt = XmlUtil.descendant(modelDocument.xmlRootElement, XbrlConst.link, "*", "{http://www.w3.org/1999/xlink}type", "extended") if extLinkElt is None:# no ext link element val.modelXbrl.error((val.EFM60303 + ".noLinkElement", "GFM.1.01.01.noLinkElement"), _('Invalid linkbase file name: %(filename)s, has no extended link element, cannot determine link type.'), modelObject=modelDocument, filename=modelDocument.basename, messageCodes=("EFM.6.03.03.noLinkElement", "EFM.6.23.01.noLinkElement", "GFM.1.01.01.noLinkElement")) elif extLinkElt.localName not in extLinkEltFileNameEnding: val.modelXbrl.error("EFM.6.03.02", _('Invalid linkbase link element %(linkElement)s in %(filename)s'), modelObject=modelDocument, linkElement=extLinkElt.localName, filename=modelDocument.basename) else: m = re.match(r"^\w+-([12][0-9]{3}[01][0-9][0-3][0-9])(_[a-z]{3}).xml$", modelDocument.basename) expectedSuffix = extLinkEltFileNameEnding[extLinkElt.localName] if m and m.group(2) == expectedSuffix: try: # check date value datetime.datetime.strptime(m.group(1),"%Y%m%d").date() # date and format are ok, check "should" part of 6.3.3 if val.fileNameBasePart: expectedFilename = "{0}-{1}{2}.xml".format(val.fileNameBasePart, val.fileNameDatePart, expectedSuffix) if modelDocument.basename != expectedFilename: val.modelXbrl.log("WARNING-SEMANTIC", ("EFM.6.03.03.matchInstance", "GFM.1.01.01.matchInstance"), _('Linkbase name warning: %(filename)s should match %(expectedFilename)s'), modelObject=modelDocument, filename=modelDocument.basename, expectedFilename=expectedFilename) except ValueError: val.modelXbrl.error((val.EFM60303, "GFM.1.01.01"), _('Invalid linkbase base file name part (date) in "{base}-{yyyymmdd}_{suffix}.xml": %(filename)s'), modelObject=modelDocument, filename=modelDocument.basename, messageCodes=("EFM.6.03.03", "EFM.6.23.01", "GFM.1.01.01")) else: val.modelXbrl.error((val.EFM60303, "GFM.1.01.01"), _('Invalid linkbase name, must match "{base}-{yyyymmdd}%(expectedSuffix)s.xml": %(filename)s'), modelObject=modelDocument, filename=modelDocument.basename, expectedSuffix=expectedSuffix, messageCodes=("EFM.6.03.03", "EFM.6.23.01", "GFM.1.01.01")) if extLinkElt.localName == "labelLink": if labelRels is None: labelRels = val.modelXbrl.relationshipSet(XbrlConst.conceptLabel) for labelElt in XmlUtil.children(extLinkElt, XbrlConst.link, "label"): # 6.10.9 if XbrlConst.isNumericRole(labelElt.role): for rel in labelRels.toModelObject(labelElt): if rel.fromModelObject is not None and not rel.fromModelObject.isNumeric: val.modelXbrl.error("EFM.6.10.09", _("Non-numeric element %(concept)s has a label role for numeric elements: %(role)s. " "Please change the role attribute."), edgarCode="du-1009-Numeric-Label-Role", modelObject=(labelElt, rel.fromModelObject), concept=rel.fromModelObject.qname, role=labelElt.role)
def final(val): if not (val.validateEBA or val.validateEIOPA): return modelXbrl = val.modelXbrl modelDocument = modelXbrl.modelDocument _statusMsg = _("validating {0} filing rules").format(val.disclosureSystem.name) modelXbrl.profileActivity() modelXbrl.modelManager.showStatus(_statusMsg) if modelDocument.type == ModelDocument.Type.INSTANCE and (val.validateEBA or val.validateEIOPA): if not modelDocument.uri.endswith(".xbrl"): modelXbrl.warning("EBA.1.1", _('XBRL instance documents SHOULD use the extension ".xbrl" but it is "%(extension)s"'), modelObject=modelDocument, extension=os.path.splitext(modelDocument.basename)[1]) modelXbrl.error("EIOPA.S.1.1.a", _('XBRL instance documents MUST use the extension ".xbrl" but it is "%(extension)s"'), modelObject=modelDocument, extension=os.path.splitext(modelDocument.basename)[1]) if val.isEIOPA_2_0_1: _encodings = ("UTF-8", "utf-8-sig") else: _encodings = ("utf-8", "UTF-8", "utf-8-sig") if modelDocument.documentEncoding not in _encodings: modelXbrl.error(("EBA.1.4", "EIOPA.1.4"), _('XBRL instance documents MUST use "UTF-8" encoding but is "%(xmlEncoding)s"'), modelObject=modelDocument, xmlEncoding=modelDocument.documentEncoding) schemaRefElts = [] schemaRefFileNames = [] for doc, docRef in modelDocument.referencesDocument.items(): if docRef.referenceType == "href": if docRef.referringModelObject.localName == "schemaRef": schemaRefElts.append(docRef.referringModelObject) schemaRefFileNames.append(doc.basename) if not UrlUtil.isAbsolute(doc.uri): modelXbrl.error(("EBA.2.2", "EIOPA.S.1.5.a" if val.isEIOPAfullVersion else "EIOPA.S.1.5.b"), _('The link:schemaRef element in submitted instances MUST resolve to the full published entry point URL: %(url)s.'), modelObject=docRef.referringModelObject, url=doc.uri, messageCodes=("EBA.2.2", "EIOPA.S.1.5.a","EIOPA.S.1.5.b")) elif docRef.referringModelObject.localName == "linkbaseRef": modelXbrl.error(("EBA.2.3","EIOPA.S.1.5.a"), _('The link:linkbaseRef element is not allowed: %(fileName)s.'), modelObject=docRef.referringModelObject, fileName=doc.basename) _numSchemaRefs = len(XmlUtil.children(modelDocument.xmlRootElement, XbrlConst.link, "schemaRef")) if _numSchemaRefs > 1: modelXbrl.error(("EIOPA.S.1.5.a", "EBA.1.5"), _('XBRL instance documents MUST reference only one entry point schema but %(numEntryPoints)s were found: %(entryPointNames)s'), modelObject=modelDocument, numEntryPoints=_numSchemaRefs, entryPointNames=', '.join(sorted(schemaRefFileNames))) ### check entry point names appropriate for filing indicator (DPM DB?) if len(schemaRefElts) != 1: modelXbrl.error("EBA.2.3", _('Any reported XBRL instance document MUST contain only one xbrli:xbrl/link:schemaRef node, but %(entryPointCount)s.'), modelObject=schemaRefElts, entryPointCount=len(schemaRefElts)) # non-streaming EBA checks if not getattr(modelXbrl, "isStreamingMode", False): val.qnReportedCurrency = None if val.isEIOPA_2_0_1 and qnMetReportingCurrency in modelXbrl.factsByQname: for _multiCurrencyFact in modelXbrl.factsByQname[qnMetReportingCurrency]: # multi-currency fact val.qnReportedCurrency = _multiCurrencyFact.xValue break validateFacts(val, modelXbrl.facts) # check sum of fact md5s (otherwise checked in streaming process) xbrlFactsCheckVersion = None expectedSumOfFactMd5s = None for pi in modelDocument.xmlRootElement.getchildren(): if isinstance(pi, etree._ProcessingInstruction) and pi.target == "xbrl-facts-check": _match = re.search("([\\w-]+)=[\"']([^\"']+)[\"']", pi.text) if _match: _matchGroups = _match.groups() if len(_matchGroups) == 2: if _matchGroups[0] == "version": xbrlFactsCheckVersion = _matchGroups[1] elif _matchGroups[0] == "sum-of-fact-md5s": try: expectedSumOfFactMd5s = Md5Sum(_matchGroups[1]) except ValueError: modelXbrl.error("EIOPA:xbrlFactsCheckError", _("Invalid sum-of-md5s %(sumOfMd5)s"), modelObject=modelXbrl, sumOfMd5=_matchGroups[1]) if xbrlFactsCheckVersion and expectedSumOfFactMd5s: sumOfFactMd5s = Md5Sum() for f in modelXbrl.factsInInstance: sumOfFactMd5s += f.md5sum if sumOfFactMd5s != expectedSumOfFactMd5s: modelXbrl.warning("EIOPA:xbrlFactsCheckWarning", _("XBRL facts sum of md5s expected %(expectedMd5)s not matched to actual sum %(actualMd5Sum)s"), modelObject=modelXbrl, expectedMd5=expectedSumOfFactMd5s, actualMd5Sum=sumOfFactMd5s) else: modelXbrl.info("info", _("Successful XBRL facts sum of md5s."), modelObject=modelXbrl) if any(badError in modelXbrl.errors for badError in ("EBA.2.1", "EIOPA.2.1", "EIOPA.S.1.5.a/EIOPA.S.1.5.b")): pass # skip checking filingIndicators if bad errors elif not val.filingIndicators: modelXbrl.error(("EBA.1.6", "EIOPA.1.6.a"), _('Missing filing indicators. Reported XBRL instances MUST include appropriate (positive) filing indicator elements'), modelObject=modelDocument) elif all(filed == False for filed in val.filingIndicators.values()): modelXbrl.error(("EBA.1.6", "EIOPA.1.6.a"), _('All filing indicators are filed="false". Reported XBRL instances MUST include appropriate (positive) filing indicator elements'), modelObject=modelDocument) if val.numFilingIndicatorTuples > 1: modelXbrl.warning(("EBA.1.6.2", "EIOPA.1.6.2"), _('Multiple filing indicators tuples when not in streaming mode (info).'), modelObject=modelXbrl.factsByQname[qnFIndicators]) if len(val.cntxDates) > 1: modelXbrl.error(("EBA.2.13","EIOPA.2.13"), _('Contexts must have the same date: %(dates)s.'), # when streaming values are no longer available, but without streaming they can be logged modelObject=set(_cntx for _cntxs in val.cntxDates.values() for _cntx in _cntxs), dates=', '.join(XmlUtil.dateunionValue(_dt, subtractOneDay=True) for _dt in val.cntxDates.keys())) if val.unusedCntxIDs: if val.isEIOPA_2_0_1: modelXbrl.error("EIOPA.2.7", _('Unused xbrli:context nodes MUST NOT be present in the instance: %(unusedContextIDs)s.'), modelObject=[modelXbrl.contexts[unusedCntxID] for unusedCntxID in val.unusedCntxIDs if unusedCntxID in modelXbrl.contexts], unusedContextIDs=", ".join(sorted(val.unusedCntxIDs))) else: modelXbrl.warning(("EBA.2.7", "EIOPA.2.7"), _('Unused xbrli:context nodes SHOULD NOT be present in the instance: %(unusedContextIDs)s.'), modelObject=[modelXbrl.contexts[unusedCntxID] for unusedCntxID in val.unusedCntxIDs if unusedCntxID in modelXbrl.contexts], unusedContextIDs=", ".join(sorted(val.unusedCntxIDs))) if len(val.cntxEntities) > 1: modelXbrl.error(("EBA.2.9", "EIOPA.2.9"), _('All entity identifiers and schemes MUST be the same, %(count)s found: %(entities)s.'), modelObject=modelDocument, count=len(val.cntxEntities), entities=", ".join(sorted(str(cntxEntity) for cntxEntity in val.cntxEntities))) for _scheme, _LEI in val.cntxEntities: if (_scheme in ("http://standards.iso.org/iso/17442", "http://standard.iso.org/iso/17442", "LEI") or (not val.isEIOPAfullVersion and _scheme == "PRE-LEI")): result = LeiUtil.checkLei(_LEI) if result == LeiUtil.LEI_INVALID_LEXICAL: modelXbrl.error("EIOPA.S.2.8.c", _("Context has lexically invalid LEI %(lei)s."), modelObject=modelDocument, lei=_LEI) elif result == LeiUtil.LEI_INVALID_CHECKSUM: modelXbrl.error("EIOPA.S.2.8.c", _("Context has LEI checksum error in %(lei)s."), modelObject=modelDocument, lei=_LEI) if _scheme == "http://standard.iso.org/iso/17442": modelXbrl.warning("EIOPA.S.2.8.c", _("Warning, context has entity scheme %(scheme)s should be plural: http://standards.iso.org/iso/17442."), modelObject=modelDocument, scheme=_scheme) elif _scheme == "SC": pass # anything is ok for Specific Code else: modelXbrl.error("EIOPA.S.2.8.c", _("Context has unrecognized entity scheme %(scheme)s."), modelObject=modelDocument, scheme=_scheme) if val.unusedUnitIDs: if val.isEIOPA_2_0_1: modelXbrl.error("EIOPA.2.22", _('Unused xbrli:unit nodes MUST NOT be present in the instance: %(unusedUnitIDs)s.'), modelObject=[modelXbrl.units[unusedUnitID] for unusedUnitID in val.unusedUnitIDs if unusedUnitID in modelXbrl.units], unusedUnitIDs=", ".join(sorted(val.unusedUnitIDs))) else: modelXbrl.warning(("EBA.2.22", "EIOPA.2.22"), _('Unused xbrli:unit nodes SHOULD NOT be present in the instance: %(unusedUnitIDs)s.'), modelObject=[modelXbrl.units[unusedUnitID] for unusedUnitID in val.unusedUnitIDs if unusedUnitID in modelXbrl.units], unusedUnitIDs=", ".join(sorted(val.unusedUnitIDs))) if len(val.currenciesUsed) > 1: modelXbrl.error(("EBA.3.1","EIOPA.3.1"), _("There MUST be only one currency but %(numCurrencies)s were found: %(currencies)s.'"), modelObject=val.currenciesUsed.values(), numCurrencies=len(val.currenciesUsed), currencies=", ".join(str(c) for c in val.currenciesUsed.keys())) elif val.isEIOPA_2_0_1 and any(_measure.localName != val.reportingCurrency for _measure in val.currenciesUsed.keys()): modelXbrl.error("EIOPA.3.1", _("There MUST be only one currency but reporting currency %(reportingCurrency)s differs from unit currencies: %(currencies)s.'"), modelObject=val.currenciesUsed.values(), reportingCurrency=val.reportingCurrency, currencies=", ".join(str(c) for c in val.currenciesUsed.keys())) if val.prefixesUnused: modelXbrl.warning(("EBA.3.4", "EIOPA.3.4"), _("There SHOULD be no unused prefixes but these were declared: %(unusedPrefixes)s.'"), modelObject=modelDocument, unusedPrefixes=', '.join(sorted(val.prefixesUnused))) for ns, prefixes in val.namespacePrefixesUsed.items(): nsDocs = modelXbrl.namespaceDocs.get(ns) if nsDocs: for nsDoc in nsDocs: nsDocPrefix = XmlUtil.xmlnsprefix(nsDoc.xmlRootElement, ns) if any(prefix != nsDocPrefix for prefix in prefixes if prefix is not None): modelXbrl.warning(("EBA.3.5", "EIOPA.3.5"), _("Prefix for namespace %(namespace)s is %(declaredPrefix)s but these were found %(foundPrefixes)s"), modelObject=modelDocument, namespace=ns, declaredPrefix=nsDocPrefix, foundPrefixes=', '.join(sorted(prefixes - {None}))) elif ns in CANONICAL_PREFIXES and any(prefix != CANONICAL_PREFIXES[ns] for prefix in prefixes if prefix is not None): modelXbrl.warning(("EBA.3.5", "EIOPA.3.5"), _("Prefix for namespace %(namespace)s is %(declaredPrefix)s but these were found %(foundPrefixes)s"), modelObject=modelDocument, namespace=ns, declaredPrefix=CANONICAL_PREFIXES[ns], foundPrefixes=', '.join(sorted(prefixes - {None}))) modelXbrl.profileActivity(_statusMsg, minTimeToShow=0.0) modelXbrl.modelManager.showStatus(None) del val.prefixNamespace, val.namespacePrefix, val.idObjects, val.typedDomainElements del val.utrValidator, val.firstFact, val.footnotesRelationshipSet
def stepAxis(self, op, p, sourceSequence): targetSequence = [] for node in sourceSequence: if not isinstance( node, (ModelObject, etree._ElementTree, ModelAttribute)): raise XPathException( self.progHeader, 'err:XPTY0020', _('Axis step {0} context item is not a node: {1}').format( op, node)) targetNodes = [] if isinstance(p, QNameDef): ns = p.namespaceURI localname = p.localName axis = p.axis if p.isAttribute: if isinstance(node, ModelObject): attrTag = p.localName if p.unprefixed else p.clarkNotation modelAttribute = None try: modelAttribute = node.xAttributes[attrTag] except (AttributeError, TypeError, IndexError, KeyError): # may be lax or deferred validated try: xmlValidate(node.modelXbrl, node, p) modelAttribute = node.xAttributes[attrTag] except (AttributeError, TypeError, IndexError, KeyError): pass if modelAttribute is None: value = node.get(attrTag) if value is not None: targetNodes.append( ModelAttribute(node, p.clarkNotation, UNKNOWN, value, value, value)) elif modelAttribute.xValid >= VALID: targetNodes.append(modelAttribute) elif op == '/' or op is None: if axis is None or axis == "child": if isinstance(node, (ModelObject, etree._ElementTree)): targetNodes = XmlUtil.children(node, ns, localname) elif axis == "parent": if isinstance(node, ModelAttribute): parentNode = [node.modelElement] else: parentNode = [XmlUtil.parent(node)] if (isinstance(node, ModelObject) and (not ns or ns == parentNode.namespaceURI or ns == "*") and (localname == parentNode.localName or localname == "*")): targetNodes = [parentNode] elif axis == "self": if (isinstance(node, ModelObject) and (not ns or ns == node.namespaceURI or ns == "*") and (localname == node.localName or localname == "*")): targetNodes = [node] elif axis.startswith("descendant"): if isinstance(node, (ModelObject, etree._ElementTree)): targetNodes = XmlUtil.descendants( node, ns, localname) if (axis.endswith("-or-self") and isinstance(node, ModelObject) and (not ns or ns == node.namespaceURI or ns == "*") and (localname == node.localName or localname == "*")): targetNodes.append(node) elif axis.startswith("ancestor"): if isinstance(node, ModelObject): targetNodes = [ ancestor for ancestor in XmlUtil.ancestors(node) if ((not ns or ns == ancestor.namespaceURI or ns == "*") and ( localname == ancestor.localName or localname == "*")) ] if (axis.endswith("-or-self") and isinstance(node, ModelObject) and (not ns or ns == node.namespaceURI or ns == "*") and (localname == node.localName or localname == "*")): targetNodes.insert(0, node) elif axis.endswith("-sibling"): if isinstance(node, ModelObject): targetNodes = [ sibling for sibling in node.itersiblings( preceding=axis.startswith("preceding")) if ((not ns or ns == sibling.namespaceURI or ns == "*") and ( localname == sibling.localName or localname == "*")) ] elif axis == "preceding": if isinstance(node, ModelObject): for preceding in node.getroottree().iter(): if preceding == node: break elif ((not ns or ns == preceding.namespaceURI or ns == "*") and (localname == preceding.localName or localname == "*")): targetNodes.append(preceding) elif axis == "following": if isinstance(node, ModelObject): foundNode = False for following in node.getroottree().iter(): if following == node: foundNode = True elif (foundNode and (not ns or ns == following.namespaceURI or ns == "*") and (localname == following.localName or localname == "*")): targetNodes.append(following) elif op == '//': if isinstance(node, (ModelObject, etree._ElementTree)): targetNodes = XmlUtil.descendants(node, ns, localname) elif op == '..': if isinstance(node, ModelAttribute): targetNodes = [node.modelElement] else: targetNodes = [XmlUtil.parent(node)] elif isinstance(p, OperationDef) and isinstance(p.name, QNameDef): if isinstance(node, ModelObject): if p.name.localName == "text": # note this is not string value, just child text targetNodes = [node.textValue] # todo: add element, attribute, node, etc... elif p == '*': # wildcard if op == '/' or op is None: if isinstance(node, (ModelObject, etree._ElementTree)): targetNodes = XmlUtil.children(node, '*', '*') elif op == '//': if isinstance(node, (ModelObject, etree._ElementTree)): targetNodes = XmlUtil.descendants(node, '*', '*') targetSequence.extend(targetNodes) return targetSequence
def validate(val, modelXbrl, infosetModelXbrl): infoset = infosetModelXbrl.modelDocument if infoset.type == Type.INSTANCE: # compare facts (assumed out of order) infosetFacts = defaultdict(list) for fact in infosetModelXbrl.facts: infosetFacts[fact.qname].append(fact) if len(modelXbrl.factsInInstance) != len(infosetModelXbrl.factsInInstance): modelXbrl.error("arelle:infosetTest", _("Fact counts mismatch, testcase instance %(foundFactCount)s, infoset instance %(expectedFactCount)s"), modelObject=(modelXbrl.modelDocument, infosetModelXbrl.modelDocument), foundFactCount=len(modelXbrl.factsInInstance), expectedFactCount=len(infosetModelXbrl.factsInInstance)) else: for i, instFact in enumerate(modelXbrl.facts): infosetFact = None for fact in infosetFacts[instFact.qname]: if fact.isTuple and fact.isDuplicateOf(instFact, deemP0Equal=True): infosetFact = fact break elif fact.isItem and fact.isVEqualTo(instFact, deemP0Equal=True): infosetFact = fact break if infosetFact is None: # takes precision/decimals into account if fact is not None: fact.isVEqualTo(instFact, deemP0Equal=True) modelXbrl.error("arelle:infosetTest", _("Fact %(factNumber)s mismatch %(concept)s"), modelObject=instFact, factNumber=(i+1), concept=instFact.qname) else: ptvPeriodType = infosetFact.get("{http://www.xbrl.org/2003/ptv}periodType") ptvBalance = infosetFact.get("{http://www.xbrl.org/2003/ptv}balance") if ptvPeriodType and ptvPeriodType != instFact.concept.periodType: modelXbrl.error("arelle:infosetTest", _("Fact %(factNumber)s periodType mismatch %(concept)s expected %(expectedPeriodType)s found %(foundPeriodType)s"), modelObject=(instFact, infosetFact), factNumber=(i+1), concept=instFact.qname, expectedPeriodType=ptvPeriodType, foundPeriodType=instFact.concept.periodType) if ptvBalance and ptvBalance != instFact.concept.balance: modelXbrl.error("arelle:infosetTest", _("Fact %(factNumber)s balance mismatch %(concept)s expected %(expectedBalance)s found %(foundBalance)s"), modelObject=(instFact, infosetFact), factNumber=(i+1), concept=instFact.qname, expectedBalance=ptvBalance, foundBalance=instFact.concept.balance) elif infoset.type == Type.ARCSINFOSET: # compare arcs for arcElt in XmlUtil.children(infoset.xmlRootElement, "http://www.xbrl.org/2003/ptv", "arc"): linkType = arcElt.get("linkType") arcRole = arcElt.get("arcRole") extRole = arcElt.get("extRole") fromObj = resolvePath(modelXbrl, arcElt.get("fromPath")) if fromObj is None: modelXbrl.error("arelle:infosetTest", _("Arc fromPath not found: %(fromPath)s"), modelObject=arcElt, fromPath=arcElt.get("fromPath")) continue if linkType in ("label", "reference"): labelLang = arcElt.get("labelLang") resRole = arcElt.get("resRole") if linkType == "label": expectedLabel = XmlUtil.text(arcElt) foundLabel = fromObj.label(preferredLabel=resRole,fallbackToQname=False,lang=None,strip=True,linkrole=extRole) if foundLabel != expectedLabel: modelXbrl.error("arelle:infosetTest", _("Label expected='%(expectedLabel)s', found='%(foundLabel)s'"), modelObject=arcElt, expectedLabel=expectedLabel, foundLabel=foundLabel) continue elif linkType == "reference": expectedRef = XmlUtil.innerText(arcElt) referenceFound = False for refrel in modelXbrl.relationshipSet(XbrlConst.conceptReference,extRole).fromModelObject(fromObj): ref = refrel.toModelObject if resRole == ref.role: foundRef = XmlUtil.innerText(ref) if foundRef != expectedRef: modelXbrl.error("arelle:infosetTest", _("Reference inner text expected='%(expectedRef)s, found='%(foundRef)s'"), modelObject=arcElt, expectedRef=expectedRef, foundRef=foundRef) referenceFound = True break if referenceFound: continue modelXbrl.error("arelle:infosetTest", _("%(linkType)s not found containing '%(text)s' linkRole %(linkRole)s"), modelObject=arcElt, linkType=linkType.title(), text=XmlUtil.innerText(arcElt), linkRole=extRole) else: toObj = resolvePath(modelXbrl, arcElt.get("toPath")) if toObj is None: modelXbrl.error("arelle:infosetTest", _("Arc toPath not found: %(toPath)s"), modelObject=arcElt, toPath=arcElt.get("toPath")) continue weight = arcElt.get("weight") if weight is not None: weight = float(weight) order = arcElt.get("order") if order is not None: order = float(order) preferredLabel = arcElt.get("preferredLabel") found = False for rel in modelXbrl.relationshipSet(arcRole, extRole).fromModelObject(fromObj): if (rel.toModelObject == toObj and (weight is None or rel.weight == weight) and (order is None or rel.order == order)): found = True if not found: modelXbrl.error("arelle:infosetTest", _("Arc not found: from %(toPath)s, to %(toPath)s, role %(arcRole)s, linkRole $(extRole)s"), modelObject=arcElt, fromPath=arcElt.get("fromPath"), toPath=arcElt.get("toPath"), arcRole=arcRole, linkRole=extRole) continue # validate dimensions of each fact factElts = XmlUtil.children(modelXbrl.modelDocument.xmlRootElement, None, "*") for itemElt in XmlUtil.children(infoset.xmlRootElement, None, "item"): try: qnElt = XmlUtil.child(itemElt,None,"qnElement") factQname = qname(qnElt, XmlUtil.text(qnElt)) sPointer = int(XmlUtil.child(itemElt,None,"sPointer").text) factElt = factElts[sPointer - 1] # 1-based xpath indexing if factElt.qname != factQname: modelXbrl.error("arelle:infosetTest", _("Fact %(sPointer)s mismatch Qname, expected %(qnElt)s, observed %(factQname)s"), modelObject=itemElt, sPointer=sPointer, qnElt=factQname, factQname=factElt.qname) elif not factElt.isItem or factElt.context is None: modelXbrl.error("arelle:infosetTest", _("Fact %(sPointer)s has no context: %(qnElt)s"), modelObject=(itemElt,factElt), sPointer=sPointer, qnElt=factQname) else: context = factElt.context memberElts = XmlUtil.children(itemElt,None,"member") numNonDefaults = 0 for memberElt in memberElts: dimElt = XmlUtil.child(memberElt, None, "qnDimension") qnDim = qname(dimElt, XmlUtil.text(dimElt)) isDefault = XmlUtil.text(XmlUtil.child(memberElt, None, "bDefaulted")) == "true" if not isDefault: numNonDefaults += 1 if not ((qnDim in context.qnameDims and not isDefault) or (qnDim in factElt.modelXbrl.qnameDimensionDefaults and isDefault)): modelXbrl.error("arelle:infosetTest", _("Fact %(sPointer)s (qnElt)s dimension mismatch %(qnDim)s"), modelObject=(itemElt, factElt, context), sPointer=sPointer, qnElt=factQname, qnDim=qnDim) if numNonDefaults != len(context.qnameDims): modelXbrl.error("arelle:infosetTest", _("Fact %(sPointer)s (qnElt)s dimensions count mismatch"), modelObject=(itemElt, factElt, context), sPointer=sPointer, qnElt=factQname) except (IndexError, ValueError, AttributeError) as err: modelXbrl.error("arelle:infosetTest", _("Invalid entity fact dimensions infoset sPointer: %(test)s, error details: %(error)s"), modelObject=itemElt, test=XmlUtil.innerTextList(itemElt), error=str(err))
def final(val): if not (val.validateEBA or val.validateEIOPA): return modelXbrl = val.modelXbrl modelDocument = modelXbrl.modelDocument _statusMsg = _("validating {0} filing rules").format(val.disclosureSystem.name) modelXbrl.profileActivity() modelXbrl.modelManager.showStatus(_statusMsg) if modelDocument.type == ModelDocument.Type.INSTANCE and (val.validateEBA or val.validateEIOPA): if not modelDocument.uri.endswith(".xbrl"): modelXbrl.warning(("EBA.1.1", "EIOPA.S.1.1.a"), _('XBRL instance documents SHOULD use the extension ".xbrl" but it is "%(extension)s"'), modelObject=modelDocument, extension=os.path.splitext(modelDocument.basename)[1]) if modelDocument.documentEncoding.lower() not in ("utf-8", "utf-8-sig"): modelXbrl.error(("EBA.1.4", "EIOPA.1.4"), _('XBRL instance documents MUST use "UTF-8" encoding but is "%(xmlEncoding)s"'), modelObject=modelDocument, xmlEncoding=modelDocument.documentEncoding) schemaRefElts = [] schemaRefFileNames = [] for doc, docRef in modelDocument.referencesDocument.items(): if docRef.referenceType == "href": if docRef.referringModelObject.localName == "schemaRef": schemaRefElts.append(docRef.referringModelObject) schemaRefFileNames.append(doc.basename) if not UrlUtil.isAbsolute(doc.uri): modelXbrl.error(("EBA.2.2", "EBA.S.1.5.a" if val.isEIOPAfullVersion else "EBA.S.1.5.b"), _('The link:schemaRef element in submitted instances MUST resolve to the full published entry point URL: %(url)s.'), modelObject=docRef.referringModelObject, url=doc.uri, messageCodes=("EBA.2.2", "EBA.S.1.5.a","EBA.S.1.5.b")) elif docRef.referringModelObject.localName == "linkbaseRef": modelXbrl.error(("EBA.2.3","EBA.S.1.5.a"), _('The link:linkbaseRef element is not allowed: %(fileName)s.'), modelObject=docRef.referringModelObject, fileName=doc.basename) _numSchemaRefs = len(XmlUtil.children(modelDocument.xmlRootElement, XbrlConst.link, "schemaRef")) if _numSchemaRefs > 1: modelXbrl.error(("EIOPA.S.1.5.a", "EBA.1.5"), _('XBRL instance documents MUST reference only one entry point schema but %(numEntryPoints)s were found: %(entryPointNames)s'), modelObject=modelDocument, numEntryPoints=_numSchemaRefs, entryPointNames=', '.join(sorted(schemaRefFileNames))) ### check entry point names appropriate for filing indicator (DPM DB?) if len(schemaRefElts) != 1: modelXbrl.error("EBA.2.3", _('Any reported XBRL instance document MUST contain only one xbrli:xbrl/link:schemaRef node, but %(entryPointCount)s.'), modelObject=schemaRefElts, entryPointCount=len(schemaRefElts)) # non-streaming EBA checks if not getattr(modelXbrl, "isStreamingMode", False): validateFacts(val, modelXbrl.facts) # check sum of fact md5s (otherwise checked in streaming process) xbrlFactsCheckVersion = None expectedSumOfFactMd5s = None for pi in modelDocument.xmlRootElement.getchildren(): if isinstance(pi, etree._ProcessingInstruction) and pi.target == "xbrl-facts-check": _match = re.search("([\\w-]+)=[\"']([^\"']+)[\"']", pi.text) if _match: _matchGroups = _match.groups() if len(_matchGroups) == 2: if _matchGroups[0] == "version": xbrlFactsCheckVersion = _matchGroups[1] elif _matchGroups[0] == "sum-of-fact-md5s": try: expectedSumOfFactMd5s = Md5Sum(_matchGroups[1]) except ValueError: modelXbrl.error("EIOPA:xbrlFactsCheckError", _("Invalid sum-of-md5s %(sumOfMd5)s"), modelObject=modelXbrl, sumOfMd5=_matchGroups[1]) if xbrlFactsCheckVersion and expectedSumOfFactMd5s: sumOfFactMd5s = Md5Sum() for f in modelXbrl.factsInInstance: sumOfFactMd5s += f.md5sum if sumOfFactMd5s != expectedSumOfFactMd5s: modelXbrl.warning("EIOPA:xbrlFactsCheckWarning", _("XBRL facts sum of md5s expected %(expectedMd5)s not matched to actual sum %(actualMd5Sum)s"), modelObject=modelXbrl, expectedMd5=expectedSumOfFactMd5s, actualMd5Sum=sumOfFactMd5s) else: modelXbrl.info("info", _("Successful XBRL facts sum of md5s."), modelObject=modelXbrl) if not val.filingIndicators: modelXbrl.error(("EBA.1.6", "EIOPA.1.6.a"), _('Missing filing indicators. Reported XBRL instances MUST include appropriate (positive) filing indicator elements'), modelObject=modelDocument) elif all(filed == False for filed in val.filingIndicators.values()): modelXbrl.error(("EBA.1.6", "EIOPA.1.6.a"), _('All filing indicators are filed="false". Reported XBRL instances MUST include appropriate (positive) filing indicator elements'), modelObject=modelDocument) if val.numFilingIndicatorTuples > 1: modelXbrl.warning("EBA.1.6.2|EIOPA.1.6.2", _('Multiple filing indicators tuples when not in streaming mode (info).'), modelObject=modelXbrl.factsByQname[qnFIndicators]) if len(val.cntxDates) > 1: modelXbrl.error("EBA.2.13", _('Contexts must have the same date: %(dates)s.'), # when streaming values are no longer available, but without streaming they can be logged modelObject=set(_cntx for _cntxs in val.cntxDates.values() for _cntx in _cntxs), dates=', '.join(XmlUtil.dateunionValue(_dt, subtractOneDay=True) for _dt in val.cntxDates.keys())) if val.unusedCntxIDs: modelXbrl.warning(("EBA.2.7", "EIOPA.2.7"), _('Unused xbrli:context nodes SHOULD NOT be present in the instance: %(unusedContextIDs)s.'), modelObject=[modelXbrl.contexts[unusedCntxID] for unusedCntxID in val.unusedCntxIDs if unusedCntxID in modelXbrl.contexts], unusedContextIDs=", ".join(sorted(val.unusedCntxIDs))) if len(val.cntxEntities) > 1: modelXbrl.warning(("EBA.2.9", "EIOPA.2.9"), _('All entity identifiers and schemes must be the same, %(count)s found: %(entities)s.'), modelObject=modelDocument, count=len(val.cntxEntities), entities=", ".join(sorted(str(cntxEntity) for cntxEntity in val.cntxEntities))) for _scheme, _LEI in val.cntxEntities: if (_scheme in ("http://standard.iso.org/iso/17442", "LEI") or (not val.isEIOPAfullVersion and _scheme == "PRE-LEI")): result = LeiUtil.checkLei(_LEI) if result == LeiUtil.LEI_INVALID_LEXICAL: modelXbrl.error("EIOPA.S.2.8.c", _("Context has lexically invalid LEI %(lei)s."), modelObject=modelDocument, lei=_LEI) elif result == LeiUtil.LEI_INVALID_CHECKSUM: modelXbrl.error("EIOPA.S.2.8.c", _("Context has LEI checksum error in %(lei)s."), modelObject=modelDocument, lei=_LEI) elif _scheme == "SC": pass # anything is ok for Specific Code else: modelXbrl.error("EIOPA.S.2.8.c", _("Context has unrecognized entity scheme %(scheme)s."), modelObject=modelDocument, scheme=_scheme) if val.unusedUnitIDs: modelXbrl.warning(("EBA.2.22", "EIOPA.2.22"), _('Unused xbrli:unit nodes SHOULD NOT be present in the instance: %(unusedUnitIDs)s.'), modelObject=[modelXbrl.units[unusedUnitID] for unusedUnitID in val.unusedUnitIDs if unusedUnitID in modelXbrl.units], unusedUnitIDs=", ".join(sorted(val.unusedUnitIDs))) if len(val.currenciesUsed) > 1: modelXbrl.error(("EBA.3.1","EIOPA.3.1"), _("There MUST be only one currency but %(numCurrencies)s were found: %(currencies)s.'"), modelObject=val.currenciesUsed.values(), numCurrencies=len(val.currenciesUsed), currencies=", ".join(str(c) for c in val.currenciesUsed.keys())) if val.prefixesUnused: modelXbrl.warning(("EBA.3.4", "EIOPA.3.4"), _("There SHOULD be no unused prefixes but these were declared: %(unusedPrefixes)s.'"), modelObject=modelDocument, unusedPrefixes=', '.join(sorted(val.prefixesUnused))) for ns, prefixes in val.namespacePrefixesUsed.items(): nsDocs = modelXbrl.namespaceDocs.get(ns) if nsDocs: for nsDoc in nsDocs: nsDocPrefix = XmlUtil.xmlnsprefix(nsDoc.xmlRootElement, ns) if any(prefix != nsDocPrefix for prefix in prefixes if prefix is not None): modelXbrl.warning(("EBA.3.5", "EIOPA.3.5"), _("Prefix for namespace %(namespace)s is %(declaredPrefix)s but these were found %(foundPrefixes)s"), modelObject=modelDocument, namespace=ns, declaredPrefix=nsDocPrefix, foundPrefixes=', '.join(sorted(prefixes - {None}))) modelXbrl.profileActivity(_statusMsg, minTimeToShow=0.0) modelXbrl.modelManager.showStatus(None) del val.prefixNamespace, val.namespacePrefix, val.idObjects, val.typedDomainElements del val.utrValidator, val.firstFact, val.footnotesRelationshipSet
def explicitDims(self): return {(self.prefixedNameQname(e.get("dimension")), self.prefixedNameQname(XmlUtil.text(qn))) for e in XmlUtil.children(self, XbrlConst.formula, "explicitDimension") for m in XmlUtil.children(e, XbrlConst.formula, "member") for qn in XmlUtil.children(m, XbrlConst.formula, "qname")}
def explicitDims(self): return {(self.prefixedNameQname(e.getAttribute("dimension")), self.prefixedNameQname(e.getAttribute("value"))) for e in XmlUtil.children(self.element, XbrlConst.euRend, "explicitDimCoord")}
def doObject(self, fObj, fromRel, pIndent, visited): if fObj is None: return cIndent = pIndent + " " if isinstance(fObj, (ModelValueAssertion, ModelExistenceAssertion, ModelFormula)): varSetType = "formula" if isinstance(fObj, ModelFormula) else "assertion" eltNbr = self.eltTypeCount[varSetType] = self.eltTypeCount.get(varSetType, 0) + 1 _id = fObj.id or "{}{}".format(varSetType, eltNbr) self.xf = "{}{} {} {{".format(pIndent, varSetType, _id) for arcrole in (XbrlConst.elementLabel, XbrlConst.assertionSatisfiedMessage, XbrlConst.assertionUnsatisfiedMessage): for modelRel in self.modelXbrl.relationshipSet(arcrole).fromModelObject(fObj): self.doObject(modelRel.toModelObject, modelRel, cIndent, visited) if fObj.aspectModel == "non-dimensional": self.xf = "{}aspect-model-non-dimensional;".format(cIndent) if fObj.implicitFiltering == "false": self.xf = "{}no-implicit-filtering;".format(cIndent) if isinstance(fObj, ModelFormula): for attr in ("decimals", "precision", "value"): if fObj.get(attr): self.xf = "{}{} {{{}}};".format(cIndent, attr, fObj.get(attr)) if fObj.get("source"): self.xf = "{}source {};".format(cIndent, fObj.get("source")) for aspectsElt in XmlUtil.children(fObj, XbrlConst.formula, "aspects"): self.xf = "{}aspect-rules{} {{".format(cIndent, "source {}".format(aspectsElt.get("source")) if aspectsElt.get("source") else "") for ruleElt in XmlUtil.children(aspectsElt, XbrlConst.formula, "*"): self.doObject(ruleElt, None, cIndent + " ", visited) self.xf = "{}}};".format(cIndent) for arcrole in (XbrlConst.variableSetFilter, XbrlConst.variableSet, XbrlConst.variableSetPrecondition): for modelRel in self.modelXbrl.relationshipSet(arcrole).fromModelObject(fObj): self.doObject(modelRel.toModelObject, modelRel, cIndent, visited) if isinstance(fObj, ModelValueAssertion): self.xf = "{}test {{{}}}".format(cIndent, fObj.viewExpression) elif isinstance(fObj, ModelExistenceAssertion): self.xf = "{}evaluation-count {{{}}}".format(cIndent, fObj.viewExpression or ". gt 0") self.xf = "{}}};".format(pIndent) elif isinstance(fObj, ModelConsistencyAssertion): eltNbr = self.eltTypeCount["consistencyAssertion"] = self.eltTypeCount.get("consistencyAssertion", 0) + 1 _id = fObj.id or "{}{}".format("consistencyAssertion", eltNbr) self.xf = "{}consistency-assertion {} {{".format(pIndent, _id) for arcrole in (XbrlConst.elementLabel, XbrlConst.assertionSatisfiedMessage, XbrlConst.assertionUnsatisfiedMessage): for modelRel in self.modelXbrl.relationshipSet(arcrole).fromModelObject(fObj): self.doObject(modelRel.toModelObject, modelRel, cIndent, visited) if fObj.isStrict: self.xf = "{}strict;".format(cIndent) if fObj.get("proportionalAcceptanceRadius"): self.xf = "{}proportional-acceptance-radius {{{}}};".format(cIndent, fObj.get("proportionalAcceptanceRadius")) if fObj.get("absoluteAcceptanceRadius"): self.xf = "{}absolute-acceptance-radius {{{}}};".format(cIndent, fObj.get("absoluteAcceptanceRadius")) for modelRel in self.modelXbrl.relationshipSet(XbrlConst.consistencyAssertionFormula).fromModelObject(fObj): self.doObject(modelRel.toModelObject, modelRel, cIndent, visited) self.xf = "{}}};".format(pIndent) elif isinstance(fObj, ModelFactVariable) and fromRel is not None: self.xf = "{}variable ${} {{".format(pIndent, fromRel.variableQname) if fromRel.variableQname.prefix: self.xmlns[fromRel.variableQname.prefix] = fromRel.variableQname.namespaceURI if fObj.bindAsSequence == "true": self.xf = "{}bind-as-sequence".format(cIndent) if fObj.nils == "true": self.xf = "{}nils".format(cIndent) if fObj.matches == "true": self.xf = "{}matches".format(cIndent) if fObj.fallbackValue: self.xf = "{}fallback {{{}}}".format(cIndent, fObj.fallbackValue) for modelRel in self.modelXbrl.relationshipSet(XbrlConst.variableFilter).fromModelObject(fObj): self.doObject(modelRel.toModelObject, modelRel, cIndent, visited) self.xf = "{}}};".format(pIndent) elif isinstance(fObj, ModelGeneralVariable) and fromRel is not None: self.xf = "{}variable ${} {{".format(pIndent, fromRel.variableQname) if fromRel.variableQname.prefix: self.xmlns[fromRel.variableQname.prefix] = fromRel.variableQname.namespaceURI if fObj.bindAsSequence: self.xf = "{}bind-as-sequence".format(cIndent) self.xf = "{}select {{{}}}".format(cIndent, fObj.select) elif isinstance(fObj, ModelParameter): if fromRel is not None: # parameter is referenced by a different QName on arc if fromRel.variableQname.prefix: self.xmlns[fromRel.variableQname.prefix] = fromRel.variableQname.namespaceURI self.xf = "{}parameter ${} references ${};".format(pIndent, fromRel.variableQname, fObj.parameterQname) else: # root level parameter if fObj.parameterQname.prefix: self.xmlns[fObj.parameterQname.prefix] = fObj.parameterQname.namespaceURI self.xf = "{}parameter {} {{".format(pIndent, fObj.parameterQname) if fObj.isRequired: self.xf = "{}required".format(cIndent) self.xf = "{} select {{{}}}".format(cIndent, fObj.select) if fObj.asType: self.xf = "{} as {{{}}}".format(cIndent, fObj.asType) if fObj.asType.prefix: self.xmlns[fObj.asType.prefix] = fObj.asType.namespaceURI self.xf = "{}}};".format(pIndent) elif isinstance(fObj, ModelFilter): if fromRel.isComplemented: self.xf = "{}complemented".format(pIndent) if not fromRel.isCovered and fromRel.localName == "variableFilterArc": self.xf = "{}non-covering".format(pIndent) if isinstance(fObj, ModelConceptName): if len(fObj.conceptQnames) == 1 and not fObj.qnameExpressions: qn = next(iter(fObj.conceptQnames)) self.xmlns[qn.prefix] = qn.namespaceURI self.xf = "{}concept-name {};".format(pIndent, qn) elif len(fObj.qnameExpressions) == 1 and not fObj.conceptQnames: self.xf = "{}concept-name {{{}}};".format(pIndent, fObj.qnameExpressions[0]) else: self.xf = "{}concept-name".format(pIndent) for qn in fObj.conceptQnames: self.xmlns[qn.prefix] = qn.namespaceURI self.xf = "{} {}".format(pIndent, qn) for qnExpr in fObj.qnameExpressions: self.xf = "{} {}".format(pIndent, qnExpr) self.xf = "{} ;".format(pIndent) elif isinstance(fObj, ModelConceptPeriodType): self.xf = "{}concept-period {};".format(pIndent, fObj.periodType) elif isinstance(fObj, ModelConceptBalance): self.xf = "{}concept-balance {};".format(pIndent, fObj.balance) elif isinstance(fObj, (ModelConceptDataType, ModelConceptSubstitutionGroup)): self.xf = "{}{} {} {};".format(pIndent, kebabCase(fObj.localName), "strict" if fObj.strict == "true" else "non-strict", fObj.filterQname if fObj.filterQname else "{{{}}}".format(fObj.qnameExpression)) elif isinstance(fObj, ModelExplicitDimension): members = [] for memberElt in XmlUtil.children(fObj, XbrlConst.df, "member"): members.append("member") member = XmlUtil.childText(memberElt, XbrlConst.df, "qname") if member: member = str(member) # qname, must coerce to string else: member = XmlUtil.childText(memberElt, XbrlConst.df, "qnameExpression") if member: member = "{{{}}}".format(member) else: member = "$" + XmlUtil.childText(memberElt, XbrlConst.df, "variable") members.append(member) linkrole = XmlUtil.childText(memberElt, XbrlConst.df, "linkrole") if linkrole: members.append("linkrole") members.append("\"{}\"".format(linkrole)) arcrole = XmlUtil.childText(memberElt, XbrlConst.df, "arcrole") if arcrole: members.append("arcrole") members.append("\"{}\"".format(arcrole)) axis = XmlUtil.childText(memberElt, XbrlConst.df, "axis") if axis: members.append("axis") members.append(axis) self.xf = "{}explicit-dimension {}{};".format(pIndent, fObj.dimQname or "{{{}}}".format(fObj.dimQnameExpression) if fObj.dimQnameExpression else "", " ".join(members)) elif isinstance(fObj, ModelTypedDimension): # this is a ModelTestFilter not same as genera/unit/period self.xf = "{}typed-dimension {}{};".format(pIndent, fObj.dimQname or "{{{}}}".format(fObj.dimQnameExpression) if fObj.dimQnameExpression else "", " {{{}}}".format(fObj.test) if fObj.test else "") elif isinstance(fObj, ModelTestFilter): self.xf = "{}{} {{{}}};".format(pIndent, "general" if isinstance(fObj, ModelGeneral) else "unit-general-measures" if isinstance(fObj, ModelGeneralMeasures) else "period" if isinstance(fObj, ModelPeriod) else "entity-identifier" if isinstance(fObj, ModelIdentifier) else None, fObj.test) elif isinstance(fObj, ModelDateTimeFilter): self.xf = "{}{} {{{}}}{};".format(pIndent, kebabCase(fObj.localName), fObj.date, " {{{}}}".format(fObj.time) if fObj.time else "") elif isinstance(fObj, ModelInstantDuration): self.xf = "{}instant-duration {} {};".format(pIndent, fObj.boundary, fObj.variable) elif isinstance(fObj, ModelSingleMeasure): self.xf = "{}unit {} {};".format(pIndent, fObj.boundary, fObj.variable) elif isinstance(fObj, ModelEntitySpecificIdentifier): self.xf = "{}entity scheme {{{}}} value {{{}}};".format(pIndent, fObj.scheme, fObj.value) elif isinstance(fObj, ModelEntityScheme): self.xf = "{}entity-scheme {{{}}};".format(pIndent, fObj.scheme) elif isinstance(fObj, ModelEntityRegexpScheme): self.xf = "{}entity-scheme-pattern \"{}\";".format(pIndent, fObj.pattern) elif isinstance(fObj, ModelEntityRegexpIdentifier): self.xf = "{}entity-identifier-pattern \"{}\";".format(pIndent, fObj.pattern) elif isinstance(fObj, ModelMatchFilter): self.xf = "{}{} ${} {}{};".format(pIndent, kebabCase(fObj.localName), fObj.variable, fObj.dimension or "", " match-any" if fObj.matchAny else "") elif isinstance(fObj, ModelRelativeFilter): self.xf = "{}relative ${};".format(pIndent, fObj.variable) elif isinstance(fObj, ModelAncestorFilter): self.xf = "{}ancestor {};".format(pIndent, fObj.ancestorQname or "{{{}}}".format(fObj.qnameExpression) if fObj.qnameExpression else "") elif isinstance(fObj, ModelParentFilter): self.xf = "{}parent {};".format(pIndent, fObj.parentQname or "{{{}}}".format(fObj.qnameExpression) if fObj.qnameExpression else "") elif isinstance(fObj, ModelSiblingFilter): self.xf = "{}sibling ${};".format(pIndent, fObj.variable) elif isinstance(fObj, ModelNilFilter): self.xf = "{}nilled;".format(pIndent) elif isinstance(fObj, ModelAspectCover): aspects = [] for aspectElt in XmlUtil.children(fObj, XbrlConst.acf, "aspect"): aspects.append(XmlUtil.text(aspectElt)) for dimElt in XmlUtil.descendants(fObj, XbrlConst.acf, ("qname", "qnameExpression")): dimAspect = qname( dimElt, XmlUtil.text(dimElt) ) aspects.append("exclude-dimension" if dimElt.getparent().localName == "excludeDimension" else "dimension") if dimElt.localName == "qname": aspects.append(str(qname( dimElt, XmlUtil.text(dimElt) ))) else: aspects.append("{{{}}}".format(XmlUtil.text(dimElt))) self.xf = "{}aspect-cover {};".format(pIndent, " ".join(aspects)) elif isinstance(fObj, ModelConceptRelation): conceptRelationTerms = [] if fObj.sourceQname: conceptRelationTerms.append(fObj.sourceQname) elif fObj.variable: conceptRelationTerms.append("$" + fObj.variable) else: conceptRelationTerms.append("{{{}}}".format(fObj.sourceQnameExpression)) if fObj.linkrole: conceptRelationTerms.append("linkrole") conceptRelationTerms.append(fObj.linkrole) elif fObj.linkroleExpression: conceptRelationTerms.append("linkrole") conceptRelationTerms.append("{{{}}}".format(fObj.linkroleExpression)) if fObj.arcrole: conceptRelationTerms.append("arcrole") conceptRelationTerms.append(fObj.arcrole) elif fObj.arcroleExpression: conceptRelationTerms.append("arcrole") conceptRelationTerms.append("{{{}}}".format(fObj.arcroleExpression)) if fObj.axis: conceptRelationTerms.append("axis") conceptRelationTerms.append(fObj.axis) if fObj.generations is not None: conceptRelationTerms.append("generations {}".format(fObj.generations)) if fObj.test: conceptRelationTerms.append("test") conceptRelationTerms.append("{{{}}}".format(fObj.test)) self.xf = "{}concept-relation {};".format(pIndent, " ".join(conceptRelationTerms)) elif isinstance(fObj, (ModelAndFilter, ModelOrFilter)): self.xf = "{}{} {{".format(pIndent, "and" if isinstance(fObj, ModelAndFilter)else "or") if fObj not in visited: visited.add(fObj) for modelRel in self.modelXbrl.relationshipSet(XbrlConst.booleanFilter).fromModelObject(fObj): self.doObject(modelRel.toModelObject, modelRel, cIndent, visited) visited.remove(fObj) self.xf = "{}}};".format(pIndent) elif isinstance(fObj, ModelMessage): self.xf = "{}{}{} \"{}\";".format( pIndent, "satisfied-message" if fromRel.arcrole == XbrlConst.assertionSatisfiedMessage else "unsatisfied-message", " ({})".format(fObj.xmlLang) if fObj.xmlLang else "", fObj.text) elif isinstance(fObj, ModelCustomFunctionSignature): hasImplememntation = False if fObj not in visited: visited.add(fObj) for modelRel in self.modelXbrl.relationshipSet(XbrlConst.functionImplementation).fromModelObject(fObj): self.doObject(modelRel.toModelObject, modelRel, pIndent, visited) # note: use pIndent as parent doesn't show hasImplementation = True visited.remove(fObj) if not hasImplementation: self.xf = "{}abstract-function {}({}) as {};".format(pIndent, fObj.name, ", ".join(str(t) for t in fObj.inputTypes), fObj.outputType) elif isinstance(fObj, ModelCustomFunctionImplementation): sigObj = fromRel.fromModelObject self.xf = "{}function {}({}) as {} {{;".format(pIndent, sigObj.name, ", ".join("{} as {}".format(inputName, sigObj.inputTypes[i]) for i, inputName in enumerate(fObj.inputNames)), sigObj.outputType) for name, stepExpr in fObj.stepExpressions: if "\n" not in stepExpr: self.xf = "{}step ${} {{{}}};".format(cIndent, name, stepExpr) else: self.xf = "{}step ${} {{".format(cIndent, name) for exprLine in stepExpr.split("\n"): self.xf = "{} {}".format(cIndent, exprLine.lstrip()) self.xf = "{}}};".format(cIndent) self.xf = "{}return {{{}}};".format(cIndent, fObj.outputExpression) self.xf = "{}}};".format(pIndent) elif fObj.getparent().tag == "{http://xbrl.org/2008/formula}aspects": # aspect rules arg = "" if fObj.localName == "concept": if XmlUtil.hasChild(fObj, None, "qname"): arg += " " + XmlUtil.childText(fObj, None, "qname") elif XmlUtil.hasChild(fObj, None, "qnameExpression"): arg += " {" + XmlUtil.childText(fObj, None, "qnameExpression") + "}" elif fObj.localName == "entityIdentifier": if fObj.get("scheme"): arg += " scheme {" + fObj.get("scheme") + "}" if fObj.get("identifier"): arg += " identifier {" + fObj.get("identifier") + "}" elif fObj.localName == "period": if XmlUtil.hasChild(fObj, None, "forever"): arg += " forever" if XmlUtil.hasChild(fObj, None, "instant"): arg += " instant" attr = XmlUtil.childAttr(fObj, None, "instant", "value") if attr: arg += "{" + attr + "}" if XmlUtil.hasChild(fObj, None, "duration"): arg += " duration" attr = XmlUtil.childAttr(fObj, None, "duration", "start") if attr: arg += " start {" + attr + "}" attr = XmlUtil.childAttr(fObj, None, "duration", "end") if attr: arg += " end {" + attr + "}" elif fObj.localName == "unit": if fObj.get("augment") == "true": arg += " augment" if fObj.localName in ("explicitDimension", "typedDimension"): arg += " dimension " + fObj.get("dimension") if fObj.localName in ("concept", "entityIdentifier", "period"): arg += ";" else: arg += " {" self.xf = "{}{}{}".format(pIndent, kebabCase(fObj.localName), arg) if fObj.localName == "unit": for elt in fObj.iterchildren(): arg = "" if elt.get("source"): arg += " source " + elt.get("source") if elt.get("measure"): arg += " measure {" + elt.get("measure") + "}" self.xf = "{}{}{};".format(cIndent, kebabCase(elt.localName), arg) elif fObj.localName == "explicitDimension": for elt in fObj.iterchildren(): arg = "" if XmlUtil.hasChild(elt, None, "qname"): arg += " " + XmlUtil.childText(elt, None, "qname") elif XmlUtil.hasChild(elt, None, "qnameExpression"): arg += " {" + XmlUtil.childText(elt, None, "qnameExpression") + "}" self.xf = "{}{}{};".format(cIndent, kebabCase(elt.localName), arg) elif fObj.localName == "typedDimension": for elt in fObj.iterchildren(): arg = "" if XmlUtil.hasChild(elt, None, "xpath"): arg += " xpath {" + ModelXbrl.childText(elt, None, "xpath") + "}" elif XmlUtil.hasChild(elt, None, "value"): arg += " value " + strQoute(XmlUtil.xmlstring(XmlUtil.child(elt, None, "value"), stripXmlns=True, contentsOnly=False)) self.xf = "{}{}{};".format(cIndent, kebabCase(elt.localName), arg) if fObj.localName not in ("concept", "entityIdentifier", "period"): self.xf = "{}}};".format(pIndent)
def validate(val, modelXbrl, infosetModelXbrl): infoset = infosetModelXbrl.modelDocument if infoset.type == Type.INSTANCE: # compare facts (assumed out of order) infosetFacts = defaultdict(list) for fact in infosetModelXbrl.facts: infosetFacts[fact.qname].append(fact) if len(modelXbrl.factsInInstance) != len( infosetModelXbrl.factsInInstance): modelXbrl.error( "arelle:infosetTest", _("Fact counts mismatch, testcase instance %(foundFactCount)s, infoset instance %(expectedFactCount)s" ), modelObject=(modelXbrl.modelDocument, infosetModelXbrl.modelDocument), foundFactCount=len(modelXbrl.factsInInstance), expectedFactCount=len(infosetModelXbrl.factsInInstance)) else: for i, instFact in enumerate(modelXbrl.facts): infosetFact = None for fact in infosetFacts[instFact.qname]: if fact.isTuple and fact.isDuplicateOf(instFact, deemP0Equal=True): infosetFact = fact break elif fact.isItem and fact.isVEqualTo(instFact, deemP0Equal=True): infosetFact = fact break if infosetFact is None: # takes precision/decimals into account if fact is not None: fact.isVEqualTo(instFact, deemP0Equal=True) modelXbrl.error( "arelle:infosetTest", _("Fact %(factNumber)s mismatch %(concept)s"), modelObject=instFact, factNumber=(i + 1), concept=instFact.qname) else: ptvPeriodType = infosetFact.get( "{http://www.xbrl.org/2003/ptv}periodType") ptvBalance = infosetFact.get( "{http://www.xbrl.org/2003/ptv}balance") ptvDecimals = infosetFact.get( "{http://www.xbrl.org/2003/ptv}decimals") ptvPrecision = infosetFact.get( "{http://www.xbrl.org/2003/ptv}precision") if ptvPeriodType and ptvPeriodType != instFact.concept.periodType: modelXbrl.error( "arelle:infosetTest", _("Fact %(factNumber)s periodType mismatch %(concept)s expected %(expectedPeriodType)s found %(foundPeriodType)s" ), modelObject=(instFact, infosetFact), factNumber=(i + 1), concept=instFact.qname, expectedPeriodType=ptvPeriodType, foundPeriodType=instFact.concept.periodType) if ptvBalance and ptvBalance != instFact.concept.balance: modelXbrl.error( "arelle:infosetTest", _("Fact %(factNumber)s balance mismatch %(concept)s expected %(expectedBalance)s found %(foundBalance)s" ), modelObject=(instFact, infosetFact), factNumber=(i + 1), concept=instFact.qname, expectedBalance=ptvBalance, foundBalance=instFact.concept.balance) if ptvDecimals and ptvDecimals != str( inferredDecimals(fact)): modelXbrl.error( "arelle:infosetTest", _("Fact %(factNumber)s inferred decimals mismatch %(concept)s expected %(expectedDecimals)s found %(inferredDecimals)s" ), modelObject=(instFact, infosetFact), factNumber=(i + 1), concept=instFact.qname, expectedDecimals=ptvDecimals, inferredDecimals=str(inferredDecimals(fact))) if ptvPrecision and ptvPrecision != str( inferredPrecision(fact)): modelXbrl.error( "arelle:infosetTest", _("Fact %(factNumber)s inferred precision mismatch %(concept)s expected %(expectedPrecision)s found %(inferredPrecision)s" ), modelObject=(instFact, infosetFact), factNumber=(i + 1), concept=instFact.qname, expectedPrecisions=ptvPrecision, inferredPrecision=str(inferredPrecision(fact))) elif infoset.type == Type.ARCSINFOSET: # compare arcs for arcElt in XmlUtil.children(infoset.xmlRootElement, "http://www.xbrl.org/2003/ptv", "arc"): linkType = arcElt.get("linkType") arcRole = arcElt.get("arcRole") extRole = arcElt.get("extRole") fromObj = resolvePath(modelXbrl, arcElt.get("fromPath")) if fromObj is None: modelXbrl.error("arelle:infosetTest", _("Arc fromPath not found: %(fromPath)s"), modelObject=arcElt, fromPath=arcElt.get("fromPath")) continue if linkType in ("label", "reference"): labelLang = arcElt.get("labelLang") resRole = arcElt.get("resRole") if linkType == "label": expectedLabel = XmlUtil.text(arcElt) foundLabel = fromObj.label(preferredLabel=resRole, fallbackToQname=False, lang=None, strip=True, linkrole=extRole) if foundLabel != expectedLabel: modelXbrl.error( "arelle:infosetTest", _("Label expected='%(expectedLabel)s', found='%(foundLabel)s'" ), modelObject=arcElt, expectedLabel=expectedLabel, foundLabel=foundLabel) continue elif linkType == "reference": expectedRef = XmlUtil.innerText(arcElt) referenceFound = False for refrel in modelXbrl.relationshipSet( XbrlConst.conceptReference, extRole).fromModelObject(fromObj): ref = refrel.toModelObject if resRole == ref.role: foundRef = XmlUtil.innerText(ref) if foundRef != expectedRef: modelXbrl.error( "arelle:infosetTest", _("Reference inner text expected='%(expectedRef)s, found='%(foundRef)s'" ), modelObject=arcElt, expectedRef=expectedRef, foundRef=foundRef) referenceFound = True break if referenceFound: continue modelXbrl.error( "arelle:infosetTest", _("%(linkType)s not found containing '%(text)s' linkRole %(linkRole)s" ), modelObject=arcElt, linkType=linkType.title(), text=XmlUtil.innerText(arcElt), linkRole=extRole) else: toObj = resolvePath(modelXbrl, arcElt.get("toPath")) if toObj is None: modelXbrl.error("arelle:infosetTest", _("Arc toPath not found: %(toPath)s"), modelObject=arcElt, toPath=arcElt.get("toPath")) continue weight = arcElt.get("weight") if weight is not None: weight = float(weight) order = arcElt.get("order") if order is not None: order = float(order) preferredLabel = arcElt.get("preferredLabel") found = False for rel in modelXbrl.relationshipSet( arcRole, extRole).fromModelObject(fromObj): if (rel.toModelObject == toObj and (weight is None or rel.weight == weight) and (order is None or rel.order == order)): found = True if not found: modelXbrl.error( "arelle:infosetTest", _("Arc not found: from %(fromPath)s, to %(toPath)s, role %(arcRole)s, linkRole $(extRole)s" ), modelObject=arcElt, fromPath=arcElt.get("fromPath"), toPath=arcElt.get("toPath"), arcRole=arcRole, linkRole=extRole) continue # validate dimensions of each fact factElts = XmlUtil.children(modelXbrl.modelDocument.xmlRootElement, None, "*") for itemElt in XmlUtil.children(infoset.xmlRootElement, None, "item"): try: qnElt = XmlUtil.child(itemElt, None, "qnElement") factQname = qname(qnElt, XmlUtil.text(qnElt)) sPointer = int(XmlUtil.child(itemElt, None, "sPointer").text) factElt = factElts[sPointer - 1] # 1-based xpath indexing if factElt.qname != factQname: modelXbrl.error( "arelle:infosetTest", _("Fact %(sPointer)s mismatch Qname, expected %(qnElt)s, observed %(factQname)s" ), modelObject=itemElt, sPointer=sPointer, qnElt=factQname, factQname=factElt.qname) elif not factElt.isItem or factElt.context is None: modelXbrl.error( "arelle:infosetTest", _("Fact %(sPointer)s has no context: %(qnElt)s"), modelObject=(itemElt, factElt), sPointer=sPointer, qnElt=factQname) else: context = factElt.context memberElts = XmlUtil.children(itemElt, None, "member") numNonDefaults = 0 for memberElt in memberElts: dimElt = XmlUtil.child(memberElt, None, "qnDimension") qnDim = qname(dimElt, XmlUtil.text(dimElt)) isDefault = XmlUtil.text( XmlUtil.child(memberElt, None, "bDefaulted")) == "true" if not isDefault: numNonDefaults += 1 if not ( (qnDim in context.qnameDims and not isDefault) or (qnDim in factElt.modelXbrl.qnameDimensionDefaults and isDefault)): modelXbrl.error( "arelle:infosetTest", _("Fact %(sPointer)s (qnElt)s dimension mismatch %(qnDim)s" ), modelObject=(itemElt, factElt, context), sPointer=sPointer, qnElt=factQname, qnDim=qnDim) if numNonDefaults != len(context.qnameDims): modelXbrl.error( "arelle:infosetTest", _("Fact %(sPointer)s (qnElt)s dimensions count mismatch" ), modelObject=(itemElt, factElt, context), sPointer=sPointer, qnElt=factQname) except (IndexError, ValueError, AttributeError) as err: modelXbrl.error( "arelle:infosetTest", _("Invalid entity fact dimensions infoset sPointer: %(test)s, error details: %(error)s" ), modelObject=itemElt, test=XmlUtil.innerTextList(itemElt), error=str(err))
def stepAxis(self, op, p, sourceSequence): targetSequence = [] for node in sourceSequence: if not isinstance( node, (ModelObject, etree._ElementTree, ModelAttribute)): raise XPathException( self.progHeader, 'err:XPTY0020', _('Axis step {0} context item is not a node: {1}').format( op, node)) targetNodes = [] if isinstance(p, QNameDef): ns = p.namespaceURI localname = p.localName if p.isAttribute: if isinstance(node, ModelObject): attrTag = p.localName if p.unprefixed else p.clarkNotation modelAttribute = None try: modelAttribute = node.xAttributes[attrTag] except (AttributeError, TypeError, IndexError, KeyError): # may be lax or deferred validated try: validate(node.modelXbrl, node, p) modelAttribute = node.xAttributes[attrTag] except (AttributeError, TypeError, IndexError, KeyError): pass if modelAttribute is None: value = node.get(attrTag) if value is not None: targetNodes.append( ModelAttribute(node, p.clarkNotation, UNKNOWN, value, value, value)) elif modelAttribute.xValid >= VALID: targetNodes.append(modelAttribute) elif op == '/' or op is None: if isinstance(node, (ModelObject, etree._ElementTree)): targetNodes = XmlUtil.children(node, ns, localname) elif op == '//': if isinstance(node, (ModelObject, etree._ElementTree)): targetNodes = XmlUtil.descendants(node, ns, localname) elif op == '..': if isinstance(node, ModelAttribute): targetNodes = [node.modelElement] else: targetNodes = [XmlUtil.parent(node)] elif isinstance(p, OperationDef) and isinstance(p.name, QNameDef): if isinstance(node, ModelObject): if p.name.localName == "text": targetNodes = [XmlUtil.text(node)] # todo: add element, attribute, node, etc... elif p == '*': # wildcard if op == '/' or op is None: if isinstance(node, (ModelObject, etree._ElementTree)): targetNodes = XmlUtil.children(node, '*', '*') elif op == '//': if isinstance(node, (ModelObject, etree._ElementTree)): targetNodes = XmlUtil.descendants(node, '*', '*') targetSequence.extend(targetNodes) return targetSequence
def doObject(self, fObj, fromRel, pIndent, visited): if fObj is None: return cIndent = pIndent + " " if isinstance(fObj, ModelAssertionSet): self.xf = "{}assertion-set {} {{".format(pIndent, self.objectId(fObj, "assertionSet")) for modelRel in self.modelXbrl.relationshipSet(XbrlConst.assertionSet).fromModelObject(fObj): self.doObject(modelRel.toModelObject, modelRel, cIndent, visited) self.xf = "{}}};".format(pIndent) elif isinstance(fObj, (ModelValueAssertion, ModelExistenceAssertion, ModelFormula)): varSetType = "formula" if isinstance(fObj, ModelFormula) else "assertion" self.xf = "{}{} {} {{".format(pIndent, varSetType, self.objectId(fObj, varSetType)) for arcrole in (XbrlConst.elementLabel, XbrlConst.assertionSatisfiedMessage, XbrlConst.assertionUnsatisfiedMessage): for modelRel in self.modelXbrl.relationshipSet(arcrole).fromModelObject(fObj): self.doObject(modelRel.toModelObject, modelRel, cIndent, visited) if fObj.aspectModel == "non-dimensional": self.xf = "{}aspect-model-non-dimensional;".format(cIndent) if fObj.implicitFiltering == "false": self.xf = "{}no-implicit-filtering;".format(cIndent) if isinstance(fObj, ModelFormula): for attr in ("decimals", "precision", "value"): if fObj.get(attr): self.xf = "{}{} {{{}}};".format(cIndent, attr, fObj.get(attr)) if fObj.get("source"): self.xf = "{}source {};".format(cIndent, fObj.get("source")) for aspectsElt in XmlUtil.children(fObj, XbrlConst.formula, "aspects"): self.xf = "{}aspect-rules{} {{".format(cIndent, "source {}".format(aspectsElt.get("source")) if aspectsElt.get("source") else "") for ruleElt in XmlUtil.children(aspectsElt, XbrlConst.formula, "*"): self.doObject(ruleElt, None, cIndent + " ", visited) self.xf = "{}}};".format(cIndent) for arcrole in (XbrlConst.variableSetFilter, XbrlConst.variableSet, XbrlConst.variableSetPrecondition): for modelRel in self.modelXbrl.relationshipSet(arcrole).fromModelObject(fObj): self.doObject(modelRel.toModelObject, modelRel, cIndent, visited) if isinstance(fObj, ModelValueAssertion): self.xf = "{}test {{{}}};".format(cIndent, fObj.viewExpression) elif isinstance(fObj, ModelExistenceAssertion): self.xf = "{}evaluation-count {{{}}};".format(cIndent, fObj.viewExpression or ". gt 0") self.xf = "{}}};".format(pIndent) elif isinstance(fObj, ModelConsistencyAssertion): self.xf = "{}consistency-assertion {} {{".format(pIndent, self.objectId(fObj, "consistencyAssertion")) for arcrole in (XbrlConst.elementLabel, XbrlConst.assertionSatisfiedMessage, XbrlConst.assertionUnsatisfiedMessage): for modelRel in self.modelXbrl.relationshipSet(arcrole).fromModelObject(fObj): self.doObject(modelRel.toModelObject, modelRel, cIndent, visited) if fObj.isStrict: self.xf = "{}strict;".format(cIndent) if fObj.get("proportionalAcceptanceRadius"): self.xf = "{}proportional-acceptance-radius {{{}}};".format(cIndent, fObj.get("proportionalAcceptanceRadius")) if fObj.get("absoluteAcceptanceRadius"): self.xf = "{}absolute-acceptance-radius {{{}}};".format(cIndent, fObj.get("absoluteAcceptanceRadius")) for modelRel in self.modelXbrl.relationshipSet(XbrlConst.consistencyAssertionFormula).fromModelObject(fObj): self.doObject(modelRel.toModelObject, modelRel, cIndent, visited) self.xf = "{}}};".format(pIndent) elif isinstance(fObj, ModelFactVariable) and fromRel is not None: self.xf = "{}variable ${} {{".format(pIndent, fromRel.variableQname) if fromRel.variableQname.prefix: self.xmlns[fromRel.variableQname.prefix] = fromRel.variableQname.namespaceURI if fObj.bindAsSequence == "true": self.xf = "{}bind-as-sequence".format(cIndent) if fObj.nils == "true": self.xf = "{}nils".format(cIndent) if fObj.matches == "true": self.xf = "{}matches".format(cIndent) if fObj.fallbackValue: self.xf = "{}fallback {{{}}}".format(cIndent, fObj.fallbackValue) for modelRel in self.modelXbrl.relationshipSet(XbrlConst.variableFilter).fromModelObject(fObj): self.doObject(modelRel.toModelObject, modelRel, cIndent, visited) self.xf = "{}}};".format(pIndent) elif isinstance(fObj, ModelGeneralVariable) and fromRel is not None: self.xf = "{}variable ${} {{".format(pIndent, fromRel.variableQname) if fromRel.variableQname.prefix: self.xmlns[fromRel.variableQname.prefix] = fromRel.variableQname.namespaceURI if fObj.bindAsSequence: self.xf = "{}bind-as-sequence".format(cIndent) self.xf = "{}select {{{}}}".format(cIndent, fObj.select) elif isinstance(fObj, ModelParameter): if fromRel is not None: # parameter is referenced by a different QName on arc if fromRel.variableQname.prefix: self.xmlns[fromRel.variableQname.prefix] = fromRel.variableQname.namespaceURI self.xf = "{}parameter ${} references ${};".format(pIndent, fromRel.variableQname, fObj.parameterQname) else: # root level parameter if fObj.parameterQname.prefix: self.xmlns[fObj.parameterQname.prefix] = fObj.parameterQname.namespaceURI self.xf = "{}parameter {} {{".format(pIndent, fObj.parameterQname) if fObj.isRequired: self.xf = "{}required".format(cIndent) self.xf = "{} select {{{}}}".format(cIndent, fObj.select) if fObj.asType: self.xf = "{} as {{{}}}".format(cIndent, fObj.asType) if fObj.asType.prefix: self.xmlns[fObj.asType.prefix] = fObj.asType.namespaceURI self.xf = "{}}};".format(pIndent) elif isinstance(fObj, ModelFilter): if fromRel.isComplemented: self.xf = "{}complemented".format(pIndent) if not fromRel.isCovered and fromRel.localName == "variableFilterArc": self.xf = "{}non-covering".format(pIndent) if isinstance(fObj, ModelConceptName): if len(fObj.conceptQnames) == 1 and not fObj.qnameExpressions: qn = next(iter(fObj.conceptQnames)) self.xmlns[qn.prefix] = qn.namespaceURI self.xf = "{}concept-name {};".format(pIndent, qn) elif len(fObj.qnameExpressions) == 1 and not fObj.conceptQnames: self.xf = "{}concept-name {{{}}};".format(pIndent, fObj.qnameExpressions[0]) else: self.xf = "{}concept-name".format(pIndent) for qn in fObj.conceptQnames: self.xmlns[qn.prefix] = qn.namespaceURI self.xf = "{} {}".format(pIndent, qn) for qnExpr in fObj.qnameExpressions: self.xf = "{} {}".format(pIndent, qnExpr) self.xf = "{} ;".format(pIndent) elif isinstance(fObj, ModelConceptPeriodType): self.xf = "{}concept-period {};".format(pIndent, fObj.periodType) elif isinstance(fObj, ModelConceptBalance): self.xf = "{}concept-balance {};".format(pIndent, fObj.balance) elif isinstance(fObj, (ModelConceptDataType, ModelConceptSubstitutionGroup)): self.xf = "{}{} {} {};".format(pIndent, kebabCase(fObj.localName), "strict" if fObj.strict == "true" else "non-strict", fObj.filterQname if fObj.filterQname else "{{{}}}".format(fObj.qnameExpression)) elif isinstance(fObj, ModelExplicitDimension): members = [] for memberElt in XmlUtil.children(fObj, XbrlConst.df, "member"): members.append("member") member = XmlUtil.childText(memberElt, XbrlConst.df, "qname") if member: member = str(member) # qname, must coerce to string else: member = XmlUtil.childText(memberElt, XbrlConst.df, "qnameExpression") if member: member = "{{{}}}".format(member) else: member = "$" + XmlUtil.childText(memberElt, XbrlConst.df, "variable") members.append(member) linkrole = XmlUtil.childText(memberElt, XbrlConst.df, "linkrole") if linkrole: members.append("linkrole") members.append("\"{}\"".format(linkrole)) arcrole = XmlUtil.childText(memberElt, XbrlConst.df, "arcrole") if arcrole: members.append("arcrole") members.append("\"{}\"".format(arcrole)) axis = XmlUtil.childText(memberElt, XbrlConst.df, "axis") if axis: members.append("axis") members.append(axis) self.xf = "{}explicit-dimension {}{};".format(pIndent, fObj.dimQname or ("{{{}}}".format(fObj.dimQnameExpression) if fObj.dimQnameExpression else ""), " ".join(members)) elif isinstance(fObj, ModelTypedDimension): # this is a ModelTestFilter not same as genera/unit/period self.xf = "{}typed-dimension {}{};".format(pIndent, fObj.dimQname or ("{{{}}}".format(fObj.dimQnameExpression) if fObj.dimQnameExpression else ""), " {{{}}}".format(fObj.test) if fObj.test else "") elif isinstance(fObj, ModelTestFilter): self.xf = "{}{} {{{}}};".format(pIndent, "general" if isinstance(fObj, ModelGeneral) else "unit-general-measures" if isinstance(fObj, ModelGeneralMeasures) else "period" if isinstance(fObj, ModelPeriod) else "entity-identifier" if isinstance(fObj, ModelIdentifier) else None, fObj.test) elif isinstance(fObj, ModelDateTimeFilter): self.xf = "{}{} {{{}}}{};".format(pIndent, kebabCase(fObj.localName), fObj.date, " {{{}}}".format(fObj.time) if fObj.time else "") elif isinstance(fObj, ModelInstantDuration): self.xf = "{}instant-duration {} {};".format(pIndent, fObj.boundary, fObj.variable) elif isinstance(fObj, ModelSingleMeasure): self.xf = "{}unit-single-measure {};".format(pIndent, fObj.measureQname or ("{{{}}}".format(fObj.qnameExpression) if fObj.qnameExpression else "")) elif isinstance(fObj, ModelEntitySpecificIdentifier): self.xf = "{}entity scheme {{{}}} value {{{}}};".format(pIndent, fObj.scheme, fObj.value) elif isinstance(fObj, ModelEntityScheme): self.xf = "{}entity-scheme {{{}}};".format(pIndent, fObj.scheme) elif isinstance(fObj, ModelEntityRegexpScheme): self.xf = "{}entity-scheme-pattern \"{}\";".format(pIndent, fObj.pattern) elif isinstance(fObj, ModelEntityRegexpIdentifier): self.xf = "{}entity-identifier-pattern \"{}\";".format(pIndent, fObj.pattern) elif isinstance(fObj, ModelMatchFilter): self.xf = "{}{} ${} {}{};".format(pIndent, kebabCase(fObj.localName), fObj.variable, " dimension {}".format(fObj.dimension) if fObj.get("dimension") else "", " match-any" if fObj.matchAny else "") elif isinstance(fObj, ModelRelativeFilter): self.xf = "{}relative ${};".format(pIndent, fObj.variable) elif isinstance(fObj, ModelAncestorFilter): self.xf = "{}ancestor {};".format(pIndent, fObj.ancestorQname or ("{{{}}}".format(fObj.qnameExpression) if fObj.qnameExpression else "")) elif isinstance(fObj, ModelParentFilter): self.xf = "{}parent {};".format(pIndent, fObj.parentQname or ("{{{}}}".format(fObj.qnameExpression) if fObj.qnameExpression else "")) elif isinstance(fObj, ModelSiblingFilter): self.xf = "{}sibling ${};".format(pIndent, fObj.variable) elif isinstance(fObj, ModelNilFilter): self.xf = "{}nilled;".format(pIndent) elif isinstance(fObj, ModelAspectCover): aspects = [] for aspectElt in XmlUtil.children(fObj, XbrlConst.acf, "aspect"): aspects.append(XmlUtil.text(aspectElt)) for dimElt in XmlUtil.descendants(fObj, XbrlConst.acf, ("qname", "qnameExpression")): dimAspect = qname( dimElt, XmlUtil.text(dimElt) ) aspects.append("exclude-dimension" if dimElt.getparent().localName == "excludeDimension" else "dimension") if dimElt.localName == "qname": aspects.append(str(qname( dimElt, XmlUtil.text(dimElt) ))) else: aspects.append("{{{}}}".format(XmlUtil.text(dimElt))) self.xf = "{}aspect-cover {};".format(pIndent, " ".join(aspects)) elif isinstance(fObj, ModelConceptRelation): conceptRelationTerms = [] if fObj.sourceQname: conceptRelationTerms.append(fObj.sourceQname) elif fObj.variable: conceptRelationTerms.append("$" + fObj.variable) else: conceptRelationTerms.append("{{{}}}".format(fObj.sourceQnameExpression)) if fObj.linkrole: conceptRelationTerms.append("linkrole") conceptRelationTerms.append(fObj.linkrole) elif fObj.linkroleExpression: conceptRelationTerms.append("linkrole") conceptRelationTerms.append("{{{}}}".format(fObj.linkroleExpression)) if fObj.arcrole: conceptRelationTerms.append("arcrole") conceptRelationTerms.append(fObj.arcrole) elif fObj.arcroleExpression: conceptRelationTerms.append("arcrole") conceptRelationTerms.append("{{{}}}".format(fObj.arcroleExpression)) if fObj.axis: conceptRelationTerms.append("axis") conceptRelationTerms.append(fObj.axis) if fObj.generations is not None: conceptRelationTerms.append("generations {}".format(fObj.generations)) if fObj.test: conceptRelationTerms.append("test") conceptRelationTerms.append("{{{}}}".format(fObj.test)) self.xf = "{}concept-relation {};".format(pIndent, " ".join(conceptRelationTerms)) elif isinstance(fObj, (ModelAndFilter, ModelOrFilter)): self.xf = "{}{} {{".format(pIndent, "and" if isinstance(fObj, ModelAndFilter)else "or") if fObj not in visited: visited.add(fObj) for modelRel in self.modelXbrl.relationshipSet(XbrlConst.booleanFilter).fromModelObject(fObj): self.doObject(modelRel.toModelObject, modelRel, cIndent, visited) visited.remove(fObj) self.xf = "{}}};".format(pIndent) elif isinstance(fObj, ModelMessage): self.xf = "{}{}{} \"{}\";".format( pIndent, "satisfied-message" if fromRel.arcrole == XbrlConst.assertionSatisfiedMessage else "unsatisfied-message", " ({})".format(fObj.xmlLang) if fObj.xmlLang else "", fObj.text.replace('"', '""')) elif isinstance(fObj, ModelCustomFunctionSignature): hasImplememntation = False if fObj not in visited: visited.add(fObj) for modelRel in self.modelXbrl.relationshipSet(XbrlConst.functionImplementation).fromModelObject(fObj): self.doObject(modelRel.toModelObject, modelRel, pIndent, visited) # note: use pIndent as parent doesn't show hasImplementation = True visited.remove(fObj) if not hasImplementation: self.xmlns[fObj.functionQname.prefix] = fObj.functionQname.namespaceURI self.xf = "{}abstract-function {}({}) as {};".format(pIndent, fObj.name, ", ".join(str(t) for t in fObj.inputTypes), fObj.outputType) elif isinstance(fObj, ModelCustomFunctionImplementation): sigObj = fromRel.fromModelObject self.xmlns[sigObj.functionQname.prefix] = sigObj.functionQname.namespaceURI self.xf = "{}function {}({}) as {} {{".format(pIndent, sigObj.name, ", ".join("{} as {}".format(inputName, sigObj.inputTypes[i]) for i, inputName in enumerate(fObj.inputNames)), sigObj.outputType) for name, stepExpr in fObj.stepExpressions: if "\n" not in stepExpr: self.xf = "{}step ${} {{{}}};".format(cIndent, name, stepExpr) else: self.xf = "{}step ${} {{".format(cIndent, name) for exprLine in stepExpr.split("\n"): self.xf = "{} {}".format(cIndent, exprLine.lstrip()) self.xf = "{}}};".format(cIndent) self.xf = "{}return {{{}}};".format(cIndent, fObj.outputExpression) self.xf = "{}}};".format(pIndent) elif fObj.getparent().tag == "{http://xbrl.org/2008/formula}aspects": # aspect rules arg = "" if fObj.localName == "concept": if XmlUtil.hasChild(fObj, None, "qname"): arg += " " + XmlUtil.childText(fObj, None, "qname") elif XmlUtil.hasChild(fObj, None, "qnameExpression"): arg += " {" + XmlUtil.childText(fObj, None, "qnameExpression") + "}" elif fObj.localName == "entityIdentifier": if fObj.get("scheme"): arg += " scheme {" + fObj.get("scheme") + "}" if fObj.get("identifier"): arg += " identifier {" + fObj.get("identifier") + "}" elif fObj.localName == "period": if XmlUtil.hasChild(fObj, None, "forever"): arg += " forever" if XmlUtil.hasChild(fObj, None, "instant"): arg += " instant" attr = XmlUtil.childAttr(fObj, None, "instant", "value") if attr: arg += "{" + attr + "}" if XmlUtil.hasChild(fObj, None, "duration"): arg += " duration" attr = XmlUtil.childAttr(fObj, None, "duration", "start") if attr: arg += " start {" + attr + "}" attr = XmlUtil.childAttr(fObj, None, "duration", "end") if attr: arg += " end {" + attr + "}" elif fObj.localName == "unit": if fObj.get("augment") == "true": arg += " augment" if fObj.localName in ("explicitDimension", "typedDimension"): arg += " dimension " + fObj.get("dimension") if fObj.localName in ("concept", "entityIdentifier", "period"): arg += ";" else: arg += " {" self.xf = "{}{}{}".format(pIndent, kebabCase(fObj.localName), arg) if fObj.localName == "unit": for elt in fObj.iterchildren(): arg = "" if elt.get("source"): arg += " source " + elt.get("source") if elt.get("measure"): arg += " measure {" + elt.get("measure") + "}" self.xf = "{}{}{};".format(cIndent, kebabCase(elt.localName), arg) elif fObj.localName == "explicitDimension": for elt in fObj.iterchildren(): arg = "" if XmlUtil.hasChild(elt, None, "qname"): arg += " " + XmlUtil.childText(elt, None, "qname") elif XmlUtil.hasChild(elt, None, "qnameExpression"): arg += " {" + XmlUtil.childText(elt, None, "qnameExpression") + "}" self.xf = "{}{}{};".format(cIndent, kebabCase(elt.localName), arg) elif fObj.localName == "typedDimension": for elt in fObj.iterchildren(): arg = "" if XmlUtil.hasChild(elt, None, "xpath"): arg += " xpath {" + ModelXbrl.childText(elt, None, "xpath") + "}" elif XmlUtil.hasChild(elt, None, "value"): arg += " value " + strQoute(XmlUtil.xmlstring(XmlUtil.child(elt, None, "value"), stripXmlns=True, contentsOnly=False)) self.xf = "{}{}{};".format(cIndent, kebabCase(elt.localName), arg) if fObj.localName not in ("concept", "entityIdentifier", "period"): self.xf = "{}}};".format(pIndent) # check for prefixes in AST of programs of fObj if hasattr(fObj, "compile") and type(fObj.compile).__name__ == "method": fObj.compile() for _prog, _ast in fObj.__dict__.items(): if _prog.endswith("Prog") and isinstance(_ast, list): XPathParser.prefixDeclarations(_ast, self.xmlns, fObj)
def checkFilingDTS(val, modelDocument, isEFM, isGFM, visited): global targetNamespaceDatePattern, efmFilenamePattern, htmlFileNamePattern, roleTypePattern, arcroleTypePattern, \ arcroleDefinitionPattern, namePattern, linkroleDefinitionBalanceIncomeSheet if targetNamespaceDatePattern is None: targetNamespaceDatePattern = re.compile( r"/([12][0-9]{3})-([01][0-9])-([0-3][0-9])|" r"/([12][0-9]{3})([01][0-9])([0-3][0-9])|") efmFilenamePattern = re.compile( r"^[a-z0-9][a-zA-Z0-9_\.\-]*(\.xsd|\.xml|\.htm)$") htmlFileNamePattern = re.compile( r"^[a-zA-Z0-9][._a-zA-Z0-9-]*(\.htm)$") roleTypePattern = re.compile(r"^.*/role/[^/\s]+$") arcroleTypePattern = re.compile(r"^.*/arcrole/[^/\s]+$") arcroleDefinitionPattern = re.compile( r"^.*[^\\s]+.*$") # at least one non-whitespace character namePattern = re.compile( "[][()*+?\\\\/^{}|@#%^=~`\"';:,<>&$\u00a3\u20ac]" ) # u20ac=Euro, u00a3=pound sterling linkroleDefinitionBalanceIncomeSheet = re.compile( r"[^-]+-\s+Statement\s+-\s+.*(income|balance|financial\W+position)", re.IGNORECASE) nonDomainItemNameProblemPattern = re.compile( r"({0})|(FirstQuarter|SecondQuarter|ThirdQuarter|FourthQuarter|[1-4]Qtr|Qtr[1-4]|ytd|YTD|HalfYear)(?:$|[A-Z\W])" .format(re.sub(r"\W", "", (val.entityRegistrantName or "").title()))) visited.append(modelDocument) for referencedDocument, modelDocumentReference in modelDocument.referencesDocument.items( ): #6.07.01 no includes if modelDocumentReference.referenceType == "include": val.modelXbrl.error( ("EFM.6.07.01", "GFM.1.03.01"), _("Taxonomy schema %(schema)s includes %(include)s, only import is allowed" ), modelObject=modelDocumentReference.referringModelObject, schema=modelDocument.basename, include=referencedDocument.basename) if referencedDocument not in visited and ( referencedDocument.inDTS or referencedDocument.type == ModelDocument.Type.INLINEXBRLDOCUMENTSET ): # ignore EdgarRenderer added non-DTS documents checkFilingDTS(val, referencedDocument, isEFM, isGFM, visited) if modelDocument.type == ModelDocument.Type.INLINEXBRLDOCUMENTSET: return # nothing to check in inline document set surrogate parent if val.disclosureSystem.standardTaxonomiesDict is None: pass if isEFM: if modelDocument.uri in val.disclosureSystem.standardTaxonomiesDict: if modelDocument.targetNamespace: # check for duplicates of us-types, dei, and rr taxonomies match = standardNamespacesPattern.match( modelDocument.targetNamespace) if match is not None: conflictClass = match.group(2) or match.group(5) if (conflictClass == 'us-gaap' and match.group(3) < '2018') or conflictClass == 'srt': val.standardNamespaceConflicts['srt+us-gaap'].add( modelDocument ) # ignore non-srt multi-usgaap in Filing.py if conflictClass == 'us-gaap' or conflictClass == 'ifrs-full': val.standardNamespaceConflicts['ifrs+us-gaap'].add( modelDocument) if conflictClass not in ('us-gaap', 'srt'): val.standardNamespaceConflicts[conflictClass].add( modelDocument) else: if len(modelDocument.basename) > 32: val.modelXbrl.error( "EFM.5.01.01.tooManyCharacters", _("Document file name %(filename)s must not exceed 32 characters." ), edgarCode="cp-0501-File-Name-Length", modelObject=modelDocument, filename=modelDocument.basename) if modelDocument.type != ModelDocument.Type.INLINEXBRLDOCUMENTSET: if modelDocument.type == ModelDocument.Type.INLINEXBRL: _pattern = htmlFileNamePattern _suffix = ".htm" else: _pattern = efmFilenamePattern _suffix = ".xsd or .xml" if not _pattern.match(modelDocument.basename): val.modelXbrl.error( "EFM.5.01.01", _("Document file name %(filename)s must start with a-z or 0-9, contain upper or lower case letters, ., -, _, and end with %(suffix)s." ), edgarCode="cp-0501-File-Name", modelObject=modelDocument, filename=modelDocument.basename, suffix=_suffix) if (modelDocument.type == ModelDocument.Type.SCHEMA and modelDocument.targetNamespace not in val.disclosureSystem.baseTaxonomyNamespaces and modelDocument.uri.startswith(val.modelXbrl.uriDir)): val.hasExtensionSchema = True # check schema contents types # 6.7.3 check namespace for standard authority targetNamespaceAuthority = UrlUtil.authority( modelDocument.targetNamespace) if targetNamespaceAuthority in val.disclosureSystem.standardAuthorities: val.modelXbrl.error( ("EFM.6.07.03", "GFM.1.03.03"), _("The target namespace, %(targetNamespace)s cannot have the same authority (%(targetNamespaceAuthority)s) as a standard " "taxonomy, in %(schema)s. Please change your target namespace." ), edgarCode="fs-0703-Extension-Has-Standard-Namespace-Authority", modelObject=modelDocument, schema=modelDocument.basename, targetNamespace=modelDocument.targetNamespace, targetNamespaceAuthority=UrlUtil.authority( modelDocument.targetNamespace, includeScheme=False)) # 6.7.4 check namespace format if modelDocument.targetNamespace is None or not modelDocument.targetNamespace.startswith( "http://"): match = None else: targetNamespaceDate = modelDocument.targetNamespace[ len(targetNamespaceAuthority):] match = targetNamespaceDatePattern.match(targetNamespaceDate) if match is not None: try: if match.lastindex == 3: date = datetime.date(int(match.group(1)), int(match.group(2)), int(match.group(3))) elif match.lastindex == 6: date = datetime.date(int(match.group(4)), int(match.group(5)), int(match.group(6))) else: match = None except ValueError: match = None if match is None: val.modelXbrl.error( ("EFM.6.07.04", "GFM.1.03.04"), _("You did not adhere to the requirements for target namespace, for %(targetNamespace)s in %(schema)s. " "Please recheck your submission and adhere to the target namespace requirements." ), edgarCode="cp-0704-Taxonomy-Valid-Target-Namespace", modelObject=modelDocument, schema=modelDocument.basename, targetNamespace=modelDocument.targetNamespace) elif val.fileNameDate and date > val.fileNameDate: val.modelXbrl.info( ("EFM.6.07.06", "GFM.1.03.06"), _("Warning: Taxonomy schema %(schema)s namespace %(targetNamespace)s has date later than document name date %(docNameDate)s" ), modelObject=modelDocument, schema=modelDocument.basename, targetNamespace=modelDocument.targetNamespace, docNameDate=val.fileNameDate) if modelDocument.targetNamespace is not None: # 6.7.5 check prefix for _ authority = UrlUtil.authority(modelDocument.targetNamespace) if not re.match(r"(http://|https://|ftp://|urn:)\w+", authority): val.modelXbrl.error( ("EFM.6.07.05", "GFM.1.03.05"), _("Taxonomy schema %(schema)s namespace %(targetNamespace)s must be a valid URI with a valid authority for the namespace." ), edgarCode="du-0705-Namespace-Authority", modelObject=modelDocument, schema=modelDocument.basename, targetNamespace=modelDocument.targetNamespace) # may be multiple prefixes for namespace prefixes = [ (prefix if prefix is not None else "") for prefix, NS in modelDocument.xmlRootElement.nsmap.items() if NS == modelDocument.targetNamespace ] if not prefixes: prefix = None val.modelXbrl.error( ("EFM.6.07.07", "GFM.1.03.07"), _("The schema does not supply a prefix for %(targetNamespace)s without an underscore character, in file %(schema)s. " "Please provide or change a prefix"), edgarCode="du-0707-Recommended-Prefix-Disallowed", modelObject=modelDocument, schema=modelDocument.basename, targetNamespace=modelDocument.targetNamespace) else: prefix = prefixes[0] if len(prefixes) > 1: val.modelXbrl.error( ("EFM.6.07.07", "GFM.1.03.07"), _("The schema does not supply a prefix for %(targetNamespace)s without an underscore character, in file %(schema)s. " "Please provide or change a prefix"), edgarCode="du-0707-Recommended-Prefix-Disallowed", modelObject=modelDocument, schema=modelDocument.basename, targetNamespace=modelDocument.targetNamespace, prefix=", ".join(prefixes)) elif "_" in prefix: val.modelXbrl.error( ("EFM.6.07.07", "GFM.1.03.07"), _("The schema does not supply a prefix for %(targetNamespace)s without an underscore character, in file %(schema)s. " "Please provide or change a prefix"), edgarCode="du-0707-Recommended-Prefix-Disallowed", modelObject=modelDocument, schema=modelDocument.basename, targetNamespace=modelDocument.targetNamespace, prefix=prefix) for modelConcept in modelDocument.xmlRootElement.iterdescendants( tag="{http://www.w3.org/2001/XMLSchema}element"): if isinstance(modelConcept, ModelConcept): # 6.7.16 name not duplicated in standard taxonomies name = modelConcept.get("name") if name is None: name = "" if modelConcept.get("ref") is not None: continue # don't validate ref's here for c in val.modelXbrl.nameConcepts.get(name, []): if c.modelDocument != modelDocument: if not c.modelDocument.uri.startswith( val.modelXbrl.uriDir): val.modelXbrl.error( ("EFM.6.07.16", "GFM.1.03.18"), _("Your extension taxonomy contains an element, %(concept)s, which has the same name as an element in the base taxonomy, " "%(standardConcept)s. Please ensure that this extension is appropriate and if so, please change the extension concept name." ), edgarCode= "cp-0716-Element-Name-Same-As-Base", modelObject=(modelConcept, c), concept=modelConcept.qname, standardSchema=c.modelDocument.basename, standardConcept=c.qname) # 6.7.17 id properly formed _id = modelConcept.id requiredId = (prefix if prefix is not None else "") + "_" + name if _id != requiredId: val.modelXbrl.error( ("EFM.6.07.17", "GFM.1.03.19"), _("You did not adhere to the declarations for concepts by containing an 'id' attribute whose value begins with the recommended " "namespace prefix of the taxonomy, followed by an underscore, followed by an element name, for the concept %(concept)s. " "Please recheck your submission."), edgarCode="cp-0717-Element-Id", modelObject=modelConcept, concept=modelConcept.qname, id=_id, requiredId=requiredId) # 6.7.18 nillable is true nillable = modelConcept.get("nillable") if nillable != "true" and modelConcept.isItem: val.modelXbrl.error( ("EFM.6.07.18", "GFM.1.03.20"), _("Element %(concept)s is declared without a 'true' value for the nillable attribute. Please set the value to 'true'." ), edgarCode="du-0718-Nillable-Not-True", modelObject=modelConcept, schema=modelDocument.basename, concept=name, nillable=nillable) # 6.7.19 not tuple if modelConcept.isTuple: val.modelXbrl.error( ("EFM.6.07.19", "GFM.1.03.21"), _("You provided an extension concept which is a tuple, %(concept)s. Please remove tuples and check your submission." ), edgarCode="cp-0719-No-Tuple-Element", modelObject=modelConcept, concept=modelConcept.qname) # 6.7.20 no typed domain ref if modelConcept.isTypedDimension: val.modelXbrl.error( ("EFM.6.07.20", "GFM.1.03.22"), _("There is an xbrldt:typedDomainRef attribute on %(concept)s (%(typedDomainRef)s). Please remove it." ), edgarCode="du-0720-Typed-Domain-Ref-Disallowed", modelObject=modelConcept, concept=modelConcept.qname, typedDomainRef=modelConcept.typedDomainElement. qname if modelConcept.typedDomainElement is not None else modelConcept.typedDomainRef) # 6.7.21 abstract must be duration isDuration = modelConcept.periodType == "duration" if modelConcept.isAbstract and not isDuration: val.modelXbrl.error( ("EFM.6.07.21", "GFM.1.03.23"), _("Element %(concept)s is declared as an abstract item with period type 'instant'. Please change it to 'duration' or " "make the element not abstract."), edgarCode="du-0721-Abstract-Is-Instant", modelObject=modelConcept, schema=modelDocument.basename, concept=modelConcept.qname) # 6.7.22 abstract must be stringItemType ''' removed SEC EFM v.17, Edgar release 10.4, and GFM 2011-04-08 if modelConcept.abstract == "true" and modelConcept.typeQname != XbrlConst. qnXbrliStringItemType: val.modelXbrl.error(("EFM.6.07.22", "GFM.1.03.24"), _("Concept %(concept)s is abstract but type is not xbrli:stringItemType"), modelObject=modelConcept, concept=modelConcept.qname) ''' substitutionGroupQname = modelConcept.substitutionGroupQname # 6.7.23 Axis must be subs group dimension if name.endswith("Axis") ^ ( substitutionGroupQname == XbrlConst.qnXbrldtDimensionItem): val.modelXbrl.error( ("EFM.6.07.23", "GFM.1.03.25"), _("The substitution group 'xbrldt:dimensionItem' is only consistent with an element name that ends with 'Axis'. " "Please change %(conceptLocalName)s or change the substitutionGroup." ), edgarCode="du-0723-Axis-Dimension-Name-Mismatch", modelObject=modelConcept, concept=modelConcept.qname, conceptLocalName=modelConcept.qname.localName) # 6.7.24 Table must be subs group hypercube if name.endswith("Table") ^ ( substitutionGroupQname == XbrlConst.qnXbrldtHypercubeItem): val.modelXbrl.error( ("EFM.6.07.24", "GFM.1.03.26"), _("The substitution group 'xbrldt:hypercubeItem' is only allowed with an element name that ends with 'Table'. " "Please change %(conceptLocalName)s or change the substitutionGroup." ), edgarCode="du-0724-Table-Hypercube-Name-Mismatch", modelObject=modelConcept, schema=modelDocument.basename, concept=modelConcept.qname, conceptLocalName=modelConcept.qname.localName) # 6.7.25 if neither hypercube or dimension, substitution group must be item if substitutionGroupQname not in ( None, XbrlConst.qnXbrldtDimensionItem, XbrlConst.qnXbrldtHypercubeItem, XbrlConst.qnXbrliItem): val.modelXbrl.error( ("EFM.6.07.25", "GFM.1.03.27"), _("The substitution group attribute value %(substitutionGroup)s of element %(conceptLocalName)s is not allowed. " "Please change it to one of 'xbrli:item', 'xbrldt:dimensionItem' or 'xbrldt:hypercubeItem'." ), edgarCode="du-0725-Substitution-Group-Custom", modelObject=modelConcept, concept=modelConcept.qname, conceptLocalName=modelConcept.qname.localName, substitutionGroup=modelConcept. substitutionGroupQname) # 6.7.26 Table must be subs group hypercube if name.endswith( "LineItems") and modelConcept.abstract != "true": val.modelXbrl.error( ("EFM.6.07.26", "GFM.1.03.28"), _("The element %(conceptLocalName)s ends with 'LineItems' but is not abstract. Please change %(conceptLocalName)s or " "the value of the 'abstract' attribute."), edgarCode= "du-0726-LineItems-Abstract-Name-Mismatch", modelObject=modelConcept, concept=modelConcept.qname, conceptLocalName=modelConcept.qname.localName) # 6.7.27 type domainMember must end with Domain or Member conceptType = modelConcept.type isDomainItemType = conceptType is not None and conceptType.isDomainItemType endsWithDomainOrMember = name.endswith( "Domain") or name.endswith("Member") if isDomainItemType != endsWithDomainOrMember: val.modelXbrl.error( ("EFM.6.07.27", "GFM.1.03.29"), _("The type 'us-types:domainItemType' is only allowed with an element name that ends with 'Domain' or 'Member'. " "Please change %(conceptLocalName)s or change the type." ), edgarCode="du-0727-Domain-Type-Name-Mismatch", modelObject=modelConcept, concept=modelConcept.qname, conceptLocalName=modelConcept.qname.localName) # 6.7.28 domainItemType must be duration if isDomainItemType and not isDuration: val.modelXbrl.error( ("EFM.6.07.28", "GFM.1.03.30"), _("Element %(conceptLocalName)s is declared as a us-types:domainItemType with period type 'instant'. " "Please change it to 'duration' or change the item type." ), edgarCode="du-0728-Domain-Member-Is-Instant", modelObject=modelConcept, concept=modelConcept.qname, conceptLocalName=modelConcept.qname.localName) #6.7.31 (version 27) fractions if modelConcept.isFraction: val.modelXbrl.error( "EFM.6.07.31", _("Element %(concept)s is declared as a fraction item type. Change or remove the declaration." ), edgarCode="du-0731-Fraction-Item-Type", modelObject=modelConcept, concept=modelConcept.qname) #6.7.32 (version 27) instant non numeric if modelConcept.isItem and (not modelConcept.isNumeric and not isDuration and not modelConcept.isAbstract and not isDomainItemType): val.modelXbrl.error( "EFM.6.07.32", _("Declaration of element %(concept)s in %(schema)s must have xbrli:periodType of 'duration' because its base type is not numeric." ), edgarCode="rq-0732-Nonnnumeric-Must-Be-Duration", modelObject=modelConcept, schema=modelDocument.basename, concept=modelConcept.qname) # 6.8.5 semantic check, check LC3 name if name: if not name[0].isupper(): val.modelXbrl.log( "ERROR-SEMANTIC", ("EFM.6.08.05.firstLetter", "GFM.2.03.05.firstLetter"), _("Concept %(concept)s name must start with a capital letter" ), modelObject=modelConcept, concept=modelConcept.qname) if namePattern.search(name): val.modelXbrl.log( "ERROR-SEMANTIC", ("EFM.6.08.05.disallowedCharacter", "GFM.2.03.05.disallowedCharacter"), _("Concept %(concept)s has disallowed name character" ), modelObject=modelConcept, concept=modelConcept.qname) if len(name) > 200: val.modelXbrl.log( "ERROR-SEMANTIC", "EFM.6.08.05.nameLength", _("Concept %(concept)s name length %(namelength)s exceeds 200 characters" ), modelObject=modelConcept, concept=modelConcept.qname, namelength=len(name)) if isEFM: label = modelConcept.label(lang="en-US", fallbackToQname=False) if label: # allow Joe's Bar, N.A. to be JoesBarNA -- remove ', allow A. as not article "a" lc3name = ''.join( re.sub(r"['.-]", "", ( w[0] or w[2] or w[3] or w[4])).title() for w in re.findall( r"((\w+')+\w+)|(A[.-])|([.-]A(?=\W|$))|(\w+)", label ) # EFM implies this should allow - and . re.findall(r"[\w\-\.]+", label) if w[4].lower() not in ("the", "a", "an")) if not (name == lc3name or (name and lc3name and lc3name[0].isdigit() and name[1:] == lc3name and (name[0].isalpha() or name[0] == '_'))): val.modelXbrl.log( "WARNING-SEMANTIC", "EFM.6.08.05.LC3", _("Concept %(concept)s should match expected LC3 composition %(lc3name)s" ), modelObject=modelConcept, concept=modelConcept.qname, lc3name=lc3name) if conceptType is not None: # 6.8.6 semantic check if not isDomainItemType and conceptType.qname != XbrlConst.qnXbrliDurationItemType: nameProblems = nonDomainItemNameProblemPattern.findall( name) if any( any(t) for t in nameProblems ): # list of tuples with possibly nonempty strings val.modelXbrl.log( "WARNING-SEMANTIC", ("EFM.6.08.06", "GFM.2.03.06"), _("Concept %(concept)s should not contain company or period information, found: %(matches)s" ), modelObject=modelConcept, concept=modelConcept.qname, matches=", ".join(''.join(t) for t in nameProblems)) if conceptType.qname == XbrlConst.qnXbrliMonetaryItemType: if not modelConcept.balance: # 6.8.11 may not appear on a income or balance statement if any( linkroleDefinitionBalanceIncomeSheet. match(roleType.definition) for rel in val.modelXbrl. relationshipSet(XbrlConst.parentChild). toModelObject(modelConcept) for roleType in val.modelXbrl. roleTypes.get(rel.linkrole, ())): val.modelXbrl.log( "ERROR-SEMANTIC", ("EFM.6.08.11", "GFM.2.03.11"), _("Concept %(concept)s must have a balance because it appears in a statement of income or balance sheet" ), modelObject=modelConcept, concept=modelConcept.qname) # 6.11.5 semantic check, must have a documentation label stdLabel = modelConcept.label( lang="en-US", fallbackToQname=False) defLabel = modelConcept.label( preferredLabel=XbrlConst. documentationLabel, lang="en-US", fallbackToQname=False) if not defLabel or ( # want different words than std label stdLabel and re.findall(r"\w+", stdLabel) == re.findall(r"\w+", defLabel)): val.modelXbrl.log( "ERROR-SEMANTIC", ("EFM.6.11.05", "GFM.2.04.04"), _("Concept %(concept)s is monetary without a balance and must have a documentation label that disambiguates its sign" ), modelObject=modelConcept, concept=modelConcept.qname) # 6.8.16 semantic check if conceptType.qname == XbrlConst.qnXbrliDateItemType and modelConcept.periodType != "duration": val.modelXbrl.log( "ERROR-SEMANTIC", ("EFM.6.08.16", "GFM.2.03.16"), _("Concept %(concept)s of type xbrli:dateItemType must have periodType duration" ), modelObject=modelConcept, concept=modelConcept.qname) # 6.8.17 semantic check if conceptType.qname == XbrlConst.qnXbrliStringItemType and modelConcept.periodType != "duration": val.modelXbrl.log( "ERROR-SEMANTIC", ("EFM.6.08.17", "GFM.2.03.17"), _("Concept %(concept)s of type xbrli:stringItemType must have periodType duration" ), modelObject=modelConcept, concept=modelConcept.qname) # 6.7.8 check for embedded linkbase for e in modelDocument.xmlRootElement.iterdescendants( tag="{http://www.xbrl.org/2003/linkbase}linkbase"): if isinstance(e, ModelObject): val.modelXbrl.error( ("EFM.6.07.08", "GFM.1.03.08"), _("Your filing contained embedded linkbases in %(schema)s. Please recheck your submission and remove all embedded linkbases." ), edgarCode="cp-0708-No-Embedded-Linkbases", modelObject=e, schema=modelDocument.basename) break requiredUsedOns = { XbrlConst.qnLinkPresentationLink, XbrlConst.qnLinkCalculationLink, XbrlConst.qnLinkDefinitionLink } standardUsedOns = { XbrlConst.qnLinkLabel, XbrlConst.qnLinkReference, XbrlConst.qnLinkDefinitionArc, XbrlConst.qnLinkCalculationArc, XbrlConst.qnLinkPresentationArc, XbrlConst.qnLinkLabelArc, XbrlConst.qnLinkReferenceArc, # per WH, private footnote arc and footnore resource roles are not allowed XbrlConst.qnLinkFootnoteArc, XbrlConst.qnLinkFootnote, } # 6.7.9 role types authority for e in modelDocument.xmlRootElement.iterdescendants( tag="{http://www.xbrl.org/2003/linkbase}roleType"): if isinstance(e, ModelObject): roleURI = e.get("roleURI") if targetNamespaceAuthority != UrlUtil.authority(roleURI): val.modelXbrl.error( ("EFM.6.07.09", "GFM.1.03.09"), _("Role %(roleType)s does not begin with %(targetNamespace)s's scheme and authority. " "Please change the role URI or target namespace URI." ), edgarCode="du-0709-Role-Namespace-Mismatch", modelObject=e, roleType=roleURI, targetNamespaceAuthority=targetNamespaceAuthority, targetNamespace=modelDocument.targetNamespace) # 6.7.9 end with .../role/lc3 name if not roleTypePattern.match(roleURI): val.modelXbrl.warning( ("EFM.6.07.09.roleEnding", "GFM.1.03.09"), "RoleType %(roleType)s should end with /role/{LC3name}", modelObject=e, roleType=roleURI) # 6.7.10 only one role type declaration in DTS modelRoleTypes = val.modelXbrl.roleTypes.get(roleURI) if modelRoleTypes is not None: modelRoleType = modelRoleTypes[0] definition = modelRoleType.definitionNotStripped usedOns = modelRoleType.usedOns if len(modelRoleTypes) == 1: # 6.7.11 used on's for pre, cal, def if any has a used on if not usedOns.isdisjoint(requiredUsedOns) and len( requiredUsedOns - usedOns) > 0: val.modelXbrl.error( ("EFM.6.07.11", "GFM.1.03.11"), _("The role %(roleType)s did not provide a usedOn element for all three link types (presentation, " "calculation and definition), missing %(usedOn)s. Change the declaration to be for all three types of link, and resubmit." ), edgarCode= "du-0711-Role-Type-Declaration-Incomplete", modelObject=e, roleType=roleURI, usedOn=requiredUsedOns - usedOns) # 6.7.12 definition match pattern if (val.disclosureSystem.roleDefinitionPattern is not None and (definition is None or not val.disclosureSystem. roleDefinitionPattern.match(definition))): val.modelXbrl.error( ("EFM.6.07.12", "GFM.1.03.12-14"), _("The definition '%(definition)s' of role %(roleType)s does not match the expected format. " "Please check that the definition matches {number} - {type} - {text}." ), edgarCode="rq-0712-Role-Definition-Mismatch", modelObject=e, roleType=roleURI, definition=(definition or "")) if usedOns & standardUsedOns: # semantics check val.modelXbrl.log( "ERROR-SEMANTIC", ("EFM.6.08.03", "GFM.2.03.03"), _("RoleType %(roleuri)s is defined using role types already defined by standard roles for: %(qnames)s" ), modelObject=e, roleuri=roleURI, qnames=', '.join( str(qn) for qn in usedOns & standardUsedOns)) # 6.7.13 arcrole types authority for e in modelDocument.xmlRootElement.iterdescendants( tag="{http://www.xbrl.org/2003/linkbase}arcroleType"): if isinstance(e, ModelObject): arcroleURI = e.get("arcroleURI") if targetNamespaceAuthority != UrlUtil.authority(arcroleURI): val.modelXbrl.error( ("EFM.6.07.13", "GFM.1.03.15"), _("Relationship role %(arcroleType)s does not begin with %(targetNamespace)s's scheme and authority. " "Please change the relationship role URI or target namespace URI." ), edgarCode="du-0713-Arcrole-Namespace-Mismatch", modelObject=e, arcroleType=arcroleURI, targetNamespaceAuthority=targetNamespaceAuthority, targetNamespace=modelDocument.targetNamespace) # 6.7.13 end with .../arcrole/lc3 name if not arcroleTypePattern.match(arcroleURI): val.modelXbrl.warning( ("EFM.6.07.13.arcroleEnding", "GFM.1.03.15"), _("ArcroleType %(arcroleType)s should end with /arcrole/{LC3name}" ), modelObject=e, arcroleType=arcroleURI) # 6.7.15 definition match pattern modelRoleTypes = val.modelXbrl.arcroleTypes[arcroleURI] definition = modelRoleTypes[0].definition if definition is None or not arcroleDefinitionPattern.match( definition): val.modelXbrl.error( ("EFM.6.07.15", "GFM.1.03.17"), _("Relationship role declaration %(arcroleType)s is missing a definition. Please provide a definition." ), edgarCode="du-0715-Arcrole-Definition-Missing", modelObject=e, arcroleType=arcroleURI) # semantic checks usedOns = modelRoleTypes[0].usedOns if usedOns & standardUsedOns: # semantics check val.modelXbrl.log( "ERROR-SEMANTIC", ("EFM.6.08.03", "GFM.2.03.03"), _("ArcroleType %(arcroleuri)s is defined using role types already defined by standard arcroles for: %(qnames)s" ), modelObject=e, arcroleuri=arcroleURI, qnames=', '.join( str(qn) for qn in usedOns & standardUsedOns)) #6.3.3 filename check m = re.match(r"^\w+-([12][0-9]{3}[01][0-9][0-3][0-9]).xsd$", modelDocument.basename) if m: try: # check date value datetime.datetime.strptime(m.group(1), "%Y%m%d").date() # date and format are ok, check "should" part of 6.3.3 if val.fileNameBasePart: expectedFilename = "{0}-{1}.xsd".format( val.fileNameBasePart, val.fileNameDatePart) if modelDocument.basename != expectedFilename: val.modelXbrl.log( "WARNING-SEMANTIC", ("EFM.6.03.03.matchInstance", "GFM.1.01.01.matchInstance"), _('Schema file name warning: %(filename)s, should match %(expectedFilename)s' ), modelObject=modelDocument, filename=modelDocument.basename, expectedFilename=expectedFilename) except ValueError: val.modelXbrl.error( (val.EFM60303, "GFM.1.01.01"), _('Invalid schema file base name part (date) in "{base}-{yyyymmdd}.xsd": %(filename)s' ), modelObject=modelDocument, filename=modelDocument.basename, messageCodes=("EFM.6.03.03", "EFM.6.23.01", "GFM.1.01.01")) else: val.modelXbrl.error( (val.EFM60303, "GFM.1.01.01"), _('Invalid schema file name, must match "{base}-{yyyymmdd}.xsd": %(filename)s' ), modelObject=modelDocument, filename=modelDocument.basename, messageCodes=("EFM.6.03.03", "EFM.6.23.01", "GFM.1.01.01")) elif modelDocument.type == ModelDocument.Type.LINKBASE: # if it is part of the submission (in same directory) check name labelRels = None if modelDocument.filepath.startswith( val.modelXbrl.modelDocument.filepathdir): #6.3.3 filename check extLinkElt = XmlUtil.descendant( modelDocument.xmlRootElement, XbrlConst.link, "*", "{http://www.w3.org/1999/xlink}type", "extended") if extLinkElt is None: # no ext link element val.modelXbrl.error( (val.EFM60303 + ".noLinkElement", "GFM.1.01.01.noLinkElement"), _('Invalid linkbase file name: %(filename)s, has no extended link element, cannot determine link type.' ), modelObject=modelDocument, filename=modelDocument.basename, messageCodes=("EFM.6.03.03.noLinkElement", "EFM.6.23.01.noLinkElement", "GFM.1.01.01.noLinkElement")) elif extLinkElt.localName not in extLinkEltFileNameEnding: val.modelXbrl.error( "EFM.6.03.02", _('Invalid linkbase link element %(linkElement)s in %(filename)s' ), modelObject=modelDocument, linkElement=extLinkElt.localName, filename=modelDocument.basename) else: m = re.match( r"^\w+-([12][0-9]{3}[01][0-9][0-3][0-9])(_[a-z]{3}).xml$", modelDocument.basename) expectedSuffix = extLinkEltFileNameEnding[extLinkElt.localName] if m and m.group(2) == expectedSuffix: try: # check date value datetime.datetime.strptime(m.group(1), "%Y%m%d").date() # date and format are ok, check "should" part of 6.3.3 if val.fileNameBasePart: expectedFilename = "{0}-{1}{2}.xml".format( val.fileNameBasePart, val.fileNameDatePart, expectedSuffix) if modelDocument.basename != expectedFilename: val.modelXbrl.log( "WARNING-SEMANTIC", ("EFM.6.03.03.matchInstance", "GFM.1.01.01.matchInstance"), _('Linkbase name warning: %(filename)s should match %(expectedFilename)s' ), modelObject=modelDocument, filename=modelDocument.basename, expectedFilename=expectedFilename) except ValueError: val.modelXbrl.error( (val.EFM60303, "GFM.1.01.01"), _('Invalid linkbase base file name part (date) in "{base}-{yyyymmdd}_{suffix}.xml": %(filename)s' ), modelObject=modelDocument, filename=modelDocument.basename, messageCodes=("EFM.6.03.03", "EFM.6.23.01", "GFM.1.01.01")) else: val.modelXbrl.error( (val.EFM60303, "GFM.1.01.01"), _('Invalid linkbase name, must match "{base}-{yyyymmdd}%(expectedSuffix)s.xml": %(filename)s' ), modelObject=modelDocument, filename=modelDocument.basename, expectedSuffix=expectedSuffix, messageCodes=("EFM.6.03.03", "EFM.6.23.01", "GFM.1.01.01")) if extLinkElt.localName == "labelLink": if labelRels is None: labelRels = val.modelXbrl.relationshipSet( XbrlConst.conceptLabel) for labelElt in XmlUtil.children(extLinkElt, XbrlConst.link, "label"): # 6.10.9 if XbrlConst.isNumericRole(labelElt.role): for rel in labelRels.toModelObject(labelElt): if rel.fromModelObject is not None and not rel.fromModelObject.isNumeric: val.modelXbrl.error( "EFM.6.10.09", _("Non-numeric element %(concept)s has a label role for numeric elements: %(role)s. " "Please change the role attribute."), edgarCode="du-1009-Numeric-Label-Role", modelObject=(labelElt, rel.fromModelObject), concept=rel.fromModelObject.qname, role=labelElt.role)