{"id":16545499,"url":"https://github.com/Michael-F-Ellis/htmltree","last_synced_at":"2025-10-28T15:31:24.921Z","repository":{"id":38705446,"uuid":"94714459","full_name":"Michael-F-Ellis/htmltree","owner":"Michael-F-Ellis","description":"Generalized nested html element tree with recursive rendering","archived":false,"fork":false,"pushed_at":"2023-03-07T22:53:45.000Z","size":317,"stargazers_count":36,"open_issues_count":2,"forks_count":8,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-02-08T13:46:16.560Z","etag":null,"topics":["css","html","python","transcrypt"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Michael-F-Ellis.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"License.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-06-18T21:36:39.000Z","updated_at":"2024-01-15T13:39:48.000Z","dependencies_parsed_at":"2024-06-21T03:55:22.375Z","dependency_job_id":"f51e2382-7d11-4a59-9f31-2e44b6df403a","html_url":"https://github.com/Michael-F-Ellis/htmltree","commit_stats":{"total_commits":68,"total_committers":3,"mean_commits":"22.666666666666668","dds":0.02941176470588236,"last_synced_commit":"026f107b644f75ad49504bd45a72e4922292d380"},"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Michael-F-Ellis%2Fhtmltree","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Michael-F-Ellis%2Fhtmltree/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Michael-F-Ellis%2Fhtmltree/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Michael-F-Ellis%2Fhtmltree/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Michael-F-Ellis","download_url":"https://codeload.github.com/Michael-F-Ellis/htmltree/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238672389,"owners_count":19511260,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["css","html","python","transcrypt"],"created_at":"2024-10-11T19:07:03.625Z","updated_at":"2025-10-28T15:31:24.564Z","avatar_url":"https://github.com/Michael-F-Ellis.png","language":"Python","funding_links":[],"categories":["Libraries"],"sub_categories":["General HTML Generation"],"readme":"**Table of Contents**  *generated with [DocToc](http://doctoc.herokuapp.com/)*\n\n- [Python htmltree project](#python-htmltree-project)\n\t- [Create and manipulate HTML and CSS from the comfort of Python](#create-and-manipulate-html-and-css-from-the-comfort-of-python)\n\t\t- [Installation](#installation)\n\t\t- [Quick Start](#quick-start)\n\t\t\t- [Open a Python interpreter and type or paste the following](#open-a-python-interpreter-and-type-or-paste-the-following)\n\t\t\t- [Render and print the HTML](#render-and-print-the-html)\n\t\t\t- [Now add some styling and text ...](#now-add-some-styling-and-text)\n\t\t\t- [and print the result.](#and-print-the-result)\n\t\t- [Reserved words and hyphenated attributes](#reserved-words-and-hyphenated-attributes)\n\t\t- [Viewing your work](#viewing-your-work)\n\t- [Discussion](#discussion)\n\t\t- [Public members](#public-members)\n\t\t- [Rendering](#rendering)\n\t- [Usage tips](#usage-tips)\n\t\t- [Rolling your own](#rolling-your-own)\n\t\t- [Bundling](#bundling)\n\t\t- [Looping](#looping)\n\t\t- [Using htmltree with Transcrypt™](#using-htmltree-with-transcrypt)\n\t\t- [Writing CSS](#writing-css)\n\t- [List of wrapper functions](#list-of-wrapper-functions)\n# Python htmltree project\n\n## Create and manipulate HTML and CSS from the comfort of Python\n  * Easy to learn. Consistent, simple syntax.\n  * 85 predefined tag functions.\n  * Create HTML on the fly or save as static files.\n  * Flexible usage and easy extension. \n  * Run locally with CPython or as Javascript in the browser using Jacques De Hooge's [*Transcrypt™*](https://transcrypt.org/) Python to JS transpiler\n  * Dependencies: Python 3.x\n\n### Installation\n`pip install htmltree`\n\n### Quick Start\n\n#### Open a Python interpreter and type or paste the following\n```\nfrom htmltree import *\nhead = Head()\nbody = Body()\ndoc = Html(head, body)\n```\n#### Render and print the HTML\n```\n\u003e\u003e\u003e print(doc.render(0))\n\u003chtml\u003e\n  \u003chead\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n#### Now add some styling and text\n```\nwho = Meta(name=\"author\",content=\"Your Name Here\")\nhead.C.append(who)\nbody.A.update(dict(style={'background-color':'black'}))\nbanner = H1(\"Hello, htmltree!\", _class='banner', style={'color':'green'})\nbody.C.append(banner)\n```\n#### and print the result.\n```\n\u003e\u003e\u003e print(doc.render(0))\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003cmeta content=\"Your Name Here\" name=\"author\"\u003e\n  \u003c/head\u003e\n  \u003cbody style=\"background-color:black;\"\u003e\n    \u003ch1 class=\"banner\" style=\"color:green;\"\u003e\n      Hello, htmltree!\n    \u003c/h1\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\nIn the examples above, we created elements and assigned them to variables so we could alter their content later. However, we could also have written  it out all at once.\n\n```\ndoc = Html(\n        Head(\n          Meta(name=\"author\",content=\"Your Name Here\")),\n        Body(\n          H1(\"Hello, htmltree!\", _class='banner', style={'color':'green'}),\n          style={'background-color':'black'}))\n```\n\nThat's short and clean and renders exactly the same html.  It also mimics the page structure but sacrifices ease of alteration later in the execution. Your choices should come down to whether you're creating static html or dynamic content based on information that's not available until run time.\n\n### Reserved words and hyphenated attributes\nDid you notice the underscore in `H1(\"Hello, htmltree!\", _class='banner', ...)`? It's written that way because `class` is a Python keyword. Trying to use it as an identifier will raise a syntax error. \n\nAs a convenience, all the wrapper functions strip off leading and trailing underscores in attribute names, so `class_` would also work. Normal HTML doesn't use underscores in attribute names so this fix is safe to use. I think `for` as a `\u003clabel\u003e` attribute is the only other conflict in standard HTML.\n\nThe wrapper functions also replace internal underscores in attribute names with dashes. That avoids the problem of Python trying to interpret `data-role=\"magic\"` as a subtraction expression. Use `data_role=\"magic\"` instead. If you need to style with vendor-specific attributes that begin with a '-', add a trailing underscore, e.g. `_moz_style_` is converted to `-moz-style`.\n\n*The conversion happens when the element is created, not when it is rendered.* If you add, update or replace an element attribute after it is created, use the attribute's true name, e.g. `mybutton.A.update({'class': 'super-button'})` rather than `mybutton.A.update(dict(_class='super-button'))`.\n\n\n\n### Viewing your work\nUse htmltree's `renderToFile` method and Python's standard `webbrowser` module.\n```\nimport webbrowser\nfileurl = doc.renderToFile('path/to/somefile.html')\nwebbrowser.open(fileurl)\n```\n\nThe Quick Start example should look like this:\n\n![Figure 1.](htmltree/doc/img/quickstart.png)\n\n## Discussion\nImporting * from htmltree.py provides 85 wrapper functions (as of this writing) that cover the most of the common non-obsolete HTML5 tags.  To see the most up-to-date list you can do `help(htmltree)` from the command line of a Python interactive session or look futher down on this page for a listing. The function names and arguments follow simple and consistent conventions that make use of Python's `*args, **kwargs`features.\n\n- Functions are named by tag with initial caps, e.g. `Html()`\n- The signature for non-empty tags is `Tagname(*content, **attrs)`\n- The signature for empty tags is `Tagname(**attrs)` (Empty refers to elements that enclose no content and need no closing tag -- like `\u003cmeta\u003e`, `\u003cbr\u003e`, etc.)\n\nTo create, say, a div with two empty paragraphs separated by a horizontal rule element, you'd write\n\n```mydiv = Div(P(), Hr(), P(), id=42, name='puddintane')```\n\nBecause the first three args are unnamed Python knows they belong, in order, `to *content`. The last two arguments are named and therefore belong to `**attrs`, the attributes of the div. \n\nPython's rules about not mixing list and keyword arguments apply. In every element, put all the *content args first, followed by all the **attrs arguments. \n\nThe \u003cstyle\u003e tag is the only exception to the pattern. Its signature is `Style(**content)`.  This is done to reduce (but alas not completely eliminate) the need for quoting the selectors in CSS rulesets.\n- If you need to set attributes on a style element, do it in a secondary call as shown in the doctest below.\n```\n          style = Style(body=dict(margin='4px'), p=dict(color='blue'))\n          style.A.update({'type':'text/css'})\n          style.render()\n          '\u003cstyle type=\"text/css\"\u003ebody { margin:4px; } p { color:blue; }\u003c/style\u003e' \n```\n- See [Writing CSS](#writing-css) later in this document for more dicussion of `Style()`.\n\nThe design pattern for `htmltree` is \"as simple as possible but not simpler.\" Using built-in Python objects, dicts and lists, means that all the familiar methods of those objects are available when manipulating trees of Elements. Notice, for instance, the use of `update` and `append` in the Quick start examples. \n```\nbody.A.update(dict(style={'background-color':'black'}))\nbody.C.append(H1(\"Hello, htmltree!\", _class='myclass', id='myid'))\n```\nBut wait a minute! What are `body.A` and `body.C`? Read on ...\n\n### Public members\nYou can access and modify the attributes and content of an element `el` as `el.A` and `el.C` respectively. The tagname is also available as `el.T` though this is generally not so useful as the other two. \n\nThe attribute member, `el.A` is an ordinary Python dictionary containing whatever keyword arguments were passed when the element was created. You can modify it with `update()` as shown in the Quick Start example or use any of the other dictionary methods on it. You can also replace it entirely with any dict-like object that has an `items()` method that behaves like dict.items()\n\nThe content member, `el.C` is normally a Python list. It contains all the stuff that gets rendered between the closing and ending tags of an element. The list may hold an arbitrary mix of strings, ints, float, and objects. In normal usage, the objects are of type `htmltree.Element`. This is the element type returned by all the functions in htmltree.py. You can use all the normal Python list methods (append, insert, etc) to manipulate the list.\n\n(If you insert objects (other than those listed above), they should have a `render(indent=-1)` method that returns valid HTML with the same indentation conventions as the `htmltree.Element.render` method described in the next section.)\n\n### Rendering\nThe render method emits HTML. In the examples above, we've called it as doc.render(0) to display the entire document tree in indented form. Calling it with no arguments emits the HTML as a single line with no breaks or spaces. Values \u003e 0 increase the indentations by 2 spaces * the value.\n```\n\u003e\u003e\u003e print(head.render())\n\u003chead\u003e\u003cmeta name=\"author\" content=\"Your Name Here\"/\u003e\u003c/head\u003e\n\n\u003e\u003e\u003e print(head.render(0))\n\n\u003chead\u003e\n  \u003cmeta name=\"author\" content=\"Your Name Here\"/\u003e\n\u003c/head\u003e\n\n\u003e\u003e\u003e print(head.render(1))\n\n  \u003chead\u003e\n    \u003cmeta name=\"author\" content=\"Your Name Here\"/\u003e\n  \u003c/head\u003e\n```\n\nTo include the `\u003c!DOCTYPE html\u003e` declaration and the beginning of the document, you can use the flag, `doctype_declaration`.\n```\n\u003e\u003e\u003e print(doc.render(doctype_declaration=True))\n\u003c!DOCTYPE html\u003e\u003chtml\u003e\u003chead\u003e\u003c/head\u003e\u003cbody\u003e\u003c/body\u003e\u003c/html\u003e\n\n\u003e\u003e\u003e print(doc.render(0, doctype_declaration=True))\n\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n  \u003chead\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n\n\u003e\u003e\u003e print(doc.render(1, doctype_declaration=True))\n\n  \u003c!DOCTYPE html\u003e\n  \u003chtml\u003e\n    \u003chead\u003e\n    \u003c/head\u003e\n    \u003cbody\u003e\n    \u003c/body\u003e\n  \u003c/html\u003e\n```\n\nThe `renderToFile()` method also accepts the `indent` and `doctype_declaration` arguments.\n\n## Usage tips\n\n### Rolling your own\nThe simplest possible extension is wrapping a frequently used tag to save a little typing. This is already done for you for all the wrapper functions in htmltree.py. But if you need something that's not defined it only takes two lines of code (not counting the import).\n```\nfrom htmltree import KWElement\ndef Foo(*content, **attrs):\n    return KWElement('foo', *content, **wrappers)\n```\nFor an empty tag element, omit the content arg and pass None to KWElement().\n```\ndef Bar(**attrs):\n    return KWElement('bar', None, **attrs)\n```\n\n### Bundling\nWrapping commonly used fragments in a function is easy and useful, e.g. \n```\ndef docheadbody():\n    head = Head()\n    body = Body()\n    doc = Html(head, body)\n    return doc, head, body\n    \n\u003e\u003e\u003e doc, head, body = docheadbody()\n```\n\n### Looping\nUse loops to simplify the creation of many similar elements.\n```\nfor id in ('one', 'two', 'three'):\n     content = \"Help! I'm trapped in div {}.\".format(id)\n     body.C.append(Div(content, id=id))\n    \n\u003e\u003e\u003e print(body.render(0))\n\u003cbody\u003e\n  \u003cdiv id=\"one\"\u003e\n    Help! I'm trapped in div one.\n  \u003c/div\u003e\n  \u003cdiv id=\"two\"\u003e\n    Help! I'm trapped in div two.\n  \u003c/div\u003e\n  \u003cdiv id=\"three\"\u003e\n    Help! I'm trapped in div three.\n  \u003c/div\u003e\n\u003c/body\u003e\n```\n### Using *htmltree* with [*Transcrypt*](https://transcrypt.org/)\nThis project was designed from the ground up to be compatible with Transcrypt to create a pure Python development environment  for HTML/CSS/JS on both sides of the client/server divide.\n\nIf you've installed *htmltree* with `pip`, Transcrypt will find it when transpiling your Python files to JavaScript if you import it as `htmltree`. If you have a need to install and access *htmltree* by other means,  see \n  * http://www.transcrypt.org/docs/html/special_facilities.html for information about Transcrypt's module mechanism and \n  * https://github.com/Michael-F-Ellis/htmltree/issues/3 for a discussion of some specific ways to locate htmltree at compile time.\n\nAlso, look at the modules `sanitycheck.py` and `client.py` in the `tests/` directory as a template for developing and testing with htmltree and Transcrypt. For a more elaborate template with a built-in server, AJAX/JSON data updates and automatic rebuild/reload when source files change, see [NearlyPurePythonWebAppDemo](https://github.com/Michael-F-Ellis/NearlyPurePythonWebAppDemo)\n\nAll the functions should work the same as under CPython. If not, please submit an issue on GitHub so I can fix it!\n\n### Writing CSS\n\nUse the Style() element to create CSS rulesets for your HTML, for example\n```\n\u003e\u003e\u003e mystyle = Style(h2=dict(margin_top='4px', color='red'))\n\u003e\u003e\u003e doc = Html(Head(mystyle), Body(H2(\"Hello!\")))\n\u003e\u003e\u003e print(doc.render(0))\n\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003cstyle\u003e\n    h2 { color:red; margin-top:4px; }\n    \u003c/style\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    \u003ch2\u003e\n      Hello!\n    \u003c/h2\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\n#### Interlude: dict() vs {}\nIn python, `{}` and `dict()` both define dictionaries. The difference is that dict treats keyword arguments as strings, so\n```\n\u003e\u003e\u003e {'foo':1, 'bar':2} == dict(foo=1, bar=2)\nTrue\n```\nUsing `dict()` with keyword arguments saves having to quote keywords and `htmltree` tries to help by converting underscores in keywords to hyphens (as discussed earlier in this document).  On the other hand, the CSS selector syntax permits strings that are not valid Python keywords, so the best practice for non-trivial rulesets is to create a dictionary of dictionaries, as shown below, and supply that as the argument to Style(). *Don't forget the two asterisks in front!*\n```\nmycss = {\n          'a.someclass' : {'color':'red', 'margin-top':'0.5em !important'},\n          'div p #id:first-line' : {'background-style': 'none'}\n        }\nmystyle = Style(**mycss) ## Don't forget the two asterisks in front\n\n\u003e\u003e\u003e print(mystyle.render(0))\n\u003cstyle\u003e\ndiv p #id:first-line { background-style:none; }\na.someclass { color:red; margin-top:0.5em !important; }\n\u003c/style\u003e\n```\n\nIf you compare the dictionary definition of `mycss` with the rendered css\noutput, you'll see that the differences are as follows:\n\n  1. Quote every item,\n  2. A colon between each selector and its property/value pairs,\n  3. A comma after each rule definition,\n  4. Separate property:value pairs with commas instead of semicolons.\n#### Caveat: dictionaries are unordered\nWhen two CSS selectors apply with equal specificity to the same property of an element, [the last one wins](http://monc.se/kitchen/38/cascading-order-and-inheritance-in-css). You should not rely on the Style() object to render rules in any particular order because Python dictionaries are, by definition, unordered collections.\n## List of wrapper functions\n```\nHtml(*content, **attrs):\nHead(*content, **attrs):\nBody(*content, **attrs):\nLink(**attrs):\nMeta(**attrs):\nTitle(*content, **attrs):\nStyle(**content):\nAddress(*content, **attrs):\nArticle(*content, **attrs):\nAside(*content, **attrs):\nFooter(*content, **attrs):\nHeader(*content, **attrs):\nH1(*content, **attrs):\nH2(*content, **attrs):\nH3(*content, **attrs):\nH4(*content, **attrs):\nH5(*content, **attrs):\nH6(*content, **attrs):\nNav(*content, **attrs):\nSection(*content, **attrs):\nBlockquote(*content, **attrs):\nDd(*content, **attrs):\nDiv(*content, **attrs):\nDl(*content, **attrs):\nDt(*content, **attrs):\nFigcaption(*content, **attrs):\nFigure(*content, **attrs):\nHr(**attrs):\nLi(*content, **attrs):\nMain(*content, **attrs):\nOl(*content, **attrs):\nP(*content, **attrs):\nPre(*content, **attrs):\nUl(*content, **attrs):\nA(*content, **attrs):\nB(*content, **attrs):\nBr(**attrs):\nCite(*content, **attrs):\nCode(*content, **attrs):\nEm(*content, **attrs):\nI(*content, **attrs):\nS(*content, **attrs):\nSamp(*content, **attrs):\nSmall(*content, **attrs):\nSpan(*content, **attrs):\nStrong(*content, **attrs):\nSub(*content, **attrs):\nSup(*content, **attrs):\nU(*content, **attrs):\nArea(**attrs):\nAudio(*content, **attrs):\nImg(**attrs):\nMap(*content, **attrs):\nTrack(**attrs):\nVideo(*content, **attrs):\nEmbed(**attrs):\nObject(*content, **attrs):\nParam(**attrs):\nSource(**attrs):\nCanvas(*content, **attrs):\nNoscript(*content, **attrs):\nScript(*content, **attrs):\nCaption(*content, **attrs):\nCol(**attrs):\nTable(*content, **attrs):\nTbody(*content, **attrs):\nTd(*content, **attrs):\nTfoot(*content, **attrs):\nTh(*content, **attrs):\nThead(*content, **attrs):\nTr(*content, **attrs):\nButton(*content, **attrs):\nDatalist(*content, **attrs):\nFieldset(*content, **attrs):\nForm(*content, **attrs):\nInput(**attrs):\nLabel(*content, **attrs):\nLegend(*content, **attrs):\nMeter(*content, **attrs):\nOptgroup(*content, **attrs):\nOption(*content, **attrs):\nOutput(*content, **attrs):\nProgress(*content, **attrs):\nSelect(*content, **attrs):\nTextarea(*content, **attrs):\n```\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FMichael-F-Ellis%2Fhtmltree","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FMichael-F-Ellis%2Fhtmltree","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FMichael-F-Ellis%2Fhtmltree/lists"}