def visit_element(self, start, end, children): if self.translatestack and self.translatestack[-1]: self.translatestack[-1].add_element(start) attributes = start['ns_attrs'] plain_attrs = dict( (a['name'].split(':')[-1], a['value']) for a in start['attrs']) new_domain = attributes.get((I18N_NS, 'domain')) if new_domain: self.domainstack.append(new_domain) elif self.domainstack: self.domainstack.append(self.domainstack[-1]) i18n_translate = attributes.get((I18N_NS, 'translate')) if i18n_translate is not None: self.translatestack.append( TranslateContext( self.domainstack[-1] if self.domainstack else None, i18n_translate, self.filename, self.linenumber)) else: self.translatestack.append(None) if self.domainstack: i18n_attributes = attributes.get((I18N_NS, 'attributes')) if i18n_attributes: parts = [p.strip() for p in i18n_attributes.split(';')] for msgid in parts: if ' ' not in msgid: if msgid not in plain_attrs: continue self.add_message(plain_attrs[msgid]) else: try: (attr, msgid) = msgid.split() except ValueError: continue if attr not in plain_attrs: continue self.add_message(msgid, u'Default: %s' % plain_attrs[attr]) for (attribute, value) in attributes.items(): value = decode_htmlentities(value) for source in self.get_code_for_attribute(attribute, value): self.parse_python(source) for child in children: self.visit(*child) if self.domainstack: self.domainstack.pop() translate = self.translatestack.pop() if translate and not translate.ignore() and translate.domain and \ (self.target_domain is None or translate.domain == self.target_domain): self.messages.append(translate.message())
def visit_element(self, start, end, children): if self.translatestack and self.translatestack[-1]: self.translatestack[-1].add_element(start) attributes = start['ns_attrs'] plain_attrs = dict((a['name'].split(':')[-1], a['value']) for a in start['attrs']) new_domain = attributes.get((I18N_NS, 'domain')) if new_domain: self.domainstack.append(new_domain) elif self.domainstack: self.domainstack.append(self.domainstack[-1]) i18n_translate = attributes.get((I18N_NS, 'translate')) if i18n_translate is not None: self.translatestack.append(TranslateContext( self.domainstack[-1] if self.domainstack else None, i18n_translate, self.filename, self.linenumber)) else: self.translatestack.append(None) if self.domainstack: i18n_attributes = attributes.get((I18N_NS, 'attributes')) if i18n_attributes: parts = [p.strip() for p in i18n_attributes.split(';')] for msgid in parts: if ' ' not in msgid: if msgid not in plain_attrs: continue self.add_message(plain_attrs[msgid]) else: try: (attr, msgid) = msgid.split() except ValueError: continue if attr not in plain_attrs: continue self.add_message(msgid, u'Default: %s' % plain_attrs[attr]) for (attribute, value) in attributes.items(): value = decode_htmlentities(value) for source in self.get_code_for_attribute(attribute, value): self.parse_python(source) for child in children: self.visit(*child) if self.domainstack: self.domainstack.pop() translate = self.translatestack.pop() if translate and not translate.ignore() and translate.domain and \ (self.target_domain is None or translate.domain == self.target_domain): self.messages.append(translate.message())
def visit_text(self, data): if self.target_domain is None or self.target_domain == self.domainstack[-1][0]: default_engine = self.config["default-engine"] for line in data.splitlines(): line = decode_htmlentities(line) try: for source in get_python_expressions(line, default_engine): if UNDERSCORE_CALL.search(source): self.parse_python(source) except SyntaxError: print("Aborting due to Python syntax error in %s[%d]: %s" % (self.filename, self.linenumber, line)) sys.exit(1) if self.translatestack[-1]: self.translatestack[-1].add_text(data) self.linenumber += get_newline_count(data)
def visit_text(self, data): if self.target_domain is None or self.target_domain == self.domainstack[ -1][0]: default_engine = self.config['default-engine'] for line in data.splitlines(): line = decode_htmlentities(line) try: for source in get_python_expressions(line, default_engine): if UNDERSCORE_CALL.search(source): self.parse_python(source) except SyntaxError: print('Aborting due to Python syntax error in %s[%d]: %s' % (self.filename, self.linenumber, line)) sys.exit(1) if self.translatestack[-1]: self.translatestack[-1].add_text(data) self.linenumber += get_newline_count(data)
def visit_element(self, start, end, children): self.linenumber += get_newline_count(start["prefix"] + start["name"]) if self.translatestack and self.translatestack[-1]: self.translatestack[-1].add_element(start) attributes = start["ns_attrs"] plain_attrs = get_plain_attrs(start["attrs"]) childs_lineno = self.linenumber post_offset = [x[2] for x in plain_attrs.values()] if post_offset: childs_lineno += max(post_offset) childs_lineno += get_newline_count(start["suffix"]) new_domain = attributes.get((I18N_NS, "domain")) old_domain = self.domainstack[-1][0] if self.domainstack else None new_context = attributes.get((I18N_NS, "context")) old_context = self.domainstack[-1][1] if self.domainstack else None new_comment = attributes.get((I18N_NS, "comment")) old_comment = self.domainstack[-1][2] if self.domainstack else None if new_domain or new_context or new_comment: self.domainstack.append((new_domain or old_domain, new_context or old_context, new_comment or old_comment)) elif self.domainstack: self.domainstack.append(self.domainstack[-1]) current_domain = self.domainstack[-1][0] include_domain = self.target_domain is None or self.target_domain == current_domain i18n_translate = attributes.get((I18N_NS, "translate")) if i18n_translate is not None: ctx = TranslateContext( self.domainstack[-1][0] if self.domainstack else None, self.domainstack[-1][1] if self.domainstack else None, i18n_translate, self.domainstack[-1][2] if self.domainstack else None, self.filename, childs_lineno, ) if self.translatestack: ctx.parent = self.translatestack[-1] if ctx.parent is not None: ctx.parent.register_child(start, ctx) self.translatestack.append(ctx) else: self.translatestack.append(None) if self.domainstack: i18n_attributes = attributes.get((I18N_NS, "attributes")) if i18n_attributes and include_domain: parts = [p.strip() for p in i18n_attributes.split(";")] for msgid in parts: if " " not in msgid: if msgid not in plain_attrs: continue value, offset, post_offset = plain_attrs[msgid] self.add_message(value, self.domainstack[-1][2] or "", offset=offset) else: try: (attr, msgid) = msgid.split() except ValueError: continue if attr not in plain_attrs: continue value, offset, post_offset = plain_attrs[attr] self.add_message(msgid, u"Default: %s" % value, offset=offset) for (attribute, value) in attributes.items(): value = decode_htmlentities(value) for source in self.get_code_for_attribute(attribute, value): self.parse_python(source) self.linenumber = childs_lineno for child in children: self.visit(*child) if end is not None: self.linenumber += get_newline_count(end["prefix"] + end["name"]) post_offset = [x[2] for x in get_plain_attrs(end["attrs"]).values()] if post_offset: self.linenumber += max(post_offset) self.linenumber += get_newline_count(end["suffix"]) if self.domainstack: self.domainstack.pop() translate = self.translatestack.pop() if translate and not translate.ignore() and translate.domain and include_domain: self.messages.append(translate)
def prev_visit_element(self, start, end, children): ns = start['ns_attrs'] for (prefix, attr), encoded in tuple(ns.items()): if prefix == TAL: ns[prefix, attr] = decode_htmlentities(encoded) # Validate namespace attributes validate_attributes(ns, TAL, tal.WHITELIST) validate_attributes(ns, I18N, i18n.WHITELIST) # Check attributes for language errors self._check_attributes(start['namespace'], ns) # Remember whitespace for item repetition if self._last is not None: self._whitespace = "\n" + " " * len(self._last.rsplit('\n', 1)[-1]) # Set element-local whitespace whitespace = self._whitespace # Set up switch try: clause = ns[TAL, 'switch'] except KeyError: switch = None else: switch = nodes.Value(clause) self._switches.append(switch) body = [] content = nodes.Sequence(body) # tal:content try: clause = ns[TAL, 'content'] except KeyError: pass else: key, value = tal.parse_substitution(clause) xlate = True if ns.get((I18N, 'translate')) == '' else False content = self._make_content_node(value, content, key, xlate) if end is None: # Make sure start-tag has opening suffix. start['suffix'] = ">" # Explicitly set end-tag. end = { 'prefix': '</', 'name': start['name'], 'space': '', 'suffix': '>' } # i18n:translate try: clause = ns[I18N, 'translate'] except KeyError: pass else: dynamic = ns.get((TAL, 'content')) or ns.get((TAL, 'replace')) if not dynamic: content = nodes.Translate(clause, content) # tal:attributes try: clause = ns[TAL, 'attributes'] except KeyError: TAL_ATTRIBUTES = {} else: TAL_ATTRIBUTES = tal.parse_attributes(clause) # i18n:attributes try: clause = ns[I18N, 'attributes'] except KeyError: I18N_ATTRIBUTES = {} else: I18N_ATTRIBUTES = i18n.parse_attributes(clause) # Prepare attributes from TAL language prepared = tal.prepare_attributes( start['attrs'], TAL_ATTRIBUTES, I18N_ATTRIBUTES, ns, self.DROP_NS ) # Create attribute nodes STATIC_ATTRIBUTES = self._create_static_attributes(prepared) ATTRIBUTES = self._create_attributes_nodes( prepared, I18N_ATTRIBUTES ) # Start- and end nodes start_tag = nodes.Start( start['name'], self._maybe_trim(start['prefix']), self._maybe_trim(start['suffix']), ATTRIBUTES ) stag = start_tag end_tag = nodes.End( end['name'], end['space'], self._maybe_trim(end['prefix']), self._maybe_trim(end['suffix']), ) if end is not None else None # tal:omit-tag try: clause = ns[TAL, 'omit-tag'] except KeyError: omit = False else: clause = clause.strip() if clause == "": omit = True else: expression = nodes.Negate(nodes.Value(clause)) omit = expression # Wrap start- and end-tags in condition start_tag = nodes.Condition(expression, start_tag) if end_tag is not None: end_tag = nodes.Condition(expression, end_tag) if omit is True or start['namespace'] in self.DROP_NS: inner = content else: inner = nodes.Element( start_tag, end_tag, content, ) # Assign static attributes dictionary to "attrs" value inner = nodes.Define( [nodes.Alias(["attrs"], STATIC_ATTRIBUTES)], inner, ) if omit is not False: inner = nodes.Cache([omit], inner) # tal:replace try: clause = ns[TAL, 'replace'] except KeyError: pass else: key, value = tal.parse_substitution(clause) xlate = True if ns.get((I18N, 'translate')) == '' else False inner = self._make_content_node(value, inner, key, xlate) # tal:define try: clause = ns[TAL, 'define'] except KeyError: DEFINE = skip else: defines = tal.parse_defines(clause) if defines is None: raise ParseError("Invalid define syntax.", clause) DEFINE = partial( nodes.Define, [nodes.Assignment( names, nodes.Value(expr), context == "local") for (context, names, expr) in defines], ) # tal:case try: clause = ns[TAL, 'case'] except KeyError: CASE = skip else: value = nodes.Value(clause) for switch in reversed(self._switches): if switch is not None: break else: raise LanguageError( "Must define switch on a parent element.", clause ) CASE = lambda node: nodes.Define( [nodes.Assignment(["default"], switch, True)], nodes.Condition( nodes.Equality(switch, value), node, ) ) # tal:repeat try: clause = ns[TAL, 'repeat'] except KeyError: REPEAT = skip else: defines = tal.parse_defines(clause) assert len(defines) == 1 context, names, expr = defines[0] expression = nodes.Value(expr) REPEAT = partial( nodes.Repeat, names, expression, context == "local", whitespace ) # tal:condition try: clause = ns[TAL, 'condition'] except KeyError: CONDITION = skip else: expression = nodes.Value(clause) CONDITION = partial(nodes.Condition, expression) # tal:switch if switch is None: SWITCH = skip else: SWITCH = partial(nodes.Cache, [switch]) # i18n:domain try: clause = ns[I18N, 'domain'] except KeyError: DOMAIN = skip else: DOMAIN = partial(nodes.Domain, clause) # i18n:name try: clause = ns[I18N, 'name'] except KeyError: NAME = skip else: NAME = partial(nodes.Name, clause) # The "slot" node next is the first node level that can serve # as a macro slot slot = wrap( inner, DEFINE, CASE, CONDITION, REPEAT, SWITCH, DOMAIN, ) slot = wrap( slot, NAME ) # tal:on-error try: clause = ns[TAL, 'on-error'] except KeyError: ON_ERROR = skip else: key, value = tal.parse_substitution(clause) translate = True if ns.get((I18N, 'translate')) == '' else False fallback = self._make_content_node(value, None, key, translate) if omit is False and start['namespace'] not in self.DROP_NS: fallback = nodes.Element( start_tag, end_tag, fallback, ) ON_ERROR = partial(nodes.OnError, fallback, 'error') clause = ns.get((META, 'interpolation')) if clause in ('false', 'off'): INTERPOLATION = False elif clause in ('true', 'on'): INTERPOLATION = True elif clause is None: INTERPOLATION = self._interpolation[-1] else: raise LanguageError("Bad interpolation setting.", clause) self._interpolation.append(INTERPOLATION) # Visit content body for child in children: body.append(self.visit(*child)) self._switches.pop() self._interpolation.pop() slot = wrap( slot, ON_ERROR ) stag.replayable = False if self.binds: try: bind_replay_clause = ns[(PRAMTAL, "bind-replay")] splits = bind_replay_clause.split(" ") if len(splits) != 2: raise LanguageError("Invalid bind replay.", bind_replay_clause) events = splits[1].split(";") slot = BindReplay(splits[0], events, slot) stag.replayable = True except KeyError: pass if self.binds: try: bind_clause = ns[(PRAMTAL, "bind-change")] bind_splits = bind_clause.strip().split(" ") if len(bind_splits) > 3: raise LanguageError("Invalid bind syntax.", bind_clause) if not "#" in bind_splits[0]: raise LanguageError("No bind model target specified.", bind_clause) first_split = bind_splits[0][1:] if "." in first_split: bind_model,bind_ons = first_split.split(".", 1) bind_ons = bind_ons.strip().split(".") else: bind_model = first_split bind_ons = [] bind_attrs = [] if len(bind_splits) > 1: bind_attrs = bind_splits[1].split(";") stag.replayable = True slot = BindChange(bind_model, bind_ons, bind_attrs, slot ) except KeyError: pass stag.repeatable = False if self.binds: try: bind_repeat_clause = ns[(PRAMTAL, "bind-repeat")] except KeyError: pass else: splits = bind_repeat_clause.split(" ") if len(splits) != 2: raise LanguageError("Invalid define model.", bind_repeat_clause) if not splits[0].startswith("#"): raise LanguageError("need # in alias.", bind_repeat_clause) slot = BindRepeat(splits[0][1:], splits[1], slot) stag.repeatable = True try: define_model_clause = ns[(PRAMTAL, "define-model")] except KeyError: pass else: pairs = define_model_clause.split(";") models = [] for pair in pairs: pair = pair.strip() splits = pair.split(" ") if len(splits) != 2: raise LanguageError("Invalid define model.", pair) if not splits[0].startswith("#"): raise LanguageError("need # in alias.", pair) models.append((splits[0][1:], splits[1])) slot = DefineModel(models, slot) return slot
def visit_element(self, start, end, children): self.linenumber += get_newline_count(start['prefix'] + start['name']) if self.translatestack and self.translatestack[-1]: self.translatestack[-1].add_element(start) attributes = start['ns_attrs'] plain_attrs = get_plain_attrs(start['attrs']) childs_lineno = self.linenumber post_offset = [x[2] for x in plain_attrs.values()] if post_offset: childs_lineno += max(post_offset) childs_lineno += get_newline_count(start['suffix']) new_domain = attributes.get((I18N_NS, 'domain')) old_domain = self.domainstack[-1][0] if self.domainstack else None new_context = attributes.get((I18N_NS, 'context')) old_context = self.domainstack[-1][1] if self.domainstack else None new_comment = attributes.get((I18N_NS, 'comment')) old_comment = self.domainstack[-1][2] if self.domainstack else None if new_domain or new_context or new_comment: self.domainstack.append( (new_domain or old_domain, new_context or old_context, new_comment or old_comment)) elif self.domainstack: self.domainstack.append(self.domainstack[-1]) current_domain = self.domainstack[-1][0] include_domain = self.target_domain is None or self.target_domain == current_domain i18n_translate = attributes.get((I18N_NS, 'translate')) if i18n_translate is not None: ctx = TranslateContext( self.domainstack[-1][0] if self.domainstack else None, self.domainstack[-1][1] if self.domainstack else None, i18n_translate, self.domainstack[-1][2] if self.domainstack else None, self.filename, childs_lineno) if self.translatestack: ctx.parent = self.translatestack[-1] if ctx.parent is not None: ctx.parent.register_child(start, ctx) self.translatestack.append(ctx) else: self.translatestack.append(None) if self.domainstack: i18n_attributes = attributes.get((I18N_NS, 'attributes')) if i18n_attributes and include_domain: parts = [p.strip() for p in i18n_attributes.split(';')] for msgid in parts: if ' ' not in msgid: if msgid not in plain_attrs: continue value, offset, post_offset = plain_attrs[msgid] self.add_message(value, self.domainstack[-1][2] or '', offset=offset) else: try: (attr, msgid) = msgid.split() except ValueError: continue if attr not in plain_attrs: continue value, offset, post_offset = plain_attrs[attr] self.add_message(msgid, u'Default: %s' % value, offset=offset) for (attribute, value) in attributes.items(): value = decode_htmlentities(value) for source in self.get_code_for_attribute(attribute, value): self.parse_python(source) self.linenumber = childs_lineno for child in children: self.visit(*child) if end is not None: self.linenumber += get_newline_count(end['prefix'] + end['name']) post_offset = [ x[2] for x in get_plain_attrs(end['attrs']).values() ] if post_offset: self.linenumber += max(post_offset) self.linenumber += get_newline_count(end['suffix']) if self.domainstack: self.domainstack.pop() translate = self.translatestack.pop() if translate and not translate.ignore( ) and translate.domain and include_domain: self.messages.append(translate)
def visit_element(self, start, end, children): ns = start['ns_attrs'] ADDITIONAL_ATTRS = {} for (prefix, attr), encoded in tuple(ns.items()): if prefix == TAL: ns[prefix, attr] = decode_htmlentities(encoded) # Validate namespace attributes validate_attributes(ns, TAL, tal.WHITELIST) validate_attributes(ns, METAL, metal.WHITELIST) validate_attributes(ns, I18N, i18n.WHITELIST) # Check attributes for language errors self._check_attributes(start['namespace'], ns) # Remember whitespace for item repetition if self._last is not None: self._whitespace = "\n" + " " * len(self._last.rsplit('\n', 1)[-1]) # Set element-local whitespace whitespace = self._whitespace # Set up switch try: clause = ns[TAL, 'switch'] except KeyError: switch = None else: value = nodes.Value(clause) switch = value, nodes.Copy(value) self._switches.append(switch) body = [] # Include macro use_macro = ns.get((METAL, 'use-macro')) extend_macro = ns.get((METAL, 'extend-macro')) if use_macro or extend_macro: omit = True slots = [] self._use_macro.append(slots) if use_macro: inner = nodes.UseExternalMacro( nodes.Value(use_macro), slots, False ) else: inner = nodes.UseExternalMacro( nodes.Value(extend_macro), slots, True ) # -or- include tag else: content = nodes.Sequence(body) # tal:content try: clause = ns[TAL, 'content'] except KeyError: pass else: clause = self.get_semantic(clause, ns, 'content') key, value = tal.parse_substitution(clause) xlate = True if ns.get((I18N, 'translate')) == '' else False content = self._make_content_node(value, content, key, xlate) if end is None: # Make sure start-tag has opening suffix. start['suffix'] = ">" # Explicitly set end-tag. end = { 'prefix': '</', 'name': start['name'], 'space': '', 'suffix': '>' } # i18n:translate try: clause = ns[I18N, 'translate'] except KeyError: pass else: dynamic = ns.get((TAL, 'content')) or ns.get((TAL, 'replace')) if not dynamic: content = nodes.Translate(clause, content) # tal:attributes try: clause = ns[TAL, 'attributes'] except KeyError: TAL_ATTRIBUTES = [] else: TAL_ATTRIBUTES = tal.parse_attributes(clause) # i18n:attributes try: clause = ns[I18N, 'attributes'] except KeyError: I18N_ATTRIBUTES = {} else: I18N_ATTRIBUTES = i18n.parse_attributes(clause) # Prepare attributes from TAL language prepared = tal.prepare_attributes( start['attrs'], TAL_ATTRIBUTES, I18N_ATTRIBUTES, ns, self.DROP_NS ) # Create attribute nodes STATIC_ATTRIBUTES = self._create_static_attributes(prepared) ATTRIBUTES = self._create_attributes_nodes( prepared, I18N_ATTRIBUTES, STATIC_ATTRIBUTES ) # Start- and end nodes start_tag = nodes.Start( start['name'], self._maybe_trim(start['prefix']), self._maybe_trim(start['suffix']), ATTRIBUTES ) end_tag = nodes.End( end['name'], end['space'], self._maybe_trim(end['prefix']), self._maybe_trim(end['suffix']), ) if end is not None else None # tal:omit-tag try: clause = ns[TAL, 'omit-tag'] except KeyError: omit = False else: clause = clause.strip() if clause == "": omit = True else: expression = nodes.Negate(nodes.Value(clause)) omit = expression # Wrap start- and end-tags in condition start_tag = nodes.Condition(expression, start_tag) if end_tag is not None: end_tag = nodes.Condition(expression, end_tag) if omit is True or start['namespace'] in self.DROP_NS: inner = content else: inner = nodes.Element( start_tag, end_tag, content, ) # Assign static attributes dictionary to "attrs" value inner = nodes.Define( [nodes.Alias(["attrs"], STATIC_ATTRIBUTES or EMPTY_DICT)], inner, ) if omit is not False: inner = nodes.Cache([omit], inner) # tal:replace try: clause = ns[TAL, 'replace'] except KeyError: pass else: clause = self.get_semantic(clause, ns, 'replace') key, value = tal.parse_substitution(clause) xlate = True if ns.get((I18N, 'translate')) == '' else False inner = self._make_content_node(value, inner, key, xlate) # metal:define-slot try: clause = ns[METAL, 'define-slot'] except KeyError: DEFINE_SLOT = skip else: DEFINE_SLOT = partial(nodes.DefineSlot, clause) # tal:define try: clause = ns[TAL, 'define'] except KeyError: DEFINE = skip else: defines = tal.parse_defines(clause) if defines is None: raise ParseError("Invalid define syntax.", clause) DEFINE = partial( nodes.Define, [nodes.Assignment( names, nodes.Value(expr), context == "local") for (context, names, expr) in defines], ) # tal:case try: clause = ns[TAL, 'case'] except KeyError: CASE = skip else: value = nodes.Value(clause) for switch in reversed(self._switches): if switch is not None: break else: raise LanguageError( "Must define switch on a parent element.", clause ) CASE = lambda node: nodes.Define( [nodes.Alias(["default"], switch[1], False)], nodes.Condition( nodes.Equality(switch[0], value), nodes.Cancel([switch[0]], node), )) # tal:repeat try: clause = ns[TAL, 'repeat'] except KeyError: REPEAT = skip else: defines = tal.parse_defines(clause) assert len(defines) == 1 context, names, expr = defines[0] expression = nodes.Value(expr) if start['namespace'] == TAL: self._last = None self._whitespace = whitespace.lstrip('\n') whitespace = "" REPEAT = partial( nodes.Repeat, names, expression, context == "local", whitespace ) # tal:condition try: clause = ns[TAL, 'condition'] except KeyError: CONDITION = skip else: expression = nodes.Value(clause) CONDITION = partial(nodes.Condition, expression) # tal:switch if switch is None: SWITCH = skip else: SWITCH = partial(nodes.Cache, list(switch)) # i18n:domain try: clause = ns[I18N, 'domain'] except KeyError: DOMAIN = skip else: DOMAIN = partial(nodes.Domain, clause) # i18n:name try: clause = ns[I18N, 'name'] except KeyError: NAME = skip else: if not clause.strip(): NAME = skip else: NAME = partial(nodes.Name, clause) # The "slot" node next is the first node level that can serve # as a macro slot slot = wrap( inner, DEFINE_SLOT, DEFINE, CASE, CONDITION, REPEAT, SWITCH, DOMAIN, ) # metal:fill-slot try: clause = ns[METAL, 'fill-slot'] except KeyError: pass else: if not clause.strip(): raise LanguageError( "Must provide a non-trivial string for metal:fill-slot.", clause ) index = -(1 + int(bool(use_macro or extend_macro))) try: slots = self._use_macro[index] except IndexError: raise LanguageError( "Cannot use metal:fill-slot without metal:use-macro.", clause ) slots = self._use_macro[index] slots.append(nodes.FillSlot(clause, slot)) # metal:define-macro try: clause = ns[METAL, 'define-macro'] except KeyError: pass else: self._macros[clause] = slot slot = nodes.UseInternalMacro(clause) slot = wrap( slot, NAME ) # tal:on-error try: clause = ns[TAL, 'on-error'] except KeyError: ON_ERROR = skip else: key, value = tal.parse_substitution(clause) translate = True if ns.get((I18N, 'translate')) == '' else False fallback = self._make_content_node(value, None, key, translate) if omit is False and start['namespace'] not in self.DROP_NS: start_tag = copy(start_tag) start_tag.attributes = nodes.Sequence( start_tag.attributes.extract( lambda attribute: isinstance(attribute, nodes.Attribute) and isinstance(attribute.expression, ast.Str) ) ) if end_tag is None: # Make sure start-tag has opening suffix. We don't # allow self-closing element here. start_tag.suffix = ">" # Explicitly set end-tag. end_tag = nodes.End(start_tag.name, '', '</', '>',) fallback = nodes.Element( start_tag, end_tag, fallback, ) ON_ERROR = partial(nodes.OnError, fallback, 'error') clause = ns.get((META, 'interpolation')) if clause in ('false', 'off'): INTERPOLATION = False elif clause in ('true', 'on'): INTERPOLATION = True elif clause is None: INTERPOLATION = self._interpolation[-1] else: raise LanguageError("Bad interpolation setting.", clause) self._interpolation.append(INTERPOLATION) # Visit content body for child in children: body.append(self.visit(*child)) self._switches.pop() self._interpolation.pop() if use_macro: self._use_macro.pop() return wrap( slot, ON_ERROR )
def prev_visit_element(self, start, end, children): ns = start['ns_attrs'] for (prefix, attr), encoded in tuple(ns.items()): if prefix == TAL: ns[prefix, attr] = decode_htmlentities(encoded) # Validate namespace attributes validate_attributes(ns, TAL, tal.WHITELIST) validate_attributes(ns, I18N, i18n.WHITELIST) # Check attributes for language errors self._check_attributes(start['namespace'], ns) # Remember whitespace for item repetition if self._last is not None: self._whitespace = "\n" + " " * len(self._last.rsplit('\n', 1)[-1]) # Set element-local whitespace whitespace = self._whitespace # Set up switch try: clause = ns[TAL, 'switch'] except KeyError: switch = None else: switch = nodes.Value(clause) self._switches.append(switch) body = [] content = nodes.Sequence(body) # tal:content try: clause = ns[TAL, 'content'] except KeyError: pass else: key, value = tal.parse_substitution(clause) xlate = True if ns.get((I18N, 'translate')) == '' else False content = self._make_content_node(value, content, key, xlate) if end is None: # Make sure start-tag has opening suffix. start['suffix'] = ">" # Explicitly set end-tag. end = { 'prefix': '</', 'name': start['name'], 'space': '', 'suffix': '>' } # i18n:translate try: clause = ns[I18N, 'translate'] except KeyError: pass else: dynamic = ns.get((TAL, 'content')) or ns.get((TAL, 'replace')) if not dynamic: content = nodes.Translate(clause, content) # tal:attributes try: clause = ns[TAL, 'attributes'] except KeyError: TAL_ATTRIBUTES = {} else: TAL_ATTRIBUTES = tal.parse_attributes(clause) # i18n:attributes try: clause = ns[I18N, 'attributes'] except KeyError: I18N_ATTRIBUTES = {} else: I18N_ATTRIBUTES = i18n.parse_attributes(clause) # Prepare attributes from TAL language prepared = tal.prepare_attributes(start['attrs'], TAL_ATTRIBUTES, I18N_ATTRIBUTES, ns, self.DROP_NS) # Create attribute nodes STATIC_ATTRIBUTES = self._create_static_attributes(prepared) ATTRIBUTES = self._create_attributes_nodes(prepared, I18N_ATTRIBUTES) # Start- and end nodes start_tag = nodes.Start(start['name'], self._maybe_trim(start['prefix']), self._maybe_trim(start['suffix']), ATTRIBUTES) stag = start_tag end_tag = nodes.End( end['name'], end['space'], self._maybe_trim(end['prefix']), self._maybe_trim(end['suffix']), ) if end is not None else None # tal:omit-tag try: clause = ns[TAL, 'omit-tag'] except KeyError: omit = False else: clause = clause.strip() if clause == "": omit = True else: expression = nodes.Negate(nodes.Value(clause)) omit = expression # Wrap start- and end-tags in condition start_tag = nodes.Condition(expression, start_tag) if end_tag is not None: end_tag = nodes.Condition(expression, end_tag) if omit is True or start['namespace'] in self.DROP_NS: inner = content else: inner = nodes.Element( start_tag, end_tag, content, ) # Assign static attributes dictionary to "attrs" value inner = nodes.Define( [nodes.Alias(["attrs"], STATIC_ATTRIBUTES)], inner, ) if omit is not False: inner = nodes.Cache([omit], inner) # tal:replace try: clause = ns[TAL, 'replace'] except KeyError: pass else: key, value = tal.parse_substitution(clause) xlate = True if ns.get((I18N, 'translate')) == '' else False inner = self._make_content_node(value, inner, key, xlate) # tal:define try: clause = ns[TAL, 'define'] except KeyError: DEFINE = skip else: defines = tal.parse_defines(clause) if defines is None: raise ParseError("Invalid define syntax.", clause) DEFINE = partial( nodes.Define, [ nodes.Assignment(names, nodes.Value(expr), context == "local") for (context, names, expr) in defines ], ) # tal:case try: clause = ns[TAL, 'case'] except KeyError: CASE = skip else: value = nodes.Value(clause) for switch in reversed(self._switches): if switch is not None: break else: raise LanguageError("Must define switch on a parent element.", clause) CASE = lambda node: nodes.Define([ nodes.Assignment(["default"], switch, True) ], nodes.Condition( nodes.Equality(switch, value), node, )) # tal:repeat try: clause = ns[TAL, 'repeat'] except KeyError: REPEAT = skip else: defines = tal.parse_defines(clause) assert len(defines) == 1 context, names, expr = defines[0] expression = nodes.Value(expr) REPEAT = partial(nodes.Repeat, names, expression, context == "local", whitespace) # tal:condition try: clause = ns[TAL, 'condition'] except KeyError: CONDITION = skip else: expression = nodes.Value(clause) CONDITION = partial(nodes.Condition, expression) # tal:switch if switch is None: SWITCH = skip else: SWITCH = partial(nodes.Cache, [switch]) # i18n:domain try: clause = ns[I18N, 'domain'] except KeyError: DOMAIN = skip else: DOMAIN = partial(nodes.Domain, clause) # i18n:name try: clause = ns[I18N, 'name'] except KeyError: NAME = skip else: NAME = partial(nodes.Name, clause) # The "slot" node next is the first node level that can serve # as a macro slot slot = wrap( inner, DEFINE, CASE, CONDITION, REPEAT, SWITCH, DOMAIN, ) slot = wrap(slot, NAME) # tal:on-error try: clause = ns[TAL, 'on-error'] except KeyError: ON_ERROR = skip else: key, value = tal.parse_substitution(clause) translate = True if ns.get((I18N, 'translate')) == '' else False fallback = self._make_content_node(value, None, key, translate) if omit is False and start['namespace'] not in self.DROP_NS: fallback = nodes.Element( start_tag, end_tag, fallback, ) ON_ERROR = partial(nodes.OnError, fallback, 'error') clause = ns.get((META, 'interpolation')) if clause in ('false', 'off'): INTERPOLATION = False elif clause in ('true', 'on'): INTERPOLATION = True elif clause is None: INTERPOLATION = self._interpolation[-1] else: raise LanguageError("Bad interpolation setting.", clause) self._interpolation.append(INTERPOLATION) # Visit content body for child in children: body.append(self.visit(*child)) self._switches.pop() self._interpolation.pop() slot = wrap(slot, ON_ERROR) stag.replayable = False if self.binds: try: bind_replay_clause = ns[(PRAMTAL, "bind-replay")] splits = bind_replay_clause.split(" ") if len(splits) != 2: raise LanguageError("Invalid bind replay.", bind_replay_clause) events = splits[1].split(";") slot = BindReplay(splits[0], events, slot) stag.replayable = True except KeyError: pass if self.binds: try: bind_clause = ns[(PRAMTAL, "bind-change")] bind_splits = bind_clause.strip().split(" ") if len(bind_splits) > 3: raise LanguageError("Invalid bind syntax.", bind_clause) if not "#" in bind_splits[0]: raise LanguageError("No bind model target specified.", bind_clause) first_split = bind_splits[0][1:] if "." in first_split: bind_model, bind_ons = first_split.split(".", 1) bind_ons = bind_ons.strip().split(".") else: bind_model = first_split bind_ons = [] bind_attrs = [] if len(bind_splits) > 1: bind_attrs = bind_splits[1].split(";") stag.replayable = True slot = BindChange(bind_model, bind_ons, bind_attrs, slot) except KeyError: pass stag.repeatable = False if self.binds: try: bind_repeat_clause = ns[(PRAMTAL, "bind-repeat")] except KeyError: pass else: splits = bind_repeat_clause.split(" ") if len(splits) != 2: raise LanguageError("Invalid define model.", bind_repeat_clause) if not splits[0].startswith("#"): raise LanguageError("need # in alias.", bind_repeat_clause) slot = BindRepeat(splits[0][1:], splits[1], slot) stag.repeatable = True try: define_model_clause = ns[(PRAMTAL, "define-model")] except KeyError: pass else: pairs = define_model_clause.split(";") models = [] for pair in pairs: pair = pair.strip() splits = pair.split(" ") if len(splits) != 2: raise LanguageError("Invalid define model.", pair) if not splits[0].startswith("#"): raise LanguageError("need # in alias.", pair) models.append((splits[0][1:], splits[1])) slot = DefineModel(models, slot) return slot
def __call__(self, name, engine): """The strategy is to find possible expression strings and call the ``validate`` function of the parser to validate. For every possible starting point, the longest possible expression is tried first, then the second longest and so forth. Example 1: ${'expressions use the ${<expression>} format'} The entire expression is attempted first and it is also the only one that validates. Example 2: ${'Hello'} ${'world!'} Validation of the longest possible expression (the entire string) will fail, while the second round of attempts, ``${'Hello'}`` and ``${'world!'}`` respectively, validate. """ body = [] nodes = [] text = self.expression expr_map = {} translate = self.translate while text: matched = text m = self.regex.search(matched) if m is None: nodes.append(ast.Str(s=text)) break part = text[:m.start()] text = text[m.start():] if part: node = ast.Str(s=part) nodes.append(node) if not body: target = name else: target = store("%s_%d" % (name.id, text.pos)) while True: d = groupdict(m, matched) string = d["expression"] or d["variable"] or "" string = decode_htmlentities(string) try: compiler = engine.parse(string) body += compiler.assign_text(target) except ExpressionError: matched = matched[m.start():m.end() - 1] m = self.regex.search(matched) if m is None: raise else: break # If one or more expressions are not simple names, we # disable translation. if RE_NAME.match(string) is None: translate = False # if this is the first expression, use the provided # assignment name; otherwise, generate one (here based # on the string position) node = load(target.id) nodes.append(node) expr_map[node] = safe_native(string) text = text[len(m.group()):] if len(nodes) == 1: target = nodes[0] if translate and isinstance(target, ast.Str): target = template( "translate(msgid, domain=__i18n_domain)", msgid=target, mode="eval", ) else: if translate: formatting_string = "" keys = [] values = [] for node in nodes: if isinstance(node, ast.Str): formatting_string += node.s else: string = expr_map[node] formatting_string += "${%s}" % string keys.append(ast.Str(s=string)) values.append(node) target = template( "translate(msgid, mapping=mapping, domain=__i18n_domain)", msgid=ast.Str(s=formatting_string), mapping=ast.Dict(keys=keys, values=values), mode="eval" ) else: nodes = [ template( "NODE", NODE=node, mode="eval" ) for node in nodes ] target = ast.BinOp( left=ast.Str(s="%s" * len(nodes)), op=ast.Mod(), right=ast.Tuple(elts=nodes, ctx=ast.Load())) body += [ast.Assign(targets=[name], value=target)] return body
def __call__(self, name, engine): """The strategy is to find possible expression strings and call the ``validate`` function of the parser to validate. For every possible starting point, the longest possible expression is tried first, then the second longest and so forth. Example 1: ${'expressions use the ${<expression>} format'} The entire expression is attempted first and it is also the only one that validates. Example 2: ${'Hello'} ${'world!'} Validation of the longest possible expression (the entire string) will fail, while the second round of attempts, ``${'Hello'}`` and ``${'world!'}`` respectively, validate. """ body = [] nodes = [] text = self.expression expr_map = {} translate = self.translate while text: matched = text m = self.regex.search(matched) if m is None: nodes.append(ast.Str(s=text)) break part = text[:m.start()] text = text[m.start():] if part: node = ast.Str(s=part) nodes.append(node) if not body: target = name else: target = store("%s_%d" % (name.id, text.pos)) while True: d = groupdict(m, matched) string = d["expression"] or d["variable"] or "" string = decode_htmlentities(string) try: compiler = engine.parse(string) body += compiler.assign_text(target) except ExpressionError: matched = matched[m.start():m.end() - 1] m = self.regex.search(matched) if m is None: raise else: break # If one or more expressions are not simple names, we # disable translation. if RE_NAME.match(string) is None: translate = False # if this is the first expression, use the provided # assignment name; otherwise, generate one (here based # on the string position) node = load(target.id) nodes.append(node) expr_map[node] = safe_native(string) text = text[len(m.group()):] if len(nodes) == 1: target = nodes[0] if translate and isinstance(target, ast.Str): target = template( "translate(msgid, domain=__i18n_domain)", msgid=target, mode="eval", ) else: if translate: formatting_string = "" keys = [] values = [] for node in nodes: if isinstance(node, ast.Str): formatting_string += node.s else: string = expr_map[node] formatting_string += "${%s}" % string keys.append(ast.Str(s=string)) values.append(node) target = template( "translate(msgid, mapping=mapping, domain=__i18n_domain)", msgid=ast.Str(s=formatting_string), mapping=ast.Dict(keys=keys, values=values), mode="eval") else: nodes = [ template("NODE", NODE=node, mode="eval") for node in nodes ] target = ast.BinOp(left=ast.Str(s="%s" * len(nodes)), op=ast.Mod(), right=ast.Tuple(elts=nodes, ctx=ast.Load())) body += [ast.Assign(targets=[name], value=target)] return body