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))