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))]
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]]
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)
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)
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:]
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)]
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)
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))
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)
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))
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]