########################################################################
# $Header: /var/local/cvsroot/4Suite/Ft/Xml/Xslt/AttributeInfo.py,v 1.29 2005/03/19 00:12:16 jkloth Exp $
"""
Classes that support validation and evaluation of attribute values in
XSLT instruction elements

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

from Ft import TranslateMessage as _

import cStringIO, traceback

from Ft.Xml.XPath import Conversions
from Ft.Xml.XPath import RuntimeException as XPathRuntimeException
from Ft.Xml.XPath import parser
_xpath_parser = parser.new()

from Ft.Xml.Xslt import XsltException, XsltRuntimeException, Error
from Ft.Xml.Xslt import parser
_xpattern_parser = parser.new()
del parser

from Ft.Xml import XML_NAMESPACE, XMLNS_NAMESPACE, EMPTY_NAMESPACE
from Ft.Xml.Lib.XmlString import IsQName, SplitQName

from AttributeValueTemplate import AttributeValueTemplate


class AttributeInfo:

    display = 'unknown'

    def __init__(self, required=0, default=None, description=''):
        self.required = required
        self.default = default
        self.description = description
        return

    def __str__(self):
        return self.display

    def prepare(self, element, value):
        if value is None:
            return self.default
        return value
    reprocess = prepare

    def validate(self, validation):
        return 1


class _ConstantValue:
    def __init__(self, value):
        self.value = value

    def isConstant(self):
        return 1

    def evaluate(self, context):
        return self.value

    def __repr__(self):
        return repr(self.value)

    def __nonzero__(self):
        return self.value is not None


class Choice(AttributeInfo):

    def __init__(self, values, required=0, default=None, description=''):
        AttributeInfo.__init__(self, required, default, description)
        self.values = values
        return

    def prepare(self, element, value):
        if value is None:
            return self.default
        if value not in self.values:
            # check for a AttributeInfo instance
            allowed = filter(lambda v, t=type(self): type(v) is t, self.values)
            for info in allowed:
                try:
                    allowed.prepare(element, value)
                    return value
                except:
                    pass
            # if we get here it is an error
            raise XsltException(Error.INVALID_ATTR_CHOICE, value, str(self))
        return value
    reprocess = prepare

    def __str__(self):
        return ' | '.join(map(lambda v: '"' + v + '"', self.values))


class Avt:

    def __str__(self):
        return '{ %s }' % self.display

    def prepare(self, element, value):
        if value is None:
            return _ConstantValue(self.reprocess(element, self.default))
        elif '{' not in value and '}' not in value:
            return _ConstantValue(self.reprocess(element, value))
        try:
            return AttributeValueTemplate(value, self, element)
        except SyntaxError, error:
            # an error from the XPath parser
            raise XsltException(Error.INVALID_AVT, value,
                                element.baseUri, element.lineNumber,
                                element.columnNumber, str(error))
        except XsltException, error:
            raise XsltException(Error.INVALID_AVT, value,
                                element.baseUri, element.lineNumber,
                                element.columnNumber, error.args[0])


class ChoiceAvt(Avt, Choice):

    def __str__(self):
        return '{ %s }' % Choice.__str__(self)


class AnyAvt(Avt, AttributeInfo):
    display = _('any avt')


class String(AttributeInfo):
    display = _('string')


class StringAvt(Avt, String):
    pass


class Char(AttributeInfo):
    """
    A string value with a length of one
    """
    display = _('char')

    def prepare(self, element, value):
        if value is None:
            return self.default
        if len(value) > 1:
            raise XsltException(Error.INVALID_CHAR_ATTR, value)
        return value
    reprocess = prepare


class CharAvt(Avt, Char):
    pass


class Number(AttributeInfo):
    display = _('number')

    def prepare(self, element, value):
        if value is None:
            return self.default
        try:
            return float(value or self.default)
        except:
            raise XsltException(Error.INVALID_NUMBER_ATTR, value)
    reprocess = prepare


class NumberAvt(Avt, Number):
    reprocess = Number.prepare


class UriReference(AttributeInfo):
    display = _('uri-reference')

    def __init__(self, required=0, default=None, description='', isNsName=0):
        AttributeInfo.__init__(self, required, default, description)
        self._isNsName = isNsName

    def prepare(self, element, value):
        if value is None:
            return self.default
        if self._isNsName and \
            value == XML_NAMESPACE or value == XMLNS_NAMESPACE:
            raise XsltException(Error.INVALID_NS_URIREF_ATTR, value)
        return value
    reprocess = prepare


class UriReferenceAvt(Avt, UriReference):
    pass


class Id(AttributeInfo):
    display = _('id')

    def prepare(self, element, value):
        if value is None:
            return self.default
        if not value:
            raise XsltException(Error.INVALID_ID_ATTR, value)
        return value
    reprocess = prepare


class IdAvt(Avt, Id):
    pass


class QName(AttributeInfo):
    display = _('qname')

    def prepare(self, element, value):
        if value is None:
            if self.default is None:
                return None
            value = self.default
        elif not IsQName(value):
            raise XsltException(Error.INVALID_QNAME_ATTR, value)
        
        prefix, local = SplitQName(value)
        if prefix:
            try:
                namespace = element.namespaces[prefix]
            except KeyError:
                raise XsltRuntimeException(Error.UNDEFINED_PREFIX,
                                           element, prefix)
        else:
            namespace = EMPTY_NAMESPACE
        return (namespace, local)
    reprocess = prepare


class QNameAvt(Avt, QName):
    pass


class RawQName(QName):

    def prepare(self, element, value):
        if value is None:
            if self.default is None:
                return None
            value = self.default
        elif not IsQName(value):
            raise XsltException(Error.INVALID_QNAME_ATTR, value)
        return SplitQName(value)
    reprocess = prepare


class RawQNameAvt(Avt, RawQName):
    pass
        

class NCName(AttributeInfo):
    display = _('ncname')

    def prepare(self, element, value):
        if value is None:
            return self.default
        if not value:
            raise XsltException(Error.INVALID_NCNAME_ATTR, value)
        if ':' in value:
            raise XsltException(Error.INVALID_NCNAME_ATTR, value)
        return value
    reprocess = prepare


class NCNameAvt(Avt, NCName):
    pass


class Prefix(AttributeInfo):
    display = _('prefix')

    def prepare(self, element, value):
        if value is None:
            return self.default
        if not value:
            raise XsltException(Error.INVALID_PREFIX_ATTR, value)
        if ':' in value:
            raise XsltException(Error.INVALID_PREFIX_ATTR, value)
        if value == '#default':
            value = None
        return value
    reprocess = prepare


class PrefixAvt(Avt, Prefix):
    pass


class NMToken(AttributeInfo):
    display = _('nmtoken')

    def prepare(self, element, value):
        if value is None:
            return self.default
        if not value:
            raise XsltException(Error.INVALID_NMTOKEN_ATTR, value)
        return value
    reprocess = prepare


class NMTokenAvt(Avt, NMToken):
    pass


class QNameButNotNCName(AttributeInfo):
    display = _('qname-but-not-ncname')

    def prepare(self, element, value):
        if value is None:
            if self.default is None:
                return None
            value = self.default
        elif not value:
            raise XsltException(Error.QNAME_BUT_NOT_NCNAME, value)

        try:
            index = value.index(':')
        except ValueError:
            raise XsltException(Error.QNAME_BUT_NOT_NCNAME, value)
        prefix, local = value[:index], value[index+1:]
        try:
            namespace = element.namespaces[prefix]
        except KeyError:
            raise XsltRuntimeException(Error.UNDEFINED_PREFIX,
                                       element, prefix)
        return (namespace, local)
    reprocess = prepare


class Token(AttributeInfo):
    """
    An attribute whose value is used as an XPath NameTest
    """
    display = _('token')

    def prepare(self, element, value):
        # a 'token' is really an XPath NameTest; '*' | NCName ':' '*' | QName
        # From XPath 1.0 section 2.3:
        #  if the QName does not have a prefix, then the namespace URI is null
        index = value.rfind(':')
        if index == -1:
            namespace = None
            local = value
        else:
            prefix = value[:index]
            local = value[index+1:]
            try:
                namespace = element.namespaces[prefix]
            except KeyError:
                raise XsltRuntimeException(Error.UNDEFINED_PREFIX,
                                           element, prefix)
        return (namespace, local)
    reprocess = prepare


class TokenAvt(Avt, Token):
    pass


class ExpressionWrapper:

    def __init__(self, expression, element, original):
        self.expression = expression
        self.element = element
        self.original = original
        return

    def __nonzero__(self):
        # True if self.expression is not None, which is always the case
        # otherwise this instance would not exist!
        return True

    def __getattr__(self, attr):
        """Make this behave as if it was the expression object itself."""
        return getattr(self.expression, attr)

    # Provide the copy/pickle helpers so as to not get them from the
    # wrapped expression.
    def __getstate__(self):
        return (self.expression, self.element, self.original)

    def __setstate__(self, state):
        self.expression, self.element, self.original = state
        return

    def evaluate(self,context):
        try:
            return self.expression.evaluate(context)
        except XPathRuntimeException, e:
            import MessageSource
            e.message = MessageSource.EXPRESSION_POSITION_INFO % (
                self.element.baseUri, self.element.lineNumber,
                self.element.columnNumber, self.original, str(e))
            # By modifying the exception value directly, we do not need
            # to raise with that value, thus leaving the frame stack
            # intact (original traceback is displayed).
            raise
        except XsltRuntimeException, e:
            import MessageSource
            e.message = MessageSource.XSLT_EXPRESSION_POSITION_INFO % (
                str(e), self.original)
            # By modifying the exception value directly, we do not need
            # to raise with that value, thus leaving the frame stack
            # intact (original traceback is displayed).
            raise
        except Exception, e:
            import MessageSource
            tb = cStringIO.StringIO()
            tb.write("Lower-level traceback:\n")
            traceback.print_exc(1000, tb)
            raise RuntimeError(MessageSource.EXPRESSION_POSITION_INFO % (
                self.element.baseUri, self.element.lineNumber,
                self.element.columnNumber, self.original, tb.getvalue()))

class Expression(AttributeInfo):
    """
    An attribute whose value is used as an XPath expression
    """
    display = _('expression')

    def prepare(self, element, value):
        if value is None:
            if self.default is None:
                return None
            value = self.default
        try:
            expression = _xpath_parser.parse(value)
        except SyntaxError, error:
            raise XsltException(Error.INVALID_EXPRESSION, value,
                                element.baseUri, element.lineNumber,
                                element.columnNumber, str(error))
        return ExpressionWrapper(expression, element, value)

class NodeSetExpression(Expression):
    display = _('node-set-expression')

class StringExpression(Expression):
    display = _('string-expression')

class NumberExpression(Expression):
    display = _('number-expression')

class BooleanExpression(Expression):
    display = _('boolean-expression')


class Pattern(AttributeInfo):
    """
    An attribute whose value is used as an XPattern expression
    """
    display = _('pattern')

    def prepare(self, element, value):
        if value is None:
            if self.default:
                value = self.default
            else:
                return None
        try:
            return _xpattern_parser.parse(value)
        except SyntaxError, error:
            raise XsltException(Error.INVALID_PATTERN, value,
                                element.baseUri, element.lineNumber,
                                element.columnNumber, str(error))

class Tokens(Token):
    """
    A whitespace separated list of tokens (see Token for description of a token)
    """
    display = _('tokens')

    def prepare(self, element, value):
        if value is None:
            return []
        tokens = []
        for token in value.split():
            prepared = Token.prepare(self, element, token)
            tokens.append(prepared)
        return tokens
    reprocess = prepare

class TokensAvt(Avt, Tokens):
    pass


class QNames(QName):
    """
    A whitespace separated list of qnames (see QName for description of a qname)
    """
    display = _('qnames')

    def prepare(self, element, value):
        if value is None:
            return []
        qnames = []
        for qname in value.split():
            prepared = QName.prepare(self, element, qname)
            qnames.append(prepared)
        return qnames
    reprocess = prepare

class QNamesAvt(Avt, QNames):
    pass


class Prefixes(Prefix):
    """
    A whitespace separated list of prefixes (see Prefix for more information)
    """
    display = _('prefixes')

    def prepare(self, element, value):
        if value is None:
            return []
        prefixes = []
        for prefix in value.split():
            prepared = Prefix.prepare(self, element, prefix)
            prefixes.append(prepared)
        return prefixes
    reprocess = prepare

class PrefixesAvt(Avt, Prefixes):
    pass


class YesNo(AttributeInfo):

    display = '"yes" | "no"'

    def prepare(self, element, value):
        if value is None:
            return self.default and self.default == 'yes'
        elif value not in ['yes', 'no']:
            raise XsltException(Error.INVALID_ATTR_CHOICE, value, str(self))
        return value == 'yes'
    reprocess = prepare

class YesNoAvt(Avt, YesNo):
    pass

