text: '''
  Created on Dec 30, 2010
  
  @author: Mark V Systems Limited
  (c) Copyright 2010 Mark V Systems Limited, All rights reserved.
  '''
  from __future__ import division  # expect 3.2 integer division even in 2.7
  from arelle.XPathParser import (VariableRef, QNameDef, OperationDef, RangeDecl, Expr, ProgHeader,
                            exceptionErrorIndication)
  from arelle import (ModelXbrl, XbrlConst, XmlUtil)
  from arelle.ModelObject import ModelObject, ModelAttribute
  from arelle.ModelInstanceObject import ModelFact, ModelInlineFact
  from arelle.ModelValue import (qname,QName,dateTime, DateTime, DATEUNION, DATE, DATETIME, anyURI, AnyURI)
  from arelle.XmlValidate import UNKNOWN, VALID, VALID_NO_CONTENT, validate as xmlValidate
  from arelle.PluginManager import pluginClassMethods
  from decimal import Decimal, InvalidOperation
  from lxml import etree
  from types import LambdaType
  
  # deferred types initialization
  boolean = None
  testTypeCompatiblity = None
  Trace = None
  
  class XPathException(Exception):
      def __init__(self, progStep, code, message):
          self.column = None
          if isinstance(progStep, OperationDef):
              self.line = progStep.sourceStr
              self.column = progStep.loc
          elif isinstance(progStep, ProgHeader):
              self.line = progStep.sourceStr
          elif isinstance(progStep, XPathContext) and progStep.progHeader:
              self.line = progStep.sourceStr
          else:
              self.line = "(not available)"
          self.code = str(code)  # called with qname or string, qname -> prefixed name string
          self.message = message
          self.args = ( self.__repr__(), )
      def __repr__(self):
          if self.column:
              return _('[{0}] exception at {1} in {2}').format(self.code, self.column, self.message)
          else:
              return _('[{0}] exception {1}').format(self.code, self.message)
      @property
      def sourceErrorIndication(self):
          return exceptionErrorIndication(self)
              
      
  class FunctionNumArgs(Exception):
      def __init__(self, errCode='err:XPST0017', errText=None):
          self.errCode = errCode
          self.errText = errText or _('Number of arguments do not match signature arity')
          self.args = ( self.__repr__(), )
      def __repr__(self):
          return _("Exception: Number of arguments mismatch")
      
  class FunctionArgType(Exception):
      def __init__(self, argIndex, expectedType, foundObject='', errCode='err:XPTY0004'):
          self.errCode = errCode
          self.argNum = (argIndex + 1) if isinstance(argIndex, _NUM_TYPES) else argIndex # may be string
          self.expectedType = expectedType
          self.foundObject = foundObject
          self.args = ( self.__repr__(), )
      def __repr__(self):
          return _("[{0}]: Arg {1} expected type {2}").format(self.errCode, self.argNum, self.expectedType)
      
  class FunctionNotAvailable(Exception):
      def __init__(self, name=None):
          self.name = name
          self.args = ( self.__repr__(), )
      def __repr__(self):
          return _("Exception, function implementation not available: {0}").format(self.name)
      
  class RunTimeExceededException(Exception):
      def __init__(self):
          self.args = ( self.__repr__(), )
      def __repr__(self):
          return _("Formula run time exceeded")
  
     
  def create(modelXbrl, inputXbrlInstance=None, sourceElement=None):
      global boolean, testTypeCompatiblity, Trace
      if boolean is None:
          from arelle.FunctionUtil import testTypeCompatiblity
          from arelle.ModelFormulaObject import Trace
          from arelle.FunctionFn import boolean
  
      return XPathContext(modelXbrl, 
                          inputXbrlInstance if inputXbrlInstance else modelXbrl.modelDocument,
                          sourceElement)
  
  # note: 2.2% execution time savings by having these sets/lists as constant instead of in expression where used
  VALUE_OPS = {'+', '-', '*', 'div', 'idiv', 'mod', 'to', 'gt', 'ge', 'eq', 'ne', 'lt', 'le'}
  GENERALCOMPARISON_OPS = {'>', '>=', '=', '!=', '<', '<='}
  NODECOMPARISON_OPS = {'is', '>>', '<<'}
  COMBINING_OPS = {'intersect','except','union','|'}
  LOGICAL_OPS = {'and', 'or'}
  UNARY_OPS = {'u+', 'u-'}
  FORSOMEEVERY_OPS = {'for','some','every'}
  PATH_OPS = {'/', '//', 'rootChild', 'rootDescendant'}
  SEQUENCE_TYPES = (tuple,list,set)
  
  class XPathContext:
      def __init__(self, modelXbrl, inputXbrlInstance, sourceElement, inScopeVars=None):
          self.modelXbrl = modelXbrl
          self.isRunTimeExceeded = False
          self.inputXbrlInstance = inputXbrlInstance
          self.outputLastContext = {}   # last context element output per output instance
          self.outputLastUnit = {}
          self.outputLastFact = {}
          self.outputFirstFact = {}
          self.sourceElement = sourceElement
          self.contextItem = self.inputXbrlInstance.xmlRootElement
          self.progHeader = None
          self.traceType = None
          self.variableSet = None
          self.inScopeVars = {} if inScopeVars is None else inScopeVars
          self.cachedFilterResults = {}
          if inputXbrlInstance: 
              self.inScopeVars[XbrlConst.qnStandardInputInstance] = inputXbrlInstance.modelXbrl
          self.customFunctions = {}
          for pluginXbrlMethod in pluginClassMethods("Formula.CustomFunctions"):
              self.customFunctions.update(pluginXbrlMethod())
          
      def copy(self):  # shallow copy (for such as for Table LB table processiong
          xpCtxCpy = XPathContext(self.modelXbrl, self.inputXbrlInstance, self.sourceElement, 
                                  self.inScopeVars.copy())
          # note: not currently duplicating cachedFilterResults
          return xpCtxCpy
              
      def close(self):
          self.outputLastContext.clear() # dereference
          self.outputLastUnit.clear()
          self.outputLastFact.clear()
          self.outputFirstFact.clear()
          self.inScopeVars.clear()
          self.cachedFilterResults.clear()
          self.__dict__.clear() # dereference everything
          
      def runTimeExceededCallback(self):
          self.isRunTimeExceeded = True
          
      @property
      def formulaOptions(self):
          return self.modelXbrl.modelManager.formulaOptions
          
      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.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 evaluateBooleanValue(self, exprStack, contextItem=None):
          if len(exprStack) > 0 and isinstance(exprStack[0], ProgHeader):
              progHeader = exprStack[0]
              return self.effectiveBooleanValue(progHeader, self.evaluate(exprStack,contextItem))
          return False
                      
      def evaluateAtomicValue(self, exprStack, type, contextItem=None):
          if exprStack and len(exprStack) > 0 and isinstance(exprStack[0], ProgHeader):
              progHeader = exprStack[0]
              result = self.atomize( progHeader, self.evaluate( exprStack, contextItem=contextItem ) )
              if isinstance(type, QName) and type.namespaceURI == XbrlConst.xsd:
                  type = "xs:" + type.localName
              if isinstance(type,str):
                  prefix,sep,localName = type.rpartition(':')
                  if prefix == 'xs':
                      if localName.endswith('*'): localName = localName[:-1]
                      if isinstance(result, (tuple,list,set)):
                          from arelle import (FunctionXs)
                          if type.endswith('*'):
                              return[FunctionXs.call(self,progHeader,localName,(r,)) for r in result]
                          elif len(result) > 0:
                              return FunctionXs.call(self,progHeader,localName,(result[0],))
                  elif localName.startswith("item()"):
                      return result # can be any type
              else: # no conversion
                  if len(result) == 0: return None
                  elif len(result) == 1: return result[0]
                  else: return result
          return None
                      
      def evaluateRangeVars(self, op, p, args, contextItem, result):
          if isinstance(p, RangeDecl):
              r = self.evaluate(p.bindingSeq, contextItem=contextItem)
              if len(r) == 1: # should be an expr single
                  r = r[0]
                  if isinstance(r, (tuple,list,set)):
                      if len(r) == 1 and isinstance(r[0],_RANGE):
                          r = r[0]
                      rvQname = p.rangeVar.name
                      hasPrevValue = rvQname in self.inScopeVars
                      if hasPrevValue: 
                          prevValue = self.inScopeVars[rvQname]
                      for rv in r:
                          self.inScopeVars[rvQname] = rv 
                          self.evaluateRangeVars(op, args[0], args[1:], contextItem, result)
                          if op != 'for' and len(result) > 0:
                              break	# short circuit evaluation
                      if op == 'every' and len(result) == 0:
                          result.append( True )   # true if no false result returned during iteration
                      if hasPrevValue: 
                          self.inScopeVars[rvQname] = prevValue
          elif isinstance(p, Expr):
              if p.name == 'return':
                  result.append( self.evaluate(p.expr, contextItem=contextItem) )
              elif p.name == 'satisfies':
                  boolresult = self.effectiveBooleanValue(p, self.evaluate(p.expr, contextItem=contextItem))
                  if (op == 'every') != boolresult:
                      # stop short circuit eval
                      result.append( boolresult )
              
      def isNodeSequence(self, x):
          for el in x:
              if not isinstance(el,ModelObject):
                  return False
          return True
  
      def stepAxis(self, op, p, sourceSequence):
          targetSequence = []
          for node in sourceSequence:
              if not isinstance(node,(ModelObject, etree._ElementTree, ModelAttribute)):
                  raise XPathException(self.progHeader, 'err:XPTY0020', _('Axis step {0} context item is not a node: {1}').format(op, node))
              targetNodes = []
              if isinstance(p,QNameDef):
                  ns = p.namespaceURI; localname = p.localName; axis = p.axis
                  if p.isAttribute:
                      if isinstance(node,ModelObject):
                          attrTag = p.localName if p.unprefixed else p.clarkNotation
                          modelAttribute = None
                          try:
                              modelAttribute = node.xAttributes[attrTag]
                          except (AttributeError, TypeError, IndexError, KeyError):
                              # may be lax or deferred validated
                              try:
                                  xmlValidate(node.modelXbrl, node, p)
                                  modelAttribute = node.xAttributes[attrTag]
                              except (AttributeError, TypeError, IndexError, KeyError):
                                  pass
                          if modelAttribute is None:
                              value = node.get(attrTag)
                              if value is not None:
                                  targetNodes.append(ModelAttribute(node,p.clarkNotation,UNKNOWN,value,value,value))
                          elif modelAttribute.xValid >= VALID:
                                  targetNodes.append(modelAttribute)
                  elif op == '/' or op is None:
                      if axis is None or axis == "child":
                          if isinstance(node,(ModelObject, etree._ElementTree)):
                              targetNodes = XmlUtil.children(node, ns, localname)
                      elif axis == "parent":
                          if isinstance(node,ModelAttribute):
                              parentNode = [ node.modelElement ]
                          else:
                              parentNode = [ XmlUtil.parent(node) ]
                          if (isinstance(node,ModelObject) and
                                  (not ns or ns == parentNode.namespaceURI or ns == "*") and
                              (localname == parentNode.localName or localname == "*")):
                              targetNodes = [ parentNode ]
                      elif axis == "self":
                          if (isinstance(node,ModelObject) and
                                  (not ns or ns == node.namespaceURI or ns == "*") and
                              (localname == node.localName or localname == "*")):
                              targetNodes = [ node ]
                      elif axis.startswith("descendant"):
                          if isinstance(node,(ModelObject, etree._ElementTree)):
                              targetNodes = XmlUtil.descendants(node, ns, localname)
                              if (axis.endswith("-or-self") and
                                  isinstance(node,ModelObject) and
                                  (not ns or ns == node.namespaceURI or ns == "*") and
                                  (localname == node.localName or localname == "*")):
                                  targetNodes.append(node) 
                      elif axis.startswith("ancestor"):
                          if isinstance(node,ModelObject):
                              targetNodes = [ancestor
                                             for ancestor in XmlUtil.ancestors(node)
                                             if ((not ns or ns == ancestor.namespaceURI or ns == "*") and
                                                 (localname == ancestor.localName or localname == "*"))]
                              if (axis.endswith("-or-self") and
                                  isinstance(node,ModelObject) and
                                  (not ns or ns == node.namespaceURI or ns == "*") and
                                  (localname == node.localName or localname == "*")):
                                  targetNodes.insert(0, node) 
                      elif axis.endswith("-sibling"):
                          if isinstance(node,ModelObject):
                              targetNodes = [sibling
                                             for sibling in node.itersiblings(preceding=axis.startswith("preceding"))
                                             if ((not ns or ns == sibling.namespaceURI or ns == "*") and
                                                 (localname == sibling.localName or localname == "*"))]
                      elif axis == "preceding":
                          if isinstance(node,ModelObject):
                              for preceding in node.getroottree().iter():
                                  if preceding == node:
                                      break
                                  elif ((not ns or ns == preceding.namespaceURI or ns == "*") and
                                        (localname == preceding.localName or localname == "*")):
                                      targetNodes.append(preceding)
                      elif axis == "following":
                          if isinstance(node,ModelObject):
                              foundNode = False
                              for following in node.getroottree().iter():
                                  if following == node:
                                      foundNode = True
                                  elif (foundNode and
                                        (not ns or ns == following.namespaceURI or ns == "*") and
                                        (localname == following.localName or localname == "*")):
                                      targetNodes.append(following)
                  elif op == '//':
                      if isinstance(node,(ModelObject, etree. _ElementTree)):
                          targetNodes = XmlUtil.descendants(node, ns, localname)
                  elif op == '..':
                      if isinstance(node,ModelAttribute):
                          targetNodes = [ node.modelElement ]
                      else:
                          targetNodes = [ XmlUtil.parent(node) ]
              elif isinstance(p, OperationDef) and isinstance(p.name,QNameDef):
                  if isinstance(node,ModelObject):
                      if p.name.localName == "text": # note this is not string value, just child text
                          targetNodes = [node.textValue]
                      # todo: add element, attribute, node, etc...
              elif p == '*':  # wildcard
                  if op == '/' or op is None:
                      if isinstance(node,(ModelObject, etree._ElementTree)):
                          targetNodes = XmlUtil.children(node, '*', '*')
                  elif op == '//':
                      if isinstance(node,(ModelObject, etree._ElementTree)):
                          targetNodes = XmlUtil.descendants(node, '*', '*')
              targetSequence.extend(targetNodes)
          return targetSequence
          
      def predicate(self, p, sourceSequence):
          targetSequence = []
          sourcePosition = 0
          for item in sourceSequence:
              sourcePosition += 1
              predicateResult = self.evaluate(p.args, contextItem=item)
              if len(predicateResult) == 1: predicateResult = predicateResult[0] # first result
              if len(predicateResult) == 1 and isinstance(predicateResult[0],_NUM_TYPES):
                  result = predicateResult[0]
                  if isinstance(result, bool):  # note that bool is subclass of int
                      if result:
                          targetSequence.append(item)
                  elif sourcePosition == result:
                      targetSequence.append(item)
              elif self.effectiveBooleanValue(p, predicateResult):
                      targetSequence.append(item)
          return targetSequence
              
      def atomize(self, p, x):
          # sequence
          if isinstance(x, SEQUENCE_TYPES):
              sequence = []
              for item in self.flattenSequence(x):
                  atomizedItem = self.atomize(p, item)
                  if atomizedItem != []:
                      sequence.append(atomizedItem)
              return sequence
          # individual items
          if isinstance(x, _RANGE): 
              return x
          baseXsdType = None
          e = None
          if isinstance(x, ModelFact):
              if x.isTuple:
                  raise XPathException(p, 'err:FOTY0012', _('Atomizing tuple {0} that does not have a typed value').format(x))
              if x.isNil:
                  return []
              baseXsdType = x.concept.baseXsdType
              v = x.value # resolves default value
              e = x
          elif isinstance(x, ModelAttribute): # ModelAttribute is a tuple (below), check this first!
              return x.xValue
          else:
              if isinstance(x, ModelObject):
                  e = x
              if e is not None:
                  if getattr(e, "xValid", 0) == VALID_NO_CONTENT:
                      raise XPathException(p, 'err:FOTY0012', _('Atomizing element {0} that does not have a typed value').format(x))
                  if e.get("{http://www.w3.org/2001/XMLSchema-instance}nil") == "true":
                      return []
                  try:
                      if e.xValid >= VALID:
                          return e.xValue
                  except AttributeError:
                      pass
                  modelXbrl = x.modelXbrl
                  modelConcept = modelXbrl.qnameConcepts.get(qname(x))
                  if modelConcept is not None:
                      baseXsdType = modelConcept.baseXsdType
                  v = x.stringValue
          if baseXsdType in ("float", "double"):
              try:
                  x = float(v)
              except ValueError:
                  raise XPathException(p, 'err:FORG0001', _('Atomizing {0} to a {1} does not have a proper value').format(x,baseXsdType))
          elif baseXsdType == "decimal":
              try:
                  x = Decimal(v)
              except InvalidOperation:
                  raise XPathException(p, 'err:FORG0001', _('Atomizing {0} to decimal does not have a proper value'))
          elif baseXsdType in ("integer",
                               "nonPositiveInteger","negativeInteger","nonNegativeInteger","positiveInteger",
                               "long","unsignedLong",
                               "int","unsignedInt",
                               "short","unsignedShort",
                               "byte","unsignedByte"):
              try:
                  x = _INT(v)
              except ValueError:
                  raise XPathException(p, 'err:FORG0001', _('Atomizing {0} to an integer does not have a proper value').format(x))
          elif baseXsdType == "boolean":
              x = (v == "true" or v == "1")
          elif baseXsdType == "QName" and e is not None:
              x = qname(e, v)
          elif baseXsdType == "anyURI":
              x = anyURI(v.strip())
          elif baseXsdType in ("normalizedString","token","language","NMTOKEN","Name","NCName","ID","IDREF","ENTITY"):
              x = v.strip()
          elif baseXsdType == "XBRLI_DATEUNION":
              x = dateTime(v, type=DATEUNION)
          elif baseXsdType == "date":
              x = dateTime(v, type=DATE)
          elif baseXsdType == "dateTime":
              x = dateTime(v, type=DATETIME)
          elif baseXsdType == "noContent":
              x = None # can't be atomized
          elif baseXsdType:
              x = str(v)
          return x
      
      def effectiveBooleanValue(self, p, x):
          return boolean( self, p, None, (self.flattenSequence(x),) )
      
      def traceEffectiveVariableValue(self, elt, varname):
          # used for tracing variable value
          if varname.startswith('$'):
              varQname = qname(elt,varname[1:])
              if varQname in self.inScopeVars:
                  varValue = self.inScopeVars[varQname]
                  if isinstance(varValue, ModelFact):
                      return varValue.effectiveValue
                  else:
                      return str(varValue)
              else:
                  return varname
          else: # not a variable name
              return varname
  
      # flatten into a sequence
      def flattenSequence(self, x, sequence=None):
          if sequence is None: 
              if not isinstance(x, SEQUENCE_TYPES):
                  if x is None:
                      return [] # none as atomic value is an empty sequence in xPath semantics
                  return [x]
              sequence = []
          for el in x:
              if isinstance(el, SEQUENCE_TYPES):
                  self.flattenSequence(el, sequence)
              else:
                  sequence.append(el)
          return sequence
      '''  (note: slice operation makes the below slower than the above by about 15%)
      def flattenSequence(self, x):
          sequenceTypes=SEQUENCE_TYPES
          if not isinstance(x, sequenceTypes):
              return [x]
          needsFlattening = False  # no need to do anything
          for i, e in enumerate(x):
              if isinstance(e, sequenceTypes):
                  needsFlattening = True # needs action at i
                  break            
          if needsFlattening:
              x = list(x) # start with fresh copy of list
              while i < len(x):
                  if isinstance(x[i], sequenceTypes):
                      x[i:i+1] = list(x[i])
                  else:
                      i += 1
          return x            
      '''
      
      # order nodes
      def documentOrderedNodes(self, x):
          l = set()  # must have unique nodes only
          for e in x:
              if isinstance(e,ModelObject):
                  h = e.sourceline
              elif isinstance(e,ModelAttribute):
                  h = e.modelElement.sourceline
              else:
                  h = 0
              l.add((h,e))
          return [e for h,e in sorted(l, key=lambda h: h[0] or 0)]  # or 0 in case sourceline is None
      
      def modelItem(self, x):
          if isinstance(x, (ModelFact, ModelInlineFact)) and x.isItem:
              return x
          return None
  
      def modelInstance(self, x):
          if isinstance(x, ModelXbrl.ModelXbrl):
              return x
          if isinstance(x, ModelObject):
              return x.modelXbrl
          return None
          
          