| 1 | # -*- coding: utf-8 -*- |
|---|
| 2 | # |
|---|
| 3 | # Copyright (C) 2006 Edgewall Software |
|---|
| 4 | # Copyright (C) 2006 Matthew Good |
|---|
| 5 | # All rights reserved. |
|---|
| 6 | # |
|---|
| 7 | # This software is licensed as described in the file COPYING, which |
|---|
| 8 | # you should have received as part of this distribution. The terms |
|---|
| 9 | # are also available at http://genshi.edgewall.org/wiki/License. |
|---|
| 10 | # |
|---|
| 11 | # This software consists of voluntary contributions made by many |
|---|
| 12 | # individuals. For the exact contribution history, see the revision |
|---|
| 13 | # history and logs, available at http://genshi.edgewall.org/log/. |
|---|
| 14 | |
|---|
| 15 | """Basic support for the template engine plugin API used by TurboGears and |
|---|
| 16 | CherryPy/Buffet. |
|---|
| 17 | """ |
|---|
| 18 | |
|---|
| 19 | from pkg_resources import resource_filename |
|---|
| 20 | |
|---|
| 21 | from genshi.core import Attrs, Stream, QName |
|---|
| 22 | from genshi.eval import Undefined |
|---|
| 23 | from genshi.input import HTML, XML |
|---|
| 24 | from genshi.template import Context, MarkupTemplate, Template, TemplateLoader, \ |
|---|
| 25 | TextTemplate |
|---|
| 26 | |
|---|
| 27 | def ET(element): |
|---|
| 28 | """Converts the given ElementTree element to a markup stream.""" |
|---|
| 29 | tag_name = element.tag |
|---|
| 30 | if tag_name.startswith('{'): |
|---|
| 31 | tag_name = tag_name[1:] |
|---|
| 32 | tag_name = QName(tag_name) |
|---|
| 33 | attrib = Attrs(element.items()) |
|---|
| 34 | |
|---|
| 35 | yield (Stream.START, (tag_name, attrib), (None, -1, -1)) |
|---|
| 36 | if element.text: |
|---|
| 37 | yield Stream.TEXT, element.text, (None, -1, -1) |
|---|
| 38 | for child in element.getchildren(): |
|---|
| 39 | for item in ET(child): |
|---|
| 40 | yield item |
|---|
| 41 | yield Stream.END, tag_name, (None, -1, -1) |
|---|
| 42 | if element.tail: |
|---|
| 43 | yield Stream.TEXT, element.tail, (None, -1, -1) |
|---|
| 44 | |
|---|
| 45 | |
|---|
| 46 | class AbstractTemplateEnginePlugin(object): |
|---|
| 47 | """Implementation of the plugin API.""" |
|---|
| 48 | |
|---|
| 49 | template_class = None |
|---|
| 50 | extension = None |
|---|
| 51 | |
|---|
| 52 | def __init__(self, extra_vars_func=None, options=None): |
|---|
| 53 | if options is None: |
|---|
| 54 | options = {} |
|---|
| 55 | # TODO get loader_args from the options dict |
|---|
| 56 | |
|---|
| 57 | self.loader = TemplateLoader(auto_reload=True) |
|---|
| 58 | self.options = options |
|---|
| 59 | self.get_extra_vars = extra_vars_func |
|---|
| 60 | |
|---|
| 61 | def load_template(self, templatename, template_string=None): |
|---|
| 62 | """Find a template specified in python 'dot' notation, or load one from |
|---|
| 63 | a string. |
|---|
| 64 | """ |
|---|
| 65 | if template_string is not None: |
|---|
| 66 | return self.template_class(template_string) |
|---|
| 67 | |
|---|
| 68 | divider = templatename.rfind('.') |
|---|
| 69 | if divider >= 0: |
|---|
| 70 | package = templatename[:divider] |
|---|
| 71 | basename = templatename[divider + 1:] + self.extension |
|---|
| 72 | templatename = resource_filename(package, basename) |
|---|
| 73 | |
|---|
| 74 | return self.loader.load(templatename, cls=self.template_class) |
|---|
| 75 | |
|---|
| 76 | def render(self, info, format='html', fragment=False, template=None): |
|---|
| 77 | """Render the template to a string using the provided info.""" |
|---|
| 78 | return self.transform(info, template).render(method=format) |
|---|
| 79 | |
|---|
| 80 | def transform(self, info, template): |
|---|
| 81 | """Render the output to an event stream.""" |
|---|
| 82 | if not isinstance(template, Template): |
|---|
| 83 | template = self.load_template(template) |
|---|
| 84 | ctxt = Context(**info) |
|---|
| 85 | |
|---|
| 86 | # Some functions for Kid compatibility |
|---|
| 87 | def defined(name): |
|---|
| 88 | return ctxt.get(name, Undefined) is not Undefined |
|---|
| 89 | ctxt['defined'] = defined |
|---|
| 90 | def value_of(name, default=None): |
|---|
| 91 | return ctxt.get(name, default) |
|---|
| 92 | ctxt['value_of'] = value_of |
|---|
| 93 | |
|---|
| 94 | return template.generate(ctxt) |
|---|
| 95 | |
|---|
| 96 | |
|---|
| 97 | class MarkupTemplateEnginePlugin(AbstractTemplateEnginePlugin): |
|---|
| 98 | """Implementation of the plugin API for markup templates.""" |
|---|
| 99 | |
|---|
| 100 | template_class = MarkupTemplate |
|---|
| 101 | extension = '.html' |
|---|
| 102 | |
|---|
| 103 | def transform(self, info, template): |
|---|
| 104 | """Render the output to an event stream.""" |
|---|
| 105 | data = {'ET': ET, 'HTML': HTML, 'XML': XML} |
|---|
| 106 | if self.get_extra_vars: |
|---|
| 107 | data.update(self.get_extra_vars()) |
|---|
| 108 | data.update(info) |
|---|
| 109 | return super(MarkupTemplateEnginePlugin, self).transform(data, template) |
|---|
| 110 | |
|---|
| 111 | |
|---|
| 112 | class TextTemplateEnginePlugin(AbstractTemplateEnginePlugin): |
|---|
| 113 | """Implementation of the plugin API for text templates.""" |
|---|
| 114 | |
|---|
| 115 | template_class = TextTemplate |
|---|
| 116 | extension = '.txt' |
|---|
| 117 | |
|---|
| 118 | def transform(self, info, template): |
|---|
| 119 | """Render the output to an event stream.""" |
|---|
| 120 | data = {} |
|---|
| 121 | if self.get_extra_vars: |
|---|
| 122 | data.update(self.get_extra_vars()) |
|---|
| 123 | data.update(info) |
|---|
| 124 | return super(TextTemplateEnginePlugin, self).transform(data, template) |
|---|