├── .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 | ![test1](test1.PNG) 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 | Example Image 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 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 |
Header 1Header 2Header 3
Row 1, Cell 1Row 1, Cell 2Row 1, Cell 3
Row 2, Cell 1Row 2, Cell 2Row 2, Cell 3
43 | 44 | 48 | Visit Me 49 | Example Image 50 |
51 |
52 |
53 |
54 |
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