# HG changeset patch # User cmlenz # Date 1221079989 0 # Node ID 1b6968d31089d3587d591322ed50e4f7d37c2dee # Parent 09531799bac2b0c44a65db0b7b5a056f76d14d7a Merged the custom-directives branch back into trunk. diff --git a/genshi/filters/i18n.py b/genshi/filters/i18n.py --- a/genshi/filters/i18n.py +++ b/genshi/filters/i18n.py @@ -23,7 +23,8 @@ from genshi.core import Attrs, Namespace, QName, START, END, TEXT, START_NS, \ END_NS, XML_NAMESPACE, _ensure -from genshi.template.base import Template, EXPR, SUB +from genshi.template.base import DirectiveFactory, EXPR, SUB, _apply_directives +from genshi.template.directives import Directive from genshi.template.markup import MarkupTemplate, EXEC __all__ = ['Translator', 'extract'] @@ -32,7 +33,42 @@ I18N_NAMESPACE = Namespace('http://genshi.edgewall.org/i18n') -class Translator(object): +class CommentDirective(Directive): + + __slots__ = [] + + @classmethod + def attach(cls, template, stream, value, namespaces, pos): + return None, stream + + +class MsgDirective(Directive): + + __slots__ = ['params'] + + def __init__(self, value, template, hints=None, namespaces=None, + lineno=-1, offset=-1): + Directive.__init__(self, None, template, namespaces, lineno, offset) + self.params = [name.strip() for name in value.split(',')] + + def __call__(self, stream, directives, ctxt, **vars): + msgbuf = MessageBuffer(self.params) + + stream = iter(stream) + yield stream.next() # the outer start tag + previous = stream.next() + for event in stream: + msgbuf.append(*previous) + previous = event + + gettext = ctxt.get('_i18n.gettext') + for event in msgbuf.translate(gettext(msgbuf.format())): + yield event + + yield previous # the outer end tag + + +class Translator(DirectiveFactory): """Can extract and translate localizable strings from markup streams and templates. @@ -85,12 +121,18 @@ exclude specific parts of a template from being extracted and translated. """ + directives = [ + ('comment', CommentDirective), + ('msg', MsgDirective) + ] + IGNORE_TAGS = frozenset([ QName('script'), QName('http://www.w3.org/1999/xhtml}script'), QName('style'), QName('http://www.w3.org/1999/xhtml}style') ]) INCLUDE_ATTRS = frozenset(['abbr', 'alt', 'label', 'prompt', 'standby', 'summary', 'title']) + NAMESPACE = I18N_NAMESPACE def __init__(self, translate=NullTranslations(), ignore_tags=IGNORE_TAGS, include_attrs=INCLUDE_ATTRS, extract_text=True): @@ -113,7 +155,7 @@ self.include_attrs = include_attrs self.extract_text = extract_text - def __call__(self, stream, ctxt=None, search_text=True, msgbuf=None): + def __call__(self, stream, ctxt=None, search_text=True): """Translate any localizable strings in the given stream. This function shouldn't be called directly. Instead, an instance of @@ -126,23 +168,23 @@ :param ctxt: the template context (not used) :param search_text: whether text nodes should be translated (used internally) - :param msgbuf: a `MessageBuffer` object or `None` (used internally) :return: the localized stream """ ignore_tags = self.ignore_tags include_attrs = self.include_attrs + skip = 0 + xml_lang = XML_NAMESPACE['lang'] + if type(self.translate) is FunctionType: gettext = self.translate else: gettext = self.translate.ugettext - if not self.extract_text: - search_text = False + if ctxt: + ctxt['_i18n.gettext'] = gettext - ns_prefixes = [] - skip = 0 - i18n_comment = I18N_NAMESPACE['comment'] - i18n_msg = I18N_NAMESPACE['msg'] - xml_lang = XML_NAMESPACE['lang'] + extract_text = self.extract_text + if not extract_text: + search_text = False for kind, data, pos in stream: @@ -168,7 +210,7 @@ changed = False for name, value in attrs: newval = value - if search_text and isinstance(value, basestring): + if extract_text and isinstance(value, basestring): if name in include_attrs: newval = gettext(value) else: @@ -182,48 +224,23 @@ if changed: attrs = Attrs(new_attrs) - if msgbuf: - msgbuf.append(kind, data, pos) - continue - elif i18n_msg in attrs: - params = attrs.get(i18n_msg) - if params and type(params) is list: # event tuple - params = params[0][1] - msgbuf = MessageBuffer(params) - attrs -= (i18n_comment, i18n_msg) - yield kind, (tag, attrs), pos elif search_text and kind is TEXT: - if not msgbuf: - text = data.strip() - if text: - data = data.replace(text, unicode(gettext(text))) - yield kind, data, pos - else: - msgbuf.append(kind, data, pos) - - elif msgbuf and kind is EXPR: - msgbuf.append(kind, data, pos) - - elif not skip and msgbuf and kind is END: - msgbuf.append(kind, data, pos) - if not msgbuf.depth: - for event in msgbuf.translate(gettext(msgbuf.format())): - yield event - msgbuf = None - yield kind, data, pos + text = data.strip() + if text: + data = data.replace(text, unicode(gettext(text))) + yield kind, data, pos elif kind is SUB: - subkind, substream = data - new_substream = list(self(substream, ctxt, msgbuf=msgbuf)) - yield kind, (subkind, new_substream), pos - - elif kind is START_NS and data[1] == I18N_NAMESPACE: - ns_prefixes.append(data[0]) - - elif kind is END_NS and data in ns_prefixes: - ns_prefixes.remove(data) + directives, substream = data + # If this is an i18n:msg directive, no need to translate text + # nodes here + is_msg = filter(None, [isinstance(d, MsgDirective) + for d in directives]) + substream = list(self(substream, ctxt, + search_text=not is_msg)) + yield kind, (directives, substream), pos else: yield kind, data, pos @@ -372,7 +389,9 @@ :param lineno: the line number on which the first stream event belonging to the message was found """ - self.params = [name.strip() for name in params.split(',')] + if isinstance(params, basestring): + params = [name.strip() for name in params.split(',')] + self.params = params self.comment = comment self.lineno = lineno self.string = [] diff --git a/genshi/filters/tests/i18n.py b/genshi/filters/tests/i18n.py --- a/genshi/filters/tests/i18n.py +++ b/genshi/filters/tests/i18n.py @@ -174,7 +174,9 @@

""") gettext = lambda s: u"Für Details siehe bitte [1:Hilfe]." - tmpl.filters.insert(0, Translator(gettext)) + translator = Translator(gettext) + tmpl.filters.insert(0, translator) + tmpl.add_directives(Translator.NAMESPACE, translator) self.assertEqual("""

Für Details siehe bitte Hilfe.

""", tmpl.generate().render()) @@ -200,7 +202,9 @@

""") gettext = lambda s: u"Für Details siehe bitte [1:[2:Hilfeseite]]." - tmpl.filters.insert(0, Translator(gettext)) + translator = Translator(gettext) + tmpl.filters.insert(0, translator) + tmpl.add_directives(Translator.NAMESPACE, translator) self.assertEqual("""

Für Details siehe bitte Hilfeseite.

""", tmpl.generate().render()) @@ -225,7 +229,9 @@

""") gettext = lambda s: u"[1:] Einträge pro Seite anzeigen." - tmpl.filters.insert(0, Translator(gettext)) + translator = Translator(gettext) + tmpl.filters.insert(0, translator) + tmpl.add_directives(Translator.NAMESPACE, translator) self.assertEqual("""

Einträge pro Seite anzeigen.

""", tmpl.generate().render()) @@ -250,7 +256,9 @@

""") gettext = lambda s: u"Für [2:Details] siehe bitte [1:Hilfe]." - tmpl.filters.insert(0, Translator(gettext)) + translator = Translator(gettext) + tmpl.filters.insert(0, translator) + tmpl.add_directives(Translator.NAMESPACE, translator) self.assertEqual("""

Für Details siehe bitte Hilfe.

""", tmpl.generate().render()) @@ -276,7 +284,9 @@

""") gettext = lambda s: u"[1:] Einträge pro Seite, beginnend auf Seite [2:]." - tmpl.filters.insert(0, Translator(gettext)) + translator = Translator(gettext) + tmpl.filters.insert(0, translator) + tmpl.add_directives(Translator.NAMESPACE, translator) self.assertEqual("""

Eintr\xc3\xa4ge pro Seite, beginnend auf Seite .

""", tmpl.generate().render()) @@ -301,7 +311,9 @@

""") gettext = lambda s: u"Hallo, %(name)s!" - tmpl.filters.insert(0, Translator(gettext)) + translator = Translator(gettext) + tmpl.filters.insert(0, translator) + tmpl.add_directives(Translator.NAMESPACE, translator) self.assertEqual("""

Hallo, Jim!

""", tmpl.generate(user=dict(name='Jim')).render()) @@ -314,7 +326,9 @@

""") gettext = lambda s: u"%(name)s, sei gegrüßt!" - tmpl.filters.insert(0, Translator(gettext)) + translator = Translator(gettext) + tmpl.filters.insert(0, translator) + tmpl.add_directives(Translator.NAMESPACE, translator) self.assertEqual("""

Jim, sei gegrüßt!

""", tmpl.generate(user=dict(name='Jim')).render()) @@ -327,7 +341,9 @@

""") gettext = lambda s: u"Sei gegrüßt, [1:Alter]!" - tmpl.filters.insert(0, Translator(gettext)) + translator = Translator(gettext) + tmpl.filters.insert(0, translator) + tmpl.add_directives(Translator.NAMESPACE, translator) self.assertEqual("""

Sei gegrüßt, Alter!

""", tmpl.generate(anchor='42').render()) @@ -352,7 +368,9 @@

""") gettext = lambda s: u"%(name)s schrieb dies um %(time)s" - tmpl.filters.insert(0, Translator(gettext)) + translator = Translator(gettext) + tmpl.filters.insert(0, translator) + tmpl.add_directives(Translator.NAMESPACE, translator) entry = { 'author': 'Jim', 'time': datetime(2008, 4, 1, 14, 30) @@ -403,18 +421,48 @@

Foo

""") gettext = lambda s: u"Voh" - tmpl.filters.insert(0, Translator(gettext)) + translator = Translator(gettext) + tmpl.filters.insert(0, translator) + tmpl.add_directives(Translator.NAMESPACE, translator) self.assertEqual("""

Voh

""", tmpl.generate().render()) + def test_extract_i18n_msg_with_attr(self): + tmpl = MarkupTemplate(""" +

Foo

+ """) + translator = Translator() + messages = list(translator.extract(tmpl.stream)) + self.assertEqual(2, len(messages)) + self.assertEqual((3, None, u'Foo bar', []), messages[0]) + self.assertEqual((3, None, u'Foo', []), messages[1]) + + def test_translate_i18n_msg_with_attr(self): + tmpl = MarkupTemplate(""" +

Foo

+ """) + gettext = lambda s: u"Voh" + translator = Translator(DummyTranslations({ + 'Foo': u'Voh', + 'Foo bar': u'Voh bär' + })) + tmpl.filters.insert(0, translator) + tmpl.add_directives(Translator.NAMESPACE, translator) + self.assertEqual(""" +

Voh

+ """, tmpl.generate().render()) + def test_translate_with_translations_object(self): tmpl = MarkupTemplate("""

Foo

""") - translations = DummyTranslations({'Foo': 'Voh'}) - tmpl.filters.insert(0, Translator(translations)) + translator = Translator(DummyTranslations({'Foo': 'Voh'})) + tmpl.filters.insert(0, translator) + tmpl.add_directives(Translator.NAMESPACE, translator) self.assertEqual("""

Voh

""", tmpl.generate().render()) diff --git a/genshi/template/base.py b/genshi/template/base.py --- a/genshi/template/base.py +++ b/genshi/template/base.py @@ -26,8 +26,8 @@ from genshi.core import Attrs, Stream, StreamEventKind, START, TEXT, _ensure from genshi.input import ParseError -__all__ = ['Context', 'Template', 'TemplateError', 'TemplateRuntimeError', - 'TemplateSyntaxError', 'BadDirectiveError'] +__all__ = ['Context', 'DirectiveFactory', 'Template', 'TemplateError', + 'TemplateRuntimeError', 'TemplateSyntaxError', 'BadDirectiveError'] __docformat__ = 'restructuredtext en' @@ -301,8 +301,8 @@ ctxt.frames[0].update(top) -class TemplateMeta(type): - """Meta class for templates.""" +class DirectiveFactoryMeta(type): + """Meta class for directive factories.""" def __new__(cls, name, bases, d): if 'directives' in d: @@ -312,13 +312,44 @@ return type.__new__(cls, name, bases, d) -class Template(object): +class DirectiveFactory(object): + """Base for classes that provide a set of template directives. + + :since: version 0.6 + """ + __metaclass__ = DirectiveFactoryMeta + + directives = [] + """A list of `(name, cls)` tuples that define the set of directives + provided by this factory. + """ + + def compare_directives(self): + """Return a function that takes two directive classes and compares + them to determine their relative ordering. + """ + def _get_index(cls): + if cls in self._dir_order: + return self._dir_order.index(cls) + return 0 + return lambda a, b: cmp(_get_index(a[0]), _get_index(b[0])) + + def get_directive(self, name): + """Return the directive class for the given name. + + :param name: the directive name as used in the template + :return: the directive class + :see: `Directive` + """ + return self._dir_by_name.get(name) + + +class Template(DirectiveFactory): """Abstract template base class. This class implements most of the template processing model, but does not specify the syntax of templates. """ - __metaclass__ = TemplateMeta EXEC = StreamEventKind('EXEC') """Stream event kind representing a Python code suite to execute.""" @@ -363,13 +394,14 @@ self.lookup = lookup self.allow_exec = allow_exec self._init_filters() + self._prepared = False if isinstance(source, basestring): source = StringIO(source) else: source = source try: - self.stream = list(self._prepare(self._parse(source, encoding))) + self._stream = self._parse(source, encoding) except ParseError, e: raise TemplateSyntaxError(e.msg, self.filepath, e.lineno, e.offset) @@ -390,6 +422,13 @@ if self.loader: self.filters.append(self._include) + def _get_stream(self): + if not self._prepared: + self._stream = list(self._prepare(self._stream)) + self._prepared = True + return self._stream + stream = property(_get_stream) + def _parse(self, source, encoding): """Parse the template. diff --git a/genshi/template/markup.py b/genshi/template/markup.py --- a/genshi/template/markup.py +++ b/genshi/template/markup.py @@ -42,8 +42,8 @@ """ - DIRECTIVE_NAMESPACE = Namespace('http://genshi.edgewall.org/') - XINCLUDE_NAMESPACE = Namespace('http://www.w3.org/2001/XInclude') + DIRECTIVE_NAMESPACE = 'http://genshi.edgewall.org/' + XINCLUDE_NAMESPACE = 'http://www.w3.org/2001/XInclude' directives = [('def', DefDirective), ('match', MatchDirective), @@ -60,6 +60,13 @@ serializer = 'xml' _number_conv = Markup + def __init__(self, source, filepath=None, filename=None, loader=None, + encoding=None, lookup='strict', allow_exec=True): + Template.__init__(self, source, filepath=filepath, filename=filename, + loader=loader, encoding=encoding, lookup=lookup, + allow_exec=allow_exec) + self.add_directives(self.DIRECTIVE_NAMESPACE, self) + def _init_filters(self): Template._init_filters(self) # Make sure the include filter comes after the match filter @@ -70,45 +77,58 @@ self.filters.append(self._include) def _parse(self, source, encoding): - streams = [[]] # stacked lists of events of the "compiled" template - dirmap = {} # temporary mapping of directives to elements - ns_prefix = {} - depth = 0 - fallbacks = [] - includes = [] - if not isinstance(source, Stream): source = XMLParser(source, filename=self.filename, encoding=encoding) + stream = [] for kind, data, pos in source: - stream = streams[-1] - if kind is START_NS: - # Strip out the namespace declaration for template directives - prefix, uri = data - ns_prefix[prefix] = uri - if uri not in (self.DIRECTIVE_NAMESPACE, - self.XINCLUDE_NAMESPACE): + if kind is TEXT: + for kind, data, pos in interpolate(data, self.filepath, pos[1], + pos[2], lookup=self.lookup): stream.append((kind, data, pos)) - elif kind is END_NS: - uri = ns_prefix.pop(data, None) - if uri and uri not in (self.DIRECTIVE_NAMESPACE, - self.XINCLUDE_NAMESPACE): + elif kind is PI and data[0] == 'python': + if not self.allow_exec: + raise TemplateSyntaxError('Python code blocks not allowed', + self.filepath, *pos[1:]) + try: + suite = Suite(data[1], self.filepath, pos[1], + lookup=self.lookup) + except SyntaxError, err: + raise TemplateSyntaxError(err, self.filepath, + pos[1] + (err.lineno or 1) - 1, + pos[2] + (err.offset or 0)) + stream.append((EXEC, suite, pos)) + + elif kind is COMMENT: + if not data.lstrip().startswith('!'): stream.append((kind, data, pos)) - elif kind is START: - # Record any directive attributes in start tags + else: + stream.append((kind, data, pos)) + + return stream + + def _extract_directives(self, stream, namespace, factory): + depth = 0 + dirmap = {} # temporary mapping of directives to elements + new_stream = [] + ns_prefix = {} # namespace prefixes in use + + for kind, data, pos in stream: + + if kind is START: tag, attrs = data directives = [] strip = False - if tag in self.DIRECTIVE_NAMESPACE: - cls = self._dir_by_name.get(tag.localname) + if tag.namespace == namespace: + cls = factory.get_directive(tag.localname) if cls is None: - raise BadDirectiveError(tag.localname, self.filepath, - pos[1]) + raise BadDirectiveError(tag.localname, + self.filepath, pos[1]) args = dict([(name.localname, value) for name, value in attrs if not name.namespace]) directives.append((cls, args, ns_prefix.copy(), pos)) @@ -116,53 +136,104 @@ new_attrs = [] for name, value in attrs: - if name in self.DIRECTIVE_NAMESPACE: - cls = self._dir_by_name.get(name.localname) + if name.namespace == namespace: + cls = factory.get_directive(name.localname) if cls is None: raise BadDirectiveError(name.localname, self.filepath, pos[1]) - directives.append((cls, value, ns_prefix.copy(), pos)) + if type(value) is list and len(value) == 1: + value = value[0][1] + directives.append((cls, value, ns_prefix.copy(), + pos)) else: - if value: - value = list(interpolate(value, self.filepath, - pos[1], pos[2], - lookup=self.lookup)) - if len(value) == 1 and value[0][0] is TEXT: - value = value[0][1] - else: - value = [(TEXT, u'', pos)] new_attrs.append((name, value)) new_attrs = Attrs(new_attrs) if directives: - index = self._dir_order.index - directives.sort(lambda a, b: cmp(index(a[0]), index(b[0]))) - dirmap[(depth, tag)] = (directives, len(stream), strip) + directives.sort(self.compare_directives()) + dirmap[(depth, tag)] = (directives, len(new_stream), + strip) - if tag in self.XINCLUDE_NAMESPACE: + new_stream.append((kind, (tag, new_attrs), pos)) + depth += 1 + + elif kind is END: + depth -= 1 + new_stream.append((kind, data, pos)) + + # If there have have directive attributes with the + # corresponding start tag, move the events inbetween into + # a "subprogram" + if (depth, data) in dirmap: + directives, offset, strip = dirmap.pop((depth, data)) + substream = new_stream[offset:] + if strip: + substream = substream[1:-1] + new_stream[offset:] = [ + (SUB, (directives, substream), pos) + ] + + elif kind is SUB: + directives, substream = data + substream = self._extract_directives(substream, namespace, + factory) + + if len(substream) == 1 and substream[0][0] is SUB: + added_directives, substream = substream[0][1] + directives += added_directives + + new_stream.append((kind, (directives, substream), pos)) + + elif kind is START_NS: + # Strip out the namespace declaration for template + # directives + prefix, uri = data + ns_prefix[prefix] = uri + if uri != namespace: + new_stream.append((kind, data, pos)) + + elif kind is END_NS: + uri = ns_prefix.pop(data, None) + if uri and uri != namespace: + new_stream.append((kind, data, pos)) + + else: + new_stream.append((kind, data, pos)) + + return new_stream + + def _extract_includes(self, stream): + streams = [[]] # stacked lists of events of the "compiled" template + prefixes = {} + fallbacks = [] + includes = [] + xinclude_ns = Namespace(self.XINCLUDE_NAMESPACE) + + for kind, data, pos in stream: + stream = streams[-1] + + if kind is START: + # Record any directive attributes in start tags + tag, attrs = data + if tag in xinclude_ns: if tag.localname == 'include': - include_href = new_attrs.get('href') + include_href = attrs.get('href') if not include_href: raise TemplateSyntaxError('Include misses required ' 'attribute "href"', self.filepath, *pos[1:]) - includes.append((include_href, new_attrs.get('parse'))) + includes.append((include_href, attrs.get('parse'))) streams.append([]) elif tag.localname == 'fallback': streams.append([]) fallbacks.append(streams[-1]) - else: - stream.append((kind, (tag, new_attrs), pos)) - - depth += 1 + stream.append((kind, (tag, attrs), pos)) elif kind is END: - depth -= 1 - - if fallbacks and data == self.XINCLUDE_NAMESPACE['fallback']: + if fallbacks and data == xinclude_ns['fallback']: assert streams.pop() is fallbacks[-1] - elif data == self.XINCLUDE_NAMESPACE['include']: + elif data == xinclude_ns['include']: fallback = None if len(fallbacks) == len(includes): fallback = fallbacks.pop() @@ -183,37 +254,12 @@ else: stream.append((kind, data, pos)) - # If there have have directive attributes with the corresponding - # start tag, move the events inbetween into a "subprogram" - if (depth, data) in dirmap: - directives, start_offset, strip = dirmap.pop((depth, data)) - substream = stream[start_offset:] - if strip: - substream = substream[1:-1] - stream[start_offset:] = [(SUB, (directives, substream), - pos)] + elif kind is START_NS and data[1] == xinclude_ns: + # Strip out the XInclude namespace + prefixes[data[0]] = data[1] - elif kind is PI and data[0] == 'python': - if not self.allow_exec: - raise TemplateSyntaxError('Python code blocks not allowed', - self.filepath, *pos[1:]) - try: - suite = Suite(data[1], self.filepath, pos[1], - lookup=self.lookup) - except SyntaxError, err: - raise TemplateSyntaxError(err, self.filepath, - pos[1] + (err.lineno or 1) - 1, - pos[2] + (err.offset or 0)) - stream.append((EXEC, suite, pos)) - - elif kind is TEXT: - for kind, data, pos in interpolate(data, self.filepath, pos[1], - pos[2], lookup=self.lookup): - stream.append((kind, data, pos)) - - elif kind is COMMENT: - if not data.lstrip().startswith('!'): - stream.append((kind, data, pos)) + elif kind is END_NS and data in prefixes: + prefixes.pop(data) else: stream.append((kind, data, pos)) @@ -221,6 +267,45 @@ assert len(streams) == 1 return streams[0] + def _interpolate_attrs(self, stream): + for kind, data, pos in stream: + + if kind is START: + # Record any directive attributes in start tags + tag, attrs = data + new_attrs = [] + for name, value in attrs: + if value: + value = list(interpolate(value, self.filepath, pos[1], + pos[2], lookup=self.lookup)) + if len(value) == 1 and value[0][0] is TEXT: + value = value[0][1] + else: + value = [(TEXT, u'', pos)] + new_attrs.append((name, value)) + data = tag, Attrs(new_attrs) + + yield kind, data, pos + + def _prepare(self, stream): + return Template._prepare(self, + self._extract_includes(self._interpolate_attrs(stream)) + ) + + def add_directives(self, namespace, factory): + """Register a custom `DirectiveFactory` for a given namespace. + + :param namespace: the namespace URI + :type namespace: `basestring` + :param factory: the directive factory to register + :type factory: `DirectiveFactory` + :since: version 0.6 + """ + assert not self._prepared, 'Too late for adding directives, ' \ + 'template already prepared' + self._stream = self._extract_directives(self._stream, namespace, + factory) + def _match(self, stream, ctxt, start=0, end=None, **vars): """Internal stream filter that applies any defined match templates to the stream. diff --git a/genshi/template/tests/directives.py b/genshi/template/tests/directives.py --- a/genshi/template/tests/directives.py +++ b/genshi/template/tests/directives.py @@ -504,10 +504,10 @@ """ try: MarkupTemplate(""" - - empty - - """, filename='test.html') + + empty + + """, filename='test.html').generate() self.fail('ExpectedTemplateSyntaxError') except TemplateSyntaxError, e: self.assertEqual('test.html', e.filename) @@ -963,9 +963,9 @@ def test_as_element(self): try: - tmpl = MarkupTemplate(""" + MarkupTemplate(""" Foo - """, filename='test.html') + """, filename='test.html').generate() self.fail('Expected TemplateSyntaxError') except TemplateSyntaxError, e: self.assertEqual('test.html', e.filename) @@ -981,9 +981,9 @@ expression is supplied. """ try: - tmpl = MarkupTemplate(""" + MarkupTemplate(""" Foo - """, filename='test.html') + """, filename='test.html').generate() self.fail('Expected TemplateSyntaxError') except TemplateSyntaxError, e: self.assertEqual('test.html', e.filename) diff --git a/genshi/template/tests/markup.py b/genshi/template/tests/markup.py --- a/genshi/template/tests/markup.py +++ b/genshi/template/tests/markup.py @@ -86,8 +86,8 @@ def test_directive_value_syntax_error(self): xml = """

""" try: - tmpl = MarkupTemplate(xml, filename='test.html') - self.fail('Expected SyntaxError') + tmpl = MarkupTemplate(xml, filename='test.html').generate() + self.fail('Expected TemplateSyntaxError') except TemplateSyntaxError, e: self.assertEqual('test.html', e.filename) self.assertEqual(1, e.lineno) @@ -98,7 +98,7 @@

""" try: tmpl = MarkupTemplate(xml, filename='test.html') - self.fail('Expected SyntaxError') + self.fail('Expected TemplateSyntaxError') except TemplateSyntaxError, e: self.assertEqual('test.html', e.filename) self.assertEqual(2, e.lineno) @@ -111,7 +111,7 @@

""" try: tmpl = MarkupTemplate(xml, filename='test.html') - self.fail('Expected SyntaxError') + self.fail('Expected TemplateSyntaxError') except TemplateSyntaxError, e: self.assertEqual('test.html', e.filename) self.assertEqual(3, e.lineno) @@ -130,7 +130,8 @@ def test_text_noescape_quotes(self): """ - Verify that outputting context data in text nodes doesn't escape quotes. + Verify that outputting context data in text nodes doesn't escape + quotes. """ tmpl = MarkupTemplate("""
$myvar diff --git a/genshi/template/text.py b/genshi/template/text.py --- a/genshi/template/text.py +++ b/genshi/template/text.py @@ -216,7 +216,7 @@ (self.filepath, lineno, 0))] elif command: - cls = self._dir_by_name.get(command) + cls = self.get_directive(command) if cls is None: raise BadDirectiveError(command) directive = cls, value, None, (self.filepath, lineno, 0) @@ -312,7 +312,7 @@ pos = (self.filename, lineno, 0) stream.append((INCLUDE, (value.strip(), None, []), pos)) elif command != '#': - cls = self._dir_by_name.get(command) + cls = self.get_directive(command) if cls is None: raise BadDirectiveError(command) directive = cls, value, None, (self.filepath, lineno, 0)