Mercurial > genshi > mirror
changeset 233:88ec2b306296 trunk
* Added implementation of a simple text-based template engine. Closes #47.
* Added upgrade instructions.
author | cmlenz |
---|---|
date | Tue, 12 Sep 2006 13:30:26 +0000 |
parents | 43d3f2d2ec9d |
children | 39c424b80edd |
files | UPGRADE.txt examples/bench/bigtable.py examples/transform/run.py genshi/plugin.py genshi/template.py genshi/tests/template.py |
diffstat | 6 files changed, 472 insertions(+), 197 deletions(-) [+] |
line wrap: on
line diff
new file mode 100644 --- /dev/null +++ b/UPGRADE.txt @@ -0,0 +1,29 @@ +Upgrading Genshi +================ + +Upgrading from Markup +--------------------- + +Prior to version 0.3, the name of the Genshi project was "Markup". The +name change means that you will have to adjust your import statements +and the namespace URI of XML templates, among other things: + + * The package name was changed from "markup" to "genshi". Please + adjust any import statements referring to the old package name. + * The namespace URI for directives in Genshi XML templates has changed + from http://markup.edgewall.org/ to http://genshi.edgewall.org/. + Please update the xmlns:py declaration in your template files + accordingly. + +Furthermore, due to the inclusion of a text-based template language, +the class: + + `markup.template.Template` + +has been renamed to: + + `markup.template.MarkupTemplate` + +If you've been using the Template class directly, you'll need to +update your code (a simple find/replace should do--the API itself +did not change).
--- a/examples/bench/bigtable.py +++ b/examples/bench/bigtable.py @@ -12,7 +12,7 @@ import cElementTree as cet from elementtree import ElementTree as et from genshi.builder import tag -from genshi.template import Template +from genshi.template import MarkupTemplate import neo_cgi import neo_cs import neo_util @@ -38,7 +38,7 @@ table = [dict(a=1,b=2,c=3,d=4,e=5,f=6,g=7,h=8,i=9,j=10) for x in range(1000)] -genshi_tmpl = Template(""" +genshi_tmpl = MarkupTemplate(""" <table xmlns:py="http://genshi.edgewall.org/"> <tr py:for="row in table"> <td py:for="c in row.values()" py:content="c"/> @@ -46,7 +46,7 @@ </table> """) -genshi_tmpl2 = Template(""" +genshi_tmpl2 = MarkupTemplate(""" <table xmlns:py="http://genshi.edgewall.org/">$table</table> """)
--- a/examples/transform/run.py +++ b/examples/transform/run.py @@ -5,11 +5,11 @@ import sys from genshi.input import HTMLParser -from genshi.template import Context, Template +from genshi.template import Context, MarkupTemplate def transform(html_filename, tmpl_filename): tmpl_fileobj = open(tmpl_filename) - tmpl = Template(tmpl_fileobj, tmpl_filename) + tmpl = MarkupTemplate(tmpl_fileobj, tmpl_filename) tmpl_fileobj.close() html_fileobj = open(html_filename)
--- a/genshi/plugin.py +++ b/genshi/plugin.py @@ -21,7 +21,7 @@ from genshi.core import Attrs, Stream, QName from genshi.eval import Undefined from genshi.input import HTML, XML -from genshi.template import Context, Template, TemplateLoader +from genshi.template import Context, MarkupTemplate, Template, TemplateLoader def ET(element): """Converts the given ElementTree element to a markup stream.""" @@ -59,7 +59,7 @@ a string. """ if template_string is not None: - return Template(template_string) + return MarkupTemplate(template_string) divider = templatename.rfind('.') if divider >= 0:
--- a/genshi/template.py +++ b/genshi/template.py @@ -31,7 +31,8 @@ from genshi.path import Path __all__ = ['BadDirectiveError', 'TemplateError', 'TemplateSyntaxError', - 'TemplateNotFound', 'Template', 'TemplateLoader'] + 'TemplateNotFound', 'MarkupTemplate', 'TextTemplate', + 'TemplateLoader'] class TemplateError(Exception): @@ -216,7 +217,7 @@ of `(name, value)` tuples. The items in that dictionary or sequence are added as attributes to the element: - >>> tmpl = Template('''<ul xmlns:py="http://genshi.edgewall.org/"> + >>> tmpl = MarkupTemplate('''<ul xmlns:py="http://genshi.edgewall.org/"> ... <li py:attrs="foo">Bar</li> ... </ul>''') >>> print tmpl.generate(foo={'class': 'collapse'}) @@ -269,7 +270,7 @@ This directive replaces the content of the element with the result of evaluating the value of the `py:content` attribute: - >>> tmpl = Template('''<ul xmlns:py="http://genshi.edgewall.org/"> + >>> tmpl = MarkupTemplate('''<ul xmlns:py="http://genshi.edgewall.org/"> ... <li py:content="bar">Hello</li> ... </ul>''') >>> print tmpl.generate(bar='Bye') @@ -305,7 +306,7 @@ A named template function can be used just like a normal Python function from template expressions: - >>> tmpl = Template('''<div xmlns:py="http://genshi.edgewall.org/"> + >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"> ... <p py:def="echo(greeting, name='world')" class="message"> ... ${greeting}, ${name}! ... </p> @@ -321,7 +322,7 @@ If a function does not require parameters, the parenthesis can be omitted both when defining and when calling it: - >>> tmpl = Template('''<div xmlns:py="http://genshi.edgewall.org/"> + >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"> ... <p py:def="helloworld" class="message"> ... Hello, world! ... </p> @@ -394,7 +395,7 @@ """Implementation of the `py:for` template directive for repeating an element based on an iterable in the context data. - >>> tmpl = Template('''<ul xmlns:py="http://genshi.edgewall.org/"> + >>> tmpl = MarkupTemplate('''<ul xmlns:py="http://genshi.edgewall.org/"> ... <li py:for="item in items">${item}</li> ... </ul>''') >>> print tmpl.generate(items=[1, 2, 3]) @@ -439,7 +440,7 @@ """Implementation of the `py:if` template directive for conditionally excluding elements from being output. - >>> tmpl = Template('''<div xmlns:py="http://genshi.edgewall.org/"> + >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"> ... <b py:if="foo">${bar}</b> ... </div>''') >>> print tmpl.generate(foo=True, bar='Hello') @@ -460,7 +461,7 @@ class MatchDirective(Directive): """Implementation of the `py:match` template directive. - >>> tmpl = Template('''<div xmlns:py="http://genshi.edgewall.org/"> + >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"> ... <span py:match="greeting"> ... Hello ${select('@name')} ... </span> @@ -496,7 +497,7 @@ This directive replaces the element with the result of evaluating the value of the `py:replace` attribute: - >>> tmpl = Template('''<div xmlns:py="http://genshi.edgewall.org/"> + >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"> ... <span py:replace="bar">Hello</span> ... </div>''') >>> print tmpl.generate(bar='Bye') @@ -507,7 +508,7 @@ This directive is equivalent to `py:content` combined with `py:strip`, providing a less verbose way to achieve the same effect: - >>> tmpl = Template('''<div xmlns:py="http://genshi.edgewall.org/"> + >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"> ... <span py:content="bar" py:strip="">Hello</span> ... </div>''') >>> print tmpl.generate(bar='Bye') @@ -528,7 +529,7 @@ When the value of the `py:strip` attribute evaluates to `True`, the element is stripped from the output - >>> tmpl = Template('''<div xmlns:py="http://genshi.edgewall.org/"> + >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"> ... <div py:strip="True"><b>foo</b></div> ... </div>''') >>> print tmpl.generate() @@ -541,7 +542,7 @@ This directive is particulary interesting for named template functions or match templates that do not generate a top-level element: - >>> tmpl = Template('''<div xmlns:py="http://genshi.edgewall.org/"> + >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"> ... <div py:def="echo(what)" py:strip=""> ... <b>${what}</b> ... </div> @@ -582,7 +583,7 @@ If no `py:when` directive is matched then the fallback directive `py:otherwise` will be used. - >>> tmpl = Template('''<div xmlns:py="http://genshi.edgewall.org/" + >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/" ... py:choose=""> ... <span py:when="0 == 1">0</span> ... <span py:when="1 == 1">1</span> @@ -596,7 +597,7 @@ If the `py:choose` directive contains an expression, the nested `py:when` directives are tested for equality to the `py:choose` expression: - >>> tmpl = Template('''<div xmlns:py="http://genshi.edgewall.org/" + >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/" ... py:choose="2"> ... <span py:when="1">1</span> ... <span py:when="2">2</span> @@ -679,7 +680,7 @@ """Implementation of the `py:with` template directive, which allows shorthand access to variables and expressions. - >>> tmpl = Template('''<div xmlns:py="http://genshi.edgewall.org/"> + >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"> ... <span py:with="y=7; z=x+10">$x $y $z</span> ... </div>''') >>> print tmpl.generate(x=42) @@ -729,11 +730,22 @@ for name, expr in self.vars])) +class TemplateMeta(type): + """Meta class for templates.""" + + def __new__(cls, name, bases, d): + if 'directives' in d: + d['_dir_by_name'] = dict(d['directives']) + d['_dir_order'] = [directive[1] for directive in d['directives']] + + return type.__new__(cls, name, bases, d) + + class Template(object): """Can parse a template and transform it into the corresponding output based on context data. """ - NAMESPACE = Namespace('http://genshi.edgewall.org/') + __metaclass__ = TemplateMeta EXPR = StreamEventKind('EXPR') # an expression SUB = StreamEventKind('SUB') # a "subprogram" @@ -750,10 +762,8 @@ ('content', ContentDirective), ('attrs', AttrsDirective), ('strip', StripDirective)] - _dir_by_name = dict(directives) - _dir_order = [directive[1] for directive in directives] - def __init__(self, source, basedir=None, filename=None): + def __init__(self, source, basedir=None, filename=None, loader=None): """Initialize a template from either a string or a file-like object.""" if isinstance(source, basestring): self.source = StringIO(source) @@ -766,107 +776,22 @@ else: self.filepath = None - self.filters = [] - self.stream = self.parse() + self.filters = [self._flatten, self._eval] + + self.stream = self._parse() def __repr__(self): return '<%s "%s">' % (self.__class__.__name__, self.filename) - def parse(self): + def _parse(self): """Parse the template. - The parsing stage parses the XML template and constructs a list of + The parsing stage parses the template and constructs a list of directives that will be executed in the render stage. The input is - split up into literal output (markup that does not depend on the - context data) and actual directives (commands or variable - substitution). + split up into literal output (text that does not depend on the context + data) and directives or expressions. """ - stream = [] # list of events of the "compiled" template - dirmap = {} # temporary mapping of directives to elements - ns_prefix = {} - depth = 0 - - for kind, data, pos in XMLParser(self.source, filename=self.filename): - - if kind is START_NS: - # Strip out the namespace declaration for template directives - prefix, uri = data - if uri == self.NAMESPACE: - ns_prefix[prefix] = uri - else: - stream.append((kind, data, pos)) - - elif kind is END_NS: - if data in ns_prefix: - del ns_prefix[data] - else: - stream.append((kind, data, pos)) - - elif kind is START: - # Record any directive attributes in start tags - tag, attrib = data - directives = [] - strip = False - - if tag in self.NAMESPACE: - cls = self._dir_by_name.get(tag.localname) - if cls is None: - raise BadDirectiveError(tag.localname, pos[0], pos[1]) - value = attrib.get(getattr(cls, 'ATTRIBUTE', None), '') - directives.append(cls(value, *pos)) - strip = True - - new_attrib = [] - for name, value in attrib: - if name in self.NAMESPACE: - cls = self._dir_by_name.get(name.localname) - if cls is None: - raise BadDirectiveError(name.localname, pos[0], - pos[1]) - directives.append(cls(value, *pos)) - else: - if value: - value = list(self._interpolate(value, *pos)) - if len(value) == 1 and value[0][0] is TEXT: - value = value[0][1] - else: - value = [(TEXT, u'', pos)] - new_attrib.append((name, value)) - - if directives: - directives.sort(lambda a, b: cmp(self._dir_order.index(a.__class__), - self._dir_order.index(b.__class__))) - dirmap[(depth, tag)] = (directives, len(stream), strip) - - stream.append((kind, (tag, Attrs(new_attrib)), pos)) - depth += 1 - - elif kind is END: - depth -= 1 - 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 TEXT: - for kind, data, pos in self._interpolate(data, *pos): - stream.append((kind, data, pos)) - - elif kind is COMMENT: - if not data.lstrip().startswith('!'): - stream.append((kind, data, pos)) - - else: - stream.append((kind, data, pos)) - - return stream + raise NotImplementedError _FULL_EXPR_RE = re.compile(r'(?<!\$)\$\{(.+?)\}', re.DOTALL) _SHORT_EXPR_RE = re.compile(r'(?<!\$)\$([a-zA-Z][a-zA-Z0-9_\.]*)') @@ -931,7 +856,7 @@ ctxt = Context(**kwargs) stream = self.stream - for filter_ in [self._flatten, self._eval, self._match] + self.filters: + for filter_ in self.filters: stream = filter_(iter(stream), ctxt) return Stream(stream) @@ -1080,6 +1005,290 @@ SUB = Template.SUB +class MarkupTemplate(Template): + """Can parse a template and transform it into the corresponding output + based on context data. + """ + NAMESPACE = Namespace('http://genshi.edgewall.org/') + + directives = [('def', DefDirective), + ('match', MatchDirective), + ('when', WhenDirective), + ('otherwise', OtherwiseDirective), + ('for', ForDirective), + ('if', IfDirective), + ('choose', ChooseDirective), + ('with', WithDirective), + ('replace', ReplaceDirective), + ('content', ContentDirective), + ('attrs', AttrsDirective), + ('strip', StripDirective)] + + def __init__(self, source, basedir=None, filename=None, loader=None): + """Initialize a template from either a string or a file-like object.""" + Template.__init__(self, source, basedir=basedir, filename=filename, + loader=loader) + + self.filters.append(self._match) + if loader: + from genshi.filters import IncludeFilter + self.filters.append(IncludeFilter(loader)) + + def _parse(self): + """Parse the template. + + The parsing stage parses the XML template and constructs a list of + directives that will be executed in the render stage. The input is + split up into literal output (markup that does not depend on the + context data) and actual directives (commands or variable + substitution). + """ + stream = [] # list of events of the "compiled" template + dirmap = {} # temporary mapping of directives to elements + ns_prefix = {} + depth = 0 + + for kind, data, pos in XMLParser(self.source, filename=self.filename): + + if kind is START_NS: + # Strip out the namespace declaration for template directives + prefix, uri = data + if uri == self.NAMESPACE: + ns_prefix[prefix] = uri + else: + stream.append((kind, data, pos)) + + elif kind is END_NS: + if data in ns_prefix: + del ns_prefix[data] + else: + stream.append((kind, data, pos)) + + elif kind is START: + # Record any directive attributes in start tags + tag, attrib = data + directives = [] + strip = False + + if tag in self.NAMESPACE: + cls = self._dir_by_name.get(tag.localname) + if cls is None: + raise BadDirectiveError(tag.localname, pos[0], pos[1]) + value = attrib.get(getattr(cls, 'ATTRIBUTE', None), '') + directives.append(cls(value, *pos)) + strip = True + + new_attrib = [] + for name, value in attrib: + if name in self.NAMESPACE: + cls = self._dir_by_name.get(name.localname) + if cls is None: + raise BadDirectiveError(name.localname, pos[0], + pos[1]) + directives.append(cls(value, *pos)) + else: + if value: + value = list(self._interpolate(value, *pos)) + if len(value) == 1 and value[0][0] is TEXT: + value = value[0][1] + else: + value = [(TEXT, u'', pos)] + new_attrib.append((name, value)) + + if directives: + index = self._dir_order.index + directives.sort(lambda a, b: cmp(index(a.__class__), + index(b.__class__))) + dirmap[(depth, tag)] = (directives, len(stream), strip) + + stream.append((kind, (tag, Attrs(new_attrib)), pos)) + depth += 1 + + elif kind is END: + depth -= 1 + 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 TEXT: + for kind, data, pos in self._interpolate(data, *pos): + stream.append((kind, data, pos)) + + elif kind is COMMENT: + if not data.lstrip().startswith('!'): + stream.append((kind, data, pos)) + + else: + stream.append((kind, data, pos)) + + return stream + + def _match(self, stream, ctxt=None, match_templates=None): + """Internal stream filter that applies any defined match templates + to the stream. + """ + if match_templates is None: + match_templates = ctxt._match_templates + nsprefix = {} # mapping of namespace prefixes to URIs + + tail = [] + def _strip(stream): + depth = 1 + while 1: + kind, data, pos = stream.next() + if kind is START: + depth += 1 + elif kind is END: + depth -= 1 + if depth > 0: + yield kind, data, pos + else: + tail[:] = [(kind, data, pos)] + break + + for kind, data, pos in stream: + + # We (currently) only care about start and end events for matching + # We might care about namespace events in the future, though + if not match_templates or kind not in (START, END): + yield kind, data, pos + continue + + for idx, (test, path, template, directives) in \ + enumerate(match_templates): + + if test(kind, data, pos, nsprefix, ctxt) is True: + + # Let the remaining match templates know about the event so + # they get a chance to update their internal state + for test in [mt[0] for mt in match_templates[idx + 1:]]: + test(kind, data, pos, nsprefix, ctxt) + + # Consume and store all events until an end event + # corresponding to this start event is encountered + content = [(kind, data, pos)] + content += list(self._match(_strip(stream), ctxt)) + tail + + kind, data, pos = tail[0] + for test in [mt[0] for mt in match_templates]: + test(kind, data, pos, nsprefix, ctxt) + + # Make the select() function available in the body of the + # match template + def select(path): + return Stream(content).select(path) + ctxt.push(dict(select=select)) + + # Recursively process the output + template = _apply_directives(template, ctxt, directives) + for event in self._match(self._eval(self._flatten(template, + ctxt), + ctxt), ctxt, + match_templates[:idx] + + match_templates[idx + 1:]): + yield event + + ctxt.pop() + break + + else: # no matches + yield kind, data, pos + + +class TextTemplate(Template): + """Implementation of a simple text-based template engine. + + >>> tmpl = TextTemplate('''Dear $name, + ... + ... We have the following items for you: + ... #for item in items + ... * $item + ... #endfor + ... + ... All the best, + ... Foobar''') + >>> print tmpl.generate(name='Joe', items=[1, 2, 3]).render('text') + Dear Joe, + <BLANKLINE> + We have the following items for you: + * 1 + * 2 + * 3 + <BLANKLINE> + All the best, + Foobar + """ + directives = [('def', DefDirective), + ('comment', StripDirective), + ('when', WhenDirective), + ('otherwise', OtherwiseDirective), + ('for', ForDirective), + ('if', IfDirective), + ('choose', ChooseDirective), + ('with', WithDirective)] + + _directive_re = re.compile('^\s*#(\w+.*)\n?', re.MULTILINE) + + def _parse(self): + stream = [] # list of events of the "compiled" template + dirmap = {} # temporary mapping of directives to elements + depth = 0 + + source = self.source.read() + offset = 0 + lineno = 1 + for idx, mo in enumerate(self._directive_re.finditer(source)): + start, end = mo.span() + if start > offset: + text = source[offset:start] + for kind, data, pos in self._interpolate(text, self.filename, + lineno, 0): + stream.append((kind, data, pos)) + lineno += len(text.splitlines()) + + text = source[start:end].lstrip().lstrip('#') + lineno += len(text.splitlines()) + directive = text.split(None, 1) + if len(directive) > 1: + command, value = directive + else: + command, value = directive[0], None + + if not command.startswith('end'): + cls = self._dir_by_name.get(command) + if cls is None: + raise BadDirectiveError(command) + directive = cls(value, self.filename, lineno, 0) + dirmap[depth] = (directive, len(stream)) + depth += 1 + else: + depth -= 1 + command = command[3:] + if depth in dirmap: + directive, start_offset = dirmap.pop(depth) + substream = stream[start_offset:] + stream[start_offset:] = [(SUB, ([directive], substream), + (self.filename, lineno, 0))] + + offset = end + + if offset < len(source): + text = source[offset:] + for kind, data, pos in self._interpolate(text, self.filename, + lineno, 0): + stream.append((kind, data, pos)) + + return stream + + class TemplateLoader(object): """Responsible for loading templates from files on the specified search path. @@ -1100,7 +1309,7 @@ template file, and returns the corresponding `Template` object: >>> template = loader.load(os.path.basename(path)) - >>> isinstance(template, Template) + >>> isinstance(template, MarkupTemplate) True Template instances are cached: requesting a template with the same name @@ -1126,7 +1335,7 @@ self._cache = {} self._mtime = {} - def load(self, filename, relative_to=None): + def load(self, filename, relative_to=None, cls=MarkupTemplate): """Load the template with the given name. If the `filename` parameter is relative, this method searches the search @@ -1150,9 +1359,8 @@ @param relative_to: the filename of the template from which the new template is being loaded, or `None` if the template is being loaded directly + @param cls: the class of the template object to instantiate """ - from genshi.filters import IncludeFilter - if relative_to: filename = os.path.join(os.path.dirname(relative_to), filename) filename = os.path.normpath(filename) @@ -1179,8 +1387,8 @@ try: fileobj = open(filepath, 'U') try: - tmpl = Template(fileobj, basedir=dirname, filename=filename) - tmpl.filters.append(IncludeFilter(self)) + tmpl = cls(fileobj, basedir=dirname, filename=filename, + loader=self) finally: fileobj.close() self._cache[filename] = tmpl
--- a/genshi/tests/template.py +++ b/genshi/tests/template.py @@ -18,9 +18,10 @@ import sys import tempfile +from genshi import template from genshi.core import Markup, Stream -from genshi.template import BadDirectiveError, Template, TemplateLoader, \ - TemplateSyntaxError +from genshi.template import BadDirectiveError, MarkupTemplate, Template, \ + TemplateLoader, TemplateSyntaxError, TextTemplate class AttrsDirectiveTestCase(unittest.TestCase): @@ -30,7 +31,7 @@ """ Verify that the directive has access to the loop variables. """ - tmpl = Template("""<doc xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> <elem py:for="item in items" py:attrs="item"/> </doc>""") items = [{'id': 1, 'class': 'foo'}, {'id': 2, 'class': 'bar'}] @@ -43,7 +44,7 @@ Verify that an attribute value that evaluates to `None` removes an existing attribute of that name. """ - tmpl = Template("""<doc xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> <elem class="foo" py:attrs="{'class': 'bar'}"/> </doc>""") self.assertEqual("""<doc> @@ -55,7 +56,7 @@ Verify that an attribute value that evaluates to `None` removes an existing attribute of that name. """ - tmpl = Template("""<doc xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> <elem class="foo" py:attrs="{'class': None}"/> </doc>""") self.assertEqual("""<doc> @@ -72,7 +73,7 @@ Verify that, if multiple `py:when` bodies match, only the first is output. """ - tmpl = Template("""<div xmlns:py="http://genshi.edgewall.org/" py:choose=""> + tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/" py:choose=""> <span py:when="1 == 1">1</span> <span py:when="2 == 2">2</span> <span py:when="3 == 3">3</span> @@ -82,7 +83,7 @@ </div>""", str(tmpl.generate())) def test_otherwise(self): - tmpl = Template("""<div xmlns:py="http://genshi.edgewall.org/" py:choose=""> + tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/" py:choose=""> <span py:when="False">hidden</span> <span py:otherwise="">hello</span> </div>""") @@ -94,7 +95,7 @@ """ Verify that `py:choose` blocks can be nested: """ - tmpl = Template("""<doc xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> <div py:choose="1"> <div py:when="1" py:choose="3"> <span py:when="2">2</span> @@ -114,7 +115,7 @@ """ Verify more complex nesting. """ - tmpl = Template("""<doc xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> <div py:choose="1"> <div py:when="1" py:choose=""> <span py:when="2">OK</span> @@ -134,7 +135,7 @@ """ Verify more complex nesting using otherwise. """ - tmpl = Template("""<doc xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> <div py:choose="1"> <div py:when="1" py:choose="2"> <span py:when="1">FAIL</span> @@ -155,7 +156,7 @@ Verify that a when directive with a strip directive actually strips of the outer element. """ - tmpl = Template("""<doc xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> <div py:choose="" py:strip=""> <span py:otherwise="">foo</span> </div> @@ -169,7 +170,7 @@ Verify that a `when` directive outside of a `choose` directive is reported as an error. """ - tmpl = Template("""<doc xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> <div py:when="xy" /> </doc>""") self.assertRaises(TemplateSyntaxError, str, tmpl.generate()) @@ -179,7 +180,7 @@ Verify that an `otherwise` directive outside of a `choose` directive is reported as an error. """ - tmpl = Template("""<doc xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> <div py:otherwise="" /> </doc>""") self.assertRaises(TemplateSyntaxError, str, tmpl.generate()) @@ -189,7 +190,7 @@ Verify that an `when` directive that doesn't have a `test` attribute is reported as an error. """ - tmpl = Template("""<doc xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> <div py:choose="" py:strip=""> <py:when>foo</py:when> </div> @@ -201,7 +202,7 @@ Verify that an `otherwise` directive can be used without a `test` attribute. """ - tmpl = Template("""<doc xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> <div py:choose="" py:strip=""> <py:otherwise>foo</py:otherwise> </div> @@ -214,7 +215,7 @@ """ Verify that the directive can also be used as an element. """ - tmpl = Template("""<doc xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> <py:choose> <py:when test="1 == 1">1</py:when> <py:when test="2 == 2">2</py:when> @@ -225,6 +226,23 @@ 1 </doc>""", str(tmpl.generate())) + def test_in_text_template(self): + """ + Verify that the directive works as expected in a text template. + """ + tmpl = TextTemplate("""#choose + #when 1 == 1 + 1 + #endwhen + #when 2 == 2 + 2 + #endwhen + #when 3 == 3 + 3 + #endwhen + #endchoose""") + self.assertEqual(""" 1\n""", str(tmpl.generate())) + class DefDirectiveTestCase(unittest.TestCase): """Tests for the `py:def` template directive.""" @@ -234,7 +252,7 @@ Verify that a named template function with a strip directive actually strips of the outer element. """ - tmpl = Template("""<doc xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> <div py:def="echo(what)" py:strip=""> <b>${what}</b> </div> @@ -245,7 +263,7 @@ </doc>""", str(tmpl.generate())) def test_exec_in_replace(self): - tmpl = Template("""<div xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/"> <p py:def="echo(greeting, name='world')" class="message"> ${greeting}, ${name}! </p> @@ -261,7 +279,7 @@ """ Verify that the directive can also be used as an element. """ - tmpl = Template("""<doc xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> <py:def function="echo(what)"> <b>${what}</b> </py:def> @@ -276,7 +294,7 @@ Verify that a template function defined inside a conditional block can be called from outside that block. """ - tmpl = Template("""<doc xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> <py:if test="semantic"> <strong py:def="echo(what)">${what}</strong> </py:if> @@ -293,7 +311,7 @@ """ Verify that keyword arguments work with `py:def` directives. """ - tmpl = Template("""<doc xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> <b py:def="echo(what, bold=False)" py:strip="not bold">${what}</b> ${echo('foo')} </doc>""") @@ -302,7 +320,7 @@ </doc>""", str(tmpl.generate())) def test_invocation_in_attribute(self): - tmpl = Template("""<doc xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> <py:def function="echo(what)">${what or 'something'}</py:def> <p class="${echo('foo')}">bar</p> </doc>""") @@ -311,7 +329,7 @@ </doc>""", str(tmpl.generate())) def test_invocation_in_attribute_none(self): - tmpl = Template("""<doc xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> <py:def function="echo()">${None}</py:def> <p class="${echo()}">bar</p> </doc>""") @@ -322,7 +340,7 @@ def test_function_raising_typeerror(self): def badfunc(): raise TypeError - tmpl = Template("""<html xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/"> <div py:def="dobadfunc()"> ${badfunc()} </div> @@ -331,7 +349,7 @@ self.assertRaises(TypeError, list, tmpl.generate(badfunc=badfunc)) def test_def_in_matched(self): - tmpl = Template("""<doc xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> <head py:match="head">${select('*')}</head> <head> <py:def function="maketitle(test)"><b py:replace="test" /></py:def> @@ -342,6 +360,19 @@ <head><title>True</title></head> </doc>""", str(tmpl.generate())) + def test_in_text_template(self): + """ + Verify that the directive works as expected in a text template. + """ + tmpl = TextTemplate(""" + #def echo(greeting, name='world') + ${greeting}, ${name}! + #enddef + ${echo('Hi', name='you')} + """) + self.assertEqual(""" Hi, you! + """, str(tmpl.generate())) + class ForDirectiveTestCase(unittest.TestCase): """Tests for the `py:for` template directive.""" @@ -351,7 +382,7 @@ Verify that the combining the `py:for` directive with `py:strip` works correctly. """ - tmpl = Template("""<doc xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> <div py:for="item in items" py:strip=""> <b>${item}</b> </div> @@ -368,7 +399,7 @@ """ Verify that the directive can also be used as an element. """ - tmpl = Template("""<doc xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> <py:for each="item in items"> <b>${item}</b> </py:for> @@ -385,7 +416,7 @@ """ Verify that assignment to tuples works correctly. """ - tmpl = Template("""<doc xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> <py:for each="k, v in items"> <p>key=$k, value=$v</p> </py:for> @@ -399,7 +430,7 @@ """ Verify that assignment to nested tuples works correctly. """ - tmpl = Template("""<doc xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> <py:for each="idx, (k, v) in items"> <p>$idx: key=$k, value=$v</p> </py:for> @@ -418,7 +449,7 @@ Verify that the combining the `py:if` directive with `py:strip` works correctly. """ - tmpl = Template("""<doc xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> <b py:if="foo" py:strip="">${bar}</b> </doc>""") self.assertEqual("""<doc> @@ -429,7 +460,7 @@ """ Verify that the directive can also be used as an element. """ - tmpl = Template("""<doc xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> <py:if test="foo">${bar}</py:if> </doc>""") self.assertEqual("""<doc> @@ -445,7 +476,7 @@ Verify that a match template can produce the same kind of element that it matched without entering an infinite recursion. """ - tmpl = Template("""<doc xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> <elem py:match="elem" py:strip=""> <div class="elem">${select('text()')}</div> </elem> @@ -460,7 +491,7 @@ Verify that a match template can produce the same kind of element that it matched without entering an infinite recursion. """ - tmpl = Template("""<doc xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> <elem py:match="elem"> <div class="elem">${select('text()')}</div> </elem> @@ -476,7 +507,7 @@ """ Verify that the directive can also be used as an element. """ - tmpl = Template("""<doc xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> <py:match path="elem"> <div class="elem">${select('text()')}</div> </py:match> @@ -491,7 +522,7 @@ Match directives are applied recursively, meaning that they are also applied to any content they may have produced themselves: """ - tmpl = Template("""<doc xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> <elem py:match="elem"> <div class="elem"> ${select('*')} @@ -522,7 +553,7 @@ themselves output the element they match, avoiding recursion is even more complex, but should work. """ - tmpl = Template("""<html xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/"> <body py:match="body"> <div id="header"/> ${select('*')} @@ -543,7 +574,7 @@ </html>""", str(tmpl.generate())) def test_select_all_attrs(self): - tmpl = Template("""<doc xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> <div py:match="elem" py:attrs="select('@*')"> ${select('text()')} </div> @@ -556,7 +587,7 @@ </doc>""", str(tmpl.generate())) def test_select_all_attrs_empty(self): - tmpl = Template("""<doc xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> <div py:match="elem" py:attrs="select('@*')"> ${select('text()')} </div> @@ -569,7 +600,7 @@ </doc>""", str(tmpl.generate())) def test_select_all_attrs_in_body(self): - tmpl = Template("""<doc xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> <div py:match="elem"> Hey ${select('text()')} ${select('@*')} </div> @@ -582,7 +613,7 @@ </doc>""", str(tmpl.generate())) def test_def_in_match(self): - tmpl = Template("""<doc xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<doc xmlns:py="http://genshi.edgewall.org/"> <py:def function="maketitle(test)"><b py:replace="test" /></py:def> <head py:match="head">${select('*')}</head> <head><title>${maketitle(True)}</title></head> @@ -592,7 +623,7 @@ </doc>""", str(tmpl.generate())) def test_match_with_xpath_variable(self): - tmpl = Template("""<div xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/"> <span py:match="*[name()=$tagname]"> Hello ${select('@name')} </span> @@ -608,7 +639,7 @@ </div>""", str(tmpl.generate(tagname='sayhello'))) def test_content_directive_in_match(self): - tmpl = Template("""<html xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/"> <div py:match="foo">I said <q py:content="select('text()')">something</q>.</div> <foo>bar</foo> </html>""") @@ -617,7 +648,7 @@ </html>""", str(tmpl.generate())) def test_cascaded_matches(self): - tmpl = Template("""<html xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/"> <body py:match="body">${select('*')}</body> <head py:match="head">${select('title')}</head> <body py:match="body">${select('*')}<hr /></body> @@ -630,7 +661,7 @@ </html>""", str(tmpl.generate())) def test_multiple_matches(self): - tmpl = Template("""<html xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<html xmlns:py="http://genshi.edgewall.org/"> <input py:match="form//input" py:attrs="select('@*')" value="${values[str(select('@name'))]}" /> <form><p py:for="field in fields"> @@ -661,7 +692,7 @@ # FIXME #def test_match_after_step(self): - # tmpl = Template("""<div xmlns:py="http://genshi.edgewall.org/"> + # tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/"> # <span py:match="div/greeting"> # Hello ${select('@name')} # </span> @@ -678,7 +709,7 @@ """Tests for the `py:strip` template directive.""" def test_strip_false(self): - tmpl = Template("""<div xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/"> <div py:strip="False"><b>foo</b></div> </div>""") self.assertEqual("""<div> @@ -686,7 +717,7 @@ </div>""", str(tmpl.generate())) def test_strip_empty(self): - tmpl = Template("""<div xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/"> <div py:strip=""><b>foo</b></div> </div>""") self.assertEqual("""<div> @@ -698,7 +729,7 @@ """Tests for the `py:with` template directive.""" def test_shadowing(self): - tmpl = Template("""<div xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/"> ${x} <span py:with="x = x * 2" py:replace="x"/> ${x} @@ -710,7 +741,7 @@ </div>""", str(tmpl.generate(x=42))) def test_as_element(self): - tmpl = Template("""<div xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/"> <py:with vars="x = x * 2">${x}</py:with> </div>""") self.assertEqual("""<div> @@ -718,7 +749,7 @@ </div>""", str(tmpl.generate(x=42))) def test_multiple_vars_same_name(self): - tmpl = Template("""<div xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/"> <py:with vars=" foo = 'bar'; foo = foo.replace('r', 'z') @@ -731,7 +762,7 @@ </div>""", str(tmpl.generate(x=42))) def test_multiple_vars_single_assignment(self): - tmpl = Template("""<div xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/"> <py:with vars="x = y = z = 1">${x} ${y} ${z}</py:with> </div>""") self.assertEqual("""<div> @@ -739,7 +770,7 @@ </div>""", str(tmpl.generate(x=42))) def test_nested_vars_single_assignment(self): - tmpl = Template("""<div xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/"> <py:with vars="x, (y, z) = (1, (2, 3))">${x} ${y} ${z}</py:with> </div>""") self.assertEqual("""<div> @@ -747,7 +778,7 @@ </div>""", str(tmpl.generate(x=42))) def test_multiple_vars_trailing_semicolon(self): - tmpl = Template("""<div xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/"> <py:with vars="x = x * 2; y = x / 2;">${x} ${y}</py:with> </div>""") self.assertEqual("""<div> @@ -755,7 +786,7 @@ </div>""", str(tmpl.generate(x=42))) def test_semicolon_escape(self): - tmpl = Template("""<div xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/"> <py:with vars="x = 'here is a semicolon: ;'; y = 'here are two semicolons: ;;' ;"> ${x} ${y} @@ -816,36 +847,42 @@ self.assertEqual(Stream.TEXT, parts[2][0]) self.assertEqual(' baz', parts[2][1]) + +class MarkupTemplateTestCase(unittest.TestCase): + """Tests for basic template processing, expression evaluation and error + reporting. + """ + def test_interpolate_mixed3(self): - tmpl = Template('<root> ${var} $var</root>') + tmpl = MarkupTemplate('<root> ${var} $var</root>') self.assertEqual('<root> 42 42</root>', str(tmpl.generate(var=42))) def test_interpolate_leading_trailing_space(self): - tmpl = Template('<root>${ foo }</root>') + tmpl = MarkupTemplate('<root>${ foo }</root>') self.assertEqual('<root>bar</root>', str(tmpl.generate(foo='bar'))) def test_interpolate_multiline(self): - tmpl = Template("""<root>${dict( + tmpl = MarkupTemplate("""<root>${dict( bar = 'baz' )[foo]}</root>""") self.assertEqual('<root>baz</root>', str(tmpl.generate(foo='bar'))) def test_interpolate_non_string_attrs(self): - tmpl = Template('<root attr="${1}"/>') + tmpl = MarkupTemplate('<root attr="${1}"/>') self.assertEqual('<root attr="1"/>', str(tmpl.generate())) def test_interpolate_list_result(self): - tmpl = Template('<root>$foo</root>') + tmpl = MarkupTemplate('<root>$foo</root>') self.assertEqual('<root>buzz</root>', str(tmpl.generate(foo=('buzz',)))) def test_empty_attr(self): - tmpl = Template('<root attr=""/>') + tmpl = MarkupTemplate('<root attr=""/>') self.assertEqual('<root attr=""/>', str(tmpl.generate())) def test_bad_directive_error(self): xml = '<p xmlns:py="http://genshi.edgewall.org/" py:do="nothing" />' try: - tmpl = Template(xml, filename='test.html') + tmpl = MarkupTemplate(xml, filename='test.html') except BadDirectiveError, e: self.assertEqual('test.html', e.filename) if sys.version_info[:2] >= (2, 4): @@ -854,7 +891,7 @@ def test_directive_value_syntax_error(self): xml = """<p xmlns:py="http://genshi.edgewall.org/" py:if="bar'" />""" try: - tmpl = Template(xml, filename='test.html') + tmpl = MarkupTemplate(xml, filename='test.html') self.fail('Expected SyntaxError') except TemplateSyntaxError, e: self.assertEqual('test.html', e.filename) @@ -866,7 +903,7 @@ Foo <em>${bar"}</em> </p>""" try: - tmpl = Template(xml, filename='test.html') + tmpl = MarkupTemplate(xml, filename='test.html') self.fail('Expected SyntaxError') except TemplateSyntaxError, e: self.assertEqual('test.html', e.filename) @@ -880,7 +917,7 @@ </p>""" try: - tmpl = Template(xml, filename='test.html') + tmpl = MarkupTemplate(xml, filename='test.html') self.fail('Expected SyntaxError') except TemplateSyntaxError, e: self.assertEqual('test.html', e.filename) @@ -892,7 +929,7 @@ Verify that outputting context data that is a `Markup` instance is not escaped. """ - tmpl = Template("""<div xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/"> $myvar </div>""") self.assertEqual("""<div> @@ -903,7 +940,7 @@ """ Verify that outputting context data in text nodes doesn't escape quotes. """ - tmpl = Template("""<div xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/"> $myvar </div>""") self.assertEqual("""<div> @@ -914,7 +951,7 @@ """ Verify that outputting context data in attribtes escapes quotes. """ - tmpl = Template("""<div xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/"> <elem class="$myvar"/> </div>""") self.assertEqual("""<div> @@ -922,7 +959,7 @@ </div>""", str(tmpl.generate(myvar='"foo"'))) def test_directive_element(self): - tmpl = Template("""<div xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/"> <py:if test="myvar">bar</py:if> </div>""") self.assertEqual("""<div> @@ -930,7 +967,7 @@ </div>""", str(tmpl.generate(myvar='"foo"'))) def test_normal_comment(self): - tmpl = Template("""<div xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/"> <!-- foo bar --> </div>""") self.assertEqual("""<div> @@ -938,7 +975,7 @@ </div>""", str(tmpl.generate())) def test_template_comment(self): - tmpl = Template("""<div xmlns:py="http://genshi.edgewall.org/"> + tmpl = MarkupTemplate("""<div xmlns:py="http://genshi.edgewall.org/"> <!-- !foo --> <!--!bar--> </div>""") @@ -1044,7 +1081,7 @@ def suite(): suite = unittest.TestSuite() - suite.addTest(doctest.DocTestSuite(Template.__module__)) + suite.addTest(doctest.DocTestSuite(template)) suite.addTest(unittest.makeSuite(AttrsDirectiveTestCase, 'test')) suite.addTest(unittest.makeSuite(ChooseDirectiveTestCase, 'test')) suite.addTest(unittest.makeSuite(DefDirectiveTestCase, 'test')) @@ -1054,6 +1091,7 @@ suite.addTest(unittest.makeSuite(StripDirectiveTestCase, 'test')) suite.addTest(unittest.makeSuite(WithDirectiveTestCase, 'test')) suite.addTest(unittest.makeSuite(TemplateTestCase, 'test')) + suite.addTest(unittest.makeSuite(MarkupTemplateTestCase, 'test')) suite.addTest(unittest.makeSuite(TemplateLoaderTestCase, 'test')) return suite