test_ext.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655
  1. # -*- coding: utf-8 -*-
  2. import re
  3. import pytest
  4. from jinja2 import contextfunction
  5. from jinja2 import DictLoader
  6. from jinja2 import Environment
  7. from jinja2 import nodes
  8. from jinja2._compat import BytesIO
  9. from jinja2._compat import itervalues
  10. from jinja2._compat import text_type
  11. from jinja2.exceptions import TemplateAssertionError
  12. from jinja2.ext import Extension
  13. from jinja2.lexer import count_newlines
  14. from jinja2.lexer import Token
  15. importable_object = 23
  16. _gettext_re = re.compile(r"_\((.*?)\)", re.DOTALL)
  17. i18n_templates = {
  18. "master.html": '<title>{{ page_title|default(_("missing")) }}</title>'
  19. "{% block body %}{% endblock %}",
  20. "child.html": '{% extends "master.html" %}{% block body %}'
  21. "{% trans %}watch out{% endtrans %}{% endblock %}",
  22. "plural.html": "{% trans user_count %}One user online{% pluralize %}"
  23. "{{ user_count }} users online{% endtrans %}",
  24. "plural2.html": "{% trans user_count=get_user_count() %}{{ user_count }}s"
  25. "{% pluralize %}{{ user_count }}p{% endtrans %}",
  26. "stringformat.html": '{{ _("User: %(num)s")|format(num=user_count) }}',
  27. }
  28. newstyle_i18n_templates = {
  29. "master.html": '<title>{{ page_title|default(_("missing")) }}</title>'
  30. "{% block body %}{% endblock %}",
  31. "child.html": '{% extends "master.html" %}{% block body %}'
  32. "{% trans %}watch out{% endtrans %}{% endblock %}",
  33. "plural.html": "{% trans user_count %}One user online{% pluralize %}"
  34. "{{ user_count }} users online{% endtrans %}",
  35. "stringformat.html": '{{ _("User: %(num)s", num=user_count) }}',
  36. "ngettext.html": '{{ ngettext("%(num)s apple", "%(num)s apples", apples) }}',
  37. "ngettext_long.html": "{% trans num=apples %}{{ num }} apple{% pluralize %}"
  38. "{{ num }} apples{% endtrans %}",
  39. "transvars1.html": "{% trans %}User: {{ num }}{% endtrans %}",
  40. "transvars2.html": "{% trans num=count %}User: {{ num }}{% endtrans %}",
  41. "transvars3.html": "{% trans count=num %}User: {{ count }}{% endtrans %}",
  42. "novars.html": "{% trans %}%(hello)s{% endtrans %}",
  43. "vars.html": "{% trans %}{{ foo }}%(foo)s{% endtrans %}",
  44. "explicitvars.html": '{% trans foo="42" %}%(foo)s{% endtrans %}',
  45. }
  46. languages = {
  47. "de": {
  48. "missing": u"fehlend",
  49. "watch out": u"pass auf",
  50. "One user online": u"Ein Benutzer online",
  51. "%(user_count)s users online": u"%(user_count)s Benutzer online",
  52. "User: %(num)s": u"Benutzer: %(num)s",
  53. "User: %(count)s": u"Benutzer: %(count)s",
  54. "%(num)s apple": u"%(num)s Apfel",
  55. "%(num)s apples": u"%(num)s Äpfel",
  56. }
  57. }
  58. @contextfunction
  59. def gettext(context, string):
  60. language = context.get("LANGUAGE", "en")
  61. return languages.get(language, {}).get(string, string)
  62. @contextfunction
  63. def ngettext(context, s, p, n):
  64. language = context.get("LANGUAGE", "en")
  65. if n != 1:
  66. return languages.get(language, {}).get(p, p)
  67. return languages.get(language, {}).get(s, s)
  68. i18n_env = Environment(
  69. loader=DictLoader(i18n_templates), extensions=["jinja2.ext.i18n"]
  70. )
  71. i18n_env.globals.update({"_": gettext, "gettext": gettext, "ngettext": ngettext})
  72. i18n_env_trimmed = Environment(extensions=["jinja2.ext.i18n"])
  73. i18n_env_trimmed.policies["ext.i18n.trimmed"] = True
  74. i18n_env_trimmed.globals.update(
  75. {"_": gettext, "gettext": gettext, "ngettext": ngettext}
  76. )
  77. newstyle_i18n_env = Environment(
  78. loader=DictLoader(newstyle_i18n_templates), extensions=["jinja2.ext.i18n"]
  79. )
  80. newstyle_i18n_env.install_gettext_callables(gettext, ngettext, newstyle=True)
  81. class ExampleExtension(Extension):
  82. tags = set(["test"])
  83. ext_attr = 42
  84. context_reference_node_cls = nodes.ContextReference
  85. def parse(self, parser):
  86. return nodes.Output(
  87. [
  88. self.call_method(
  89. "_dump",
  90. [
  91. nodes.EnvironmentAttribute("sandboxed"),
  92. self.attr("ext_attr"),
  93. nodes.ImportedName(__name__ + ".importable_object"),
  94. self.context_reference_node_cls(),
  95. ],
  96. )
  97. ]
  98. ).set_lineno(next(parser.stream).lineno)
  99. def _dump(self, sandboxed, ext_attr, imported_object, context):
  100. return "%s|%s|%s|%s|%s" % (
  101. sandboxed,
  102. ext_attr,
  103. imported_object,
  104. context.blocks,
  105. context.get("test_var"),
  106. )
  107. class DerivedExampleExtension(ExampleExtension):
  108. context_reference_node_cls = nodes.DerivedContextReference
  109. class PreprocessorExtension(Extension):
  110. def preprocess(self, source, name, filename=None):
  111. return source.replace("[[TEST]]", "({{ foo }})")
  112. class StreamFilterExtension(Extension):
  113. def filter_stream(self, stream):
  114. for token in stream:
  115. if token.type == "data":
  116. for t in self.interpolate(token):
  117. yield t
  118. else:
  119. yield token
  120. def interpolate(self, token):
  121. pos = 0
  122. end = len(token.value)
  123. lineno = token.lineno
  124. while 1:
  125. match = _gettext_re.search(token.value, pos)
  126. if match is None:
  127. break
  128. value = token.value[pos : match.start()]
  129. if value:
  130. yield Token(lineno, "data", value)
  131. lineno += count_newlines(token.value)
  132. yield Token(lineno, "variable_begin", None)
  133. yield Token(lineno, "name", "gettext")
  134. yield Token(lineno, "lparen", None)
  135. yield Token(lineno, "string", match.group(1))
  136. yield Token(lineno, "rparen", None)
  137. yield Token(lineno, "variable_end", None)
  138. pos = match.end()
  139. if pos < end:
  140. yield Token(lineno, "data", token.value[pos:])
  141. class TestExtensions(object):
  142. def test_extend_late(self):
  143. env = Environment()
  144. env.add_extension("jinja2.ext.autoescape")
  145. t = env.from_string('{% autoescape true %}{{ "<test>" }}{% endautoescape %}')
  146. assert t.render() == "&lt;test&gt;"
  147. def test_loop_controls(self):
  148. env = Environment(extensions=["jinja2.ext.loopcontrols"])
  149. tmpl = env.from_string(
  150. """
  151. {%- for item in [1, 2, 3, 4] %}
  152. {%- if item % 2 == 0 %}{% continue %}{% endif -%}
  153. {{ item }}
  154. {%- endfor %}"""
  155. )
  156. assert tmpl.render() == "13"
  157. tmpl = env.from_string(
  158. """
  159. {%- for item in [1, 2, 3, 4] %}
  160. {%- if item > 2 %}{% break %}{% endif -%}
  161. {{ item }}
  162. {%- endfor %}"""
  163. )
  164. assert tmpl.render() == "12"
  165. def test_do(self):
  166. env = Environment(extensions=["jinja2.ext.do"])
  167. tmpl = env.from_string(
  168. """
  169. {%- set items = [] %}
  170. {%- for char in "foo" %}
  171. {%- do items.append(loop.index0 ~ char) %}
  172. {%- endfor %}{{ items|join(', ') }}"""
  173. )
  174. assert tmpl.render() == "0f, 1o, 2o"
  175. def test_extension_nodes(self):
  176. env = Environment(extensions=[ExampleExtension])
  177. tmpl = env.from_string("{% test %}")
  178. assert tmpl.render() == "False|42|23|{}|None"
  179. def test_contextreference_node_passes_context(self):
  180. env = Environment(extensions=[ExampleExtension])
  181. tmpl = env.from_string('{% set test_var="test_content" %}{% test %}')
  182. assert tmpl.render() == "False|42|23|{}|test_content"
  183. def test_contextreference_node_can_pass_locals(self):
  184. env = Environment(extensions=[DerivedExampleExtension])
  185. tmpl = env.from_string(
  186. '{% for test_var in ["test_content"] %}{% test %}{% endfor %}'
  187. )
  188. assert tmpl.render() == "False|42|23|{}|test_content"
  189. def test_identifier(self):
  190. assert ExampleExtension.identifier == __name__ + ".ExampleExtension"
  191. def test_rebinding(self):
  192. original = Environment(extensions=[ExampleExtension])
  193. overlay = original.overlay()
  194. for env in original, overlay:
  195. for ext in itervalues(env.extensions):
  196. assert ext.environment is env
  197. def test_preprocessor_extension(self):
  198. env = Environment(extensions=[PreprocessorExtension])
  199. tmpl = env.from_string("{[[TEST]]}")
  200. assert tmpl.render(foo=42) == "{(42)}"
  201. def test_streamfilter_extension(self):
  202. env = Environment(extensions=[StreamFilterExtension])
  203. env.globals["gettext"] = lambda x: x.upper()
  204. tmpl = env.from_string("Foo _(bar) Baz")
  205. out = tmpl.render()
  206. assert out == "Foo BAR Baz"
  207. def test_extension_ordering(self):
  208. class T1(Extension):
  209. priority = 1
  210. class T2(Extension):
  211. priority = 2
  212. env = Environment(extensions=[T1, T2])
  213. ext = list(env.iter_extensions())
  214. assert ext[0].__class__ is T1
  215. assert ext[1].__class__ is T2
  216. def test_debug(self):
  217. env = Environment(extensions=["jinja2.ext.debug"])
  218. t = env.from_string("Hello\n{% debug %}\nGoodbye")
  219. out = t.render()
  220. for value in ("context", "cycler", "filters", "abs", "tests", "!="):
  221. assert "'{}'".format(value) in out
  222. class TestInternationalization(object):
  223. def test_trans(self):
  224. tmpl = i18n_env.get_template("child.html")
  225. assert tmpl.render(LANGUAGE="de") == "<title>fehlend</title>pass auf"
  226. def test_trans_plural(self):
  227. tmpl = i18n_env.get_template("plural.html")
  228. assert tmpl.render(LANGUAGE="de", user_count=1) == "Ein Benutzer online"
  229. assert tmpl.render(LANGUAGE="de", user_count=2) == "2 Benutzer online"
  230. def test_trans_plural_with_functions(self):
  231. tmpl = i18n_env.get_template("plural2.html")
  232. def get_user_count():
  233. get_user_count.called += 1
  234. return 1
  235. get_user_count.called = 0
  236. assert tmpl.render(LANGUAGE="de", get_user_count=get_user_count) == "1s"
  237. assert get_user_count.called == 1
  238. def test_complex_plural(self):
  239. tmpl = i18n_env.from_string(
  240. "{% trans foo=42, count=2 %}{{ count }} item{% "
  241. "pluralize count %}{{ count }} items{% endtrans %}"
  242. )
  243. assert tmpl.render() == "2 items"
  244. pytest.raises(
  245. TemplateAssertionError,
  246. i18n_env.from_string,
  247. "{% trans foo %}...{% pluralize bar %}...{% endtrans %}",
  248. )
  249. def test_trans_stringformatting(self):
  250. tmpl = i18n_env.get_template("stringformat.html")
  251. assert tmpl.render(LANGUAGE="de", user_count=5) == "Benutzer: 5"
  252. def test_trimmed(self):
  253. tmpl = i18n_env.from_string(
  254. "{%- trans trimmed %} hello\n world {% endtrans -%}"
  255. )
  256. assert tmpl.render() == "hello world"
  257. def test_trimmed_policy(self):
  258. s = "{%- trans %} hello\n world {% endtrans -%}"
  259. tmpl = i18n_env.from_string(s)
  260. trimmed_tmpl = i18n_env_trimmed.from_string(s)
  261. assert tmpl.render() == " hello\n world "
  262. assert trimmed_tmpl.render() == "hello world"
  263. def test_trimmed_policy_override(self):
  264. tmpl = i18n_env_trimmed.from_string(
  265. "{%- trans notrimmed %} hello\n world {% endtrans -%}"
  266. )
  267. assert tmpl.render() == " hello\n world "
  268. def test_trimmed_vars(self):
  269. tmpl = i18n_env.from_string(
  270. '{%- trans trimmed x="world" %} hello\n {{ x }} {% endtrans -%}'
  271. )
  272. assert tmpl.render() == "hello world"
  273. def test_trimmed_varname_trimmed(self):
  274. # unlikely variable name, but when used as a variable
  275. # it should not enable trimming
  276. tmpl = i18n_env.from_string(
  277. "{%- trans trimmed = 'world' %} hello\n {{ trimmed }} {% endtrans -%}"
  278. )
  279. assert tmpl.render() == " hello\n world "
  280. def test_extract(self):
  281. from jinja2.ext import babel_extract
  282. source = BytesIO(
  283. """
  284. {{ gettext('Hello World') }}
  285. {% trans %}Hello World{% endtrans %}
  286. {% trans %}{{ users }} user{% pluralize %}{{ users }} users{% endtrans %}
  287. """.encode(
  288. "ascii"
  289. )
  290. ) # make python 3 happy
  291. assert list(babel_extract(source, ("gettext", "ngettext", "_"), [], {})) == [
  292. (2, "gettext", u"Hello World", []),
  293. (3, "gettext", u"Hello World", []),
  294. (4, "ngettext", (u"%(users)s user", u"%(users)s users", None), []),
  295. ]
  296. def test_extract_trimmed(self):
  297. from jinja2.ext import babel_extract
  298. source = BytesIO(
  299. """
  300. {{ gettext(' Hello \n World') }}
  301. {% trans trimmed %} Hello \n World{% endtrans %}
  302. {% trans trimmed %}{{ users }} \n user
  303. {%- pluralize %}{{ users }} \n users{% endtrans %}
  304. """.encode(
  305. "ascii"
  306. )
  307. ) # make python 3 happy
  308. assert list(babel_extract(source, ("gettext", "ngettext", "_"), [], {})) == [
  309. (2, "gettext", u" Hello \n World", []),
  310. (4, "gettext", u"Hello World", []),
  311. (6, "ngettext", (u"%(users)s user", u"%(users)s users", None), []),
  312. ]
  313. def test_extract_trimmed_option(self):
  314. from jinja2.ext import babel_extract
  315. source = BytesIO(
  316. """
  317. {{ gettext(' Hello \n World') }}
  318. {% trans %} Hello \n World{% endtrans %}
  319. {% trans %}{{ users }} \n user
  320. {%- pluralize %}{{ users }} \n users{% endtrans %}
  321. """.encode(
  322. "ascii"
  323. )
  324. ) # make python 3 happy
  325. opts = {"trimmed": "true"}
  326. assert list(babel_extract(source, ("gettext", "ngettext", "_"), [], opts)) == [
  327. (2, "gettext", u" Hello \n World", []),
  328. (4, "gettext", u"Hello World", []),
  329. (6, "ngettext", (u"%(users)s user", u"%(users)s users", None), []),
  330. ]
  331. def test_comment_extract(self):
  332. from jinja2.ext import babel_extract
  333. source = BytesIO(
  334. """
  335. {# trans first #}
  336. {{ gettext('Hello World') }}
  337. {% trans %}Hello World{% endtrans %}{# trans second #}
  338. {#: third #}
  339. {% trans %}{{ users }} user{% pluralize %}{{ users }} users{% endtrans %}
  340. """.encode(
  341. "utf-8"
  342. )
  343. ) # make python 3 happy
  344. assert list(
  345. babel_extract(source, ("gettext", "ngettext", "_"), ["trans", ":"], {})
  346. ) == [
  347. (3, "gettext", u"Hello World", ["first"]),
  348. (4, "gettext", u"Hello World", ["second"]),
  349. (6, "ngettext", (u"%(users)s user", u"%(users)s users", None), ["third"]),
  350. ]
  351. class TestScope(object):
  352. def test_basic_scope_behavior(self):
  353. # This is what the old with statement compiled down to
  354. class ScopeExt(Extension):
  355. tags = set(["scope"])
  356. def parse(self, parser):
  357. node = nodes.Scope(lineno=next(parser.stream).lineno)
  358. assignments = []
  359. while parser.stream.current.type != "block_end":
  360. lineno = parser.stream.current.lineno
  361. if assignments:
  362. parser.stream.expect("comma")
  363. target = parser.parse_assign_target()
  364. parser.stream.expect("assign")
  365. expr = parser.parse_expression()
  366. assignments.append(nodes.Assign(target, expr, lineno=lineno))
  367. node.body = assignments + list(
  368. parser.parse_statements(("name:endscope",), drop_needle=True)
  369. )
  370. return node
  371. env = Environment(extensions=[ScopeExt])
  372. tmpl = env.from_string(
  373. """\
  374. {%- scope a=1, b=2, c=b, d=e, e=5 -%}
  375. {{ a }}|{{ b }}|{{ c }}|{{ d }}|{{ e }}
  376. {%- endscope -%}
  377. """
  378. )
  379. assert tmpl.render(b=3, e=4) == "1|2|2|4|5"
  380. class TestNewstyleInternationalization(object):
  381. def test_trans(self):
  382. tmpl = newstyle_i18n_env.get_template("child.html")
  383. assert tmpl.render(LANGUAGE="de") == "<title>fehlend</title>pass auf"
  384. def test_trans_plural(self):
  385. tmpl = newstyle_i18n_env.get_template("plural.html")
  386. assert tmpl.render(LANGUAGE="de", user_count=1) == "Ein Benutzer online"
  387. assert tmpl.render(LANGUAGE="de", user_count=2) == "2 Benutzer online"
  388. def test_complex_plural(self):
  389. tmpl = newstyle_i18n_env.from_string(
  390. "{% trans foo=42, count=2 %}{{ count }} item{% "
  391. "pluralize count %}{{ count }} items{% endtrans %}"
  392. )
  393. assert tmpl.render() == "2 items"
  394. pytest.raises(
  395. TemplateAssertionError,
  396. i18n_env.from_string,
  397. "{% trans foo %}...{% pluralize bar %}...{% endtrans %}",
  398. )
  399. def test_trans_stringformatting(self):
  400. tmpl = newstyle_i18n_env.get_template("stringformat.html")
  401. assert tmpl.render(LANGUAGE="de", user_count=5) == "Benutzer: 5"
  402. def test_newstyle_plural(self):
  403. tmpl = newstyle_i18n_env.get_template("ngettext.html")
  404. assert tmpl.render(LANGUAGE="de", apples=1) == "1 Apfel"
  405. assert tmpl.render(LANGUAGE="de", apples=5) == u"5 Äpfel"
  406. def test_autoescape_support(self):
  407. env = Environment(extensions=["jinja2.ext.autoescape", "jinja2.ext.i18n"])
  408. env.install_gettext_callables(
  409. lambda x: u"<strong>Wert: %(name)s</strong>",
  410. lambda s, p, n: s,
  411. newstyle=True,
  412. )
  413. t = env.from_string(
  414. '{% autoescape ae %}{{ gettext("foo", name='
  415. '"<test>") }}{% endautoescape %}'
  416. )
  417. assert t.render(ae=True) == "<strong>Wert: &lt;test&gt;</strong>"
  418. assert t.render(ae=False) == "<strong>Wert: <test></strong>"
  419. def test_autoescape_macros(self):
  420. env = Environment(autoescape=False, extensions=["jinja2.ext.autoescape"])
  421. template = (
  422. "{% macro m() %}<html>{% endmacro %}"
  423. "{% autoescape true %}{{ m() }}{% endautoescape %}"
  424. )
  425. assert env.from_string(template).render() == "<html>"
  426. def test_num_used_twice(self):
  427. tmpl = newstyle_i18n_env.get_template("ngettext_long.html")
  428. assert tmpl.render(apples=5, LANGUAGE="de") == u"5 Äpfel"
  429. def test_num_called_num(self):
  430. source = newstyle_i18n_env.compile(
  431. """
  432. {% trans num=3 %}{{ num }} apple{% pluralize
  433. %}{{ num }} apples{% endtrans %}
  434. """,
  435. raw=True,
  436. )
  437. # quite hacky, but the only way to properly test that. The idea is
  438. # that the generated code does not pass num twice (although that
  439. # would work) for better performance. This only works on the
  440. # newstyle gettext of course
  441. assert (
  442. re.search(r"u?'\%\(num\)s apple', u?'\%\(num\)s " r"apples', 3", source)
  443. is not None
  444. )
  445. def test_trans_vars(self):
  446. t1 = newstyle_i18n_env.get_template("transvars1.html")
  447. t2 = newstyle_i18n_env.get_template("transvars2.html")
  448. t3 = newstyle_i18n_env.get_template("transvars3.html")
  449. assert t1.render(num=1, LANGUAGE="de") == "Benutzer: 1"
  450. assert t2.render(count=23, LANGUAGE="de") == "Benutzer: 23"
  451. assert t3.render(num=42, LANGUAGE="de") == "Benutzer: 42"
  452. def test_novars_vars_escaping(self):
  453. t = newstyle_i18n_env.get_template("novars.html")
  454. assert t.render() == "%(hello)s"
  455. t = newstyle_i18n_env.get_template("vars.html")
  456. assert t.render(foo="42") == "42%(foo)s"
  457. t = newstyle_i18n_env.get_template("explicitvars.html")
  458. assert t.render() == "%(foo)s"
  459. class TestAutoEscape(object):
  460. def test_scoped_setting(self):
  461. env = Environment(extensions=["jinja2.ext.autoescape"], autoescape=True)
  462. tmpl = env.from_string(
  463. """
  464. {{ "<HelloWorld>" }}
  465. {% autoescape false %}
  466. {{ "<HelloWorld>" }}
  467. {% endautoescape %}
  468. {{ "<HelloWorld>" }}
  469. """
  470. )
  471. assert tmpl.render().split() == [
  472. u"&lt;HelloWorld&gt;",
  473. u"<HelloWorld>",
  474. u"&lt;HelloWorld&gt;",
  475. ]
  476. env = Environment(extensions=["jinja2.ext.autoescape"], autoescape=False)
  477. tmpl = env.from_string(
  478. """
  479. {{ "<HelloWorld>" }}
  480. {% autoescape true %}
  481. {{ "<HelloWorld>" }}
  482. {% endautoescape %}
  483. {{ "<HelloWorld>" }}
  484. """
  485. )
  486. assert tmpl.render().split() == [
  487. u"<HelloWorld>",
  488. u"&lt;HelloWorld&gt;",
  489. u"<HelloWorld>",
  490. ]
  491. def test_nonvolatile(self):
  492. env = Environment(extensions=["jinja2.ext.autoescape"], autoescape=True)
  493. tmpl = env.from_string('{{ {"foo": "<test>"}|xmlattr|escape }}')
  494. assert tmpl.render() == ' foo="&lt;test&gt;"'
  495. tmpl = env.from_string(
  496. '{% autoescape false %}{{ {"foo": "<test>"}'
  497. "|xmlattr|escape }}{% endautoescape %}"
  498. )
  499. assert tmpl.render() == " foo=&#34;&amp;lt;test&amp;gt;&#34;"
  500. def test_volatile(self):
  501. env = Environment(extensions=["jinja2.ext.autoescape"], autoescape=True)
  502. tmpl = env.from_string(
  503. '{% autoescape foo %}{{ {"foo": "<test>"}'
  504. "|xmlattr|escape }}{% endautoescape %}"
  505. )
  506. assert tmpl.render(foo=False) == " foo=&#34;&amp;lt;test&amp;gt;&#34;"
  507. assert tmpl.render(foo=True) == ' foo="&lt;test&gt;"'
  508. def test_scoping(self):
  509. env = Environment(extensions=["jinja2.ext.autoescape"])
  510. tmpl = env.from_string(
  511. '{% autoescape true %}{% set x = "<x>" %}{{ x }}'
  512. '{% endautoescape %}{{ x }}{{ "<y>" }}'
  513. )
  514. assert tmpl.render(x=1) == "&lt;x&gt;1<y>"
  515. def test_volatile_scoping(self):
  516. env = Environment(extensions=["jinja2.ext.autoescape"])
  517. tmplsource = """
  518. {% autoescape val %}
  519. {% macro foo(x) %}
  520. [{{ x }}]
  521. {% endmacro %}
  522. {{ foo().__class__.__name__ }}
  523. {% endautoescape %}
  524. {{ '<testing>' }}
  525. """
  526. tmpl = env.from_string(tmplsource)
  527. assert tmpl.render(val=True).split()[0] == "Markup"
  528. assert tmpl.render(val=False).split()[0] == text_type.__name__
  529. # looking at the source we should see <testing> there in raw
  530. # (and then escaped as well)
  531. env = Environment(extensions=["jinja2.ext.autoescape"])
  532. pysource = env.compile(tmplsource, raw=True)
  533. assert "<testing>\\n" in pysource
  534. env = Environment(extensions=["jinja2.ext.autoescape"], autoescape=True)
  535. pysource = env.compile(tmplsource, raw=True)
  536. assert "&lt;testing&gt;\\n" in pysource
  537. def test_overlay_scopes(self):
  538. class MagicScopeExtension(Extension):
  539. tags = set(["overlay"])
  540. def parse(self, parser):
  541. node = nodes.OverlayScope(lineno=next(parser.stream).lineno)
  542. node.body = list(
  543. parser.parse_statements(("name:endoverlay",), drop_needle=True)
  544. )
  545. node.context = self.call_method("get_scope")
  546. return node
  547. def get_scope(self):
  548. return {"x": [1, 2, 3]}
  549. env = Environment(extensions=[MagicScopeExtension])
  550. tmpl = env.from_string(
  551. """
  552. {{- x }}|{% set z = 99 %}
  553. {%- overlay %}
  554. {{- y }}|{{ z }}|{% for item in x %}[{{ item }}]{% endfor %}
  555. {%- endoverlay %}|
  556. {{- x -}}
  557. """
  558. )
  559. assert tmpl.render(x=42, y=23) == "42|23|99|[1][2][3]|42"