Skip to content

html (16)

ansi2html

ansi2html(ansi_string: str, styles: dict[int, dict[str, str]] | None = None) -> str

Convert ansi string to colored HTML.

Example

Jinja call:

{{ "\033[31;1;4mHello\033[0m" | ansi2html }}
Result: <span style="color: red; font_weight: bold; text_decoration: underline">Hello</span>

DocStrings

Parameters:

Name Type Description Default
ansi_string str

text with ANSI color codes.

required
styles dict[int, dict[str, str]] | None

A mapping from ANSI codes to a dict with css

None

Returns:

Type Description
str

HTML string

Source code in src/jinjarope/htmlfilters.py
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
def ansi2html(ansi_string: str, styles: dict[int, dict[str, str]] | None = None) -> str:
    """Convert ansi string to colored HTML.

    Args:
        ansi_string: text with ANSI color codes.
        styles: A mapping from ANSI codes to a dict with css

    Returns:
        HTML string
    """
    styles = styles or ANSI_STYLES
    previous_end = 0
    in_span = False
    ansi_codes: list[int] = []
    ansi_finder = re.compile("\033\\[([\\d;]*)([a-zA-z])")
    parts = []
    for match in ansi_finder.finditer(ansi_string):
        parts.append(ansi_string[previous_end : match.start()])
        previous_end = match.end()
        params, command = match.groups()

        if command not in "mM":
            continue

        try:
            params = [int(p) for p in params.split(";")]
        except ValueError:
            params = [0]

        for i, v in enumerate(params):
            if v == 0:
                params = params[i + 1 :]
                if in_span:
                    in_span = False
                    parts.append("</span>")
                ansi_codes = []
                if not params:
                    continue

        ansi_codes.extend(params)
        if in_span:
            parts.append("</span>")
            in_span = False

        if not ansi_codes:
            continue

        style = [
            "; ".join([f"{k}: {v}" for k, v in styles[k].items()]).strip()
            for k in ansi_codes
            if k in styles
        ]
        parts.append(f'<span style="{"; ".join(style)}">')

        in_span = True

    parts.append(ansi_string[previous_end:])
    if in_span:
        parts.append("</span>")
        in_span = False
    return "".join(parts)

clean_svg

clean_svg(text: str) -> str

Strip off unwanted stuff from svg text which might be added by external libs.

Example

Jinja call:

{{ '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<body></body>' | clean_svg }}
Result: <body></body>

DocStrings

Parameters:

Name Type Description Default
text str

The text to cleanup / filter

required
Source code in src/jinjarope/htmlfilters.py
132
133
134
135
136
137
138
139
140
141
142
def clean_svg(text: str) -> str:
    """Strip off unwanted stuff from svg text which might be added by external libs.

    Removes xml headers and doctype declarations.

    Args:
        text: The text to cleanup / filter
    """
    text = re.sub(r"<\?xml version.*\?>\s*", "", text, flags=re.DOTALL)
    text = re.sub(r"<!DOCTYPE svg.*?>", "", text, flags=re.DOTALL)
    return text.strip()

format_css_rule

format_css_rule(dct: 'Mapping') -> 'str'

Format a nested dictionary as CSS rule.

Example

Jinja call:

{{ {"a": {"b": "c"} } | format_css_rule }}
Result: `a { b: c; }

`

DocStrings

Parameters:

Name Type Description Default
dct Mapping

The mapping to convert to CSS text

required
Source code in src/jinjarope/htmlfilters.py
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
def format_css_rule(dct: Mapping) -> str:
    """Format a nested dictionary as CSS rule.

    Mapping must be of shape {".a": {"b": "c"}}

    Args:
        dct: The mapping to convert to CSS text
    """
    data: dict[str, list] = {}

    def _parse(obj, selector: str = ""):
        for key, value in obj.items():
            if hasattr(value, "items"):
                rule = selector + " " + key
                data[rule] = []
                _parse(value, rule)

            else:
                prop = data[selector]
                prop.append(f"\t{key}: {value};\n")

    _parse(dct)
    string = ""
    for key, value in sorted(data.items()):
        if data[key]:
            string += key[1:] + " {\n" + "".join(value) + "}\n\n"
    return string

format_js_map

format_js_map(mapping: dict | str, indent: int = 4) -> str

Return JS map str for given dictionary.

Example

Jinja call:

{{ {"abc": "def"} | format_js_map }}
Result: { abc: 'def', }

DocStrings

Parameters:

Name Type Description Default
mapping dict | str

Dictionary to dump

required
indent int

The amount of indentation for the key-value pairs

4
Source code in src/jinjarope/htmlfilters.py
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
def format_js_map(mapping: dict | str, indent: int = 4) -> str:
    """Return JS map str for given dictionary.

    Args:
        mapping: Dictionary to dump
        indent: The amount of indentation for the key-value pairs
    """
    dct = json.loads(mapping) if isinstance(mapping, str) else mapping
    rows: list[str] = []
    indent_str = " " * indent
    for k, v in dct.items():
        match v:
            case bool():
                rows.append(f"{indent_str}{k}: {str(v).lower()},")
            case dict():
                rows.append(f"{indent_str}{k}: {format_js_map(v)},")
            case None:
                rows.append(f"{indent_str}{k}: null,")
            case _:
                rows.append(f"{indent_str}{k}: {v!r},")
    row_str = "\n" + "\n".join(rows) + "\n"
    return f"{{{row_str}}}"

format_xml

format_xml(str_or_elem: str | xml.etree.ElementTree.Element, indent: str | int = ' ', level: int = 0, method: Literal['xml', 'html', 'text', 'c14n'] = 'html', short_empty_elements: bool = True, add_declaration: bool = False) -> str

(Pretty)print given XML.

Example

Jinja call:

{{ '<a><b><c>Hello!</c></b></a>' | format_xml }}
Result: <a> <b> <c>Hello!</c> </b> </a>

Example

Jinja call:

{{ '<a><b><c>Hello!</c></b></a>' | format_xml(indent=4) }}
Result: <a> <b> <c>Hello!</c> </b> </a>

Example

Jinja call:

{{ '<br></br>' | format_xml(short_empty_elements=True, method="xml") }}
Result: <br />

Example

Jinja call:

{{ '<br></br>' | format_xml(short_empty_elements=True, add_declaration=True, method="xml") }}
Result: <?xml version='1.0' encoding='utf-8'?> <br />

DocStrings

Parameters:

Name Type Description Default
str_or_elem str | Element

XML to prettyprint

required
indent str | int

Amount of spaces to use for indentation

' '
level int

Initial indentation level

0
method Literal['xml', 'html', 'text', 'c14n']

Output method

'html'
short_empty_elements bool

Whether empty elements should get printed in short form (applies when mode is "xml")

True
add_declaration bool

whether a XML declaration should be printed (applies when mode is "xml")

False
Source code in src/jinjarope/htmlfilters.py
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
@functools.lru_cache
def format_xml(
    str_or_elem: str | ET.Element,
    indent: str | int = "  ",
    level: int = 0,
    method: Literal["xml", "html", "text", "c14n"] = "html",
    short_empty_elements: bool = True,
    add_declaration: bool = False,
) -> str:
    """(Pretty)print given XML.

    Args:
        str_or_elem: XML to prettyprint
        indent: Amount of spaces to use for indentation
        level: Initial indentation level
        method: Output method
        short_empty_elements: Whether empty elements should get printed in short form
                              (applies when mode is "xml")
        add_declaration: whether a XML declaration should be printed
                         (applies when mode is "xml")
    """
    if isinstance(str_or_elem, str):
        str_or_elem = ET.fromstring(str_or_elem)
    space = indent if isinstance(indent, str) else indent * " "
    ET.indent(str_or_elem, space=space, level=level)
    return ET.tostring(
        str_or_elem,
        encoding="unicode",
        method=method,
        xml_declaration=add_declaration,
        short_empty_elements=short_empty_elements,
    )

html_link(text: str | None = None, link: str | None = None, **kwargs: Any) -> str

Create a html link.

Example

Jinja call:

{{ "Test" | html_link("https://google.com") }}
Result: <a href='https://google.com'>Test</a>

DocStrings

Parameters:

Name Type Description Default
text str | None

Text to show for the link

None
link str | None

Target url

None
kwargs Any

additional key-value pairs to be inserted as attributes. Key strings will have "_" stripped from the end to allow using keywords.

{}
Source code in src/jinjarope/htmlfilters.py
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
def html_link(text: str | None = None, link: str | None = None, **kwargs: Any) -> str:
    """Create a html link.

    If link is empty string or None, just the text will get returned.

    Args:
        text: Text to show for the link
        link: Target url
        kwargs: additional key-value pairs to be inserted as attributes.
                Key strings will have "_" stripped from the end to allow using keywords.
    """
    if not link:
        return text or ""
    attrs = [f'{k.rstrip("_")}="{v}"' for k, v in kwargs.items()]
    attr_str = (" " + " ".join(attrs)) if attrs else ""
    return f"<a href={link!r}{attr_str}>{text or link}</a>"

inject_javascript

inject_javascript(html_content: ~ContentType, javascript: str, *, position: Literal['body', 'head', 'end_head', 'end_body'] = 'end_body') -> ~ContentType

Injects JavaScript code into an HTML string or bytes object.

Example

Jinja call:

{{ "<body>some HTML</body>" | inject_javascript("var number = 1;") }}
Result: <body>some HTML<script>var number = 1;</script></body>

Example

Jinja call:

{{ "<head>HEAD</head><body>some HTML</body>" | inject_javascript("var number = 1;", position="head") }}
Result: <head><script>var number = 1;</script>HEAD</head><body>some HTML</body>

DocStrings

Parameters:

Name Type Description Default
html_content ContentType

The HTML content to inject the JavaScript into

required
javascript str

The JavaScript code to inject

required
position Position

The position to inject the JavaScript ('body' by default)

'end_body'

Returns:

Type Description
ContentType

The modified HTML content with the same type as the input

Raises:

Type Description
ValueError

If the specified position tag is not found in the HTML content

TypeError

If the input type is neither str nor bytes

Source code in src/jinjarope/htmlfilters.py
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
def inject_javascript(
    html_content: ContentType,
    javascript: str,
    *,
    position: Position = "end_body",
) -> ContentType:
    """Injects JavaScript code into an HTML string or bytes object.

    Args:
        html_content: The HTML content to inject the JavaScript into
        javascript: The JavaScript code to inject
        position: The position to inject the JavaScript ('body' by default)

    Returns:
        The modified HTML content with the same type as the input

    Raises:
        ValueError: If the specified position tag is not found in the HTML content
        TypeError: If the input type is neither str nor bytes
    """
    # Convert bytes to str if needed
    is_bytes = isinstance(html_content, bytes)
    working_content: str = html_content.decode() if is_bytes else html_content  # type: ignore[assignment, attr-defined]

    # Prepare the JavaScript tag
    script_tag = f"<script>{javascript}</script>"

    # Define the injection patterns
    patterns = {
        "body": (r"<body[^>]*>", lambda m: f"{m.group(0)}{script_tag}"),
        "head": (r"<head[^>]*>", lambda m: f"{m.group(0)}{script_tag}"),
        "end_head": (r"</head>", lambda m: f"{script_tag}{m.group(0)}"),
        "end_body": (r"</body>", lambda m: f"{script_tag}{m.group(0)}"),
    }

    if position not in patterns:
        msg = f"Invalid position: {position}. Must be one of {list(patterns.keys())}"
        raise ValueError(msg)

    pattern, replacement = patterns[position]
    modified_content = re.sub(pattern, replacement, working_content, count=1)

    # If no substitution was made, the tag wasn't found
    if modified_content == working_content:
        msg = f"Could not find {position} tag in HTML content"
        raise ValueError(msg)
    # Return the same type as input
    return modified_content.encode() if is_bytes else modified_content  # type: ignore[return-value]

normalize_url

normalize_url(path: str, url: str | None = None, base: str = '') -> str

Return a URL relative to the given url or using the base.

Example

Jinja call:

{{ "test" | normalize_url(base="https://phil65.github.io") }}
Result: https://phil65.github.io/test

DocStrings

Parameters:

Name Type Description Default
path str

The path to normalize

required
url str | None

Optional relative url

None
base str

Base path

''
Source code in src/jinjarope/htmlfilters.py
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
@functools.lru_cache
def normalize_url(path: str, url: str | None = None, base: str = "") -> str:
    """Return a URL relative to the given url or using the base.

    Args:
        path: The path to normalize
        url: Optional relative url
        base: Base path
    """
    path, relative_level = _get_norm_url(path)
    if relative_level == -1:
        return path
    if url is None:
        return posixpath.join(base, path)
    result = relative_url(url, path)
    if relative_level > 0:
        result = "../" * relative_level + result
    return result

relative_url

relative_url(url_a: str, url_b: str) -> str

Compute the relative path from URL A to URL B.

Example

Jinja call:

{{ "https://phil65.github.io" | relative_url("https://phil65.github.io/mknodes") }}
Result: mknodes

DocStrings

Parameters:

Name Type Description Default
url_a str

URL A.

required
url_b str

URL B.

required

Returns:

Type Description
str

The relative URL to go from A to B.

Source code in src/jinjarope/htmlfilters.py
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
@functools.lru_cache
def relative_url(url_a: str, url_b: str) -> str:
    """Compute the relative path from URL A to URL B.

    Args:
        url_a: URL A.
        url_b: URL B.

    Returns:
        The relative URL to go from A to B.
    """
    parts_a = url_a.split("/")
    if "#" in url_b:
        url_b, anchor = url_b.split("#", 1)
    else:
        anchor = None
    parts_b = url_b.split("/")

    # remove common left parts
    while parts_a and parts_b and parts_a[0] == parts_b[0]:
        parts_a.pop(0)
        parts_b.pop(0)

    # go up as many times as remaining a parts' depth
    levels = len(parts_a) - 1
    parts_relative = [".."] * levels + parts_b
    relative = "/".join(parts_relative)
    return f"{relative}#{anchor}" if anchor else relative

relative_url_mkdocs

relative_url_mkdocs(url: str, other: str) -> str

Return given url relative to other (MkDocs implementation).

Example

Jinja call:

{{ "https://phil65.github.io" | relative_url_mkdocs("https://phil65.github.io/mknodes") }}
Result: ..

DocStrings

Parameters:

Name Type Description Default
url str

URL A.

required
other str

URL B.

required
Source code in src/jinjarope/htmlfilters.py
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
def relative_url_mkdocs(url: str, other: str) -> str:
    """Return given url relative to other (MkDocs implementation).

    Both are operated as slash-separated paths, similarly to the 'path' part of a URL.
    The last component of `other` is skipped if it contains a dot (considered a file).
    Actual URLs (with schemas etc.) aren't supported. The leading slash is ignored.
    Paths are normalized ('..' works as parent directory), but going higher than the
    root has no effect ('foo/../../bar' ends up just as 'bar').

    Args:
        url: URL A.
        other: URL B.
    """
    # Remove filename from other url if it has one.
    dirname, _, basename = other.rpartition("/")
    if "." in basename:
        other = dirname

    other_parts = _norm_parts(other)
    dest_parts = _norm_parts(url)
    common = 0
    for a, b in zip(other_parts, dest_parts):
        if a != b:
            break
        common += 1

    rel_parts = [".."] * (len(other_parts) - common) + dest_parts[common:]
    relurl = "/".join(rel_parts) or "."
    return relurl + "/" if url.endswith("/") else relurl

svg_to_data_uri

svg_to_data_uri(svg: str) -> str

Wrap svg as data URL.

Example

Jinja call:

{{ "<svg>...</svg>" | svg_to_data_uri }}
Result: url('data:image/svg+xml;charset=utf-8,<svg>...</svg>')

DocStrings

Parameters:

Name Type Description Default
svg str

The svg to wrap into a data URL

required
Source code in src/jinjarope/htmlfilters.py
120
121
122
123
124
125
126
127
128
129
def svg_to_data_uri(svg: str) -> str:
    """Wrap svg as data URL.

    Args:
        svg: The svg to wrap into a data URL
    """
    if not isinstance(svg, str):
        msg = "Invalid type: %r"
        raise TypeError(msg, type(svg))
    return f"url('data:image/svg+xml;charset=utf-8,{svg}')"

url_to_b64

url_to_b64(image_url: str) -> str | None

Convert an image URL to a base64-encoded string.

Example

Jinja call:

{{ "https://picsum.photos/100/100" | url_to_b64 }}
Result: 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

DocStrings

Parameters:

Name Type Description Default
image_url str

The URL of the image to convert.

required

Returns:

Type Description
str | None

The base64-encoded string of the image.

Raises:

Type Description
RequestException

If there's an error downloading the image.

Source code in src/jinjarope/htmlfilters.py
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
def url_to_b64(image_url: str) -> str | None:
    """Convert an image URL to a base64-encoded string.

    Args:
        image_url: The URL of the image to convert.

    Returns:
        The base64-encoded string of the image.

    Raises:
        requests.RequestException: If there's an error downloading the image.
    """
    # Download the image
    response = requests.get(image_url)
    response.raise_for_status()
    image_data = response.content

    # Encode the image to base64
    return base64.b64encode(image_data).decode("utf-8")

urlencode

urlencode(value: Union[str, Mapping[str, Any], Iterable[Tuple[str, Any]]]) -> str

Quote data for use in a URL path or query using UTF-8.

Example

Jinja call:

{{ "Some text" | urlencode }}
Result: Some%20text

Example

Jinja call:

{{ {"key": "value with spaces"} | urlencode }}
Result: key=value+with+spaces

DocStrings
Source code in .venv/lib/python3.12/site-packages/jinja2/filters.py
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
def do_urlencode(
    value: t.Union[str, t.Mapping[str, t.Any], t.Iterable[t.Tuple[str, t.Any]]],
) -> str:
    """Quote data for use in a URL path or query using UTF-8.

    Basic wrapper around :func:`urllib.parse.quote` when given a
    string, or :func:`urllib.parse.urlencode` for a dict or iterable.

    :param value: Data to quote. A string will be quoted directly. A
        dict or iterable of ``(key, value)`` pairs will be joined as a
        query string.

    When given a string, "/" is not quoted. HTTP servers treat "/" and
    "%2F" equivalently in paths. If you need quoted slashes, use the
    ``|replace("/", "%2F")`` filter.

    .. versionadded:: 2.7
    """
    if isinstance(value, str) or not isinstance(value, abc.Iterable):
        return url_quote(value)

    if isinstance(value, dict):
        items: t.Iterable[t.Tuple[str, t.Any]] = value.items()
    else:
        items = value  # type: ignore

    return "&".join(
        f"{url_quote(k, for_qs=True)}={url_quote(v, for_qs=True)}" for k, v in items
    )

urlize

urlize(eval_ctx: 'EvalContext', value: str, trim_url_limit: Optional[int] = None, nofollow: bool = False, target: Optional[str] = None, rel: Optional[str] = None, extra_schemes: Optional[Iterable[str]] = None) -> str

Convert URLs in text into clickable links.

Example

Jinja call:

{{ "some text" | urlize }}
Result: some text

Example

Jinja call:

{{ "https://example.com" | urlize(target="_blank") }}
Result: <a href="https://example.com" rel="noopener" target="_blank">https://example.com</a>

DocStrings
Source code in .venv/lib/python3.12/site-packages/jinja2/filters.py
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
@pass_eval_context
def do_urlize(
    eval_ctx: "EvalContext",
    value: str,
    trim_url_limit: t.Optional[int] = None,
    nofollow: bool = False,
    target: t.Optional[str] = None,
    rel: t.Optional[str] = None,
    extra_schemes: t.Optional[t.Iterable[str]] = None,
) -> str:
    """Convert URLs in text into clickable links.

    This may not recognize links in some situations. Usually, a more
    comprehensive formatter, such as a Markdown library, is a better
    choice.

    Works on ``http://``, ``https://``, ``www.``, ``mailto:``, and email
    addresses. Links with trailing punctuation (periods, commas, closing
    parentheses) and leading punctuation (opening parentheses) are
    recognized excluding the punctuation. Email addresses that include
    header fields are not recognized (for example,
    ``mailto:address@example.com?cc=copy@example.com``).

    :param value: Original text containing URLs to link.
    :param trim_url_limit: Shorten displayed URL values to this length.
    :param nofollow: Add the ``rel=nofollow`` attribute to links.
    :param target: Add the ``target`` attribute to links.
    :param rel: Add the ``rel`` attribute to links.
    :param extra_schemes: Recognize URLs that start with these schemes
        in addition to the default behavior. Defaults to
        ``env.policies["urlize.extra_schemes"]``, which defaults to no
        extra schemes.

    .. versionchanged:: 3.0
        The ``extra_schemes`` parameter was added.

    .. versionchanged:: 3.0
        Generate ``https://`` links for URLs without a scheme.

    .. versionchanged:: 3.0
        The parsing rules were updated. Recognize email addresses with
        or without the ``mailto:`` scheme. Validate IP addresses. Ignore
        parentheses and brackets in more cases.

    .. versionchanged:: 2.8
       The ``target`` parameter was added.
    """
    policies = eval_ctx.environment.policies
    rel_parts = set((rel or "").split())

    if nofollow:
        rel_parts.add("nofollow")

    rel_parts.update((policies["urlize.rel"] or "").split())
    rel = " ".join(sorted(rel_parts)) or None

    if target is None:
        target = policies["urlize.target"]

    if extra_schemes is None:
        extra_schemes = policies["urlize.extra_schemes"] or ()

    for scheme in extra_schemes:
        if _uri_scheme_re.fullmatch(scheme) is None:
            raise FilterArgumentError(f"{scheme!r} is not a valid URI scheme prefix.")

    rv = urlize(
        value,
        trim_url_limit=trim_url_limit,
        rel=rel,
        target=target,
        extra_schemes=extra_schemes,
    )

    if eval_ctx.autoescape:
        rv = Markup(rv)

    return rv

wrap_in_elem

wrap_in_elem(text: str | None, tag: str, add_linebreaks: bool = False, **kwargs: Any) -> str

Wrap given text in an HTML/XML tag (with attributes).

Example

Jinja call:

{{ "abc" | wrap_in_elem("test") }}
Result: <test>abc</test>

DocStrings

Parameters:

Name Type Description Default
text str | None

Text to wrap

required
tag str

Tag to wrap text in

required
add_linebreaks bool

Adds a linebreak before and after the text

False
kwargs Any

additional key-value pairs to be inserted as attributes for tag. Key strings will have "_" stripped from the end to allow using keywords.

{}
Source code in src/jinjarope/htmlfilters.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
def wrap_in_elem(
    text: str | None,
    tag: str,
    add_linebreaks: bool = False,
    **kwargs: Any,
) -> str:
    """Wrap given text in an HTML/XML tag (with attributes).

    If text is empty, just return an empty string.

    Args:
        text: Text to wrap
        tag: Tag to wrap text in
        add_linebreaks: Adds a linebreak before and after the text
        kwargs: additional key-value pairs to be inserted as attributes for tag.
                Key strings will have "_" stripped from the end to allow using keywords.
    """
    if not text:
        return ""
    attrs = [f'{k.rstrip("_")}="{v}"' for k, v in kwargs.items()]
    attr_str = (" " + " ".join(attrs)) if attrs else ""
    nl = "\n" if add_linebreaks else ""
    return f"<{tag}{attr_str}>{nl}{text}{nl}</{tag}>"

xmlattr

xmlattr(eval_ctx: 'EvalContext', d: Mapping[str, Any], autospace: bool = True) -> str

Create an SGML/XML attribute string based on the items in a dict.

Example

Jinja call:

<ul{{ {'class': 'my_list', 'missing': none,
        'id': 'some-id'}|xmlattr }}>
Result: <ul class="my_list" id="some-id">

Example

Jinja call:

<div{{ {'data-items': ['a', 'b'], 'disabled': true}|xmlattr }}>
Result: <div data-items="[&#39;a&#39;, &#39;b&#39;]" disabled="True">

DocStrings
Source code in .venv/lib/python3.12/site-packages/jinja2/filters.py
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
@pass_eval_context
def do_xmlattr(
    eval_ctx: "EvalContext", d: t.Mapping[str, t.Any], autospace: bool = True
) -> str:
    """Create an SGML/XML attribute string based on the items in a dict.

    **Values** that are neither ``none`` nor ``undefined`` are automatically
    escaped, safely allowing untrusted user input.

    User input should not be used as **keys** to this filter. If any key
    contains a space, ``/`` solidus, ``>`` greater-than sign, or ``=`` equals
    sign, this fails with a ``ValueError``. Regardless of this, user input
    should never be used as keys to this filter, or must be separately validated
    first.

    .. sourcecode:: html+jinja

        <ul{{ {'class': 'my_list', 'missing': none,
                'id': 'list-%d'|format(variable)}|xmlattr }}>
        ...
        </ul>

    Results in something like this:

    .. sourcecode:: html

        <ul class="my_list" id="list-42">
        ...
        </ul>

    As you can see it automatically prepends a space in front of the item
    if the filter returned something unless the second parameter is false.

    .. versionchanged:: 3.1.4
        Keys with ``/`` solidus, ``>`` greater-than sign, or ``=`` equals sign
        are not allowed.

    .. versionchanged:: 3.1.3
        Keys with spaces are not allowed.
    """
    items = []

    for key, value in d.items():
        if value is None or isinstance(value, Undefined):
            continue

        if _attr_key_re.search(key) is not None:
            raise ValueError(f"Invalid character in attribute name: {key!r}")

        items.append(f'{escape(key)}="{escape(value)}"')

    rv = " ".join(items)

    if autospace and rv:
        rv = " " + rv

    if eval_ctx.autoescape:
        rv = Markup(rv)

    return rv