class NumberElement(XsltElement):

    category = CategoryTypes.INSTRUCTION
    content = ContentInfo.Empty
    legalAttrs = {
        'level':
        AttributeInfo.Choice(['single', 'multiple', 'any'], default='single'),
        'count':
        AttributeInfo.Pattern(),
        'from':
        AttributeInfo.Pattern(),
        'value':
        AttributeInfo.Expression(),
        'format':
        AttributeInfo.StringAvt(default='1'),
        'lang':
        AttributeInfo.NMToken(),
        'letter-value':
        AttributeInfo.ChoiceAvt(['alphabetic', 'traditional']),
        'grouping-separator':
        AttributeInfo.CharAvt(),
        'grouping-size':
        AttributeInfo.NumberAvt(default=0),
    }

    doesSetup = 1

    def setup(self):
        if self._level == 'single':
            if not self._count and not self._from:
                self._level = SIMPLE
            else:
                self._level = SINGLE
        elif self._level == 'multiple':
            self._level = MULTIPLE
        elif self._level == 'any':
            self._level = ANY

        if self._format.isConstant():
            self._formatter = self.createFormatter(self._format.evaluate(None),
                                                   language=self._lang)
        else:
            self._formatter = None
        return

    def createFormatter(self, format, language=None, letterValue=None):
        """
        Creates a formatter appropriate for the given language and
        letterValue, or a default, English-based formatter. Raises an
        exception if the language or letterValue is unsupported.
        Currently, if the language value is given, it must indicate
        English.
        """
        # lang specifies the language whose alphabet is to be used
        #  for numbering when a format token is alphabetic.
        #
        # "if no lang value is specified, the language should be
        # determined from the system environment." -- unsupported;
        # we just default to English.
        if language and not language.lower().startswith('en'):
            raise XsltRuntimeException(Error.UNSUPPORTED_NUMBER_LANG_VALUE,
                                       self, language)

        # letter-value says how to resolve the ambiguity that arises when
        # you want alphabetic numbering to start with some letter, but
        # that letter, when used in a format token, normally indicates
        # some other numbering system. e.g., in English, the format token
        # "A" means to use the letter "A" for 1, "B" for 2, etc., and
        # "B" means to use the letter "B" for 1, "C" for 2, etc.,
        # but "I" indicates Roman numbering. letter-value="alphabetic" can
        # force the interpretation to be that "I" instead means to use the
        # letter "I" for 1, "J" for 2, etc.
        # Valid values are 'traditional' or 'alphabetic'.
        #
        # Our DefaultFormatter only supports English language and
        # traditional, not alphabetic, letter-value.
        if letterValue and letterValue != 'traditional':
            if not language or language.lower().startswith('en'):
                raise XsltRuntimeException(
                    Error.UNSUPPORTED_NUMBER_LETTER_FOR_LANG, self,
                    letterValue, language or 'en')

        return DefaultFormatter(format)

    def instantiate(self, context, processor):
        # get value(s) to format
        if self._value:
            value = Conversions.NumberValue(self._value.evaluate(context))
            if not number.finite(value) or value < 0.5:
                # This is an error.  However, recovery is to just write
                # the number as if the string() function was used.
                processor.writers[-1].text(Conversions.StringValue(value))
                return
            else:
                values = [int(round(value))]
        else:
            node = context.node
            if self._level == SINGLE:
                value = self._single_value(context, node, self._count,
                                           self._from)
                if value == 0:
                    values = []
                else:
                    values = [value]
            elif self._level == MULTIPLE:
                values = self._multiple_values(context, node)
            elif self._level == ANY:
                value = self._any_value(context, node)
                if value == 0:
                    values = []
                else:
                    values = [value]
            else:
                # 'single' without count or from attributes
                value = 1
                prev = node.previousSibling
                type = node.nodeType
                expanded = (node.namespaceURI, node.localName)
                while prev:
                    if prev.nodeType == type and \
                       (prev.namespaceURI, prev.localName) == expanded:
                        value += 1
                    prev = prev.previousSibling
                values = [value]

        # format the value(s)
        grouping_size = int(self._grouping_size.evaluate(context))
        if grouping_size:
            grouping_separator = self._grouping_separator.evaluate(context)
        else:
            grouping_separator = None

        formatter = self._formatter
        if not formatter:
            format = self._format and self._format.evaluate(
                context) or DEFAULT_FORMAT
            lang = self._lang and self._lang.evaluate(context) or DEFAULT_LANG
            letter_value = self._letter_value.evaluate(context) or ''
            formatter = self.createFormatter(format, lang, letter_value)

        numstr = formatter.format(values, grouping_size, grouping_separator)
        processor.writers[-1].text(numstr)
        return

    def _single_value(self, context, node, countPattern, fromPattern):
        if not countPattern:
            if not node.localName:
                # text, comment and processing instruction
                countPattern = NodeTypeTest(node)
            else:
                countPattern = NameTest(node)

        if fromPattern:
            start = node.parentNode
            while start and not fromPattern.match(context, start):
                start = start.parentNode
        else:
            start = node.rootNode

        while not countPattern.match(context, node):
            node = node.parentNode
            if node is None or node == start:
                return 0

        value = 0
        while node:
            value += 1
            node = node.previousSibling
            while node and not countPattern.match(context, node):
                node = node.previousSibling
        return value

    def _multiple_values(self, context, node):
        if not self._count:
            if not node.localName:
                # text, comment and processing instruction
                count = NodeTypeTest(node)
            else:
                count = NameTest(node)
        else:
            count = self._count

        values = []
        while node:
            if count.match(context, node):
                value = self._single_value(context, node, count, None)
                values.insert(0, value)
            node = node.parentNode
            if node and self._from and self._from.match(context, node):
                break
        return values

    def _any_value(self, context, node):
        if not self._count:
            if not node.localName:
                # text, comment and processing instruction
                count = NodeTypeTest(node)
            else:
                count = NameTest(node)
        else:
            count = self._count

        value = 0
        while node:
            if self._from and self._from.match(context, node):
                break
            if count.match(context, node):
                value += 1
            if not node.previousSibling:
                node = node.parentNode
            else:
                node = node.previousSibling
                while node.lastChild:
                    node = node.lastChild
        return value
class SortElement(XsltElement):
    category = None
    content = ContentInfo.Empty
    legalAttrs = {
        'select' : AttributeInfo.StringExpression(default='.'),
        'lang' : AttributeInfo.NMTokenAvt(),
        # We don't support any additional data-types, hence no
        # AttributeInfo.QNameButNotNCName()
        'data-type' : AttributeInfo.ChoiceAvt(['text', 'number'],
                                              default='text'),
        'order' : AttributeInfo.ChoiceAvt(['ascending', 'descending'],
                                          default='ascending'),
        'case-order' : AttributeInfo.ChoiceAvt(['upper-first', 'lower-first']),
        }

    doesSetup = 1

    def setup(self):
        # optimize for constant AVT attribute values (i.e., no {})
        if self._data_type.isConstant() and self._order.isConstant() and \
           self._case_order.isConstant():
            self._comparer = self.makeComparer(self._order.evaluate(None),
                                               self._data_type.evaluate(None),
                                               self._case_order.evaluate(None),
                                               )
        else:
            self._comparer = None
        return

    def makeComparer(self, order, data_type, case_order):
        #if order not in ['ascending', 'descending']:
        #    raise XsltException(Error.ILLEGAL_SORT_ORDER_VALUE)
        #if data_type not in ['text', 'number']:
        #    raise XsltException(Error.ILLEGAL_SORT_DATA_TYPE_VALUE)
        #if case_order and case_order not in ['upper-first', 'lower-first']:
        #    raise XsltException(Error.ILLEGAL_SORT_CASE_ORDER_VALUE)

        if data_type == 'number':
            comparer = FloatCompare
        else:
            if case_order == 'lower-first':
                comparer = LowerFirstCompare
            elif case_order == 'upper-first':
                comparer = UpperFirstCompare
            else:
                # use default for this locale
                comparer = cmp

        if order == 'descending':
            comparer = Descending(comparer)

        return comparer

    def getComparer(self, context):
        if self._comparer: return self._comparer

        data_type = self._data_type.evaluate(context)
        order = self._order.evaluate(context)
        case_order = self._case_order and self._case_order.evaluate(context)
        return self.makeComparer(order, data_type, case_order)

    def evaluate(self, context):
        return Conversions.StringValue(self._select.evaluate(context))