Ejemplo n.º 1
0
    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
Ejemplo n.º 2
0
    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
Ejemplo n.º 3
0
    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
Ejemplo n.º 4
0
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