| LEFT | RIGHT |
| 1 # coding: utf-8 | 1 # coding: utf-8 |
| 2 | 2 |
| 3 # This file is part of the Adblock Plus web scripts, | 3 # This file is part of the Adblock Plus web scripts, |
| 4 # Copyright (C) 2006-2015 Eyeo GmbH | 4 # Copyright (C) 2006-2015 Eyeo GmbH |
| 5 # | 5 # |
| 6 # Adblock Plus is free software: you can redistribute it and/or modify | 6 # Adblock Plus is free software: you can redistribute it and/or modify |
| 7 # it under the terms of the GNU General Public License version 3 as | 7 # it under the terms of the GNU General Public License version 3 as |
| 8 # published by the Free Software Foundation. | 8 # published by the Free Software Foundation. |
| 9 # | 9 # |
| 10 # Adblock Plus is distributed in the hope that it will be useful, | 10 # Adblock Plus is distributed in the hope that it will be useful, |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 52 self._whitelist = whitelist | 52 self._whitelist = whitelist |
| 53 | 53 |
| 54 def parse(self, text, pagename): | 54 def parse(self, text, pagename): |
| 55 self.reset() | 55 self.reset() |
| 56 self._string = [] | 56 self._string = [] |
| 57 self._fixed_strings = [] | 57 self._fixed_strings = [] |
| 58 self._inside_fixed = False | 58 self._inside_fixed = False |
| 59 self._attrs = {} | 59 self._attrs = {} |
| 60 self._pagename = pagename | 60 self._pagename = pagename |
| 61 | 61 |
| 62 # Force-escape ampersands, otherwise the parser will autocomplete bogus |
| 63 # entities. |
| 64 text = re.sub(r"&(?!\S+;)", "&", text) |
| 65 |
| 62 try: | 66 try: |
| 63 self.feed(text) | 67 self.feed(text) |
| 64 return "".join(self._string), self._attrs, ["".join(s) for s in self._fixe
d_strings] | 68 return "".join(self._string), self._attrs, ["".join(s) for s in self._fixe
d_strings] |
| 65 finally: | 69 finally: |
| 66 self._string = None | 70 self._string = None |
| 67 self._attrs = None | 71 self._attrs = None |
| 68 self._pagename = None | 72 self._pagename = None |
| 69 self._inside_fixed = False | 73 self._inside_fixed = False |
| 70 self._fixed_strings = None | 74 self._fixed_strings = None |
| 71 | 75 |
| (...skipping 27 matching lines...) Expand all Loading... |
| 99 # HTML escaping is applied when this string is inserted into the document. | 103 # HTML escaping is applied when this string is inserted into the document. |
| 100 self._append_text(data) | 104 self._append_text(data) |
| 101 | 105 |
| 102 def handle_entityref(self, name): | 106 def handle_entityref(self, name): |
| 103 self._append_text(self.unescape("&%s;" % name)) | 107 self._append_text(self.unescape("&%s;" % name)) |
| 104 | 108 |
| 105 def handle_charref(self, name): | 109 def handle_charref(self, name): |
| 106 self._append_text(self.unescape("&#%s;" % name)) | 110 self._append_text(self.unescape("&#%s;" % name)) |
| 107 | 111 |
| 108 class Converter: | 112 class Converter: |
| 109 whitelist = set(["a", "em", "strong"]) | 113 whitelist = {"a", "em", "strong", "code", "span"} |
| 110 missing_translations = 0 | 114 missing_translations = 0 |
| 111 total_translations = 0 | 115 total_translations = 0 |
| 112 | 116 |
| 113 def __init__(self, params, key="pagedata"): | 117 def __init__(self, params, key="pagedata"): |
| 114 self._params = params | 118 self._params = params |
| 115 self._key = key | 119 self._key = key |
| 116 self._attribute_parser = AttributeParser(self.whitelist) | 120 self._attribute_parser = AttributeParser(self.whitelist) |
| 117 | 121 |
| 118 # Read in any parameters specified at the beginning of the file | 122 # Read in any parameters specified at the beginning of the file |
| 119 lines = params[key].splitlines(True) | 123 lines = params[key].splitlines(True) |
| (...skipping 18 matching lines...) Expand all Loading... |
| 138 if locale == self._params["defaultlocale"]: | 142 if locale == self._params["defaultlocale"]: |
| 139 result = default | 143 result = default |
| 140 elif name in localedata: | 144 elif name in localedata: |
| 141 result = localedata[name].strip() | 145 result = localedata[name].strip() |
| 142 else: | 146 else: |
| 143 result = default | 147 result = default |
| 144 self.missing_translations += 1 | 148 self.missing_translations += 1 |
| 145 self.total_translations += 1 | 149 self.total_translations += 1 |
| 146 | 150 |
| 147 # Insert fixed strings | 151 # Insert fixed strings |
| 148 for i in range(len(fixed_strings)): | 152 for i, fixed_string in enumerate(fixed_strings, 1): |
| 149 result = re.sub(r"\{%d\}" % (i + 1), fixed_strings[i], result, 1) | 153 result = result.replace("{%d}" % i, fixed_string) |
| 150 | 154 |
| 151 # Insert attributes | 155 # Insert attributes |
| 152 result = escape(result) | 156 result = escape(result) |
| 153 def stringify_attribute((name, value)): | 157 def stringify_attribute((name, value)): |
| 154 if name == "href": | 158 return '%s="%s"' % ( |
| 155 link_locale, link = self._params["source"].resolve_link(value, locale) | 159 escape(name), |
| 156 if link: | 160 escape(self.insert_localized_strings(value, {})) |
| 157 return 'href="%s" hreflang="%s"' % (escape(link), escape(link_locale)) | 161 ) |
| 158 return '%s="%s"' % (escape(name), escape(value)) | |
| 159 | 162 |
| 160 for tag in self.whitelist: | 163 for tag in self.whitelist: |
| 161 saved = saved_attributes.get(tag, []) | 164 saved = saved_attributes.get(tag, []) |
| 162 for attrs in saved: | 165 for attrs in saved: |
| 163 attrs = map(stringify_attribute, attrs) | 166 attrs = map(stringify_attribute, attrs) |
| 164 result = re.sub( | 167 result = re.sub( |
| 165 r"%s([^<>]*?)%s" % (re_escape("<%s>" % tag), re_escape("</%s>" % tag))
, | 168 r"%s([^<>]*?)%s" % (re_escape("<%s>" % tag), re_escape("</%s>" % tag))
, |
| 166 r'<%s %s>\1</%s>' % (tag, " ".join(attrs), tag), | 169 r'<%s%s>\1</%s>' % (tag, " " + " ".join(attrs) if attrs else "", tag), |
| 167 result, 1, flags=re.S | 170 result, 1, flags=re.S |
| 168 ) | 171 ) |
| 169 result = re.sub( | 172 result = re.sub( |
| 170 r"%s([^<>]*?)%s" % (re_escape("<%s>" % tag), re_escape("</%s>" % tag)), | 173 r"%s([^<>]*?)%s" % (re_escape("<%s>" % tag), re_escape("</%s>" % tag)), |
| 171 r"<%s>\1</%s>" % (tag, tag), | 174 r"<%s>\1</%s>" % (tag, tag), |
| 172 result, flags=re.S | 175 result, flags=re.S |
| 173 ) | 176 ) |
| 174 return result | 177 return result |
| 175 | 178 |
| 176 def insert_localized_strings(self, text, escapes, to_html=lambda s: s): | 179 def insert_localized_strings(self, text, escapes, to_html=lambda s: s): |
| 177 def lookup_string(match): | 180 def lookup_string(match): |
| 178 name, comment, default = match.groups() | 181 name, comment, default = match.groups() |
| 179 default = to_html(default).strip() | 182 default = to_html(default).strip() |
| 180 | 183 |
| 181 # Note: We currently ignore the comment, it is only relevant when | 184 # Note: We currently ignore the comment, it is only relevant when |
| 182 # generating the master translation. | 185 # generating the master translation. |
| 183 return self.localize_string(name, default, self._params["localedata"], esc
apes) | 186 return self.localize_string(name, default, self._params["localedata"], esc
apes) |
| 184 | 187 |
| 185 return re.sub( | 188 return re.sub( |
| 186 r"\{\{\s*([\w\-]+)(?:\[(.*?)\])?\s+(.*?)\}\}", | 189 r"{{\s*" |
| 190 r"([\w\-]+)" # String ID |
| 191 r"(?:\[(.*?)\])?" # Optional comment |
| 192 r"\s+" |
| 193 r"((?:(?!{{).|" # Translatable text |
| 194 r"{{(?:(?!}}).)*}}" # Nested translation |
| 195 r")*?)" |
| 196 r"}}", |
| 187 lookup_string, | 197 lookup_string, |
| 188 text, | 198 text, |
| 189 flags=re.S | 199 flags=re.S |
| 190 ) | 200 ) |
| 191 | 201 |
| 192 def process_links(self, text): | 202 def process_links(self, text): |
| 193 def process_link(match): | 203 def process_link(match): |
| 194 pre, attr, url, post = match.groups() | 204 pre, attr, url, post = match.groups() |
| 195 url = jinja2.Markup(url).unescape() | 205 url = jinja2.Markup(url).unescape() |
| 196 | 206 |
| (...skipping 108 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 305 "translate": self.translate, | 315 "translate": self.translate, |
| 306 "linkify": self.linkify, | 316 "linkify": self.linkify, |
| 307 "toclist": self.toclist, | 317 "toclist": self.toclist, |
| 308 } | 318 } |
| 309 | 319 |
| 310 globals = { | 320 globals = { |
| 311 "get_string": self.get_string, | 321 "get_string": self.get_string, |
| 312 "get_page_content": self.get_page_content, | 322 "get_page_content": self.get_page_content, |
| 313 } | 323 } |
| 314 | 324 |
| 325 self._module_refs = [] |
| 315 for dirname, dictionary in [("filters", filters), ("globals", globals)]: | 326 for dirname, dictionary in [("filters", filters), ("globals", globals)]: |
| 316 for filename in self._params["source"].list_files(dirname): | 327 for filename in self._params["source"].list_files(dirname): |
| 317 root, ext = os.path.splitext(filename) | 328 root, ext = os.path.splitext(filename) |
| 318 if ext.lower() != ".py": | 329 if ext.lower() != ".py": |
| 319 continue | 330 continue |
| 320 | 331 |
| 321 path = "%s/%s" % (dirname, filename) | 332 path = "%s/%s" % (dirname, filename) |
| 322 code = self._params["source"].read_file(path) | 333 code = self._params["source"].read_file(path) |
| 323 module = imp.new_module(root.replace("/", ".")) | 334 module = imp.new_module(root.replace("/", ".")) |
| 324 exec code in module.__dict__ | 335 exec code in module.__dict__ |
| 325 | 336 |
| 326 name = os.path.basename(root) | 337 name = os.path.basename(root) |
| 327 if not hasattr(module, name): | 338 if not hasattr(module, name): |
| 328 raise Exception("Expected symbol %s not found in %s file %s" % (name,
dirname, filename)) | 339 raise Exception("Expected symbol %s not found in %s file %s" % (name,
dirname, filename)) |
| 329 dictionary[name] = getattr(module, name) | 340 dictionary[name] = getattr(module, name) |
| 330 | 341 |
| 331 # HACK: The module we created here can be garbage collected because it | 342 # HACK: The module we created here can be garbage collected because it |
| 332 # isn't added to sys.modules. If a function is called and its module is | 343 # isn't added to sys.modules. If a function is called and its module is |
| 333 # gone it might cause weird errors (imports and module variables | 344 # gone it might cause weird errors (imports and module variables |
| 334 # unavailable). We avoid this situation by explicitly referencing the | 345 # unavailable). We avoid this situation by keeping a reference. |
| 335 # module from the function so they can only be garbage collected | 346 self._module_refs.append(module) |
| 336 # together. | |
| 337 if callable(dictionary[name]): | |
| 338 dictionary[name].module_ref = module | |
| 339 | 347 |
| 340 self._env = jinja2.Environment(loader=self._SourceLoader(self._params["sourc
e"]), autoescape=True) | 348 self._env = jinja2.Environment(loader=self._SourceLoader(self._params["sourc
e"]), autoescape=True) |
| 341 self._env.filters.update(filters) | 349 self._env.filters.update(filters) |
| 342 self._env.globals.update(globals) | 350 self._env.globals.update(globals) |
| 343 | 351 |
| 344 def get_html(self, source): | 352 def get_html(self, source): |
| 345 template = self._env.from_string(source) | 353 template = self._env.from_string(source) |
| 346 module = template.make_module(self._params) | 354 module = template.make_module(self._params) |
| 347 for key, value in module.__dict__.iteritems(): | 355 for key, value in module.__dict__.iteritems(): |
| 348 if not key.startswith("_"): | 356 if not key.startswith("_"): |
| 349 self._params[key] = value | 357 self._params[key] = value |
| 350 return unicode(module) | 358 |
| 359 result = unicode(module) |
| 360 result = self.process_links(result) |
| 361 return result |
| 351 | 362 |
| 352 def translate(self, default, name, comment=None): | 363 def translate(self, default, name, comment=None): |
| 353 # Note: We currently ignore the comment, it is only relevant when | 364 # Note: We currently ignore the comment, it is only relevant when |
| 354 # generating the master translation. | 365 # generating the master translation. |
| 355 localedata = self._params["localedata"] | 366 localedata = self._params["localedata"] |
| 356 return jinja2.Markup(self.localize_string(name, default, localedata, html_es
capes)) | 367 return jinja2.Markup(self.localize_string(name, default, localedata, html_es
capes)) |
| 357 | 368 |
| 358 def get_string(self, name, page): | 369 def get_string(self, name, page): |
| 359 localedata = self._params["source"].read_locale(self._params["locale"], page
) | 370 localedata = self._params["source"].read_locale(self._params["locale"], page
) |
| 360 default = localedata[name] | 371 default = localedata[name] |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 396 stack.pop() | 407 stack.pop() |
| 397 stack[-1]["subitems"].append(item) | 408 stack[-1]["subitems"].append(item) |
| 398 stack.append(item) | 409 stack.append(item) |
| 399 return structured | 410 return structured |
| 400 | 411 |
| 401 converters = { | 412 converters = { |
| 402 "html": RawConverter, | 413 "html": RawConverter, |
| 403 "md": MarkdownConverter, | 414 "md": MarkdownConverter, |
| 404 "tmpl": TemplateConverter, | 415 "tmpl": TemplateConverter, |
| 405 } | 416 } |
| LEFT | RIGHT |