team-10/env/Lib/site-packages/streamlit/elements/html.py
2025-08-02 07:34:44 +02:00

178 lines
6.8 KiB
Python

# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import os
import re
from pathlib import Path
from typing import TYPE_CHECKING, Any, cast
from streamlit.delta_generator_singletons import get_dg_singleton_instance
from streamlit.elements.lib.layout_utils import (
LayoutConfig,
Width,
validate_width,
)
from streamlit.errors import StreamlitAPIException
from streamlit.proto.Html_pb2 import Html as HtmlProto
from streamlit.runtime.metrics_util import gather_metrics
from streamlit.string_util import clean_text
from streamlit.type_util import SupportsReprHtml, SupportsStr, has_callable_attr
if TYPE_CHECKING:
from streamlit.delta_generator import DeltaGenerator
class HtmlMixin:
@gather_metrics("html")
def html(
self,
body: str | Path | SupportsStr | SupportsReprHtml,
*, # keyword-only arguments:
width: Width = "stretch",
) -> DeltaGenerator:
"""Insert HTML into your app.
Adding custom HTML to your app impacts safety, styling, and
maintainability. We sanitize HTML with `DOMPurify
<https://github.com/cure53/DOMPurify>`_, but inserting HTML remains a
developer risk. Passing untrusted code to ``st.html`` or dynamically
loading external code can increase the risk of vulnerabilities in your
app.
``st.html`` content is **not** iframed. Executing JavaScript is not
supported at this time.
Parameters
----------
body : any
The HTML code to insert. This can be one of the following:
- A string of HTML code.
- A path to a local file with HTML code. The path can be a ``str``
or ``Path`` object. Paths can be absolute or relative to the
working directory (where you execute ``streamlit run``).
- Any object. If ``body`` is not a string or path, Streamlit will
convert the object to a string. ``body._repr_html_()`` takes
precedence over ``str(body)`` when available.
If the resulting HTML content is empty, Streamlit will raise an
error.
If ``body`` is a path to a CSS file, Streamlit will wrap the CSS
content in ``<style>`` tags automatically. When the resulting HTML
content only contains style tags, Streamlit will send the content
to the event container instead of the main container to avoid
taking up space in the app.
width : "stretch", "content", or int
The width of the HTML element. This can be one of the following:
- ``"stretch"`` (default): The width of the element matches the
width of the parent container.
- ``"content"``: The width of the element matches the width of its
content, but doesn't exceed the width of the parent container.
- An integer specifying the width in pixels: The element has a
fixed width. If the specified width is greater than the width of
the parent container, the width of the element matches the width
of the parent container.
Example
-------
>>> import streamlit as st
>>>
>>> st.html(
... "<p><span style='text-decoration: line-through double red;'>Oops</span>!</p>"
... )
.. output::
https://doc-html.streamlit.app/
height: 300px
"""
html_proto = HtmlProto()
# If body supports _repr_html_, use that.
if has_callable_attr(body, "_repr_html_"):
html_content = cast("SupportsReprHtml", body)._repr_html_()
# Check if the body is a file path. May include filesystem lookup.
elif isinstance(body, Path) or _is_file(body):
file_path = str(body)
with open(file_path, encoding="utf-8") as f:
html_content = f.read()
# If it's a CSS file, wrap the content in style tags
if Path(file_path).suffix.lower() == ".css":
html_content = f"<style>{html_content}</style>"
# OK, let's just try converting to string and hope for the best.
else:
html_content = clean_text(cast("SupportsStr", body))
# Raise an error if the body is empty
if html_content == "":
raise StreamlitAPIException("`st.html` body cannot be empty")
validate_width(width, allow_content=True)
layout_config = LayoutConfig(width=width)
# Handle the case where there are only style tags - issue #9388
# Use event container for style tags so they don't take up space in the app content
if _html_only_style_tags(html_content):
# If true, there are only style tags - send html to the event container
html_proto.body = html_content
return self._event_dg._enqueue("html", html_proto)
# Otherwise, send the html to the main container as normal
html_proto.body = html_content
return self.dg._enqueue("html", html_proto, layout_config=layout_config)
@property
def dg(self) -> DeltaGenerator:
"""Get our DeltaGenerator."""
return cast("DeltaGenerator", self)
@property
def _event_dg(self) -> DeltaGenerator:
"""Get the event delta generator."""
return get_dg_singleton_instance().event_dg
def _html_only_style_tags(html_content: str) -> bool:
"""Check if the HTML content is only style tags."""
# Pattern to match HTML comments
comment_pattern = r"<!--.*?-->"
# Pattern to match style tags and their contents (case-insensitive)
style_pattern = r"<style[^>]*>.*?</style>"
# Remove style tags and comments
html_without_comments = re.sub(comment_pattern, "", html_content, flags=re.DOTALL)
html_without_styles_and_comments = re.sub(
style_pattern, "", html_without_comments, flags=re.DOTALL | re.IGNORECASE
)
# Return whether html content is empty after removing style tags and comments
return html_without_styles_and_comments.strip() == ""
def _is_file(obj: Any) -> bool:
"""Checks if obj is a file, and doesn't throw if not.
The "not throwing" part is important!
"""
try:
return os.path.isfile(obj)
except TypeError:
return False