def __call__(self, stream, ctxt=None, search_text=True, msgbuf=None): """Translate any localizable strings in the given stream. This function shouldn't be called directly. Instead, an instance of the `Translator` class should be registered as a filter with the `Template` or the `TemplateLoader`, or applied as a regular stream filter. If used as a template filter, it should be inserted in front of all the default filters. :param stream: the markup event stream :param ctxt: the template context (not used) :param search_text: whether text nodes should be translated (used internally) :param msgbuf: a `MessageBuffer` object or `None` (used internally) :return: the localized stream """ ignore_tags = self.ignore_tags include_attrs = self.include_attrs translate = self.translate if not self.extract_text: search_text = False skip = 0 i18n_msg = I18N_NAMESPACE['msg'] ns_prefixes = [] xml_lang = XML_NAMESPACE['lang'] for kind, data, pos in stream: # skip chunks that should not be localized if skip: if kind is START: skip += 1 elif kind is END: skip -= 1 yield kind, data, pos continue # handle different events that can be localized if kind is START: tag, attrs = data if tag in self.ignore_tags or \ isinstance(attrs.get(xml_lang), basestring): skip += 1 yield kind, data, pos continue new_attrs = [] changed = False for name, value in attrs: newval = value if search_text and isinstance(value, basestring): if name in include_attrs: newval = self.translate(value) else: newval = list(self(_ensure(value), ctxt, search_text=False, msgbuf=msgbuf) ) if newval != value: value = newval changed = True new_attrs.append((name, value)) if changed: attrs = Attrs(new_attrs) if msgbuf: msgbuf.append(kind, data, pos) continue elif i18n_msg in attrs: params = attrs.get(i18n_msg) if params and type(params) is list: # event tuple params = params[0][1] msgbuf = MessageBuffer(params) attrs -= i18n_msg yield kind, (tag, attrs), pos elif search_text and kind is TEXT: if not msgbuf: text = data.strip() if text: data = data.replace(text, unicode(translate(text))) yield kind, data, pos else: msgbuf.append(kind, data, pos) elif msgbuf and kind is EXPR: msgbuf.append(kind, data, pos) elif not skip and msgbuf and kind is END: msgbuf.append(kind, data, pos) if not msgbuf.depth: for event in msgbuf.translate(translate(msgbuf.format())): yield event msgbuf = None yield kind, data, pos elif kind is SUB: subkind, substream = data new_substream = list(self(substream, ctxt, msgbuf=msgbuf)) yield kind, (subkind, new_substream), pos elif kind is START_NS and data[1] == I18N_NAMESPACE: ns_prefixes.append(data[0]) elif kind is END_NS and data in ns_prefixes: ns_prefixes.remove(data) else: yield kind, data, pos
def _parse(self, source, encoding): streams = [[]] # stacked lists of events of the "compiled" template dirmap = {} # temporary mapping of directives to elements ns_prefix = {} depth = 0 fallbacks = [] includes = [] if not isinstance(source, Stream): source = XMLParser(source, filename=self.filename, encoding=encoding) for kind, data, pos in source: stream = streams[-1] if kind is START_NS: # Strip out the namespace declaration for template directives prefix, uri = data ns_prefix[prefix] = uri if uri not in (self.DIRECTIVE_NAMESPACE, self.XINCLUDE_NAMESPACE): stream.append((kind, data, pos)) elif kind is END_NS: uri = ns_prefix.pop(data, None) if uri and uri not in (self.DIRECTIVE_NAMESPACE, self.XINCLUDE_NAMESPACE): stream.append((kind, data, pos)) elif kind is START: # Record any directive attributes in start tags tag, attrs = data directives = [] strip = False if tag in self.DIRECTIVE_NAMESPACE: cls = self._dir_by_name.get(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((cls, args, ns_prefix.copy(), pos)) strip = True new_attrs = [] for name, value in attrs: if name in self.DIRECTIVE_NAMESPACE: cls = self._dir_by_name.get(name.localname) if cls is None: raise BadDirectiveError(name.localname, self.filepath, pos[1]) directives.append((cls, value, ns_prefix.copy(), pos)) else: if value: value = list(interpolate(value, self.filepath, pos[1], pos[2], lookup=self.lookup)) if len(value) == 1 and value[0][0] is TEXT: value = value[0][1] else: value = [(TEXT, u"", pos)] new_attrs.append((name, value)) new_attrs = Attrs(new_attrs) if directives: index = self._dir_order.index directives.sort(lambda a, b: cmp(index(a[0]), index(b[0]))) dirmap[(depth, tag)] = (directives, len(stream), strip) if tag in self.XINCLUDE_NAMESPACE: if tag.localname == "include": include_href = new_attrs.get("href") if not include_href: raise TemplateSyntaxError( "Include misses required " 'attribute "href"', self.filepath, *pos[1:] ) includes.append((include_href, new_attrs.get("parse"))) streams.append([]) elif tag.localname == "fallback": streams.append([]) fallbacks.append(streams[-1]) else: stream.append((kind, (tag, new_attrs), pos)) depth += 1 elif kind is END: depth -= 1 if fallbacks and data == self.XINCLUDE_NAMESPACE["fallback"]: assert streams.pop() is fallbacks[-1] elif data == self.XINCLUDE_NAMESPACE["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}[parse or "xml"] 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)) # If there have have directive attributes with the corresponding # start tag, move the events inbetween into a "subprogram" if (depth, data) in dirmap: directives, start_offset, strip = dirmap.pop((depth, data)) substream = stream[start_offset:] if strip: substream = substream[1:-1] stream[start_offset:] = [(SUB, (directives, substream), 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 TEXT: for kind, data, pos in interpolate(data, self.filepath, pos[1], pos[2], lookup=self.lookup): stream.append((kind, data, pos))