# HG changeset patch # User cmlenz # Date 1158159178 0 # Node ID 4d81439bc0976dcbe8b92d0ca32e613d5fe994c4 # Parent 37039af315da3f8291cf4b657daaaa8ab8391243 * Added basic documentation for the text-based template language. * Directives in text templates are now closed with a simple `#end` line instead of the longer `#end`. diff --git a/doc/Makefile b/doc/Makefile --- a/doc/Makefile +++ b/doc/Makefile @@ -2,6 +2,7 @@ builder.html \ index.html \ streams.html \ + text-templates.html \ xml-templates.html \ xpath.html diff --git a/doc/index.txt b/doc/index.txt --- a/doc/index.txt +++ b/doc/index.txt @@ -23,4 +23,5 @@ * `Markup Streams `_ * `Generating Markup Programmatically `_ * `Genshi XML Template Language `_ +* `Genshi Text Template Language `_ * `Using XPath in Genshi `_ diff --git a/doc/text-templates.txt b/doc/text-templates.txt new file mode 100644 --- /dev/null +++ b/doc/text-templates.txt @@ -0,0 +1,292 @@ +.. -*- mode: rst; encoding: utf-8 -*- + +============================= +Genshi Text Template Language +============================= + +In addition to the XML-based template language, Genshi provides a simple +text-based template language, intended for basic plain text generation needs. +The language is similar to Cheetah_ or Velocity_. + +.. _cheetah: http://cheetahtemplate.org/ +.. _velocity: http://jakarta.apache.org/velocity/ + +This document describes the template language and will be most useful as +reference to those developing Genshi text templates. Templates are XML files of some +kind (such as XHTML) that include processing directives_ (elements or +attributes identified by a separate namespace) that affect how the template is +rendered, and template expressions_ that are dynamically substituted by +variable data. + + +.. contents:: Contents + :depth: 3 +.. sectnum:: + +---------- +Python API +---------- + +The Python code required for templating with Genshi is generally based on the +following pattern: + +* Attain a ``Template`` object from a string or file object containing the + template source. This can either be done directly, or through a + ``TemplateLoader`` instance. +* Call the ``generate()`` method of the template, passing any data that should + be made available to the template as keyword arguments. +* Serialize the resulting stream using its ``render()`` method. + +For example:: + + from genshi.template import TextTemplate + + tmpl = TextTemplate('$title') + stream = tmpl.generate(title='Hello, world!') + print stream.render('text') + +That code would produce the following output:: + + Hello, world! + +Using a template loader provides the advantage that “compiled” templates are +automatically cached, and only parsed again when the template file changes:: + + from genshi.template import TemplateLoader + + loader = TemplateLoader([templates_dir]) + tmpl = loader.load('test.txt' cls=TextTemplate) + stream = tmpl.generate(title='Hello, world!') + print stream.render('text') + + +.. _`expressions`: + +-------------------- +Template Expressions +-------------------- + +Python_ expressions can be used in text and attribute values. An expression is +substituted with the result of its evaluation against the template data. +Expressions need to prefixed with a dollar sign (``$``) and usually enclosed in +curly braces (``{…}``). + +.. _python: http://www.python.org/ + +If the expression starts with a letter and contains only letters and digits, +the curly braces may be omitted. In all other cases, the braces are required +so that the template processors knows where the expression ends:: + + >>> from genshi.template import TextTemplate + >>> tmpl = TextTemplate('${items[0].capitalize()} item') + >>> print tmpl.generate(items=['first', 'second']) + First item + +Expressions support the full power of Python. In addition, it is possible to +access items in a dictionary using “dotted notation” (i.e. as if they were +attributes), and vice-versa (i.e. access attributes as if they were items in +a dictionary):: + + >>> from genshi.template import TextTemplate + >>> tmpl = TextTemplate('${dict.foo}') + >>> print tmpl.generate(dict={'foo': 'bar'}) + bar + + +.. _`directives`: + +------------------- +Template Directives +------------------- + +Directives are lines starting with a ``#`` character followed by the directive +name. They can affect how the template is rendered in a number of ways: Genshi +provides directives for conditionals and looping, among others. + +For example:: + + #if foo + Bar + #end + +Directives must be on separate lines, and the ``#`` character must be be the +first non-whitespace character on that line. Each directive must be “closed” +using a ``#end`` marker. + + +Conditional Sections +==================== + +.. _`#if`: + +``#if`` +--------- + +The content is only rendered if the expression evaluates to a truth value:: + + #if foo + ${bar} + #end + +Given the data ``foo=True`` and ``bar='Hello'`` in the template context, this +would produce:: + + Hello + + +.. _`#choose`: +.. _`#when`: +.. _`#otherwise`: + +``#choose`` +------------- + +The ``#choose`` directive, in combination with the directives ``#when`` and +``#otherwise`` provides advanced contional processing for rendering one of +several alternatives. The first matching ``#when`` branch is rendered, or, if +no ``#when`` branch matches, the ``#otherwise`` branch is be rendered. + +If the ``#choose`` directive has no argument the nested ``#when`` directives +will be tested for truth:: + + The answer is: + #choose + #when 0 == 1 + 0 + #end + #when 1 == 1 + 1 + #end + #otherwise + 2 + #end + #end + +This would produce the following output:: + + The answer is: + 1 + +If the ``#choose`` does have an argument, the nested ``#when`` directives will +be tested for equality to the parent ``#choose`` value:: + + The answer is: + #choose 1 + #when 0 + 0 + #end + #when 1 + 1 + #end + #otherwise + 2 + #end + #end + +This would produce the following output:: + + The answer is: + 1 + + +Looping +======= + +.. _`#for`: + +``#for`` +---------- + +The content is repeated for every item in an iterable:: + + Your items: + #for item in items + * ${item} + #end + +Given ``items=[1, 2, 3]`` in the context data, this would produce:: + + Your items + * 1 + * 2 + * 3 + + +Snippet Reuse +============= + +.. _`#def`: +.. _`macros`: + +``#def`` +---------- + +The ``#def`` directive can be used to create macros, i.e. snippets of template +text that have a name and optionally some parameters, and that can be inserted +in other places:: + + #def greeting(name) + Hello, ${name}! + #end + ${greeting('world')} + ${greeting('everyone else')} + +The above would be rendered to:: + + Hello, world! + Hello, everyone else! + +If a macro doesn't require parameters, it can be defined as well as called +without the parenthesis. For example:: + + #def greeting + Hello, world! + #end + ${greeting} + +The above would be rendered to:: + + Hello, world! + + +Variable Binding +================ + +.. _`#with`: + +``#with`` +----------- + +The ``#with`` directive lets you assign expressions to variables, which can +be used to make expressions inside the directive less verbose and more +efficient. For example, if you need use the expression ``author.posts`` more +than once, and that actually results in a database query, assigning the results +to a variable using this directive would probably help. + +For example:: + + Magic numbers! + #with y=7; z=x+10 + $x $y $z + #end + +Given ``x=42`` in the context data, this would produce:: + + Magic numbers! + 42 7 52 + +Note that if a variable of the same name already existed outside of the scope +of the ``#with`` directive, it will **not** be overwritten. Instead, it will +have the same value it had prior to the ``#with`` assignment. Effectively, +this means that variables are immutable in Genshi. + + +.. _comments: + +-------- +Comments +-------- + +Lines where the first non-whitespace characters are ``##`` are removed from +the output, and can thus be used for comments. This can be escaped using a +backslash. diff --git a/doc/xml-templates.txt b/doc/xml-templates.txt --- a/doc/xml-templates.txt +++ b/doc/xml-templates.txt @@ -4,9 +4,9 @@ Genshi XML Template Language ============================ -Genshi provides a simple XML-based template language that is heavily inspired -by Kid_, which in turn was inspired by a number of existing template languages, -namely XSLT_, TAL_, and PHP_. +Genshi provides a XML-based template language that is heavily inspired by Kid_, +which in turn was inspired by a number of existing template languages, namely +XSLT_, TAL_, and PHP_. .. _kid: http://kid-templating.org/ .. _python: http://www.python.org/ @@ -15,8 +15,8 @@ .. _php: http://www.php.net/ This document describes the template language and will be most useful as -reference to those developing Genshi templates. Templates are XML files of some -kind (such as XHTML) that include processing directives_ (elements or +reference to those developing Genshi XML templates. Templates are XML files of +some kind (such as XHTML) that include processing directives_ (elements or attributes identified by a separate namespace) that affect how the template is rendered, and template expressions_ that are dynamically substituted by variable data. @@ -33,8 +33,8 @@ The Python code required for templating with Genshi is generally based on the following pattern: -* Attain a ``Template`` object from a string or file object containing the - template XML source. This can either be done directly, or through a +* Attain a ``MarkupTemplate`` object from a string or file object containing + the template XML source. This can either be done directly, or through a ``TemplateLoader`` instance. * Call the ``generate()`` method of the template, passing any data that should be made available to the template as keyword arguments. @@ -42,9 +42,9 @@ For example:: - from genshi.template import Template + from genshi.template import MarkupTemplate - tmpl = Template('

$title

') + tmpl = MarkupTemplate('

$title

') stream = tmpl.generate(title='Hello, world!') print stream.render('xhtml') @@ -78,9 +78,9 @@ the curly braces may be omitted. In all other cases, the braces are required so that the template processors knows where the expression ends:: - >>> from genshi.template import Context, Template - >>> tmpl = Template('${items[0].capitalize()} item') - >>> print tmpl.generate(Context(items=['first', 'second'])) + >>> from genshi.template import MarkupTemplate + >>> tmpl = MarkupTemplate('${items[0].capitalize()} item') + >>> print tmpl.generate(items=['first', 'second']) First item Expressions support the full power of Python. In addition, it is possible to @@ -88,9 +88,9 @@ attributes), and vice-versa (i.e. access attributes as if they were items in a dictionary):: - >>> from genshi.template import Context, Template - >>> tmpl = Template('${dict.foo}') - >>> print tmpl.generate(Context(dict={'foo': 'bar'})) + >>> from genshi.template import MarkupTemplate + >>> tmpl = MarkupTemplate('${dict.foo}') + >>> print tmpl.generate(dict={'foo': 'bar'}) bar diff --git a/genshi/template.py b/genshi/template.py --- a/genshi/template.py +++ b/genshi/template.py @@ -742,27 +742,16 @@ class Template(object): - """Can parse a template and transform it into the corresponding output - based on context data. + """Abstract template base class. + + This class implements most of the template processing model, but does not + specify the syntax of templates. """ __metaclass__ = TemplateMeta EXPR = StreamEventKind('EXPR') # an expression SUB = StreamEventKind('SUB') # a "subprogram" - 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.""" if isinstance(source, basestring): @@ -1006,8 +995,15 @@ class MarkupTemplate(Template): - """Can parse a template and transform it into the corresponding output - based on context data. + """Implementation of the template language for XML-based templates. + + >>> tmpl = MarkupTemplate('''
    + ...
  • ${item}
  • + ...
''') + >>> print tmpl.generate(items=[1, 2, 3]) +
    +
  • 1
  • 2
  • 3
  • +
""" NAMESPACE = Namespace('http://genshi.edgewall.org/') @@ -1035,14 +1031,7 @@ 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). - """ + """Parse the template from an XML document.""" stream = [] # list of events of the "compiled" template dirmap = {} # temporary mapping of directives to elements ns_prefix = {} @@ -1211,7 +1200,7 @@ ... We have the following items for you: ... #for item in items ... * $item - ... #endfor + ... #end ... ... All the best, ... Foobar''') @@ -1238,6 +1227,7 @@ _directive_re = re.compile('^\s*#(\w+.*)\n?', re.MULTILINE) def _parse(self): + """Parse the template from text input.""" stream = [] # list of events of the "compiled" template dirmap = {} # temporary mapping of directives to elements depth = 0 @@ -1254,7 +1244,7 @@ stream.append((kind, data, pos)) lineno += len(text.splitlines()) - text = source[start:end].lstrip().lstrip('#') + text = source[start:end].lstrip()[1:] lineno += len(text.splitlines()) directive = text.split(None, 1) if len(directive) > 1: @@ -1262,7 +1252,7 @@ else: command, value = directive[0], None - if not command.startswith('end'): + if command != 'end': cls = self._dir_by_name.get(command) if cls is None: raise BadDirectiveError(command) @@ -1271,7 +1261,6 @@ depth += 1 else: depth -= 1 - command = command[3:] if depth in dirmap: directive, start_offset = dirmap.pop(depth) substream = stream[start_offset:] diff --git a/genshi/tests/template.py b/genshi/tests/template.py --- a/genshi/tests/template.py +++ b/genshi/tests/template.py @@ -233,14 +233,14 @@ tmpl = TextTemplate("""#choose #when 1 == 1 1 - #endwhen + #end #when 2 == 2 2 - #endwhen + #end #when 3 == 3 3 - #endwhen - #endchoose""") + #end + #end""") self.assertEqual(""" 1\n""", str(tmpl.generate())) @@ -367,7 +367,7 @@ tmpl = TextTemplate(""" #def echo(greeting, name='world') ${greeting}, ${name}! - #enddef + #end ${echo('Hi', name='you')} """) self.assertEqual(""" Hi, you!