########################################################################
#
# File Name:   Conversions.py
#
# Docs:        http://docs.4suite.org/XPATH/Conversions.py.html
#
"""
The implementation of the XPath object type conversions.
WWW: http://4suite.org/XPATH        e-mail: support@4suite.org

Copyright (c) 2000-2001 Fourthought Inc, USA.   All Rights Reserved.
See  http://4suite.org/COPYRIGHT  for license and copyright information
"""

import types
from xml.dom import Node

from Ft.Lib import number, boolean

StringValue = lambda obj: _strConversions.get(type(obj), _strUnknown)(obj)

NumberValue = lambda obj: _numConversions.get(type(obj), _numUnknown)(obj)

BooleanValue = lambda obj: _boolConversions.get(type(obj), _boolUnknown)(obj)

# -- String Conversions ----------------------------------------------

def _strUnknown(object):
    # Allow for non-instance DOM node objects
    if hasattr(object, 'nodeType'):
        # Add this type to the mapping for next time through
        _strConversions[type(object)] = _strInstance
        return _strInstance(object)
    return u''

def _strInstance(object):
    if hasattr(object, 'stringValue'):
        return object.stringValue
    elif hasattr(object, 'nodeType'):
        node_type = object.nodeType
        if node_type in [Node.ELEMENT_NODE, Node.DOCUMENT_NODE]:
            # The concatenation of all text descendants
            text_elem_children = filter(lambda x:
                                        x.nodeType in [Node.TEXT_NODE, Node.ELEMENT_NODE],
                                        object.childNodes)
            return reduce(lambda x, y: StringValue(x) + StringValue(y),
                          text_elem_children,
                          '')
        if node_type in [Node.ATTRIBUTE_NODE, NAMESPACE_NODE]:
            return object.value
        if node_type in [Node.PROCESSING_INSTRUCTION_NODE, Node.COMMENT_NODE, Node.TEXT_NODE]:
            return object.data
    return u''

def _strFloat(float):
    if number.finite(float):
        if float == round(float):
            return unicode(str(long(float)))
        else:
            # 12 digits is how many Python uses for str()
            return u'%0.12g' % float
    elif number.isnan(float):
        return u'NaN'
    elif float < 0:
        return u'-Infinity'
    else:
        return u'Infinity'

_strConversions = {
    types.StringType : unicode,
    types.UnicodeType : unicode,
    types.IntType : lambda i: unicode(str(i)),
    types.LongType : lambda l: unicode(str(l)),
    types.FloatType : _strFloat,
    boolean.BooleanType : lambda b: unicode(str(b)),
    types.InstanceType : _strInstance,
    types.ListType : lambda x: x and _strConversions.get(type(x[0]), _strUnknown)(x[0]) or u'',
}

# -- Number Conversions ----------------------------------------------

def _numString(string):
    try:
        # From XPath 1.0, sect 4.4 - number():
        #  Any string that does not match "S? '-'? Number" is converted to NaN
        #   S ::= [\x20\x09\x0A\x0D]*
        #   Digits ::= [0-9]+
        #   Number ::= Digits ('.' Digits?)? | '.' Digits
        return float(string)
    except:
        # Many platforms seem to have a problem with strtod('nan'),
        # reported on Windows and FreeBSD
        return number.nan

_numUnknown = lambda object: _numString(StringValue(object))

_numConversions = {
    types.IntType : float,
    types.LongType : float,
    types.FloatType : float,
    boolean.BooleanType : float,
    types.StringType : _numString,
    types.UnicodeType : _numString,
}

# -- Boolean Conversions ---------------------------------------------

_boolConversions = {
    boolean.BooleanType : boolean.bool,
    types.IntType : boolean.bool,
    types.LongType : boolean.bool,
    types.FloatType : boolean.bool,
    types.StringType : boolean.bool,
    types.UnicodeType : boolean.bool,
    types.ListType : boolean.bool,
    }

_boolUnknown = lambda object: boolean.bool(StringValue(object))

try:
    # Use C optimized functions, if available
    from _conversions import *
except:
    from Ft.Xml.XPath import NAMESPACE_NODE

