Пример #1
0
    def _parse(self, source, encoding):
        if not isinstance(source, Stream):
            source = XMLParser(source,
                               filename=self.filename,
                               encoding=encoding)
        stream = []

        for kind, data, pos in source:

            if kind is TEXT:
                for kind, data, pos in interpolate(data,
                                                   self.filepath,
                                                   pos[1],
                                                   pos[2],
                                                   lookup=self.lookup):
                    stream.append((kind, data, pos))

            elif kind is PI and data[0] == 'python':
                if not self.allow_exec:
                    raise TemplateSyntaxError('Python code blocks not allowed',
                                              self.filepath, *pos[1:])
                try:
                    suite = Suite(data[1],
                                  self.filepath,
                                  pos[1],
                                  lookup=self.lookup)
                except SyntaxError, err:
                    raise TemplateSyntaxError(err, self.filepath,
                                              pos[1] + (err.lineno or 1) - 1,
                                              pos[2] + (err.offset or 0))
                stream.append((EXEC, suite, pos))

            elif kind is COMMENT:
                if not data.lstrip().startswith('!'):
                    stream.append((kind, data, pos))
    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, unicode):
            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))]
Пример #3
0
 def attach(cls, template, stream, value, namespaces, pos):
     if type(value) is dict:
         raise TemplateSyntaxError('The content directive can not be used '
                                   'as an element', template.filepath,
                                   *pos[1:])
     expr = cls._parse_expr(value, template, *pos[1:])
     return None, [stream[0], (EXPR, expr, pos),  stream[-1]]
Пример #4
0
 def attach(cls, template, stream, value, namespaces, pos):
     if type(value) is dict:
         raise TemplateSyntaxError(
             ("The %s directive may only be used as a "
              "tag attribute" % cls._name), template.filepath, *pos[1:])
     return super(AttributeOnly, cls).attach(template, stream, value,
                                             namespaces, pos)
Пример #5
0
 def attach(cls, template, stream, value, namespaces, pos):
     if type(value) is not dict:
         raise TemplateSyntaxError(
             "The %s directive must be an element" % cls._name,
             template.filepath, *pos[1:])
     return super(TagOnly, cls).attach(template, stream, value, namespaces,
                                       pos)
Пример #6
0
def lex(text, textpos, filepath):
    offset = pos = 0
    end = len(text)
    escaped = False

    while 1:
        if escaped:
            offset = text.find(PREFIX, offset + 2)
            escaped = False
        else:
            offset = text.find(PREFIX, pos)
        if offset < 0 or offset == end - 1:
            break
        next = text[offset + 1]

        if next == '{':
            if offset > pos:
                yield False, text[pos:offset]
            pos = offset + 2
            level = 1
            while level:
                match = token_re.match(text, pos)
                if match is None or not match.group():
                    # if there isn't a match or the match is the empty
                    # string, we're not going to match up braces ever
                    raise TemplateSyntaxError('invalid syntax', filepath,
                                              *textpos[1:])
                pos = match.end()
                tstart, tend = match.regs[3]
                token = text[tstart:tend]
                if token == '{':
                    level += 1
                elif token == '}':
                    level -= 1
            yield True, text[offset + 2:pos - 1]

        elif next in NAMESTART:
            if offset > pos:
                yield False, text[pos:offset]
                pos = offset
            pos += 1
            while pos < end:
                char = text[pos]
                if char not in NAMECHARS:
                    break
                pos += 1
            yield True, text[offset + 1:pos].strip()

        elif not escaped and next == PREFIX:
            if offset > pos:
                yield False, text[pos:offset]
            escaped = True
            pos = offset + 1

        else:
            yield False, text[pos:offset + 1]
            pos = offset + 1

    if pos < end:
        yield False, text[pos:]
Пример #7
0
 def attach(cls, template, stream, value, namespaces, pos):
     if type(value) is dict:
         value = value.get('value')
     if not value:
         raise TemplateSyntaxError('missing value for "replace" directive',
                                   template.filepath, *pos[1:])
     expr = cls._parse_expr(value, template, *pos[1:])
     return None, [(EXPR, expr, pos)]
Пример #8
0
def interpolate(text, filepath=None, lineno=-1, offset=0, lookup='strict'):
    """Parse the given string and extract expressions.
    
    This function is a generator that yields `TEXT` events for literal strings,
    and `EXPR` events for expressions, depending on the results of parsing the
    string.
    
    >>> for kind, data, pos in interpolate("hey ${foo}bar"):
    ...     print('%s %r' % (kind, data))
    TEXT 'hey '
    EXPR Expression('foo')
    TEXT 'bar'
    
    :param text: the text to parse
    :param filepath: absolute path to the file in which the text was found
                     (optional)
    :param lineno: the line number at which the text was found (optional)
    :param offset: the column number at which the text starts in the source
                   (optional)
    :param lookup: the variable lookup mechanism; either "lenient" (the
                   default), "strict", or a custom lookup class
    :return: a list of `TEXT` and `EXPR` events
    :raise TemplateSyntaxError: when a syntax error in an expression is
                                encountered
    """
    pos = [filepath, lineno, offset]

    textbuf = []
    textpos = None
    for is_expr, chunk in chain(lex(text, pos, filepath), [(True, '')]):
        if is_expr:
            if textbuf:
                yield TEXT, ''.join(textbuf), textpos
                del textbuf[:]
                textpos = None
            if chunk:
                try:
                    expr = Expression(chunk.strip(),
                                      pos[0],
                                      pos[1],
                                      lookup=lookup)
                    yield EXPR, expr, tuple(pos)
                except SyntaxError as err:
                    raise TemplateSyntaxError(err, filepath, pos[1],
                                              pos[2] + (err.offset or 0))
        else:
            textbuf.append(chunk)
            if textpos is None:
                textpos = tuple(pos)

        if '\n' in chunk:
            lines = chunk.splitlines()
            pos[1] += len(lines) - 1
            pos[2] += len(lines[-1])
        else:
            pos[2] += len(chunk)
Пример #9
0
 def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1):
     Directive.__init__(self, None, template, namespaces, lineno, offset)
     self.vars = []
     value = value.strip()
     try:
         ast = _parse(value, 'exec')
         for node in ast.body:
             if not isinstance(node, _ast.Assign):
                 raise TemplateSyntaxError('only assignment allowed in '
                                           'value of the "with" directive',
                                           template.filepath, lineno, offset)
             self.vars.append(([_assignment(n) for n in node.targets],
                               Expression(node.value, template.filepath,
                                          lineno, lookup=template.lookup)))
     except SyntaxError as err:
         err.msg += ' in expression "%s" of "%s" directive' % (value,
                                                               self.tagname)
         raise TemplateSyntaxError(err, template.filepath, lineno,
                                   offset + (err.offset or 0))
Пример #10
0
 def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1):
     if ' in ' not in value:
         raise TemplateSyntaxError('"in" keyword missing in "for" directive',
                                   template.filepath, lineno, offset)
     assign, value = value.split(' in ', 1)
     ast = _parse(assign, 'exec')
     value = 'iter(%s)' % value.strip()
     self.assign = _assignment(ast.body[0].value)
     self.filename = template.filepath
     Directive.__init__(self, value, template, namespaces, lineno, offset)
Пример #11
0
 def _parse_expr(cls, expr, template, lineno=-1, offset=-1):
     """Parses the given expression, raising a useful error message when a
     syntax error is encountered.
     """
     try:
         return expr and Expression(expr, template.filepath, lineno,
                                    lookup=template.lookup) or None
     except SyntaxError as err:
         err.msg += ' in expression "%s" of "%s" directive' % (expr,
                                                               cls.tagname)
         raise TemplateSyntaxError(err, template.filepath, lineno,
                                   offset + (err.offset or 0))
Пример #12
0
    def _extract_includes(self, stream):
        streams = [[]]  # stacked lists of events of the "compiled" template
        prefixes = {}
        fallbacks = []
        includes = []
        xinclude_ns = Namespace(self.XINCLUDE_NAMESPACE)

        for kind, data, pos in stream:
            stream = streams[-1]

            if kind is START:
                # Record any directive attributes in start tags
                tag, attrs = data
                if tag in xinclude_ns:
                    if tag.localname == 'include':
                        include_href = attrs.get('href')
                        if not include_href:
                            raise TemplateSyntaxError(
                                'Include misses required '
                                'attribute "href"', self.filepath, *pos[1:])
                        includes.append((include_href, attrs.get('parse')))
                        streams.append([])
                    elif tag.localname == 'fallback':
                        streams.append([])
                        fallbacks.append(streams[-1])
                else:
                    stream.append((kind, (tag, attrs), pos))

            elif kind is END:
                if fallbacks and data == xinclude_ns['fallback']:
                    assert streams.pop() is fallbacks[-1]
                elif data == xinclude_ns['include']:
                    fallback = None
                    if len(fallbacks) == len(includes):
                        fallback = fallbacks.pop()
                    streams.pop()  # discard anything between the include tags
                    # and the fallback element
                    stream = streams[-1]
                    href, parse = includes.pop()
                    try:
                        cls = {
                            'xml': MarkupTemplate,
                            'text': NewTextTemplate
                        }.get(parse) or self.__class__
                    except KeyError:
                        raise TemplateSyntaxError(
                            'Invalid value for "parse" '
                            'attribute of include', self.filepath, *pos[1:])
                    stream.append((INCLUDE, (href, cls, fallback), pos))
                else:
                    stream.append((kind, data, pos))

            elif kind is START_NS and data[1] == xinclude_ns:
                # Strip out the XInclude namespace
                prefixes[data[0]] = data[1]

            elif kind is END_NS and data in prefixes:
                prefixes.pop(data)

            else:
                stream.append((kind, data, pos))

        assert len(streams) == 1
        return streams[0]