python-liquid 2.0.2__py3-none-any.whl → 2.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
liquid/__init__.py CHANGED
@@ -56,7 +56,7 @@ from .tag import Tag
56
56
 
57
57
  from . import future
58
58
 
59
- __version__ = "2.0.2"
59
+ __version__ = "2.1.0"
60
60
 
61
61
  __all__ = (
62
62
  "AwareBoundTemplate",
@@ -21,6 +21,7 @@ from .filters.array import sort_natural
21
21
  from .filters.array import sum_
22
22
  from .filters.array import uniq
23
23
  from .filters.array import where
24
+ from .filters.extra import escapejs
24
25
  from .filters.extra import safe
25
26
  from .filters.math import abs_
26
27
  from .filters.math import at_least
@@ -197,3 +198,4 @@ def register(env: Environment) -> None: # noqa: PLR0915
197
198
  env.add_filter("date", date)
198
199
 
199
200
  env.add_filter("safe", safe)
201
+ env.add_filter("escapejs", escapejs)
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import re
5
6
  from typing import TYPE_CHECKING
6
7
 
7
8
  from liquid import Markup
@@ -19,3 +20,64 @@ def safe(val: str, *, environment: Environment) -> str:
19
20
  if environment.autoescape:
20
21
  return Markup(val)
21
22
  return val
23
+
24
+
25
+ # `escapejs` is inspired by https://github.com/salesforce/secure-filters and Django's
26
+ # escapejs filter, https://github.com/django/django/blob/485f483d49144a2ea5401442bc3b937a370b3ca6/django/utils/html.py#L63
27
+
28
+ _ESCAPE_MAP = {
29
+ "\\": "\\u005C",
30
+ "'": "\\u0027",
31
+ '"': "\\u0022",
32
+ ">": "\\u003E",
33
+ "<": "\\u003C",
34
+ "&": "\\u0026",
35
+ "=": "\\u003D",
36
+ "-": "\\u002D",
37
+ ";": "\\u003B",
38
+ "`": "\\u0060",
39
+ "\u2028": "\\u2028",
40
+ "\u2029": "\\u2029",
41
+ }
42
+
43
+ _ESCAPE_MAP.update({chr(c): f"\\u{c:04X}" for c in range(32)})
44
+ _ESCAPE_RE = re.compile("[" + re.escape("".join(_ESCAPE_MAP.keys())) + "]")
45
+
46
+
47
+ @with_environment
48
+ @string_filter
49
+ def escapejs(val: str, *, environment: Environment) -> str:
50
+ """Escape characters for safe use in JavaScript string literals.
51
+
52
+ This filter escapes a string for embedding inside **JavaScript string
53
+ literals**, using either single or double quotes (e.g. `'...'` or `"..."`).
54
+ It replaces control characters and potentially dangerous symbols with
55
+ their corresponding Unicode escape sequences.
56
+
57
+ **Important:** This filter does **not** make strings safe for use in
58
+ JavaScript template literals (backtick strings), or in raw JavaScript
59
+ expressions. Use it only when placing data inside quoted JS strings
60
+ within inline `<script>` blocks or event handlers.
61
+
62
+ **Recommended alternatives:**
63
+ - Pass data using HTML `data-*` attributes and read them in JS via
64
+ `element.dataset`.
65
+ - For structured data, prefer a JSON-serialization approach using the
66
+ JSON filter.
67
+
68
+ Escaped characters include:
69
+ - ASCII control characters (U+0000 to U+001F)
70
+ - Characters like quotes, angle brackets, ampersands, equals signs
71
+ - Line/paragraph separators (U+2028, U+2029)
72
+
73
+ Args:
74
+ val: The input string to escape.
75
+ environment: The active Liquid environment
76
+
77
+ Returns:
78
+ A JavaScript-safe string, with problematic characters escaped as Unicode.
79
+ """
80
+ escaped = _ESCAPE_RE.sub(lambda m: _ESCAPE_MAP[m.group()], val)
81
+ if environment.autoescape:
82
+ return Markup(escaped)
83
+ return escaped
@@ -56,7 +56,35 @@ def downcase(val: str) -> str:
56
56
  @with_environment
57
57
  @string_filter
58
58
  def escape(val: str, *, environment: Environment) -> str:
59
- """Return _val_ with the characters &, < and > converted to HTML-safe sequences."""
59
+ """Escape special characters in a string for safe use in HTML.
60
+
61
+ This filter replaces the characters `&`, `<`, `>`, `'`, and `"` with their
62
+ corresponding HTML-safe sequences:
63
+
64
+ - `&` -> `&amp;`
65
+ - `<` -> `&lt;`
66
+ - `>` -> `&gt;`
67
+ - `'` -> `&#39;`
68
+ - `"` -> `&#34;`
69
+
70
+ This helps prevent HTML injection (XSS) when rendering untrusted content in
71
+ HTML element bodies or attributes.
72
+
73
+ Important: This filter does **not** make strings safe for use in JavaScript,
74
+ including in `<script>` blocks, inline event handler attributes (e.g. `onerror`),
75
+ or other JavaScript contexts. For those cases, use the `escapejs` filter instead.
76
+
77
+ When `autoescape` is enabled in the environment, this filter uses the same
78
+ escaping logic as the environment (via `markupsafe.escape()`). Otherwise, it
79
+ falls back to Python's standard `html.escape()`.
80
+
81
+ Args:
82
+ val: The input string to escape.
83
+ environment: The current rendering environment.
84
+
85
+ Returns:
86
+ A string with HTML-special characters replaced by safe escape sequences.
87
+ """
60
88
  if environment.autoescape:
61
89
  return markupsafe_escape(str(val))
62
90
  return html.escape(val)
@@ -65,10 +93,14 @@ def escape(val: str, *, environment: Environment) -> str:
65
93
  @with_environment
66
94
  @string_filter
67
95
  def escape_once(val: str, *, environment: Environment) -> str:
68
- """Return _val_ with the characters &, < and > converted to HTML-safe sequences.
96
+ """Escape a string for HTML, but avoid double-escaping existing entities.
97
+
98
+ Converts characters like `&`, `<`, and `>` to their HTML-safe sequences,
99
+ but leaves existing HTML entities untouched (e.g., `&amp;` stays `&amp;`).
100
+
101
+ This is useful when escaping content that may already be partially escaped.
69
102
 
70
- It is safe to use `escape_one` on string values that already contain HTML escape
71
- sequences.
103
+ See the `escape` filter for details and limitations.
72
104
  """
73
105
  if environment.autoescape:
74
106
  return Markup(val).unescape()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-liquid
3
- Version: 2.0.2
3
+ Version: 2.1.0
4
4
  Summary: A Python engine for the Liquid template language.
5
5
  Project-URL: Change Log, https://github.com/jg-rp/liquid/blob/main/CHANGES.md
6
6
  Project-URL: Documentation, https://jg-rp.github.io/liquid/
@@ -105,6 +105,7 @@ $ conda install -c conda-forge python-liquid
105
105
 
106
106
  - [Python Liquid2](https://github.com/jg-rp/python-liquid2): A new Python engine for Liquid with [extra features](https://jg-rp.github.io/python-liquid2/migration/#new-features).
107
107
  - [Ruby Liquid2](https://github.com/jg-rp/ruby-liquid2): Liquid2 templates for Ruby.
108
+ - [Micro Liquid](https://github.com/jg-rp/micro-liquid): A stripped-down Liquid-like template engine for Python. You can think of it as a non-evaluating alternative to Python's [f-strings](https://docs.python.org/3/tutorial/inputoutput.html#formatted-string-literals) or [t-strings](https://docs.python.org/3.14/whatsnew/3.14.html#whatsnew314-pep750).
108
109
  - [LiquidScript](https://github.com/jg-rp/liquidscript): A JavaScript engine for Liquid with a similar high-level API to Python Liquid.
109
110
 
110
111
  ## Quick Start
@@ -1,4 +1,4 @@
1
- liquid/__init__.py,sha256=zUfwx8pnq269-kP7EQShj4j2J8Yi6D04SRDE6hWcsk8,7696
1
+ liquid/__init__.py,sha256=gQBAgPnQURpFuvzPsHbB0XVy2LtBlofIRTVfl4fyvjs,7696
2
2
  liquid/analyze_tags.py,sha256=Uc1nueKLRiIejs2JyQIC7u2pxp9l-5HJAMWlg-Qj1m8,7615
3
3
  liquid/ast.py,sha256=ec-c8F7B2_yj2FmYiOFnnvu2JSd7c4mzTDGlYeztQ_8,7653
4
4
  liquid/context.py,sha256=gSJI1mj7mdcUfiQ09_XCEj5JxAosCGEX85Uuzl9apeI,21505
@@ -22,7 +22,7 @@ liquid/tag.py,sha256=h5jexl6NjUQnM2py4s5db1ksh0s8vi31YHud5kHJt1E,1189
22
22
  liquid/template.py,sha256=hLylYHSf9_nkLUymfn8o1hGMrHRhiRfU_QRFMjLPw_A,22376
23
23
  liquid/token.py,sha256=gxqMGcGk1fWihz5gQ2BQJzqSEcY7tCAZiIjcdC34P1w,3995
24
24
  liquid/undefined.py,sha256=Y57D69YR6yXSHV4x1rg1xtoTe-6M49h-mqlSO7pcRSE,5673
25
- liquid/builtin/__init__.py,sha256=nOT-bLhh19dktie7SSFOpTaN3mVR-7tDUAJUWGStyJg,6941
25
+ liquid/builtin/__init__.py,sha256=oEzqtKMvqvfoqzcN1Sq_Qbq094hySWNTCnE5Ga-CkcU,7018
26
26
  liquid/builtin/content.py,sha256=HXo2ty2cQxsOkXJDhWmcAWC0ny73w9oABaer50zGp70,1122
27
27
  liquid/builtin/illegal.py,sha256=RRzzlqo2_scdtbMt_hZq0pr-7khgbxrdub9XH-_goPs,844
28
28
  liquid/builtin/output.py,sha256=4r_QJimFTbBdubhdIOG_nweROEOXiWHlp_xNmWXy564,2279
@@ -38,10 +38,10 @@ liquid/builtin/expressions/path.py,sha256=m1J33uIuEFlUaRXR_-G6qcsClOm0B5jbTZ0Pdt
38
38
  liquid/builtin/expressions/primitive.py,sha256=Gzaeb-dF84zsMn5ZYhe-FOosnSbkkM2C6JLqjLvK5ns,11352
39
39
  liquid/builtin/filters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
40
  liquid/builtin/filters/array.py,sha256=dMuT15EASXU4ejmNBmawdi92u0f7y_JhqVaGoP48zlg,9843
41
- liquid/builtin/filters/extra.py,sha256=5YWAz1EoQMbtu7G0yLNtiBVoWUTIehaNuZNIs1VVwN0,550
41
+ liquid/builtin/filters/extra.py,sha256=m13_1nbZyRBl3CZV9WWGLlUZueRPt8cKPEHRAioqM_o,2710
42
42
  liquid/builtin/filters/math.py,sha256=A_K2wg5gxm5Ncj9zMGDX_Lt7jjx4bRr3CywbCiCtqxo,3925
43
43
  liquid/builtin/filters/misc.py,sha256=t2ZsaDLKRc1-8XxZaiEOh7oAMqFJ4N5-tzfbpLHbmIQ,3466
44
- liquid/builtin/filters/string.py,sha256=eIzVvC3tltxMf7Mksl3ftoPY_hQ4rDD06CmPD_Y-tQo,10754
44
+ liquid/builtin/filters/string.py,sha256=3Ub1z0VeCeAT07FaDVAazw2J9vFmpqtehtEtujNZxdg,11963
45
45
  liquid/builtin/loaders/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
46
  liquid/builtin/loaders/caching_file_system_loader.py,sha256=-9riAxL8933u_w1Ppweia-WkfFlxVU3aCspdiNyD5xQ,1931
47
47
  liquid/builtin/loaders/choice_loader.py,sha256=FE23RLMOiWmsRGZv9t6QmlG7bgMEUSblnucAv3j0SBM,2816
@@ -89,7 +89,7 @@ liquid/utils/chain_map.py,sha256=nxkw3wwF6ddlGarIuL7Ii2elm4dU80LySgdQx1oift0,151
89
89
  liquid/utils/html.py,sha256=TmqOOpRMsy7fqZLj7X5ybd_XQnWW2YDAVDwTP1Gwf40,1706
90
90
  liquid/utils/lru_cache.py,sha256=p7bXOGaUwJpLsREI2lGSzK6lLna5W_k_zNXKWnPJdos,4022
91
91
  liquid/utils/text.py,sha256=1SwDECNMaqnnZ05je_AZZgxqzZd6U-mvq5jNU3W1-Qk,841
92
- python_liquid-2.0.2.dist-info/METADATA,sha256=pRrdM-kcmk3bakF2Ug4yxZ0jwlHhFekdmJ08dFL9nm8,6342
93
- python_liquid-2.0.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
94
- python_liquid-2.0.2.dist-info/licenses/LICENSE,sha256=yAFURzud5ERNHt1rZIPnTLJ92ep7q8y5yG9g5DUMR_E,1075
95
- python_liquid-2.0.2.dist-info/RECORD,,
92
+ python_liquid-2.1.0.dist-info/METADATA,sha256=hWoZ7ptvnd2teO9UkGuQeDhpYCQxoTbhG42ndbWDwJ0,6694
93
+ python_liquid-2.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
94
+ python_liquid-2.1.0.dist-info/licenses/LICENSE,sha256=yAFURzud5ERNHt1rZIPnTLJ92ep7q8y5yG9g5DUMR_E,1075
95
+ python_liquid-2.1.0.dist-info/RECORD,,