def _parse(self, source, encoding): """Parse the template from text input.""" stream = [] # list of events of the "compiled" template dirmap = {} # temporary mapping of directives to elements depth = 0 source = source.read() if not isinstance(source, str): source = source.decode(encoding or 'utf-8', 'replace') 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 interpolate(text, self.filepath, lineno, lookup=self.lookup): stream.append((kind, data, pos)) lineno += len(text.splitlines()) text = source[start:end].lstrip()[1:] lineno += len(text.splitlines()) directive = text.split(None, 1) if len(directive) > 1: command, value = directive else: command, value = directive[0], None if command == 'end': depth -= 1 if depth in dirmap: directive, start_offset = dirmap.pop(depth) substream = stream[start_offset:] stream[start_offset:] = [(SUB, ([directive], substream), (self.filepath, lineno, 0))] elif command == 'include': pos = (self.filename, lineno, 0) stream.append((INCLUDE, (value.strip(), None, []), pos)) elif command != '#': cls = self.get_directive(command) if cls is None: raise BadDirectiveError(command) directive = 0, cls, value, None, (self.filepath, lineno, 0) dirmap[depth] = (directive, len(stream)) depth += 1 offset = end if offset < len(source): text = source[offset:].replace('\\#', '#') for kind, data, pos in interpolate(text, self.filepath, lineno, lookup=self.lookup): 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.namespace == namespace: cls = factory.get_directive(tag.localname) if cls is None: raise BadDirectiveError(tag.localname, self.filepath, pos[1]) args = dict([(name.localname, value) for name, value in attrs if not name.namespace]) directives.append( (factory.get_directive_index(cls), cls, args, ns_prefix.copy(), pos)) strip = True new_attrs = [] for name, value in attrs: if name.namespace == namespace: cls = factory.get_directive(name.localname) if cls is None: raise BadDirectiveError(name.localname, self.filepath, pos[1]) if type(value) is list and len(value) == 1: value = value[0][1] directives.append( (factory.get_directive_index(cls), cls, value, ns_prefix.copy(), pos)) else: new_attrs.append((name, value)) new_attrs = Attrs(new_attrs) if directives: directives.sort() dirmap[(depth, tag)] = (directives, len(new_stream), strip) 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 _parse(self, source, encoding): """Parse the template from text input.""" stream = [] # list of events of the "compiled" template dirmap = {} # temporary mapping of directives to elements depth = 0 source = source.read() if not isinstance(source, str): source = source.decode(encoding or 'utf-8', 'replace') offset = 0 lineno = 1 _escape_sub = self._escape_re.sub def _escape_repl(mo): groups = [g for g in mo.groups() if g] if not groups: return '' return groups[0] for idx, mo in enumerate(self._directive_re.finditer(source)): start, end = mo.span(1) if start > offset: text = _escape_sub(_escape_repl, source[offset:start]) for kind, data, pos in interpolate(text, self.filepath, lineno, lookup=self.lookup): stream.append((kind, data, pos)) lineno += len(text.splitlines()) lineno += len(source[start:end].splitlines()) command, value = mo.group(2, 3) if command == 'include': pos = (self.filename, lineno, 0) value = list( interpolate(value, self.filepath, lineno, 0, lookup=self.lookup)) if len(value) == 1 and value[0][0] is TEXT: value = value[0][1] stream.append((INCLUDE, (value, None, []), pos)) elif command == 'python': if not self.allow_exec: raise TemplateSyntaxError('Python code blocks not allowed', self.filepath, lineno) try: suite = Suite(value, self.filepath, lineno, lookup=self.lookup) except SyntaxError as err: raise TemplateSyntaxError(err, self.filepath, lineno + (err.lineno or 1) - 1) pos = (self.filename, lineno, 0) stream.append((EXEC, suite, pos)) elif command == 'end': depth -= 1 if depth in dirmap: directive, start_offset = dirmap.pop(depth) substream = stream[start_offset:] stream[start_offset:] = [(SUB, ([directive], substream), (self.filepath, lineno, 0))] elif command: cls = self.get_directive(command) if cls is None: raise BadDirectiveError(command) directive = 0, cls, value, None, (self.filepath, lineno, 0) dirmap[depth] = (directive, len(stream)) depth += 1 offset = end if offset < len(source): text = _escape_sub(_escape_repl, source[offset:]) for kind, data, pos in interpolate(text, self.filepath, lineno, lookup=self.lookup): stream.append((kind, data, pos)) return stream
class NewTextTemplate(Template): r"""Implementation of a simple text-based template engine. This class will replace `OldTextTemplate` in a future release. It uses a more explicit delimiting style for directives: instead of the old style which required putting directives on separate lines that were prefixed with a ``#`` sign, directives and commenbtsr are enclosed in delimiter pairs (by default ``{% ... %}`` and ``{# ... #}``, respectively). Variable substitution uses the same interpolation syntax as for markup languages: simple references are prefixed with a dollar sign, more complex expression enclosed in curly braces. >>> tmpl = NewTextTemplate('''Dear $name, ... ... {# This is a comment #} ... We have the following items for you: ... {% for item in items %} ... * ${'Item %d' % item} ... {% end %} ... ''') >>> print(tmpl.generate(name='Joe', items=[1, 2, 3]).render(encoding=None)) Dear Joe, <BLANKLINE> <BLANKLINE> We have the following items for you: <BLANKLINE> * Item 1 <BLANKLINE> * Item 2 <BLANKLINE> * Item 3 <BLANKLINE> <BLANKLINE> By default, no spaces or line breaks are removed. If a line break should not be included in the output, prefix it with a backslash: >>> tmpl = NewTextTemplate('''Dear $name, ... ... {# This is a comment #}\ ... We have the following items for you: ... {% for item in items %}\ ... * $item ... {% end %}\ ... ''') >>> print(tmpl.generate(name='Joe', items=[1, 2, 3]).render(encoding=None)) Dear Joe, <BLANKLINE> We have the following items for you: * 1 * 2 * 3 <BLANKLINE> Backslashes are also used to escape the start delimiter of directives and comments: >>> tmpl = NewTextTemplate('''Dear $name, ... ... \{# This is a comment #} ... We have the following items for you: ... {% for item in items %}\ ... * $item ... {% end %}\ ... ''') >>> print(tmpl.generate(name='Joe', items=[1, 2, 3]).render(encoding=None)) Dear Joe, <BLANKLINE> {# This is a comment #} We have the following items for you: * 1 * 2 * 3 <BLANKLINE> :since: version 0.5 """ directives = [('def', DefDirective), ('when', WhenDirective), ('otherwise', OtherwiseDirective), ('for', ForDirective), ('if', IfDirective), ('choose', ChooseDirective), ('with', WithDirective)] serializer = 'text' _DIRECTIVE_RE = r'((?<!\\)%s\s*(\w+)\s*(.*?)\s*%s|(?<!\\)%s.*?%s)' _ESCAPE_RE = r'\\\n|\\(\\)|\\(%s)|\\(%s)' def __init__(self, source, filepath=None, filename=None, loader=None, encoding=None, lookup='strict', allow_exec=False, delims=('{%', '%}', '{#', '#}')): self.delimiters = delims Template.__init__(self, source, filepath=filepath, filename=filename, loader=loader, encoding=encoding, lookup=lookup) def _get_delims(self): return self._delims def _set_delims(self, delims): if len(delims) != 4: raise ValueError('delimiers tuple must have exactly four elements') self._delims = delims self._directive_re = re.compile( self._DIRECTIVE_RE % tuple([re.escape(d) for d in delims]), re.DOTALL) self._escape_re = re.compile( self._ESCAPE_RE % tuple([re.escape(d) for d in delims[::2]])) delimiters = property( _get_delims, _set_delims, """\ The delimiters for directives and comments. This should be a four item tuple of the form ``(directive_start, directive_end, comment_start, comment_end)``, where each item is a string. """) def _parse(self, source, encoding): """Parse the template from text input.""" stream = [] # list of events of the "compiled" template dirmap = {} # temporary mapping of directives to elements depth = 0 source = source.read() if isinstance(source, str): source = source.decode(encoding or 'utf-8', 'replace') offset = 0 lineno = 1 _escape_sub = self._escape_re.sub def _escape_repl(mo): groups = [g for g in mo.groups() if g] if not groups: return '' return groups[0] for idx, mo in enumerate(self._directive_re.finditer(source)): start, end = mo.span(1) if start > offset: text = _escape_sub(_escape_repl, source[offset:start]) for kind, data, pos in interpolate(text, self.filepath, lineno, lookup=self.lookup): stream.append((kind, data, pos)) lineno += len(text.splitlines()) lineno += len(source[start:end].splitlines()) command, value = mo.group(2, 3) if command == 'include': pos = (self.filename, lineno, 0) value = list( interpolate(value, self.filepath, lineno, 0, lookup=self.lookup)) if len(value) == 1 and value[0][0] is TEXT: value = value[0][1] stream.append((INCLUDE, (value, None, []), pos)) elif command == 'python': if not self.allow_exec: raise TemplateSyntaxError('Python code blocks not allowed', self.filepath, lineno) try: suite = Suite(value, self.filepath, lineno, lookup=self.lookup) except SyntaxError, err: raise TemplateSyntaxError(err, self.filepath, lineno + (err.lineno or 1) - 1) pos = (self.filename, lineno, 0) stream.append((EXEC, suite, pos)) elif command == 'end': depth -= 1 if depth in dirmap: directive, start_offset = dirmap.pop(depth) substream = stream[start_offset:] stream[start_offset:] = [(SUB, ([directive], substream), (self.filepath, lineno, 0))] elif command: cls = self.get_directive(command) if cls is None: raise BadDirectiveError(command) directive = 0, cls, value, None, (self.filepath, lineno, 0) dirmap[depth] = (directive, len(stream)) depth += 1