########################################################################
# $Header: /var/local/cvsroot/4Suite/Ft/Xml/XPath/Conversions.py,v 1.16 2005-08-09 15:24:01 uogbuji Exp $
"""
The implementation of the XPath object type conversions.

Copyright 2000-2005 Fourthought, Inc. (USA).
Detailed license and copyright information: http://4suite.org/COPYRIGHT
Project home, documentation, distributions: http://4suite.org/
"""

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(obj):
    # Allow for non-instance DOM node objects
    if hasattr(obj, 'nodeType'):
        # Add this type to the mapping for next time through
        _strConversions[type(obj)] = _strInstance
        return _strInstance(obj)
    return u''

def _strInstance(obj):
    if hasattr(obj, 'stringValue'):
        return obj.stringValue
    elif hasattr(obj, 'nodeType'):
        node_type = obj.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],
                                        obj.childNodes)
            return reduce(lambda x, y: StringValue(x) + StringValue(y),
                          text_elem_children,
                          '')
        if node_type in [Node.ATTRIBUTE_NODE, NAMESPACE_NODE]:
            return obj.value
        if node_type in [Node.PROCESSING_INSTRUCTION_NODE, Node.COMMENT_NODE, Node.TEXT_NODE]:
            return obj.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 = {
    str : unicode,
    unicode : unicode,
    int : lambda i: unicode(str(i)),
    long : lambda l: unicode(str(l)),
    float : _strFloat,
    boolean.BooleanType : lambda b: unicode(str(b)),
    types.InstanceType : _strInstance,
    list : 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 obj: _numString(StringValue(obj))

_numConversions = {
    int : float,
    long : float,
    float : float,
    boolean.BooleanType : float,
    str : _numString,
    unicode : _numString,
}

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

_boolConversions = {
    boolean.BooleanType : boolean.bool,
    int : boolean.bool,
    long : boolean.bool,
    float : boolean.bool,
    str : boolean.bool,
    unicode : boolean.bool,
    list : boolean.bool,
    }

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

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

