def evaluateAtomicValue(self, exprStack, type, contextItem=None): if exprStack and len(exprStack) > 0 and isinstance( exprStack[0], ProgHeader): progHeader = exprStack[0] result = self.atomize( progHeader, self.evaluate(exprStack, contextItem=contextItem)) if isinstance(type, QName) and type.namespaceURI == XbrlConst.xsd: type = "xs:" + type.localName if isinstance(type, str): prefix, sep, localName = type.rpartition(':') if prefix == 'xs': if localName.endswith('*'): localName = localName[:-1] if isinstance(result, (tuple, list, set)): from arelle import (FunctionXs) if type.endswith('*'): return [ FunctionXs.call(self, progHeader, localName, (r, )) for r in result ] elif len(result) > 0: return FunctionXs.call(self, progHeader, localName, (result[0], )) elif localName.startswith("item()"): return result # can be any type else: # no conversion if len(result) == 0: return None elif len(result) == 1: return result[0] else: return result return None
def evaluate(self, exprStack, contextItem=None, resultStack=None, parentOp=None): if resultStack is None: resultStack = [] if contextItem is None: contextItem = self.contextItem setProgHeader = False for p in exprStack: result = None if isinstance(p,QNameDef) or (p == u'*' and parentOp in (u'/', u'//')): # path step QName or wildcard # step axis operation if len(resultStack) == 0 or not self.isNodeSequence(resultStack[-1]): resultStack.append( [ contextItem, ] ) result = self.stepAxis(parentOp, p, resultStack.pop() ) elif isinstance(p,_STR_NUM_TYPES): result = p elif isinstance(p,VariableRef): if p.name in self.inScopeVars: result = self.inScopeVars[p.name] # uncomment to allow lambdas as variable values (for deferred processing if needed) #if isinstance(result, LambdaType): # result = result() # dereference lambda-valued variables if result is None: # None atomic result is XPath empty sequence result = [] # subsequent processing discards None results elif isinstance(p,OperationDef): op = p.name if isinstance(op, QNameDef): # function call args = self.evaluate(p.args, contextItem=contextItem) ns = op.namespaceURI; localname = op.localName try: from arelle import (FunctionXs, FunctionFn, FunctionXfi, FunctionIxt, FunctionCustom) if op in self.modelXbrl.modelCustomFunctionSignatures: result = FunctionCustom.call(self, p, op, contextItem, args) elif op.unprefixed and localname in set([u'attribute', u'comment', u'document-node', u'element', u'item', u'node', u'processing-instruction', u'schema-attribute', u'schema-element', u'text']): # step axis operation if len(resultStack) == 0 or not self.isNodeSequence(resultStack[-1]): if isinstance(contextItem, (tuple,list)): resultStack.append( contextItem ) else: resultStack.append( [ contextItem, ] ) result = self.stepAxis(parentOp, p, resultStack.pop() ) elif op.unprefixed or ns == XbrlConst.fn: result = FunctionFn.call(self, p, localname, contextItem, args) elif ns == XbrlConst.xfi or ns == XbrlConst.xff: result = FunctionXfi.call(self, p, localname, args) elif ns == XbrlConst.xsd: result = FunctionXs.call(self, p, localname, args) elif ns in FunctionIxt.ixtNamespaceURIs: result = FunctionIxt.call(self, p, localname, args) else: raise XPathException(p, u'err:XPST0017', _(u'Function call not identified: {0}.').format(op)) except FunctionNumArgs, err: raise XPathException(p, err.errCode, u"{}: {}".format(err.errText, op)) except FunctionArgType, err: raise XPathException(p, err.errCode, _(u'Argument {0} does not match expected type {1} for {2} {3}.') .format(err.argNum, err.expectedType, op, err.foundObject)) except FunctionNotAvailable: raise XPathException(p, u'err:XPST0017', _(u'Function named {0} does not have a custom or built-in implementation.').format(op))
def evaluateAtomicValue(self, exprStack, type, contextItem=None): if exprStack and len(exprStack) > 0 and isinstance(exprStack[0], ProgHeader): progHeader = exprStack[0] result = self.atomize( progHeader, self.evaluate( exprStack, contextItem=contextItem ) ) if isinstance(type, QName) and type.namespaceURI == XbrlConst.xsd: type = "xs:" + type.localName if isinstance(type,str): prefix,sep,localName = type.partition(':') if prefix == 'xs': if localName.endswith('*'): localName = localName[:-1] if isinstance(result, (tuple,list,set)): from arelle import (FunctionXs) if type.endswith('*'): return[FunctionXs.call(self,progHeader,localName,(r,)) for r in result] elif len(result) > 0: return FunctionXs.call(self,progHeader,localName,(result[0],)) else: # no conversion if len(result) == 0: return None elif len(result) == 1: return result[0] else: return result return None
def evaluate(self, exprStack, contextItem=None, resultStack=None, parentOp=None): if resultStack is None: resultStack = [] if contextItem is None: contextItem = self.contextItem setProgHeader = False for p in exprStack: result = None if isinstance(p, QNameDef) or (p == '*' and parentOp in ('/', '//') ): # path step QName or wildcard # step axis operation if len(resultStack) == 0 or not self.isNodeSequence( resultStack[-1]): resultStack.append([ contextItem, ]) result = self.stepAxis(parentOp, p, resultStack.pop()) elif isinstance(p, _STR_NUM_TYPES): result = p elif isinstance(p, VariableRef): if p.name in self.inScopeVars: result = self.inScopeVars[p.name] # uncomment to allow lambdas as variable values (for deferred processing if needed) #if isinstance(result, LambdaType): # result = result() # dereference lambda-valued variables if result is None: # None atomic result is XPath empty sequence result = [ ] # subsequent processing discards None results elif isinstance(p, OperationDef): op = p.name if isinstance(op, QNameDef): # function call args = self.evaluate(p.args, contextItem=contextItem) ns = op.namespaceURI localname = op.localName try: from arelle import (FunctionXs, FunctionFn, FunctionXfi, FunctionIxt, FunctionCustom) if op in self.modelXbrl.modelCustomFunctionSignatures: result = FunctionCustom.call( self, p, op, contextItem, args) elif op in self.customFunctions: # plug in method custom functions result = self.customFunctions[op]( self, p, contextItem, args) # use plug-in's method elif op.unprefixed and localname in { 'attribute', 'comment', 'document-node', 'element', 'item', 'node', 'processing-instruction', 'schema-attribute', 'schema-element', 'text' }: # step axis operation if len(resultStack ) == 0 or not self.isNodeSequence( resultStack[-1]): if isinstance(contextItem, (tuple, list)): resultStack.append(contextItem) else: resultStack.append([ contextItem, ]) result = self.stepAxis(parentOp, p, resultStack.pop()) elif op.unprefixed or ns == XbrlConst.fn: result = FunctionFn.call(self, p, localname, contextItem, args) elif ns == XbrlConst.xfi or ns == XbrlConst.xff: result = FunctionXfi.call(self, p, localname, args) elif ns == XbrlConst.xsd: result = FunctionXs.call(self, p, localname, args) elif ns in FunctionIxt.ixtNamespaceFunctions: result = FunctionIxt.call(self, p, op, args) elif op in self.modelXbrl.modelManager.customTransforms: result = self.modelXbrl.modelManager.customTransforms[ op](args[0][0]) else: raise XPathException( p, 'err:XPST0017', _('Function call not identified: {0}.').format( op)) except FunctionNumArgs as err: raise XPathException(p, err.errCode, "{}: {}".format(err.errText, op)) except FunctionArgType as err: raise XPathException( p, err.errCode, _('Argument {0} does not match expected type {1} for {2} {3}.' ).format(err.argNum, err.expectedType, op, err.foundObject)) except FunctionNotAvailable: raise XPathException( p, 'err:XPST0017', _('Function named {0} does not have a custom or built-in implementation.' ).format(op)) elif op in VALUE_OPS: # binary arithmetic operations and value comparisons s1 = self.atomize( p, resultStack.pop()) if len(resultStack) > 0 else [] s2 = self.atomize( p, self.evaluate(p.args, contextItem=contextItem)) # value comparisons if len(s1) > 1 or len(s2) > 1: raise XPathException( p, 'err:XPTY0004', _("Value operation '{0}' sequence length error"). format(op)) if len(s1) == 0 or len(s2) == 0: result = [] else: op1 = s1[0] op2 = s2[0] testTypeCompatiblity(self, p, op, op1, op2) if type(op1) != type(op2) and op in ('+', '-', '*', 'div', 'idiv', 'mod'): # check if type promotion needed (Decimal-float, not needed for integer-Decimal) if isinstance(op1, Decimal) and isinstance( op2, float): op1 = float( op1 ) # per http://http://www.w3.org/TR/xpath20/#dt-type-promotion 1b elif isinstance(op2, Decimal) and isinstance( op1, float): op2 = float(op2) if op == '+': result = op1 + op2 elif op == '-': result = op1 - op2 elif op == '*': result = op1 * op2 elif op in ('div', 'idiv', "mod"): try: if op == 'div': result = op1 / op2 elif op == 'idiv': result = op1 // op2 elif op == 'mod': result = op1 % op2 except ZeroDivisionError: raise XPathException( p, 'err:FOAR0001', _('Attempt to divide by zero: {0} {1} {2}.' ).format(op1, op, op2)) elif op == 'ge': result = op1 >= op2 elif op == 'gt': result = op1 > op2 elif op == 'le': result = op1 <= op2 elif op == 'lt': result = op1 < op2 elif op == 'eq': result = op1 == op2 elif op == 'ne': result = op1 != op2 elif op == 'to': result = _RANGE(_INT(op1), _INT(op2) + 1) elif op in GENERALCOMPARISON_OPS: # general comparisons s1 = self.atomize( p, resultStack.pop()) if len(resultStack) > 0 else [] s2 = self.atomize( p, self.evaluate(p.args, contextItem=contextItem)) result = [] for op1 in s1: for op2 in s2: testTypeCompatiblity(self, p, op, op1, op2) if op == '>=': result = op1 >= op2 elif op == '>': result = op1 > op2 elif op == '<=': result = op1 <= op2 elif op == '<': result = op1 < op2 elif op == '=': result = op1 == op2 elif op == '!=': result = op1 != op2 if result: break if result: break elif op in NODECOMPARISON_OPS: # node comparisons s1 = resultStack.pop() if len(resultStack) > 0 else [] s2 = self.evaluate(p.args, contextItem=contextItem) if len(s1) > 1 or len(s2) > 1 or not self.isNodeSequence( s1) or not self.isNodeSequence(s2[0]): raise XPathException( p, 'err:XPTY0004', _('Node comparison sequence error')) if len(s1) == 0 or len(s2[0]) == 0: result = [] else: n1 = s1[0] n2 = s2[0][0] result = False for op1 in s1: for op2 in s2: if op == 'is': result = n1 == n2 elif op == '>>': result = op1 > op2 elif op == '<<': result = op1 <= op2 if result: break elif op in COMBINING_OPS: # node comparisons s1 = resultStack.pop() if len(resultStack) > 0 else [] s2 = self.flattenSequence( self.evaluate(p.args, contextItem=contextItem)) if not self.isNodeSequence(s1) or not self.isNodeSequence( s2): raise XPathException( p, 'err:XPTY0004', _('Node operation sequence error')) set1 = set(s1) set2 = set(s2) if op == 'intersect': resultset = set1 & set2 elif op == 'except': resultset = set1 - set2 elif op == 'union' or op == '|': resultset = set1 | set2 # convert to a list in document order result = self.documentOrderedNodes(resultset) elif op in LOGICAL_OPS: # general comparisons if len(resultStack) == 0: result = [] else: op1 = self.effectiveBooleanValue(p, resultStack.pop( )) if len(resultStack) > 0 else False # consider short circuit possibilities if op == 'or' and op1: result = True elif op == 'and' and not op1: result = False else: # must evaluate other operand op2 = self.effectiveBooleanValue( p, self.evaluate(p.args, contextItem=contextItem)) if op == 'and': result = op1 and op2 elif op == 'or': result = op1 or op2 elif op in UNARY_OPS: s1 = self.atomize( p, self.evaluate(p.args, contextItem=contextItem)) if len(s1) > 1: raise XPathException( p, 'err:XPTY0004', _('Unary expression sequence length error')) if len(s1) == 0: result = [] else: op1 = s1[0] if op == 'u+': result = op1 elif op == 'u-': result = -op1 elif op == 'instance': result = False s1 = self.flattenSequence( resultStack.pop()) if len(resultStack) > 0 else [] arity = len(s1) if len(p.args) > 1: occurenceIndicator = p.args[1] if (occurenceIndicator == '?' and arity in (0,1) ) or \ (occurenceIndicator == '+' and arity >= 1) or \ (occurenceIndicator == '*'): result = True elif arity == 1: result = True if result and len(p.args) > 0: t = p.args[0] for x in s1: if isinstance(t, QNameDef): if t.namespaceURI == XbrlConst.xsd: tType = { "integer": _INT_TYPES, "string": _STR_BASE, "decimal": Decimal, "double": float, "float": float, "boolean": bool, "QName": QName, "anyURI": AnyURI, "date": DateTime, "dateTime": DateTime, }.get(t.localName) if tType: result = isinstance(x, tType) if result and tType == DateTime: result = x.dateOnly == ( t.localName == "date") elif isinstance(t, OperationDef): if t.name == "element": if isinstance(x, ModelObject): if len(t.args) >= 1: qn = t.args[0] if qn == '*' or (isinstance( qn, QNameDef) and qn == x): result = True if len(t.args ) >= 2 and isinstance( t.args[1], QNameDef): modelXbrl = x.modelDocument.modelXbrl modelConcept = modelXbrl.qnameConcepts.get( qname(x)) if not modelConcept.instanceOfType( t.args[1]): result = False else: result = False # elif t.name == "item" comes here and result stays True if not result: break elif op == 'sequence': result = self.evaluate(p.args, contextItem=contextItem) elif op == 'predicate': result = self.predicate( p, resultStack.pop()) if len(resultStack) > 0 else [] elif op in FORSOMEEVERY_OPS: # for, some, every result = [] self.evaluateRangeVars(op, p.args[0], p.args[1:], contextItem, result) elif op == 'if': test = self.effectiveBooleanValue( p, self.evaluate(p.args[0].expr[0], contextItem=contextItem)) result = self.evaluate(p.args[1 if test else 2].args, contextItem=contextItem) elif op == '.': result = contextItem elif op == '..': result = XmlUtil.parent(contextItem) elif op in PATH_OPS: if op in ('rootChild', 'rootDescendant'): # fix up for multi-instance resultStack.append([ self.inputXbrlInstance.xmlDocument, ]) op = '/' if op == 'rootChild' else '//' # contains QNameDefs and predicates if len(resultStack) > 0: innerFocusNodes = resultStack.pop() else: innerFocusNodes = contextItem navSequence = [] for innerFocusNode in self.flattenSequence( innerFocusNodes): navSequence += self.evaluate( p.args, contextItem=innerFocusNode, parentOp=op) result = self.documentOrderedNodes( self.flattenSequence(navSequence)) elif isinstance(p, ProgHeader): self.progHeader = p if p.traceType not in (Trace.MESSAGE, Trace.CUSTOM_FUNCTION): self.traceType = p.traceType setProgHeader = True if result is not None: # note: result can be False which gets appended to resultStack resultStack.append(self.flattenSequence(result)) if setProgHeader: self.progHeader = None return resultStack
def evaluate(self, exprStack, contextItem=None, resultStack=None, parentOp=None): if resultStack is None: resultStack = [] if contextItem is None: contextItem = self.contextItem setProgHeader = False for p in exprStack: result = None if isinstance(p,QNameDef) or (p == '*' and parentOp in ('/', '//')): # path step QName or wildcard # step axis operation if len(resultStack) == 0 or not self.isNodeSequence(resultStack[-1]): resultStack.append( [ contextItem, ] ) result = self.stepAxis(parentOp, p, resultStack.pop() ) elif isinstance(p,_STR_NUM_TYPES): result = p elif isinstance(p,VariableRef): if p.name in self.inScopeVars: result = self.inScopeVars[p.name] # uncomment to allow lambdas as variable values (for deferred processing if needed) #if isinstance(result, LambdaType): # result = result() # dereference lambda-valued variables if result is None: # None atomic result is XPath empty sequence result = [] # subsequent processing discards None results elif isinstance(p,OperationDef): op = p.name if isinstance(op, QNameDef): # function call args = self.evaluate(p.args, contextItem=contextItem) ns = op.namespaceURI; localname = op.localName try: from arelle import (FunctionXs, FunctionFn, FunctionXfi, FunctionIxt, FunctionCustom) if op in self.modelXbrl.modelCustomFunctionSignatures: result = FunctionCustom.call(self, p, op, contextItem, args) elif op in self.customFunctions: # plug in method custom functions result = self.customFunctions[op](self, p, contextItem, args) # use plug-in's method elif op.unprefixed and localname in {'attribute', 'comment', 'document-node', 'element', 'item', 'node', 'processing-instruction', 'schema-attribute', 'schema-element', 'text'}: # step axis operation if len(resultStack) == 0 or not self.isNodeSequence(resultStack[-1]): if isinstance(contextItem, (tuple,list)): resultStack.append( contextItem ) else: resultStack.append( [ contextItem, ] ) result = self.stepAxis(parentOp, p, resultStack.pop() ) elif op.unprefixed or ns == XbrlConst.fn: result = FunctionFn.call(self, p, localname, contextItem, args) elif ns == XbrlConst.xfi or ns == XbrlConst.xff: result = FunctionXfi.call(self, p, localname, args) elif ns == XbrlConst.xsd: result = FunctionXs.call(self, p, localname, args) elif ns in FunctionIxt.ixtNamespaceFunctions: result = FunctionIxt.call(self, p, op, args) elif op in self.modelXbrl.modelManager.customTransforms: result = self.modelXbrl.modelManager.customTransforms[op](args[0][0]) else: raise XPathException(p, 'err:XPST0017', _('Function call not identified: {0}.').format(op)) except FunctionNumArgs as err: raise XPathException(p, err.errCode, "{}: {}".format(err.errText, op)) except FunctionArgType as err: raise XPathException(p, err.errCode, _('Argument {0} does not match expected type {1} for {2} {3}.') .format(err.argNum, err.expectedType, op, err.foundObject)) except FunctionNotAvailable: raise XPathException(p, 'err:XPST0017', _('Function named {0} does not have a custom or built-in implementation.').format(op)) elif op in VALUE_OPS: # binary arithmetic operations and value comparisons s1 = self.atomize( p, resultStack.pop() ) if len(resultStack) > 0 else [] s2 = self.atomize( p, self.evaluate(p.args, contextItem=contextItem) ) # value comparisons if len(s1) > 1 or len(s2) > 1: raise XPathException(p, 'err:XPTY0004', _("Value operation '{0}' sequence length error").format(op)) if len(s1) == 0 or len(s2) == 0: result = [] else: op1 = s1[0] op2 = s2[0] testTypeCompatiblity( self, p, op, op1, op2 ) if type(op1) != type(op2) and op in ('+', '-', '*', 'div', 'idiv', 'mod'): # check if type promotion needed (Decimal-float, not needed for integer-Decimal) if isinstance(op1,Decimal) and isinstance(op2,float): op1 = float(op1) # per http://http://www.w3.org/TR/xpath20/#dt-type-promotion 1b elif isinstance(op2,Decimal) and isinstance(op1,float): op2 = float(op2) if op == '+': result = op1 + op2 elif op == '-': result = op1 - op2 elif op == '*': result = op1 * op2 elif op in ('div', 'idiv', "mod"): try: if op == 'div': result = op1 / op2 elif op == 'idiv': result = op1 // op2 elif op == 'mod': result = op1 % op2 except ZeroDivisionError: raise XPathException(p, 'err:FOAR0001', _('Attempt to divide by zero: {0} {1} {2}.') .format(op1, op, op2)) elif op == 'ge': result = op1 >= op2 elif op == 'gt': result = op1 > op2 elif op == 'le': result = op1 <= op2 elif op == 'lt': result = op1 < op2 elif op == 'eq': result = op1 == op2 elif op == 'ne': result = op1 != op2 elif op == 'to': result = _RANGE( _INT(op1), _INT(op2) + 1 ) elif op in GENERALCOMPARISON_OPS: # general comparisons s1 = self.atomize( p, resultStack.pop() ) if len(resultStack) > 0 else [] s2 = self.atomize( p, self.evaluate(p.args, contextItem=contextItem) ) result = []; for op1 in s1: for op2 in s2: testTypeCompatiblity( self, p, op, op1, op2 ) if op == '>=': result = op1 >= op2 elif op == '>': result = op1 > op2 elif op == '<=': result = op1 <= op2 elif op == '<': result = op1 < op2 elif op == '=': result = op1 == op2 elif op == '!=': result = op1 != op2 if result: break if result: break elif op in NODECOMPARISON_OPS: # node comparisons s1 = resultStack.pop() if len(resultStack) > 0 else [] s2 = self.evaluate(p.args, contextItem=contextItem) if len(s1) > 1 or len(s2) > 1 or not self.isNodeSequence(s1) or not self.isNodeSequence(s2[0]): raise XPathException(p, 'err:XPTY0004', _('Node comparison sequence error')) if len(s1) == 0 or len(s2[0]) == 0: result = [] else: n1 = s1[0] n2 = s2[0][0] result = False; for op1 in s1: for op2 in s2: if op == 'is': result = n1 == n2 elif op == '>>': result = op1 > op2 elif op == '<<': result = op1 <= op2 if result: break elif op in COMBINING_OPS: # node comparisons s1 = resultStack.pop() if len(resultStack) > 0 else [] s2 = self.flattenSequence(self.evaluate(p.args, contextItem=contextItem)) if not self.isNodeSequence(s1) or not self.isNodeSequence(s2): raise XPathException(p, 'err:XPTY0004', _('Node operation sequence error')) set1 = set(s1) set2 = set(s2) if op == 'intersect': resultset = set1 & set2 elif op == 'except': resultset = set1 - set2 elif op == 'union' or op == '|': resultset = set1 | set2 # convert to a list in document order result = self.documentOrderedNodes(resultset) elif op in LOGICAL_OPS: # general comparisons if len(resultStack) == 0: result = [] else: op1 = self.effectiveBooleanValue( p, resultStack.pop() ) if len(resultStack) > 0 else False # consider short circuit possibilities if op == 'or' and op1: result = True elif op == 'and' and not op1: result = False else: # must evaluate other operand op2 = self.effectiveBooleanValue( p, self.evaluate(p.args, contextItem=contextItem) ) if op == 'and': result = op1 and op2 elif op == 'or': result = op1 or op2 elif op in UNARY_OPS: s1 = self.atomize( p, self.evaluate(p.args, contextItem=contextItem) ) if len(s1) > 1: raise XPathException(p, 'err:XPTY0004', _('Unary expression sequence length error')) if len(s1) == 0: result = [] else: op1 = s1[0] if op == 'u+': result = op1 elif op == 'u-': result = -op1 elif op == 'instance': result = False s1 = self.flattenSequence( resultStack.pop() ) if len(resultStack) > 0 else [] arity = len(s1) if len(p.args) > 1: occurenceIndicator = p.args[1] if (occurenceIndicator == '?' and arity in (0,1) ) or \ (occurenceIndicator == '+' and arity >= 1) or \ (occurenceIndicator == '*'): result = True elif arity == 1: result = True if result and len(p.args) > 0: t = p.args[0] for x in s1: if isinstance(t, QNameDef): if t.namespaceURI == XbrlConst.xsd: tType = { "integer": _INT_TYPES, "string": _STR_BASE, "decimal": Decimal, "double": float, "float": float, "boolean": bool, "QName": QName, "anyURI": AnyURI, "date": DateTime, "dateTime": DateTime, }.get(t.localName) if tType: result = isinstance(x, tType) if result and tType == DateTime: result = x.dateOnly == (t.localName == "date") elif isinstance(t, OperationDef): if t.name == "element": if isinstance(x,ModelObject): if len(t.args) >= 1: qn = t.args[0] if qn== '*' or (isinstance(qn,QNameDef) and qn == x): result = True if len(t.args) >= 2 and isinstance(t.args[1],QNameDef): modelXbrl = x.modelDocument.modelXbrl modelConcept = modelXbrl.qnameConcepts.get(qname(x)) if not modelConcept.instanceOfType(t.args[1]): result = False else: result = False # elif t.name == "item" comes here and result stays True if not result: break elif op == 'sequence': result = self.evaluate(p.args, contextItem=contextItem) elif op == 'predicate': result = self.predicate(p, resultStack.pop()) if len(resultStack) > 0 else [] elif op in FORSOMEEVERY_OPS: # for, some, every result = [] self.evaluateRangeVars(op, p.args[0], p.args[1:], contextItem, result) elif op == 'if': test = self.effectiveBooleanValue( p, self.evaluate(p.args[0].expr[0], contextItem=contextItem) ) result = self.evaluate(p.args[1 if test else 2].args, contextItem=contextItem) elif op == '.': result = contextItem elif op == '..': result = XmlUtil.parent(contextItem) elif op in PATH_OPS: if op in ('rootChild', 'rootDescendant'): # fix up for multi-instance resultStack.append( [self.inputXbrlInstance.xmlDocument,] ) op = '/' if op == 'rootChild' else '//' # contains QNameDefs and predicates if len(resultStack) > 0: innerFocusNodes = resultStack.pop() else: innerFocusNodes = contextItem navSequence = [] for innerFocusNode in self.flattenSequence(innerFocusNodes): navSequence += self.evaluate(p.args, contextItem=innerFocusNode, parentOp=op) result = self.documentOrderedNodes(self.flattenSequence(navSequence)) elif isinstance(p,ProgHeader): self.progHeader = p if p.traceType not in (Trace.MESSAGE, Trace.CUSTOM_FUNCTION): self.traceType = p.traceType setProgHeader = True if result is not None: # note: result can be False which gets appended to resultStack resultStack.append( self.flattenSequence( result ) ) if setProgHeader: self.progHeader = None return resultStack
def evaluate(self, exprStack, contextItem=None, resultStack=None, parentOp=None): if resultStack is None: resultStack = [] if contextItem is None: contextItem = self.contextItem setProgHeader = False for p in exprStack: result = None if isinstance(p, (str, int, float)): result = p elif isinstance(p, VariableRef): if p.name in self.inScopeVars: result = self.inScopeVars[p.name] elif isinstance(p, QNameDef): # step axis operation if len(resultStack) == 0 or not self.isNodeSequence( resultStack[-1]): resultStack.append([ contextItem, ]) result = self.stepAxis(parentOp, p, resultStack.pop()) elif isinstance(p, OperationDef): op = p.name if isinstance(op, QNameDef): # function call args = self.evaluate(p.args, contextItem=contextItem) ns = op.namespaceURI localname = op.localName try: from arelle import (FunctionXs, FunctionFn, FunctionXfi, FunctionCustom) if op in self.modelXbrl.modelCustomFunctionSignatures: result = FunctionCustom.call( self, p, op, contextItem, args) elif op.unprefixed and localname in { 'attribute', 'comment', 'document-node', 'element', 'item', 'node', 'processing-instruction', 'schema-attribute', 'schema-element', 'text' }: # step axis operation if len(resultStack ) == 0 or not self.isNodeSequence( resultStack[-1]): if isinstance(contextItem, (tuple, list)): resultStack.append(contextItem) else: resultStack.append([ contextItem, ]) result = self.stepAxis(parentOp, p, resultStack.pop()) elif op.unprefixed or ns == XbrlConst.fn: result = FunctionFn.call(self, p, localname, contextItem, args) elif ns == XbrlConst.xfi or ns == XbrlConst.xff: result = FunctionXfi.call(self, p, localname, args) elif ns == XbrlConst.xsd: result = FunctionXs.call(self, p, localname, args) else: raise XPathException( p, 'err:XPST0017', _('Function call not identified.')) except FunctionNumArgs: raise XPathException( p, 'err:XPST0017', _('Number of arguments do not match signature arity.' )) except FunctionArgType as err: raise XPathException( p, 'err:XPTY0004', _('Argument {0} does not match expected type {1}.' ).format(err.argNum, err.expectedType)) except FunctionNotAvailable: raise XPathException( p, 'arelle:functDeferred', _('Function {0} is not available in this build.'). format(str(op))) elif op in { '+', '-', '*', 'div', 'idiv', 'mod', 'to', 'gt', 'ge', 'eq', 'ne', 'lt', 'le' }: # binary arithmetic operations and value comparisons s1 = self.atomize( p, resultStack.pop()) if len(resultStack) > 0 else [] s2 = self.atomize( p, self.evaluate(p.args, contextItem=contextItem)) # value comparisons if len(s1) > 1 or len(s2) > 1: raise XPathException( p, 'err:XPTY0004', _("Value operation '{0}' sequence length error"). format(op)) if len(s1) == 0 or len(s2) == 0: result = [] else: op1 = s1[0] op2 = s2[0] from arelle.FunctionUtil import (testTypeCompatiblity) testTypeCompatiblity(self, p, op, op1, op2) if op == '+': result = op1 + op2 elif op == '-': result = op1 - op2 elif op == '*': result = op1 * op2 elif op in ('div', 'idiv', "mod"): try: if op == 'div': result = op1 / op2 elif op == 'idiv': result = op1 // op2 elif op == 'mod': result = op1 % op2 except ZeroDivisionError: raise XPathException( p, 'err:FOAR0001', _('Attempt to divide by zero: {0} {1} {2}.' ).format(op1, op, op2)) elif op == 'ge': result = op1 >= op2 elif op == 'gt': result = op1 > op2 elif op == 'le': result = op1 <= op2 elif op == 'lt': result = op1 < op2 elif op == 'eq': result = op1 == op2 elif op == 'ne': result = op1 != op2 elif op == 'to': result = range(int(op1), int(op2) + 1) elif op in {'>', '>=', '=', '!=', '<', '<='}: # general comparisons s1 = self.atomize( p, resultStack.pop()) if len(resultStack) > 0 else [] s2 = self.atomize( p, self.evaluate(p.args, contextItem=contextItem)) result = [] for op1 in s1: for op2 in s2: if op == '>=': result = op1 >= op2 elif op == '>': result = op1 > op2 elif op == '<=': result = op1 <= op2 elif op == '<': result = op1 < op2 elif op == '=': result = op1 == op2 elif op == '!=': result = op1 != op2 if result: break if result: break elif op in {'is', '>>', '<<'}: # node comparisons s1 = resultStack.pop() if len(resultStack) > 0 else [] s2 = self.evaluate(p.args, contextItem=contextItem) if len(s1) > 1 or len(s2) > 1 or not self.isNodeSequence( s1) or not self.isNodeSequence(s2[0]): raise XPathException( p, 'err:XPTY0004', _('Node comparison sequence error')) if len(s1) == 0 or len(s2[0]) == 0: result = [] else: n1 = s1[0] if isinstance(n1, ModelObject.ModelObject): n1 = n1.element n2 = s2[0][0] if isinstance(n2, ModelObject.ModelObject): n2 = n2.element result = False for op1 in s1: for op2 in s2: if op == 'is': result = n1 == n2 elif op == '>>': result = op1 > op2 elif op == '<<': result = op1 <= op2 if result: break elif op in {'intersect', 'except', 'union', '|'}: # node comparisons s1 = resultStack.pop() if len(resultStack) > 0 else [] s2 = self.flattenSequence( self.evaluate(p.args, contextItem=contextItem)) if not self.isNodeSequence(s1) or not self.isNodeSequence( s2): raise XPathException( p, 'err:XPTY0004', _('Node operation sequence error')) set1 = set(s1) set2 = set(s2) if op == 'intersect': resultset = set1 & set2 elif op == 'except': resultset = set1 - set2 elif op == 'union' or op == '|': resultset = set1 | set2 # convert to a list in document order result = self.documentOrderedNodes(resultset) elif op in {'and', 'or'}: # general comparisons if len(resultStack) == 0: result = [] else: op1 = self.effectiveBooleanValue(p, resultStack.pop()) op2 = self.effectiveBooleanValue( p, self.evaluate(p.args, contextItem=contextItem)) result = False if op == 'and': result = op1 and op2 elif op == 'or': result = op1 or op2 elif op in {'u+', 'u-'}: s1 = self.atomize( p, self.evaluate(p.args, contextItem=contextItem)) if len(s1) > 1: raise XPathException( p, 'err:XPTY0004', _('Unary expression sequence length error')) if len(s1) == 0: result = [] else: op1 = s1[0] if op == 'u+': result = op1 elif op == 'u-': result = -op1 elif op == 'instance': result = False s1 = self.flattenSequence( resultStack.pop()) if len(resultStack) > 0 else [] arity = len(s1) if isinstance(s1, ModelObject.ModelObject): s1 = s1.element if len(p.args) > 1: occurenceIndicator = p.args[1] if (occurenceIndicator == '?' and arity in (0,1) ) or \ (occurenceIndicator == '+' and arity >= 1) or \ (occurenceIndicator == '*'): result = True elif arity == 1: result = True if result and len(p.args) > 0: t = p.args[0] for x in s1: if isinstance(t, QNameDef): if t.namespaceURI == XbrlConst.xsd: type = { "integer": int, "string": str, "decimal": float, "double": float, "float": float, "boolean": bool, "QName": QName, "anyURI": AnyURI, "date": DateTime, "dateTime": DateTime, }.get(t.localName) if type: result = isinstance(x, type) if result and type == DateTime: result = x.dateOnly == ( t.localName == "date") elif isinstance(t, OperationDef): if t.name == "element" and isinstance( x, xml.dom.Node): if len(t.args) >= 1: qn = t.args[0] if qn == '*' or (isinstance( qn, QNameDef) and qn == x): result = True if len(t.args) >= 2 and isinstance( t.args[1], QNameDef): modelXbrl = x.ownerDocument.modelDocument.modelXbrl modelConcept = modelXbrl.qnameConcepts.get( qname(x)) if not modelConcept.instanceOfType( t.args[1]): result = False if not result: break elif op == 'sequence': result = self.evaluate(p.args, contextItem=contextItem) elif op == 'predicate': result = self.predicate(p, resultStack.pop()) elif op in {'for', 'some', 'every'}: # for, some, every result = [] self.evaluateRangeVars(op, p.args[0], p.args[1:], contextItem, result) elif op == 'if': test = self.effectiveBooleanValue( p, self.evaluate(p.args[0].expr[0], contextItem=contextItem)) result = self.evaluate(p.args[1 if test else 2].args, contextItem=contextItem) elif op == '.': result = contextItem elif op == '..': result = XmlUtil.parent(contextItem.element if isinstance( contextItem, ModelObject.ModelObject) else contextItem) elif op in ('/', '//', 'rootChild', 'rootDescendant'): if op in ('rootChild', 'rootDescendant'): # fix up for multi-instance resultStack.append([ self.inputXbrlInstance.xmlDocument, ]) op = '/' if op == 'rootChild' else '//' # contains QNameDefs and predicates if len(resultStack) > 0: innerFocusNodes = resultStack.pop() else: innerFocusNodes = contextItem navSequence = [] for innerFocusNode in self.flattenSequence( innerFocusNodes): self.evaluate(p.args, contextItem=innerFocusNode, resultStack=navSequence, parentOp=op) result = self.documentOrderedNodes( self.flattenSequence(navSequence)) elif isinstance(p, ProgHeader): self.progHeader = p from arelle.ModelFormulaObject import Trace if p.traceType not in (Trace.MESSAGE, Trace.CUSTOM_FUNCTION): self.traceType = p.traceType setProgHeader = True if result is not None: # note: result can be False which gets appended to resultStack resultStack.append(self.flattenSequence(result)) if setProgHeader: self.progHeader = None return resultStack
def evaluate(self, exprStack, contextItem=None, resultStack=None, parentOp=None): if resultStack is None: resultStack = [] if contextItem is None: contextItem = self.contextItem setProgHeader = False for p in exprStack: result = None if isinstance(p,(str,int,float)): result = p elif isinstance(p,VariableRef): if p.name in self.inScopeVars: result = self.inScopeVars[p.name] elif isinstance(p,QNameDef): # step axis operation if len(resultStack) == 0 or not self.isNodeSequence(resultStack[-1]): resultStack.append( [ contextItem, ] ) result = self.stepAxis(parentOp, p, resultStack.pop() ) elif isinstance(p,OperationDef): op = p.name if isinstance(op, QNameDef): # function call args = self.evaluate(p.args, contextItem=contextItem) ns = op.namespaceURI; localname = op.localName try: from arelle import (FunctionXs, FunctionFn, FunctionXfi, FunctionIxt, FunctionCustom) if op in self.modelXbrl.modelCustomFunctionSignatures: result = FunctionCustom.call(self, p, op, contextItem, args) elif op.unprefixed and localname in {'attribute', 'comment', 'document-node', 'element', 'item', 'node', 'processing-instruction', 'schema-attribute', 'schema-element', 'text'}: # step axis operation if len(resultStack) == 0 or not self.isNodeSequence(resultStack[-1]): if isinstance(contextItem, (tuple,list)): resultStack.append( contextItem ) else: resultStack.append( [ contextItem, ] ) result = self.stepAxis(parentOp, p, resultStack.pop() ) elif op.unprefixed or ns == XbrlConst.fn: result = FunctionFn.call(self, p, localname, contextItem, args) elif ns == XbrlConst.xfi or ns == XbrlConst.xff: result = FunctionXfi.call(self, p, localname, args) elif ns == XbrlConst.xsd: result = FunctionXs.call(self, p, localname, args) elif ns.startswith("http://www.xbrl.org/inlineXBRL/transformation"): result = FunctionIxt.call(self, p, localname, args) else: raise XPathException(p, 'err:XPST0017', _('Function call not identified.')) except FunctionNumArgs: raise XPathException(p, 'err:XPST0017', _('Number of arguments do not match signature arity.')) except FunctionArgType as err: raise XPathException(p, err.errCode, _('Argument {0} does not match expected type {1}.') .format(err.argNum, err.expectedType)) except FunctionNotAvailable: raise XPathException(p, 'arelle:functDeferred', _('Function {0} is not available in this build.') .format(str(op))) elif op in {'+', '-', '*', 'div', 'idiv', 'mod', 'to', 'gt', 'ge', 'eq', 'ne', 'lt', 'le'}: # binary arithmetic operations and value comparisons s1 = self.atomize( p, resultStack.pop() ) if len(resultStack) > 0 else [] s2 = self.atomize( p, self.evaluate(p.args, contextItem=contextItem) ) # value comparisons if len(s1) > 1 or len(s2) > 1: raise XPathException(p, 'err:XPTY0004', _("Value operation '{0}' sequence length error").format(op)) if len(s1) == 0 or len(s2) == 0: result = [] else: op1 = s1[0] op2 = s2[0] from arelle.FunctionUtil import (testTypeCompatiblity) testTypeCompatiblity( self, p, op, op1, op2 ) if op == '+': result = op1 + op2 elif op == '-': result = op1 - op2 elif op == '*': result = op1 * op2 elif op in ('div', 'idiv', "mod"): try: if op == 'div': result = op1 / op2 elif op == 'idiv': result = op1 // op2 elif op == 'mod': result = op1 % op2 except ZeroDivisionError: raise XPathException(p, 'err:FOAR0001', _('Attempt to divide by zero: {0} {1} {2}.') .format(op1, op, op2)) elif op == 'ge': result = op1 >= op2 elif op == 'gt': result = op1 > op2 elif op == 'le': result = op1 <= op2 elif op == 'lt': result = op1 < op2 elif op == 'eq': result = op1 == op2 elif op == 'ne': result = op1 != op2 elif op == 'to': result = range( int(op1), int(op2) + 1 ) elif op in {'>', '>=', '=', '!=', '<', '<='}: # general comparisons s1 = self.atomize( p, resultStack.pop() ) if len(resultStack) > 0 else [] s2 = self.atomize( p, self.evaluate(p.args, contextItem=contextItem) ) result = []; for op1 in s1: for op2 in s2: if op == '>=': result = op1 >= op2 elif op == '>': result = op1 > op2 elif op == '<=': result = op1 <= op2 elif op == '<': result = op1 < op2 elif op == '=': result = op1 == op2 elif op == '!=': result = op1 != op2 if result: break if result: break elif op in {'is', '>>', '<<'}: # node comparisons s1 = resultStack.pop() if len(resultStack) > 0 else [] s2 = self.evaluate(p.args, contextItem=contextItem) if len(s1) > 1 or len(s2) > 1 or not self.isNodeSequence(s1) or not self.isNodeSequence(s2[0]): raise XPathException(p, 'err:XPTY0004', _('Node comparison sequence error')) if len(s1) == 0 or len(s2[0]) == 0: result = [] else: n1 = s1[0] n2 = s2[0][0] result = False; for op1 in s1: for op2 in s2: if op == 'is': result = n1 == n2 elif op == '>>': result = op1 > op2 elif op == '<<': result = op1 <= op2 if result: break elif op in {'intersect','except','union','|'}: # node comparisons s1 = resultStack.pop() if len(resultStack) > 0 else [] s2 = self.flattenSequence(self.evaluate(p.args, contextItem=contextItem)) if not self.isNodeSequence(s1) or not self.isNodeSequence(s2): raise XPathException(p, 'err:XPTY0004', _('Node operation sequence error')) set1 = set(s1) set2 = set(s2) if op == 'intersect': resultset = set1 & set2 elif op == 'except': resultset = set1 - set2 elif op == 'union' or op == '|': resultset = set1 | set2 # convert to a list in document order result = self.documentOrderedNodes(resultset) elif op in {'and', 'or'}: # general comparisons if len(resultStack) == 0: result = [] else: op1 = self.effectiveBooleanValue( p, resultStack.pop() ) op2 = self.effectiveBooleanValue( p, self.evaluate(p.args, contextItem=contextItem) ) result = False; if op == 'and': result = op1 and op2 elif op == 'or': result = op1 or op2 elif op in {'u+', 'u-'}: s1 = self.atomize( p, self.evaluate(p.args, contextItem=contextItem) ) if len(s1) > 1: raise XPathException(p, 'err:XPTY0004', _('Unary expression sequence length error')) if len(s1) == 0: result = [] else: op1 = s1[0] if op == 'u+': result = op1 elif op == 'u-': result = -op1 elif op == 'instance': result = False s1 = self.flattenSequence( resultStack.pop() ) if len(resultStack) > 0 else [] arity = len(s1) if len(p.args) > 1: occurenceIndicator = p.args[1] if (occurenceIndicator == '?' and arity in (0,1) ) or \ (occurenceIndicator == '+' and arity >= 1) or \ (occurenceIndicator == '*'): result = True elif arity == 1: result = True if result and len(p.args) > 0: t = p.args[0] for x in s1: if isinstance(t, QNameDef): if t.namespaceURI == XbrlConst.xsd: type = { "integer": int, "string": str, "decimal": float, "double": float, "float": float, "boolean": bool, "QName": QName, "anyURI": AnyURI, "date": DateTime, "dateTime": DateTime, }.get(t.localName) if type: result = isinstance(x, type) if result and type == DateTime: result = x.dateOnly == (t.localName == "date") elif isinstance(t, OperationDef): if t.name == "element" and isinstance(x,ModelObject): if len(t.args) >= 1: qn = t.args[0] if qn== '*' or (isinstance(qn,QNameDef) and qn == x): result = True if len(t.args) >= 2 and isinstance(t.args[1],QNameDef): modelXbrl = x.modelDocument.modelXbrl modelConcept = modelXbrl.qnameConcepts.get(qname(x)) if not modelConcept.instanceOfType(t.args[1]): result = False if not result: break elif op == 'sequence': result = self.evaluate(p.args, contextItem=contextItem) elif op == 'predicate': result = self.predicate(p, resultStack.pop()) elif op in {'for','some','every'}: # for, some, every result = [] self.evaluateRangeVars(op, p.args[0], p.args[1:], contextItem, result) elif op == 'if': test = self.effectiveBooleanValue( p, self.evaluate(p.args[0].expr[0], contextItem=contextItem) ) result = self.evaluate(p.args[1 if test else 2].args, contextItem=contextItem) elif op == '.': result = contextItem elif op == '..': result = XmlUtil.parent(contextItem) elif op in ('/', '//', 'rootChild', 'rootDescendant'): if op in ('rootChild', 'rootDescendant'): # fix up for multi-instance resultStack.append( [self.inputXbrlInstance.xmlDocument,] ) op = '/' if op == 'rootChild' else '//' # contains QNameDefs and predicates if len(resultStack) > 0: innerFocusNodes = resultStack.pop() else: innerFocusNodes = contextItem navSequence = [] for innerFocusNode in self.flattenSequence(innerFocusNodes): navSequence += self.evaluate(p.args, contextItem=innerFocusNode, parentOp=op) result = self.documentOrderedNodes(self.flattenSequence(navSequence)) elif isinstance(p,ProgHeader): self.progHeader = p from arelle.ModelFormulaObject import Trace if p.traceType not in (Trace.MESSAGE, Trace.CUSTOM_FUNCTION): self.traceType = p.traceType setProgHeader = True if result is not None: # note: result can be False which gets appended to resultStack resultStack.append( self.flattenSequence( result ) ) if setProgHeader: self.progHeader = None return resultStack
def validate(val): formulaOptions = val.modelXbrl.modelManager.formulaOptions XPathParser.initializeParser(val) val.modelXbrl.modelManager.showStatus(_("Compiling formulae")) initialErrorCount = val.modelXbrl.logCountErr # global parameter names parameterQnames = set() instanceQnames = set() parameterDependencies = {} instanceDependencies = defaultdict( set) # None-key entries are non-formula dependencies dependencyResolvedParameters = set() orderedParameters = [] orderedInstances = [] for paramQname, modelParameter in val.modelXbrl.qnameParameters.items(): if isinstance(modelParameter, ModelParameter): modelParameter.compile() parameterDependencies[paramQname] = modelParameter.variableRefs() parameterQnames.add(paramQname) if isinstance(modelParameter, ModelInstance): instanceQnames.add(paramQname) # duplicates checked on loading modelDocument #resolve dependencies resolvedAParameter = True while (resolvedAParameter): resolvedAParameter = False for paramQname in parameterQnames: if paramQname not in dependencyResolvedParameters and \ len(parameterDependencies[paramQname] - dependencyResolvedParameters) == 0: dependencyResolvedParameters.add(paramQname) orderedParameters.append(paramQname) resolvedAParameter = True # anything unresolved? for paramQname in parameterQnames: if paramQname not in dependencyResolvedParameters: circularOrUndefDependencies = parameterDependencies[ paramQname] - dependencyResolvedParameters undefinedVars = circularOrUndefDependencies - parameterQnames paramsCircularDep = circularOrUndefDependencies - undefinedVars if len(undefinedVars) > 0: val.modelXbrl.error( _("Undefined dependencies in parameter {0}, to names {1}" ).format(paramQname, ", ".join( (str(v) for v in undefinedVars))), "err", "xbrlve:unresolvedDependency") if len(paramsCircularDep) > 0: val.modelXbrl.error( _("Cyclic dependencies in parameter {0}, to names {1}" ).format(paramQname, ", ".join( (str(d) for d in paramsCircularDep))), "err", "xbrlve:parameterCyclicDependencies") for custFnSig in val.modelXbrl.modelCustomFunctionSignatures.values(): custFnQname = custFnSig.qname if custFnQname.namespaceURI == "XbrlConst.xfi": val.modelXbrl.error( _("Custom function {0} has namespace reserved for functions in the function registry {1}" ).format(str(custFnQname), custFnQname.namespaceURI), "err", "xbrlve:noProhibitedNamespaceForCustomFunction") # any custom function implementations? for modelRel in val.modelXbrl.relationshipSet( XbrlConst.functionImplementation).fromModelObject(custFnSig): custFnImpl = modelRel.toModelObject custFnSig.customFunctionImplementation = custFnImpl if len(custFnImpl.inputNames) != len(custFnSig.inputTypes): val.modelXbrl.error( _("Custom function {0} signature has {1} parameters but implementation has {2}, must be matching" ).format(str(custFnQname), len(custFnSig.inputTypes), len(custFnImpl.inputNames)), "err", "xbrlcfie:inputMismatch") for custFnImpl in val.modelXbrl.modelCustomFunctionImplementations: if not val.modelXbrl.relationshipSet( XbrlConst.functionImplementation).toModelObject(custFnImpl): val.modelXbrl.error( _("Custom function implementation {0} has no relationship from any custom function signature" ).format(custFnImpl.xlinkLabel), "err", "xbrlcfie:missingCFIRelationship") custFnImpl.compile() # xpathContext is needed for filter setup for expressions such as aspect cover filter # determine parameter values xpathContext = XPathContext.create(val.modelXbrl) for paramQname in orderedParameters: if not isinstance(modelParameter, ModelInstance): modelParameter = val.modelXbrl.qnameParameters[paramQname] asType = modelParameter.asType asLocalName = asType.localName if asType else "string" try: if val.parameters and paramQname in val.parameters: paramDataType, paramValue = val.parameters[paramQname] typeLocalName = paramDataType.localName if paramDataType else "string" value = FunctionXs.call(xpathContext, None, typeLocalName, [paramValue]) result = FunctionXs.call(xpathContext, None, asLocalName, [value]) if formulaOptions.traceParameterInputValue: val.modelXbrl.error( _("Parameter {0} input {1}").format( paramQname, result), "info", "formula:trace") else: result = modelParameter.evaluate(xpathContext, asType) if formulaOptions.traceParameterExpressionResult: val.modelXbrl.error( _("Parameter {0} result {1}").format( paramQname, result), "info", "formula:trace") xpathContext.inScopeVars[ paramQname] = result # make visible to subsequent parameter expression except XPathContext.XPathException as err: val.modelXbrl.error( _("Parameter \n{0} \nException: \n{1}").format( paramQname, err.message), "err", "xbrlve:parameterTypeMismatch" if err.code == "err:FORG0001" else err.code) produceOutputXbrlInstance = False instanceProducingVariableSets = defaultdict(list) for modelVariableSet in val.modelXbrl.modelVariableSets: varSetInstanceDependencies = set() if isinstance(modelVariableSet, ModelFormula): instanceQname = None for modelRel in val.modelXbrl.relationshipSet( XbrlConst.formulaInstance).fromModelObject( modelVariableSet): instance = modelRel.toModelObject if isinstance(instance, ModelInstance): if instanceQname is None: instanceQname = instance.qname else: val.modelXbrl.error( _("Multiple output instances for formula {0}, to names {1}, {2}" ).format(modelVariableSet.xlinkLabel, instanceQname, instance.qname), "info", "arelle:multipleOutputInstances") if instanceQname is None: instanceQname = XbrlConst.qnStandardOutputInstance instanceQnames.add(instanceQname) modelVariableSet.outputInstanceQname = instanceQname if val.validateSBRNL: val.modelXbrl.error( _("Formula linkbase {0} formula:formula {1} is not allowed" ).format( os.path.basename(modelVariableSet.modelDocument.uri), modelVariableSet.xlinkLabel), "err", "SBR.NL.2.3.9.03") else: instanceQname = None modelVariableSet.countSatisfied = 0 modelVariableSet.countNotSatisfied = 0 checkValidationMessages(val, modelVariableSet) instanceProducingVariableSets[instanceQname].append(modelVariableSet) modelVariableSet.outputInstanceQname = instanceQname if modelVariableSet.aspectModel not in ("non-dimensional", "dimensional"): val.modelXbrl.error( _("Variable set {0}, aspect model {1} not recognized").format( modelVariableSet.xlinkLabel, modelVariableSet.aspectModel), "err", "xbrlve:unknownAspectModel") modelVariableSet.compile() modelVariableSet.hasConsistencyAssertion = False #determine dependencies within variable sets nameVariables = {} qnameRels = {} definedNamesSet = set() for modelRel in val.modelXbrl.relationshipSet( XbrlConst.variableSet).fromModelObject(modelVariableSet): varqname = modelRel.variableQname if varqname: qnameRels[varqname] = modelRel toVariable = modelRel.toModelObject if varqname not in definedNamesSet: definedNamesSet.add(varqname) if varqname not in nameVariables: nameVariables[varqname] = toVariable elif nameVariables[varqname] != toVariable: val.modelXbrl.error( _("Multiple variables named {1} in variable set {0}" ).format(modelVariableSet.xlinkLabel, varqname), "err", "xbrlve:duplicateVariableNames") fromInstanceQnames = None for instRel in val.modelXbrl.relationshipSet( XbrlConst.instanceVariable).toModelObject(toVariable): fromInstance = instRel.fromModelObject if isinstance(fromInstance, ModelInstance): fromInstanceQname = fromInstance.qname varSetInstanceDependencies.add(fromInstanceQname) instanceDependencies[instanceQname].add( fromInstanceQname) if fromInstanceQnames is None: fromInstanceQnames = set() fromInstanceQnames.add(fromInstanceQname) if fromInstanceQnames is None: varSetInstanceDependencies.add( XbrlConst.qnStandardInputInstance) if instanceQname: instanceDependencies[instanceQname].add( XbrlConst.qnStandardInputInstance) toVariable.fromInstanceQnames = fromInstanceQnames else: val.modelXbrl.error( _("Variables name {1} cannot be determined on arc from {0}" ).format(modelVariableSet.xlinkLabel, modelRel.variablename), "err", "xbrlve:variableNameResolutionFailure") definedNamesSet |= parameterQnames variableDependencies = {} for modelRel in val.modelXbrl.relationshipSet( XbrlConst.variableSet).fromModelObject(modelVariableSet): variable = modelRel.toModelObject if isinstance( variable, (ModelParameter, ModelVariable)): # ignore anything not parameter or variable varqname = modelRel.variableQname depVars = variable.variableRefs() variableDependencies[varqname] = depVars if len(depVars ) > 0 and formulaOptions.traceVariablesDependencies: val.modelXbrl.error( _("Variable set {0}, variable {1}, dependences {2}"). format(modelVariableSet.xlinkLabel, varqname, depVars), "info", "formula:trace") definedNamesSet.add(varqname) # check for fallback value variable references if isinstance(variable, ModelFactVariable): for depVar in XPathParser.variableReferencesSet( variable.fallbackValueProg, variable.element): if depVar in qnameRels and isinstance( qnameRels[depVar].toModelObject, ModelVariable): val.modelXbrl.error( _("Variable set {0} fallbackValue '{1}' cannot refer to variable {2}" ).format(modelVariableSet.xlinkLabel, variable.fallbackValue, depVar), "err", "xbrlve:factVariableReferenceNotAllowed") # check for covering aspect not in variable set aspect model checkFilterAspectModel(val, modelVariableSet, variable.filterRelationships, xpathContext) orderedNameSet = set() orderedNameList = [] orderedAVariable = True while (orderedAVariable): orderedAVariable = False for varqname, depVars in variableDependencies.items(): if varqname not in orderedNameSet and len(depVars - parameterQnames - orderedNameSet) == 0: orderedNameList.append(varqname) orderedNameSet.add(varqname) orderedAVariable = True if varqname in instanceQnames: varSetInstanceDependencies.add(varqname) instanceDependencies[instanceQname].add(varqname) elif isinstance(nameVariables.get(varqname), ModelInstance): instqname = nameVariables[varqname].qname varSetInstanceDependencies.add(instqname) instanceDependencies[instanceQname].add(instqname) # anything unresolved? for varqname, depVars in variableDependencies.items(): if varqname not in orderedNameSet: circularOrUndefVars = depVars - parameterQnames - orderedNameSet undefinedVars = circularOrUndefVars - definedNamesSet varsCircularDep = circularOrUndefVars - undefinedVars if len(undefinedVars) > 0: val.modelXbrl.error( _("Undefined variable dependencies in variable st {0}, from variable {1} to {2}" ).format(modelVariableSet.xlinkLabel, varqname, undefinedVars), "err", "xbrlve:unresolvedDependency") if len(varsCircularDep) > 0: val.modelXbrl.error( _("Cyclic dependencies in variable set {0}, from variable {1} to {2}" ).format(modelVariableSet.xlinkLabel, varqname, varsCircularDep), "err", "xbrlve:cyclicDependencies") # check unresolved variable set dependencies for varSetDepVarQname in modelVariableSet.variableRefs(): if varSetDepVarQname not in orderedNameSet and varSetDepVarQname not in parameterQnames: val.modelXbrl.error( _("Undefined variable dependency in variable set {0}, {1}" ).format(modelVariableSet.xlinkLabel, varSetDepVarQname), "err", "xbrlve:unresolvedDependency") if varSetDepVarQname in instanceQnames: varSetInstanceDependencies.add(varSetDepVarQname) instanceDependencies[instanceQname].add(varSetDepVarQname) elif isinstance(nameVariables.get(varSetDepVarQname), ModelInstance): instqname = nameVariables[varSetDepVarQname].qname varSetInstanceDependencies.add(instqname) instanceDependencies[instanceQname].add(instqname) if formulaOptions.traceVariablesOrder: val.modelXbrl.error( _("Variable set {0}, variables order: {1}").format( modelVariableSet.xlinkLabel, orderedNameList), "info", "formula:trace") if (formulaOptions.traceVariablesDependencies and len(varSetInstanceDependencies) > 0 and varSetInstanceDependencies != {XbrlConst.qnStandardInputInstance}): val.modelXbrl.error( _("Variable set {0}, instance dependences {1}").format( modelVariableSet.xlinkLabel, varSetInstanceDependencies), "info", "formula:trace") modelVariableSet.orderedVariableRelationships = [] for varqname in orderedNameList: if varqname in qnameRels: modelVariableSet.orderedVariableRelationships.append( qnameRels[varqname]) # check existence assertion variable dependencies if isinstance(modelVariableSet, ModelExistenceAssertion): for depVar in modelVariableSet.variableRefs(): if depVar in qnameRels and isinstance( qnameRels[depVar].toModelObject, ModelVariable): val.modelXbrl.error( _("Existence Assertion {0}, cannot refer to variable {1}" ).format(modelVariableSet.xlinkLabel, depVar), "err", "xbrleae:variableReferenceNotAllowed") # check messages variable dependencies checkValidationMessageVariables(val, modelVariableSet, qnameRels) # check preconditions modelVariableSet.preconditions = [] for modelRel in val.modelXbrl.relationshipSet( XbrlConst.variableSetPrecondition).fromModelObject( modelVariableSet): precondition = modelRel.toModelObject if isinstance(precondition, ModelPrecondition): modelVariableSet.preconditions.append(precondition) # check for variable sets referencing fact or general variables for modelRel in val.modelXbrl.relationshipSet( XbrlConst.variableSetFilter).fromModelObject(modelVariableSet): varSetFilter = modelRel.toModelObject if modelRel.isCovered: val.modelXbrl.error( _("Variable set {0}, filter {1}, cannot be covered" ).format(modelVariableSet.xlinkLabel, varSetFilter.xlinkLabel), "wrn", "arelle:variableSetFilterCovered") modelRel._isCovered = False # block group filter from being able to covere for depVar in varSetFilter.variableRefs(): if depVar in qnameRels and isinstance( qnameRels[depVar].toModelObject, ModelVariable): val.modelXbrl.error( _("Variable set {0}, filter {1}, cannot refer to variable {2}" ).format(modelVariableSet.xlinkLabel, varSetFilter.xlinkLabel, depVar), "err", "xbrlve:factVariableReferenceNotAllowed") # check aspects of formula if isinstance(modelVariableSet, ModelFormula): checkFormulaRules(val, modelVariableSet, nameVariables) # determine instance dependency order orderedInstancesSet = set() stdInpInst = {XbrlConst.qnStandardInputInstance} orderedInstancesList = [] orderedAnInstance = True while (orderedAnInstance): orderedAnInstance = False for instqname, depInsts in instanceDependencies.items(): if instqname and instqname not in orderedInstancesSet and len( depInsts - stdInpInst - orderedInstancesSet) == 0: orderedInstancesList.append(instqname) orderedInstancesSet.add(instqname) orderedAnInstance = True orderedInstancesList.append( None) # assertions come after all formulas that produce outputs # anything unresolved? for instqname, depInsts in instanceDependencies.items(): if instqname not in orderedInstancesSet: # can also be satisfied from an input DTS missingDependentInstances = depInsts - stdInpInst if val.parameters: missingDependentInstances -= val.parameters.keys() if instqname: if missingDependentInstances: val.modelXbrl.error( _("Cyclic dependencies of instance {0} produced by a formula, with variables consuming instances {1}" ).format(instqname, missingDependentInstances), "err", "xbrlvarinste:instanceVariableRecursionCycle") elif instqname == XbrlConst.qnStandardOutputInstance: orderedInstancesSet.add(instqname) orderedInstancesList.append( instqname ) # standard output formula, all input dependencies in parameters ''' future check? if instance has no external input or producing formula else: val.modelXbrl.error( _("Unresolved dependencies of an assertion's variables on instances {0}").format( depInsts - stdInpInst ), "err", "xbrlvarinste:instanceVariableRecursionCycle") ''' if formulaOptions.traceVariablesOrder and len(orderedInstancesList) > 1: val.modelXbrl.error( _("Variable instances processing order: {0}").format( orderedInstancesList), "info", "formula:trace") # linked consistency assertions for modelRel in val.modelXbrl.relationshipSet( XbrlConst.consistencyAssertionFormula).modelRelationships: if modelRel.fromModelObject and modelRel.toModelObject and isinstance( modelRel.toModelObject, ModelFormula): consisAsser = modelRel.fromModelObject consisAsser.countSatisfied = 0 consisAsser.countNotSatisfied = 0 if consisAsser.hasProportionalAcceptanceRadius and consisAsser.hasAbsoluteAcceptanceRadius: val.modelXbrl.error( _("Consistency assertion {0} has both absolute and proportional acceptance radii" ).format(consisAsser.xlinkLabel), "err", "xbrlcae:acceptanceRadiusConflict") consisAsser.orderedVariableRelationships = [] for consisParamRel in val.modelXbrl.relationshipSet( XbrlConst.consistencyAssertionParameter).fromModelObject( consisAsser): if isinstance(consisParamRel.toModelObject, ModelVariable): val.modelXbrl.error( _("Consistency assertion {0} has relationship to a {1} {2}" ).format( consisAsser.xlinkLabel, consisParamRel.toModelObject.element.localName, consisParamRel.toModelObject.xlinkLabel), "err", "xbrlcae:variablesNotAllowed") else: consisAsser.orderedVariableRelationships.append( consisParamRel) consisAsser.compile() modelRel.toModelObject.hasConsistencyAssertion = True if initialErrorCount < val.modelXbrl.logCountErr: return # don't try to execute # formula output instances if instanceQnames: schemaRefs = [ val.modelXbrl.modelDocument.relativeUri(referencedDoc.uri) for referencedDoc in val.modelXbrl.modelDocument.referencesDocument.keys() if referencedDoc.type == ModelDocument.Type.SCHEMA ] outputXbrlInstance = None for instanceQname in instanceQnames: if instanceQname == XbrlConst.qnStandardInputInstance: continue # always present the standard way if val.parameters and instanceQname in val.parameters: namedInstance = val.parameters[instanceQname][1] else: # empty intermediate instance uri = val.modelXbrl.modelDocument.filepath[:-4] + "-output-XBRL-instance" if instanceQname != XbrlConst.qnStandardOutputInstance: uri = uri + "-" + instanceQname.localName uri = uri + ".xml" namedInstance = ModelXbrl.create( val.modelXbrl.modelManager, newDocumentType=ModelDocument.Type.INSTANCE, url=uri, schemaRefs=schemaRefs, isEntry=True) xpathContext.inScopeVars[instanceQname] = namedInstance if instanceQname == XbrlConst.qnStandardOutputInstance: outputXbrlInstance = namedInstance # evaluate consistency assertions # evaluate variable sets not in consistency assertions for instanceQname in orderedInstancesList: for modelVariableSet in instanceProducingVariableSets[instanceQname]: # produce variable evaluations from arelle.FormulaEvaluator import evaluate try: evaluate(xpathContext, modelVariableSet) except XPathContext.XPathException as err: val.modelXbrl.error( _("Variable set \n{0} \nException: \n{1}").format( modelVariableSet, err.message), "err", err.code) # log assertion result counts asserTests = {} for exisValAsser in val.modelXbrl.modelVariableSets: if isinstance(exisValAsser, ModelVariableSetAssertion): asserTests[exisValAsser.id] = (exisValAsser.countSatisfied, exisValAsser.countNotSatisfied) if formulaOptions.traceAssertionResultCounts: val.modelXbrl.error( _("{0} Assertion {1} evaluations : {2} satisfied, {3} not satisfied" ).format( "Existence" if isinstance(exisValAsser, ModelExistenceAssertion) else "Value", exisValAsser.id, exisValAsser.countSatisfied, exisValAsser.countNotSatisfied), "info", "formula:trace") for modelRel in val.modelXbrl.relationshipSet( XbrlConst.consistencyAssertionFormula).modelRelationships: if modelRel.fromModelObject and modelRel.toModelObject and isinstance( modelRel.toModelObject, ModelFormula): consisAsser = modelRel.fromModelObject asserTests[consisAsser.id] = (consisAsser.countSatisfied, consisAsser.countNotSatisfied) if formulaOptions.traceAssertionResultCounts: val.modelXbrl.error( _("Consistency Assertion {0} evaluations : {1} satisfied, {2} not satisfied" ).format(consisAsser.id, consisAsser.countSatisfied, consisAsser.countNotSatisfied), "info", "formula:trace") if asserTests: val.modelXbrl.error( _("Assertion results {0}").format(asserTests), "asrtNoLog", asserTests) # display output instance if outputXbrlInstance: if val.modelXbrl.formulaOutputInstance: # close prior instance, usually closed by caller to validate as it may affect UI on different thread val.modelXbrl.formulaOutputInstance.close() val.modelXbrl.formulaOutputInstance = outputXbrlInstance
def validate(val): formulaOptions = val.modelXbrl.modelManager.formulaOptions XPathParser.initializeParser(val) val.modelXbrl.modelManager.showStatus(_("Compiling formulae")) initialErrorCount = val.modelXbrl.logCountErr # global parameter names parameterQnames = set() instanceQnames = set() parameterDependencies = {} instanceDependencies = defaultdict(set) # None-key entries are non-formula dependencies dependencyResolvedParameters = set() orderedParameters = [] orderedInstances = [] for paramQname, modelParameter in val.modelXbrl.qnameParameters.items(): if isinstance(modelParameter, ModelParameter): modelParameter.compile() parameterDependencies[paramQname] = modelParameter.variableRefs() parameterQnames.add(paramQname) if isinstance(modelParameter, ModelInstance): instanceQnames.add(paramQname) # duplicates checked on loading modelDocument #resolve dependencies resolvedAParameter = True while (resolvedAParameter): resolvedAParameter = False for paramQname in parameterQnames: if paramQname not in dependencyResolvedParameters and \ len(parameterDependencies[paramQname] - dependencyResolvedParameters) == 0: dependencyResolvedParameters.add(paramQname) orderedParameters.append(paramQname) resolvedAParameter = True # anything unresolved? for paramQname in parameterQnames: if paramQname not in dependencyResolvedParameters: circularOrUndefDependencies = parameterDependencies[paramQname] - dependencyResolvedParameters undefinedVars = circularOrUndefDependencies - parameterQnames paramsCircularDep = circularOrUndefDependencies - undefinedVars if len(undefinedVars) > 0: val.modelXbrl.error( _("Undefined dependencies in parameter {0}, to names {1}").format( paramQname, ", ".join((str(v) for v in undefinedVars))), "err", "xbrlve:unresolvedDependency") if len(paramsCircularDep) > 0: val.modelXbrl.error( _("Cyclic dependencies in parameter {0}, to names {1}").format( paramQname, ", ".join((str(d) for d in paramsCircularDep)) ), "err", "xbrlve:parameterCyclicDependencies") for custFnSig in val.modelXbrl.modelCustomFunctionSignatures.values(): custFnQname = custFnSig.qname if custFnQname.namespaceURI == "XbrlConst.xfi": val.modelXbrl.error( _("Custom function {0} has namespace reserved for functions in the function registry {1}").format( str(custFnQname), custFnQname.namespaceURI ), "err", "xbrlve:noProhibitedNamespaceForCustomFunction") # any custom function implementations? for modelRel in val.modelXbrl.relationshipSet(XbrlConst.functionImplementation).fromModelObject(custFnSig): custFnImpl = modelRel.toModelObject custFnSig.customFunctionImplementation = custFnImpl if len(custFnImpl.inputNames) != len(custFnSig.inputTypes): val.modelXbrl.error( _("Custom function {0} signature has {1} parameters but implementation has {2}, must be matching").format( str(custFnQname), len(custFnSig.inputTypes), len(custFnImpl.inputNames) ), "err", "xbrlcfie:inputMismatch") for custFnImpl in val.modelXbrl.modelCustomFunctionImplementations: if not val.modelXbrl.relationshipSet(XbrlConst.functionImplementation).toModelObject(custFnImpl): val.modelXbrl.error( _("Custom function implementation {0} has no relationship from any custom function signature").format( custFnImpl.xlinkLabel), "err", "xbrlcfie:missingCFIRelationship") custFnImpl.compile() # xpathContext is needed for filter setup for expressions such as aspect cover filter # determine parameter values xpathContext = XPathContext.create(val.modelXbrl) for paramQname in orderedParameters: if not isinstance(modelParameter, ModelInstance): modelParameter = val.modelXbrl.qnameParameters[paramQname] asType = modelParameter.asType asLocalName = asType.localName if asType else "string" try: if val.parameters and paramQname in val.parameters: paramDataType, paramValue = val.parameters[paramQname] typeLocalName = paramDataType.localName if paramDataType else "string" value = FunctionXs.call(xpathContext, None, typeLocalName, [paramValue]) result = FunctionXs.call(xpathContext, None, asLocalName, [value]) if formulaOptions.traceParameterInputValue: val.modelXbrl.error( _("Parameter {0} input {1}").format( paramQname, result), "info", "formula:trace") else: result = modelParameter.evaluate(xpathContext, asType) if formulaOptions.traceParameterExpressionResult: val.modelXbrl.error( _("Parameter {0} result {1}").format( paramQname, result), "info", "formula:trace") xpathContext.inScopeVars[paramQname] = result # make visible to subsequent parameter expression except XPathContext.XPathException as err: val.modelXbrl.error( _("Parameter \n{0} \nException: \n{1}").format( paramQname, err.message), "err", "xbrlve:parameterTypeMismatch" if err.code == "err:FORG0001" else err.code) produceOutputXbrlInstance = False instanceProducingVariableSets = defaultdict(list) for modelVariableSet in val.modelXbrl.modelVariableSets: varSetInstanceDependencies = set() if isinstance(modelVariableSet, ModelFormula): instanceQname = None for modelRel in val.modelXbrl.relationshipSet(XbrlConst.formulaInstance).fromModelObject(modelVariableSet): instance = modelRel.toModelObject if isinstance(instance, ModelInstance): if instanceQname is None: instanceQname = instance.qname else: val.modelXbrl.error( _("Multiple output instances for formula {0}, to names {1}, {2}").format( modelVariableSet.xlinkLabel, instanceQname, instance.qname ), "info", "arelle:multipleOutputInstances") if instanceQname is None: instanceQname = XbrlConst.qnStandardOutputInstance instanceQnames.add(instanceQname) modelVariableSet.outputInstanceQname = instanceQname if val.validateSBRNL: val.modelXbrl.error( _("Formula linkbase {0} formula:formula {1} is not allowed").format( os.path.basename(modelVariableSet.modelDocument.uri), modelVariableSet.xlinkLabel), "err", "SBR.NL.2.3.9.03") else: instanceQname = None modelVariableSet.countSatisfied = 0 modelVariableSet.countNotSatisfied = 0 checkValidationMessages(val, modelVariableSet) instanceProducingVariableSets[instanceQname].append(modelVariableSet) modelVariableSet.outputInstanceQname = instanceQname if modelVariableSet.aspectModel not in ("non-dimensional", "dimensional"): val.modelXbrl.error( _("Variable set {0}, aspect model {1} not recognized").format( modelVariableSet.xlinkLabel, modelVariableSet.aspectModel), "err", "xbrlve:unknownAspectModel") modelVariableSet.compile() modelVariableSet.hasConsistencyAssertion = False #determine dependencies within variable sets nameVariables = {} qnameRels = {} definedNamesSet = set() for modelRel in val.modelXbrl.relationshipSet(XbrlConst.variableSet).fromModelObject(modelVariableSet): varqname = modelRel.variableQname if varqname: qnameRels[varqname] = modelRel toVariable = modelRel.toModelObject if varqname not in definedNamesSet: definedNamesSet.add(varqname) if varqname not in nameVariables: nameVariables[varqname] = toVariable elif nameVariables[varqname] != toVariable: val.modelXbrl.error( _("Multiple variables named {1} in variable set {0}").format( modelVariableSet.xlinkLabel, varqname ), "err", "xbrlve:duplicateVariableNames") fromInstanceQnames = None for instRel in val.modelXbrl.relationshipSet(XbrlConst.instanceVariable).toModelObject(toVariable): fromInstance = instRel.fromModelObject if isinstance(fromInstance, ModelInstance): fromInstanceQname = fromInstance.qname varSetInstanceDependencies.add(fromInstanceQname) instanceDependencies[instanceQname].add(fromInstanceQname) if fromInstanceQnames is None: fromInstanceQnames = set() fromInstanceQnames.add(fromInstanceQname) if fromInstanceQnames is None: varSetInstanceDependencies.add(XbrlConst.qnStandardInputInstance) if instanceQname: instanceDependencies[instanceQname].add(XbrlConst.qnStandardInputInstance) toVariable.fromInstanceQnames = fromInstanceQnames else: val.modelXbrl.error( _("Variables name {1} cannot be determined on arc from {0}").format( modelVariableSet.xlinkLabel, modelRel.variablename ), "err", "xbrlve:variableNameResolutionFailure") definedNamesSet |= parameterQnames variableDependencies = {} for modelRel in val.modelXbrl.relationshipSet(XbrlConst.variableSet).fromModelObject(modelVariableSet): variable = modelRel.toModelObject if isinstance(variable, (ModelParameter,ModelVariable)): # ignore anything not parameter or variable varqname = modelRel.variableQname depVars = variable.variableRefs() variableDependencies[varqname] = depVars if len(depVars) > 0 and formulaOptions.traceVariablesDependencies: val.modelXbrl.error(_("Variable set {0}, variable {1}, dependences {2}").format( modelVariableSet.xlinkLabel, varqname, depVars), "info", "formula:trace") definedNamesSet.add(varqname) # check for fallback value variable references if isinstance(variable, ModelFactVariable): for depVar in XPathParser.variableReferencesSet(variable.fallbackValueProg, variable.element): if depVar in qnameRels and isinstance(qnameRels[depVar].toModelObject,ModelVariable): val.modelXbrl.error(_("Variable set {0} fallbackValue '{1}' cannot refer to variable {2}").format( modelVariableSet.xlinkLabel, variable.fallbackValue, depVar), "err", "xbrlve:factVariableReferenceNotAllowed") # check for covering aspect not in variable set aspect model checkFilterAspectModel(val, modelVariableSet, variable.filterRelationships, xpathContext) orderedNameSet = set() orderedNameList = [] orderedAVariable = True while (orderedAVariable): orderedAVariable = False for varqname, depVars in variableDependencies.items(): if varqname not in orderedNameSet and len(depVars - parameterQnames - orderedNameSet) == 0: orderedNameList.append(varqname) orderedNameSet.add(varqname) orderedAVariable = True if varqname in instanceQnames: varSetInstanceDependencies.add(varqname) instanceDependencies[instanceQname].add(varqname) elif isinstance(nameVariables.get(varqname), ModelInstance): instqname = nameVariables[varqname].qname varSetInstanceDependencies.add(instqname) instanceDependencies[instanceQname].add(instqname) # anything unresolved? for varqname, depVars in variableDependencies.items(): if varqname not in orderedNameSet: circularOrUndefVars = depVars - parameterQnames - orderedNameSet undefinedVars = circularOrUndefVars - definedNamesSet varsCircularDep = circularOrUndefVars - undefinedVars if len(undefinedVars) > 0: val.modelXbrl.error( _("Undefined variable dependencies in variable st {0}, from variable {1} to {2}").format( modelVariableSet.xlinkLabel, varqname, undefinedVars), "err", "xbrlve:unresolvedDependency") if len(varsCircularDep) > 0: val.modelXbrl.error( _("Cyclic dependencies in variable set {0}, from variable {1} to {2}").format( modelVariableSet.xlinkLabel, varqname, varsCircularDep ), "err", "xbrlve:cyclicDependencies") # check unresolved variable set dependencies for varSetDepVarQname in modelVariableSet.variableRefs(): if varSetDepVarQname not in orderedNameSet and varSetDepVarQname not in parameterQnames: val.modelXbrl.error( _("Undefined variable dependency in variable set {0}, {1}").format( modelVariableSet.xlinkLabel, varSetDepVarQname), "err", "xbrlve:unresolvedDependency") if varSetDepVarQname in instanceQnames: varSetInstanceDependencies.add(varSetDepVarQname) instanceDependencies[instanceQname].add(varSetDepVarQname) elif isinstance(nameVariables.get(varSetDepVarQname), ModelInstance): instqname = nameVariables[varSetDepVarQname].qname varSetInstanceDependencies.add(instqname) instanceDependencies[instanceQname].add(instqname) if formulaOptions.traceVariablesOrder: val.modelXbrl.error(_("Variable set {0}, variables order: {1}").format( modelVariableSet.xlinkLabel, orderedNameList), "info", "formula:trace") if (formulaOptions.traceVariablesDependencies and len(varSetInstanceDependencies) > 0 and varSetInstanceDependencies != {XbrlConst.qnStandardInputInstance}): val.modelXbrl.error(_("Variable set {0}, instance dependences {1}").format( modelVariableSet.xlinkLabel, varSetInstanceDependencies), "info", "formula:trace") modelVariableSet.orderedVariableRelationships = [] for varqname in orderedNameList: if varqname in qnameRels: modelVariableSet.orderedVariableRelationships.append(qnameRels[varqname]) # check existence assertion variable dependencies if isinstance(modelVariableSet, ModelExistenceAssertion): for depVar in modelVariableSet.variableRefs(): if depVar in qnameRels and isinstance(qnameRels[depVar].toModelObject,ModelVariable): val.modelXbrl.error(_("Existence Assertion {0}, cannot refer to variable {1}").format( modelVariableSet.xlinkLabel, depVar), "err", "xbrleae:variableReferenceNotAllowed") # check messages variable dependencies checkValidationMessageVariables(val, modelVariableSet, qnameRels) # check preconditions modelVariableSet.preconditions = [] for modelRel in val.modelXbrl.relationshipSet(XbrlConst.variableSetPrecondition).fromModelObject(modelVariableSet): precondition = modelRel.toModelObject if isinstance(precondition, ModelPrecondition): modelVariableSet.preconditions.append(precondition) # check for variable sets referencing fact or general variables for modelRel in val.modelXbrl.relationshipSet(XbrlConst.variableSetFilter).fromModelObject(modelVariableSet): varSetFilter = modelRel.toModelObject if modelRel.isCovered: val.modelXbrl.error(_("Variable set {0}, filter {1}, cannot be covered").format( modelVariableSet.xlinkLabel, varSetFilter.xlinkLabel), "wrn", "arelle:variableSetFilterCovered") modelRel._isCovered = False # block group filter from being able to covere for depVar in varSetFilter.variableRefs(): if depVar in qnameRels and isinstance(qnameRels[depVar].toModelObject,ModelVariable): val.modelXbrl.error(_("Variable set {0}, filter {1}, cannot refer to variable {2}").format( modelVariableSet.xlinkLabel, varSetFilter.xlinkLabel, depVar), "err", "xbrlve:factVariableReferenceNotAllowed") # check aspects of formula if isinstance(modelVariableSet, ModelFormula): checkFormulaRules(val, modelVariableSet, nameVariables) # determine instance dependency order orderedInstancesSet = set() stdInpInst = {XbrlConst.qnStandardInputInstance} orderedInstancesList = [] orderedAnInstance = True while (orderedAnInstance): orderedAnInstance = False for instqname, depInsts in instanceDependencies.items(): if instqname and instqname not in orderedInstancesSet and len(depInsts - stdInpInst - orderedInstancesSet) == 0: orderedInstancesList.append(instqname) orderedInstancesSet.add(instqname) orderedAnInstance = True orderedInstancesList.append(None) # assertions come after all formulas that produce outputs # anything unresolved? for instqname, depInsts in instanceDependencies.items(): if instqname not in orderedInstancesSet: # can also be satisfied from an input DTS missingDependentInstances = depInsts - stdInpInst if val.parameters: missingDependentInstances -= val.parameters.keys() if instqname: if missingDependentInstances: val.modelXbrl.error( _("Cyclic dependencies of instance {0} produced by a formula, with variables consuming instances {1}").format( instqname, missingDependentInstances ), "err", "xbrlvarinste:instanceVariableRecursionCycle") elif instqname == XbrlConst.qnStandardOutputInstance: orderedInstancesSet.add(instqname) orderedInstancesList.append(instqname) # standard output formula, all input dependencies in parameters ''' future check? if instance has no external input or producing formula else: val.modelXbrl.error( _("Unresolved dependencies of an assertion's variables on instances {0}").format( depInsts - stdInpInst ), "err", "xbrlvarinste:instanceVariableRecursionCycle") ''' if formulaOptions.traceVariablesOrder and len(orderedInstancesList) > 1: val.modelXbrl.error(_("Variable instances processing order: {0}").format( orderedInstancesList), "info", "formula:trace") # linked consistency assertions for modelRel in val.modelXbrl.relationshipSet(XbrlConst.consistencyAssertionFormula).modelRelationships: if modelRel.fromModelObject and modelRel.toModelObject and isinstance(modelRel.toModelObject,ModelFormula): consisAsser = modelRel.fromModelObject consisAsser.countSatisfied = 0 consisAsser.countNotSatisfied = 0 if consisAsser.hasProportionalAcceptanceRadius and consisAsser.hasAbsoluteAcceptanceRadius: val.modelXbrl.error( _("Consistency assertion {0} has both absolute and proportional acceptance radii").format( consisAsser.xlinkLabel), "err", "xbrlcae:acceptanceRadiusConflict") consisAsser.orderedVariableRelationships = [] for consisParamRel in val.modelXbrl.relationshipSet(XbrlConst.consistencyAssertionParameter).fromModelObject(consisAsser): if isinstance(consisParamRel.toModelObject, ModelVariable): val.modelXbrl.error( _("Consistency assertion {0} has relationship to a {1} {2}").format( consisAsser.xlinkLabel, consisParamRel.toModelObject.element.localName, consisParamRel.toModelObject.xlinkLabel), "err", "xbrlcae:variablesNotAllowed") else: consisAsser.orderedVariableRelationships.append(consisParamRel) consisAsser.compile() modelRel.toModelObject.hasConsistencyAssertion = True if initialErrorCount < val.modelXbrl.logCountErr: return # don't try to execute # formula output instances if instanceQnames: schemaRefs = [val.modelXbrl.modelDocument.relativeUri(referencedDoc.uri) for referencedDoc in val.modelXbrl.modelDocument.referencesDocument.keys() if referencedDoc.type == ModelDocument.Type.SCHEMA] outputXbrlInstance = None for instanceQname in instanceQnames: if instanceQname == XbrlConst.qnStandardInputInstance: continue # always present the standard way if val.parameters and instanceQname in val.parameters: namedInstance = val.parameters[instanceQname][1] else: # empty intermediate instance uri = val.modelXbrl.modelDocument.filepath[:-4] + "-output-XBRL-instance" if instanceQname != XbrlConst.qnStandardOutputInstance: uri = uri + "-" + instanceQname.localName uri = uri + ".xml" namedInstance = ModelXbrl.create(val.modelXbrl.modelManager, newDocumentType=ModelDocument.Type.INSTANCE, url=uri, schemaRefs=schemaRefs, isEntry=True) xpathContext.inScopeVars[instanceQname] = namedInstance if instanceQname == XbrlConst.qnStandardOutputInstance: outputXbrlInstance = namedInstance # evaluate consistency assertions # evaluate variable sets not in consistency assertions for instanceQname in orderedInstancesList: for modelVariableSet in instanceProducingVariableSets[instanceQname]: # produce variable evaluations from arelle.FormulaEvaluator import evaluate try: evaluate(xpathContext, modelVariableSet) except XPathContext.XPathException as err: val.modelXbrl.error( _("Variable set \n{0} \nException: \n{1}").format( modelVariableSet, err.message), "err", err.code) # log assertion result counts asserTests = {} for exisValAsser in val.modelXbrl.modelVariableSets: if isinstance(exisValAsser, ModelVariableSetAssertion): asserTests[exisValAsser.id] = (exisValAsser.countSatisfied, exisValAsser.countNotSatisfied) if formulaOptions.traceAssertionResultCounts: val.modelXbrl.error( _("{0} Assertion {1} evaluations : {2} satisfied, {3} not satisfied").format( "Existence" if isinstance(exisValAsser, ModelExistenceAssertion) else "Value", exisValAsser.id, exisValAsser.countSatisfied, exisValAsser.countNotSatisfied), "info", "formula:trace") for modelRel in val.modelXbrl.relationshipSet(XbrlConst.consistencyAssertionFormula).modelRelationships: if modelRel.fromModelObject and modelRel.toModelObject and isinstance(modelRel.toModelObject,ModelFormula): consisAsser = modelRel.fromModelObject asserTests[consisAsser.id] = (consisAsser.countSatisfied, consisAsser.countNotSatisfied) if formulaOptions.traceAssertionResultCounts: val.modelXbrl.error( _("Consistency Assertion {0} evaluations : {1} satisfied, {2} not satisfied").format( consisAsser.id, consisAsser.countSatisfied, consisAsser.countNotSatisfied), "info", "formula:trace") if asserTests: val.modelXbrl.error( _("Assertion results {0}").format(asserTests), "asrtNoLog", asserTests) # display output instance if outputXbrlInstance: if val.modelXbrl.formulaOutputInstance: # close prior instance, usually closed by caller to validate as it may affect UI on different thread val.modelXbrl.formulaOutputInstance.close() val.modelXbrl.formulaOutputInstance = outputXbrlInstance