Beispiel #1
0
 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
Beispiel #2
0
 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
Beispiel #3
0
 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
Beispiel #4
0
 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