def newFactOpenAspects(self, factObjectId): aspectValues = {} for aspectObjId in self.factPrototypeAspectEntryObjectIds[factObjectId]: structuralNode = self.aspectEntryObjectIdsNode[aspectObjId] for aspect in structuralNode.aspectsCovered(): if aspect != Aspect.DIMENSIONS: break gridCell = self.aspectEntryObjectIdsCell[aspectObjId] value = gridCell.value # is aspect in a childStructuralNode? if value: aspectValue = self.aspectEntryValues(structuralNode, value, aspect) if aspectValue is None: # try converting value if isinstance(aspect, QName): # dimension dimConcept = self.modelXbrl.qnameConcepts[aspect] if dimConcept.isExplicitDimension: # value must be qname aspectValue = None # need to find member for the description else: typedDimElement = dimConcept.typedDomainElement aspectValue = FunctionXfi.create_element( self.modelXbrl.rendrCntx, None, (typedDimElement.qname, (), value)) if aspectValue is not None: aspectValues[aspect] = aspectValue return aspectValues
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 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