├── .gitignore
├── LICENSE
├── README.md
├── example.py
├── fletify
├── __init__.py
├── converter.py
├── parser.py
└── utils.py
├── requirements.txt
├── setup.py
└── test1.PNG
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .nox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | *.py,cover
50 | .hypothesis/
51 | .pytest_cache/
52 | cover/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | .pybuilder/
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | # For a library or package, you might want to ignore these files since the code is
87 | # intended to run in multiple environments; otherwise, check them in:
88 | # .python-version
89 |
90 | # pipenv
91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
94 | # install all needed dependencies.
95 | #Pipfile.lock
96 |
97 | # poetry
98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99 | # This is especially recommended for binary packages to ensure reproducibility, and is more
100 | # commonly ignored for libraries.
101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102 | #poetry.lock
103 |
104 | # pdm
105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106 | #pdm.lock
107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108 | # in version control.
109 | # https://pdm.fming.dev/#use-with-ide
110 | .pdm.toml
111 |
112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
113 | __pypackages__/
114 |
115 | # Celery stuff
116 | celerybeat-schedule
117 | celerybeat.pid
118 |
119 | # SageMath parsed files
120 | *.sage.py
121 |
122 | # Environments
123 | .env
124 | .venv
125 | env/
126 | venv/
127 | ENV/
128 | env.bak/
129 | venv.bak/
130 |
131 | # Spyder project settings
132 | .spyderproject
133 | .spyproject
134 |
135 | # Rope project settings
136 | .ropeproject
137 |
138 | # mkdocs documentation
139 | /site
140 |
141 | # mypy
142 | .mypy_cache/
143 | .dmypy.json
144 | dmypy.json
145 |
146 | # Pyre type checker
147 | .pyre/
148 |
149 | # pytype static type analyzer
150 | .pytype/
151 |
152 | # Cython debug symbols
153 | cython_debug/
154 |
155 | # PyCharm
156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
158 | # and can be added to the global gitignore or merged into this file. For a more nuclear
159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
160 | #.idea/
161 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Benit Mulindwa
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FletifyHTML
2 |
3 | FletifyHTML is a Python package for converting HTML content into Flet code. It allows you to embed HTML code in your Flet app
4 |
5 | ## Installation
6 | ### (Not avalaible for now,...)
7 | ```bash
8 | pip install fletify
9 | ```
10 | ### Alternative
11 | Install via Github and use the `example.py` to test Fletify
12 |
13 | ```bash
14 | pip install git+https://github.com/Benitmulindwa/FletifyHTML.git
15 | ```
16 | ## Usage
17 | ```python
18 | import flet as ft
19 | from fletify import FletifyHTML
20 |
21 | # Example HTML content
22 | html_content = "
This is a paragraph with a link
"
23 |
24 | # Create a FletifyHTML instance
25 | fletify = FletifyHTML(html_content)
26 |
27 | # Get the Flet code
28 | flet_code = fletify.get_flet()
29 |
30 | def main(page: ft.Page):
31 |
32 | #Display the output, by adding it on the page
33 | page.add(flet_code)
34 | page.update()
35 |
36 | ft.app(target=main)
37 | ```
38 | # OutPut:
39 | 
40 |
41 | `FletifyHTML()` can receive the HTML file containing the content to be displayed, `FletifyHTML(html=)`
42 |
43 | ## Features
44 |
45 | For now, **Fletify** support the following HTML tags:
46 |
47 | `img`, `ul`, `ol`, `li`, `a`, `b`, `strong`, `i`, `em`, `u`, `mark`, `span`, `div`, `p`, `code`, `h1`, `h2`, `h3`, `h4`, `h5`, `h6`, `table`, `tr`, `th`, `td`
48 | Fletify doesn't support CSS style however, it supports inline style using the `style` attribute.
49 | #### eg:
50 | ```html
51 |
52 | ```
53 | these are the supported style properties
54 | `color`, `background-color`, `font-family`, `font-size`, `text-align`, `text-decoration`, `display`, `justify-content`, `margin`, `padding`, `border-radius`, `border`, `width`, `height`
55 |
56 | ## Contributing
57 | If you'd like to contribute to FletifyHTML, please open an issue or submit a pull request.
58 |
59 | 🚨 Give FletifyHTML a try and tell us what you think about it.
60 |
61 | ## License
62 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
63 |
--------------------------------------------------------------------------------
/example.py:
--------------------------------------------------------------------------------
1 | import flet as ft
2 | from fletify import FletifyHTML
3 |
4 |
5 | html_content = """
6 |
7 |
8 |
This title is centered
9 |
10 |
This is a paragraph with a background color.
11 |
12 |
13 |
This text is in the 'mark' tag
14 |
This is a bold text
15 |
This is a STRONG text
16 |
This is a free text.This is a bold text this is la suite.this text is in span1this text is in span2remaining text
17 |
18 |
19 | function example() {
20 | console.log("This is a code block");
21 | }
22 |
23 |
24 |
This is a Table
25 |
26 |
27 |
28 | Header 1 |
29 | Header 2 |
30 | Header 3 |
31 |
32 |
33 | Row 1, Cell 1 |
34 | Row 1, Cell 2 |
35 | Row 1, Cell 3 |
36 |
37 |
38 | Row 2, Cell 1 |
39 | Row 2, Cell 2 |
40 | Row 2, Cell 3 |
41 |
42 |
43 |
44 |
45 | - Item 1
46 | - Item 2
47 |
48 |
Visit Me
49 |

50 |
55 |
56 | """
57 | flet_code = FletifyHTML(html_content)
58 |
59 |
60 | def main(page: ft.Page):
61 | page.scroll = "always"
62 |
63 | page.add(flet_code.get_flet())
64 | page.update()
65 |
66 |
67 | ft.app(target=main)
68 |
--------------------------------------------------------------------------------
/fletify/__init__.py:
--------------------------------------------------------------------------------
1 | from fletify.utils import RenderHTML
2 | from fletify.converter import convert_html_to_flet
3 |
4 |
5 | class FletifyHTML:
6 | def __init__(self, html):
7 | self.html = html
8 | if isinstance(self.html, str):
9 | self._flet = convert_html_to_flet(self.html)
10 | elif isinstance(self.html, RenderHTML):
11 | self._flet = convert_html_to_flet(self.html.display())
12 |
13 | def get_flet(self):
14 | return self._flet
15 |
--------------------------------------------------------------------------------
/fletify/converter.py:
--------------------------------------------------------------------------------
1 | from bs4 import BeautifulSoup
2 | from fletify.parser import parse_html_to_flet
3 |
4 |
5 | def convert_html_to_flet(html_content):
6 | soup = BeautifulSoup(html_content, "html.parser")
7 | flet_code = parse_html_to_flet(soup)
8 | return flet_code
9 |
--------------------------------------------------------------------------------
/fletify/parser.py:
--------------------------------------------------------------------------------
1 | import flet as ft
2 | from bs4 import NavigableString, BeautifulSoup
3 |
4 |
5 |
6 | class HTML:
7 | # ----------------------------------------------------------------------------------------------
8 | """
9 | Supported HTML tags and attributes
10 | """
11 |
12 | class Tags:
13 | IMG = "img"
14 | UL = "ul"
15 | OL = "ol"
16 | LI = "li"
17 | A = "a"
18 | B = "b"
19 | STRONG = "strong"
20 | I = "i"
21 | EM = "em"
22 | U = "u"
23 | MARK = "mark"
24 | SPAN = "span"
25 | DIV = "div"
26 | P = "p"
27 | CODE = "code"
28 | H1 = "h1"
29 | H2 = "h2"
30 | H3 = "h3"
31 | H4 = "h4"
32 | H5 = "h5"
33 | H6 = "h6"
34 | TABLE = "table"
35 | TR = "tr"
36 | TH = "th"
37 | TD = "td"
38 |
39 | class Attrs:
40 | STYLE = "style"
41 | HREF = "href"
42 | SRC = "src"
43 | WIDTH = "width"
44 | HEIGHT = "height"
45 | TYPE = "type"
46 |
47 | TEXT_STYLE_DECORATION = ["underline", "line-through", "overline"]
48 |
49 | HEADINGS_TEXT_SIZE = {
50 | Tags.H1: 32,
51 | Tags.H2: 24,
52 | Tags.H3: 18,
53 | Tags.H4: 16,
54 | Tags.H5: 13,
55 | Tags.H6: 10,
56 | }
57 |
58 | ##UPCOMING STYLE ATTRIBUTES
59 |
60 |
61 | """
62 | style_attributes = [
63 | "box-shadow",
64 | "line-height",
65 | "letter-spacing",
66 | "word-spacing",
67 | "overflow",
68 | "position",
69 | "top",
70 | "right",
71 | "bottom",
72 | "left",
73 | ]
74 | """
75 |
76 |
77 | def parse_html_to_flet(element):
78 | if element.name == HTML.Tags.DIV:
79 | style, align_style = get_style(element, is_a_mapping=True)
80 |
81 | # Map to ft.Column
82 | main_container = ft.Container(
83 | content=ft.Row([], **align_style)
84 | if "alignment" in align_style
85 | else ft.Column([]),
86 | **style,
87 | )
88 | for child in element.children:
89 | if child.name:
90 | # If there's a table ,
91 | if child.name == HTML.Tags.TABLE:
92 | # Call "html_table_to_flet()" function to display the table
93 | html_table_to_flet(element, main_container)
94 |
95 | # Recursively parse child elements
96 | child_flet = parse_html_to_flet(child)
97 | main_container.content.controls.append(child_flet)
98 | return main_container
99 |
100 | # Heading tags
101 | elif element.name in HTML.HEADINGS_TEXT_SIZE.keys():
102 | heading_text = ft.Text(
103 | value=element.text, size=HTML.HEADINGS_TEXT_SIZE[element.name]
104 | )
105 | return heading_text
106 | # Paragraph tag
107 | elif element.name == HTML.Tags.P:
108 | style = get_style(element)
109 | # Map
to ft.Text within ft.Row
110 | paragraph = ft.Row([])
111 |
112 | # Support for nested tags inside the
tag ##STILL NEED IMPROVEMENTS##
113 | if element.children:
114 | for child in element.children:
115 | if child.name:
116 | # Parse the nested element
117 | p_child = parse_html_to_flet(child)
118 | paragraph.controls.append(p_child)
119 |
120 | elif isinstance(child, NavigableString):
121 | # Handle text content directly within the
tag
122 | text_content = child.text
123 | text_element = ft.Text(
124 | spans=[ft.TextSpan(text_content, style=style[0])]
125 | )
126 |
127 | # Add the text_element to the paragraph
128 | paragraph.controls.append(text_element)
129 |
130 | return paragraph
131 | # Link tag
132 | elif element.name == HTML.Tags.A:
133 | # Map to ft.Text with a URL
134 | link = ft.Text(
135 | spans=[
136 | ft.TextSpan(
137 | element.text,
138 | url=element.get(HTML.Attrs.HREF),
139 | style=ft.TextStyle(italic=True, color="blue"),
140 | )
141 | ]
142 | )
143 | return link
144 |
145 | # Image tag
146 | elif element.name == HTML.Tags.IMG:
147 | img_style, _ = get_style(element, is_a_mapping=True)
148 |
149 | # Map
to ft.Image with a source URL
150 | image = ft.Container(
151 | content=ft.Image(src=element.get(HTML.Attrs.SRC)), **img_style
152 | )
153 | return image
154 |
155 | # HTML lists
156 | elif element.name == HTML.Tags.UL or element.name == HTML.Tags.OL:
157 | # Map and to ft.Column
158 | list_container = ft.Column(spacing=0)
159 |
160 | for i, li in enumerate(element.find_all(HTML.Tags.LI)):
161 | _leading = (
162 | ft.Text("•", size=20)
163 | if element.name == HTML.Tags.UL
164 | else ft.Text(f"{i+1}", size=16)
165 | )
166 | list_item = ft.ListTile(title=ft.Text(li.text), leading=_leading)
167 |
168 | list_container.controls.append(list_item)
169 | return list_container
170 |
171 | # Bold Tags
172 | elif element.name == HTML.Tags.B or element.name == HTML.Tags.STRONG:
173 | bold_text = ft.Text(
174 | value=element.text,
175 | weight=ft.FontWeight.BOLD
176 | if element.name == HTML.Tags.B
177 | else ft.FontWeight.W_900,
178 | )
179 | return bold_text
180 |
181 | # Italic Tag
182 | elif element.name == HTML.Tags.I or element.name == HTML.Tags.EM:
183 | italic_text = ft.Text(element.text, italic=True)
184 | return italic_text
185 |
186 | # Underline Tag
187 | elif element.name == HTML.Tags.U:
188 | underlined_text = ft.Text(
189 | spans=[
190 | ft.TextSpan(
191 | element.text,
192 | style=ft.TextStyle(decoration=ft.TextDecoration.UNDERLINE),
193 | )
194 | ]
195 | )
196 | return underlined_text
197 | # mark Tag
198 | elif element.name == HTML.Tags.MARK:
199 | style_props, _ = get_style(element, is_a_mapping=True)
200 |
201 | return ft.Text(
202 | spans=[
203 | ft.TextSpan(
204 | element.text,
205 | style=ft.TextStyle(**style_props),
206 | )
207 | ]
208 | )
209 | # Code Tag
210 | elif element.name == HTML.Tags.CODE:
211 | return ft.Markdown(
212 | element.text,
213 | selectable=True,
214 | extension_set="gitHubWeb",
215 | code_theme="atom-one-dark",
216 | )
217 |
218 | # Span Tag
219 | elif element.name == HTML.Tags.SPAN:
220 | span_style = get_style(element)
221 | return ft.Text(spans=[ft.TextSpan(element.text, style=span_style[0])])
222 |
223 | else:
224 | # Default to ft.Container for unrecognized elements
225 | container = ft.Container()
226 | for child in element.children:
227 | if child.name:
228 | child_flet = parse_html_to_flet(child)
229 | container.content = child_flet
230 | return container
231 |
232 |
233 | # ____________________________________________________________________________________________________________________________________
234 | # Parser function for html tables
235 |
236 |
237 | def html_table_to_flet(element, container):
238 | table = element.find("table", border="1")
239 | flet_table = ft.DataTable(columns=[], rows=[])
240 |
241 | if table != None:
242 | for row in table.find_all("tr"):
243 | headers = row.find_all("th")
244 | columns = row.find_all("td")
245 | if headers != []:
246 | for i in range(len(headers)):
247 | header_text = headers[i].text
248 | flet_table.columns.append(ft.DataColumn(ft.Text(header_text)))
249 |
250 | if columns != []:
251 | data_cells = []
252 | for i in range(len(columns)):
253 | cell_text = columns[i].text
254 | data_cells.append(ft.DataCell(ft.Text(cell_text)))
255 | flet_table.rows.append(ft.DataRow(cells=data_cells))
256 | container.content.controls.append(flet_table)
257 |
258 |
259 | # ___________________________________________________________________________________________________________________________________
260 | # Associate html inline styles to the corresponding flet style properties
261 | # ____________________________________________________________________________________________________________________________________
262 | html_to_flet_style_mapping = {
263 | "color": "color",
264 | "background-color": "bgcolor",
265 | "font-family": "font_family",
266 | "font-size": "size",
267 | "text-align": "text_align",
268 | "text-decoration": "decoration",
269 | "display": "display",
270 | "justify-content": "alignment",
271 | "margin": "margin",
272 | "padding": "padding",
273 | "border-radius": "border_radius",
274 | "border": "border",
275 | "width": "width",
276 | "height": "height",
277 | }
278 |
279 |
280 | def parse_inline_styles(style_string):
281 | # Parse inline styles and convert to Flet properties
282 | style_properties = {}
283 | for style_declaration in style_string.split(";"):
284 | if ":" in style_declaration:
285 | property_name, property_value = style_declaration.split(":")
286 | property_name = property_name.strip()
287 | property_value = property_value.strip()
288 |
289 | # Convert property_name to Flet style name if needed
290 | property_name = html_to_flet_style_mapping.get(property_name, None)
291 |
292 | if property_name:
293 | # Map html text-decoration values to their corresponding Flet decoration values
294 | deco_values = {
295 | "underline": ft.TextDecoration.UNDERLINE,
296 | "line-through": ft.TextDecoration.LINE_THROUGH,
297 | "overline": ft.TextDecoration.OVERLINE,
298 | }
299 | # Map html justify-content values to their corresponding Flet alignment values
300 | alignment_values = {
301 | "flex-start": ft.MainAxisAlignment.START,
302 | "center": ft.MainAxisAlignment.CENTER,
303 | "flex-end": ft.MainAxisAlignment.END,
304 | "space-between": ft.MainAxisAlignment.SPACE_BETWEEN,
305 | "space-around": ft.MainAxisAlignment.SPACE_AROUND,
306 | "space-evenly": ft.MainAxisAlignment.SPACE_EVENLY,
307 | }
308 |
309 | # Convert property_value to integer if it's a digit otherwise, keep the original value
310 | style_properties[property_name] = (
311 | int(property_value) if property_value.isdigit() else property_value
312 | )
313 | # handle decoration property
314 | if property_name == "decoration" and property_value in deco_values:
315 | style_properties["decoration"] = deco_values[property_value]
316 | # handle border property
317 | elif property_name == "border" and property_value != None:
318 | property_value = property_value.split(" ")
319 | style_properties["border"] = ft.border.all(
320 | property_value[0], property_value[-1]
321 | )
322 | elif (
323 | property_name == "alignment" and property_value in alignment_values
324 | ):
325 | style_properties["alignment"] = alignment_values[property_value]
326 |
327 | style_properties.pop("display", None)
328 | return style_properties
329 |
330 |
331 | def get_style(element, is_a_mapping: bool = False):
332 | alignment_props = {}
333 | if element.get(HTML.Attrs.STYLE):
334 | style_props = parse_inline_styles(element.get(HTML.Attrs.STYLE))
335 | if "alignment" in style_props:
336 | val = style_props.pop("alignment")
337 | alignment_props = {"alignment": val}
338 |
339 | _style = style_props if is_a_mapping else ft.TextStyle(**style_props)
340 |
341 | else:
342 | _style = {}
343 | return _style, alignment_props
344 |
--------------------------------------------------------------------------------
/fletify/utils.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 |
4 | class RenderHTML:
5 | def __init__(self, file, encoding="utf-8"):
6 | self.file = file
7 | if not os.path.exists(self.file):
8 | raise FileNotFoundError(f"No such HTML file: {self.file}")
9 | with open(self.file, encoding=encoding) as file:
10 | self.html_str = file.read()
11 |
12 | def __repr__():
13 | ...
14 |
15 | def display(self):
16 | return self.html_str
17 |
18 | __str__ = display
19 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | flet>=0.11.0
2 | bs4>=0.0.1
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import setuptools
2 |
3 |
4 | with open("README.md", "r") as desc:
5 | long_description = desc.read()
6 |
7 | setuptools.setup(
8 | name="fletify",
9 | version="0.0.1",
10 | author="Benit Mulindwa",
11 | keywords="flet html developement python",
12 | description="Embed HTML code on your flet app",
13 | long_description=long_description,
14 | long_description_content_type="text/markdown",
15 | url="https://github.com/Benitmulindwa/FletifyHTML",
16 | project_urls={
17 | "Documentation": "https://github.com/Benitmulindwa/FletifyHTML",
18 | "Source": "https://github.com/Benitmulindwa/FletifyHTML",
19 | "Tracker": "https://github.com/Benitmulindwa/FletifyHTML/issues",
20 | },
21 | packages=setuptools.find_packages(),
22 | include_package_data=True,
23 | classifiers=[
24 | "Programming Language :: Python :: 3.9",
25 | "License :: OSI Approved :: MIT License",
26 | "Operating System :: OS Independent",
27 | ],
28 | python_requires=">=3.8",
29 | install_requires=["flet>=0.11.0", "bs4>=0.0.1"],
30 | )
31 |
--------------------------------------------------------------------------------
/test1.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Benitmulindwa/FletifyHTML/afc835218a820ad4b522d95ef6c6af542267b9e7/test1.PNG
--------------------------------------------------------------------------------