HTMTL (HyperText Markup Templating Language) is a templating language that uses HTML attributes for its rendering logic. It is both a subset and superset of HTML, meaning that valid HTML is also valid HTMTL and valid HTMTL is also valid HTML, allowing you to use any editor without needing any additional editor extensions.
- Interpolation: You can interpolate data from a data dictionary into your templates.
- Modifiers: You can modify the interpolated values using modifiers.
- Conditionals: You can show or hide blocks using expressions.
- Partials: You can include other templates inside your templates.
- Loops: You can loop over iterable data.
- Extendable: You can implement custom parsers and modifiers.
<!DOCTYPE html>
<html>
<head>
<title inner-text="{title}"></title>
</head>
<body>
<h1 inner-text="{title}"></h1>
<div class="posts" when="posts">
<div iterate="posts as post">
<h2 class="post-title">
<a :href="/blog/{post.url}" inner-text="{post.title | Capitalize}"></a>
</h2>
<div class="post-date" inner-text="{post.date | Date('yyyy-MM-dd')}"></div>
<div class="post-content" inner-html="{post.body}"></div>
</div>
</div>
</body>
</html>
pip install htmtl
A simple example of how to use HTMTL with default configuration looks like this:
from htmtl import Htmtl
template = Htmtl('<p inner-text="Hello {who}"></p>', {'who': 'World'})
html = template.to_html() # returns: <p>Hello World</p>
HTMTL works by parsing attributes in the template.
Sets the inner text of the element to the value of the attribute.
HTMTL template where title
key is Hello, World!
:
<h1 inner-text="{title}"></h1>
Results in:
<h1>Hello, World!</h1>
Sets the inner HTML of the element to the value of the attribute.
HTMTL template where content
key is <p>Hello, World!</p>
:
<div inner-html="{content}"></div>
Results in:
<div>
<p>Hello, World!</p>
</div>
Sets the inner HTML of the element to the value of the parsed HTMTL template. Inherits all the same data as the parent template.
HTMTL template with data such as:
data = {
'title': 'My Web Portal Thing',
'header': '<div class="header"><h1 inner-text="title"></h1></div>'
}
And where the template is:
<div inner-partial="header"></div>
Results in:
<div>
<div class="header">
<h1>My Web Portal Thing</h1>
</div>
</div>
Sets the outer text of the element to the value of the attribute.
HTMTL template where title
key is Hello, World!
:
<h1 outer-text="{title}"></h1>
Results in:
Hello, World!
Sets the outer HTML of the element to the value of the attribute.
HTMTL template where content
key is <p>Hello, World!</p>
:
<div outer-html="{content}"></div>
Results in:
<p>Hello, World!</p>
Sets the outer HTML of the element to the value of the parsed Toretto template. Inherits all the same data as the parent template.
HTMTL template with data such as:
data = {
'title': 'My Web Portal Thing',
'header': '<div class="header"><h1 inner-text="title"></h1></div>'
}
And where the template is:
<div outer-partial="header"></div>
Results in:
<div class="header">
<h1>My Web Portal Thing</h1>
</div>
Removes the element if the attribute is false-y.
HTMTL template where show
key is False
:
<div when="show">Hello, World!</div>
Results in:
<!-- Empty -->
Removes the element if the attribute is truthy.
HTMTL template where hide
key is True
:
<div when-not="hide">Hello, World!</div>
Results in:
<!-- Empty -->
Loops anything iterable.
For example, to loop over a collection of posts
and then use post
as the variable of each iteration, you can do something like this:
<div iterate="posts as post">
<h2 inner-text="post.title"></h2>
</div>
If you do not care about using any of the iteration data, you can also entirely omit as ...
from the expression,
like so:
<div iterate="posts">
...
</div>
And, you can also assign the key of the iteration to a variable, like so:
<div iterate="posts as index:post">
<h2 :class="post-{post.index}" inner-text="post.title"></h2>
</div>
This would add the key of the iteration to as post.index
variable, but you can name it whatever you want.
You can use the :*
attribute to set any attribute on an element to the interpolated value of the generic value attribute.
For example, to set the href
attribute of an element, you can use the :href
attribute:
<a :href="/blog/{slug}">Hello, World!</a>
Results in:
<a href="/blog/hello-world">Hello, World!</a>
If the slug
key is hello-world
.
All interpolated expressions can be modified using modifiers. Modifiers are applied to the value of the attribute, and they can be chained, like so:
<h1 inner-text="{title | Uppercase | Reverse}"></h1>
Note that if you have nothing other than the interpolated variable in the attribute, then you can omit the curly brackets, and so this would also work:
<h1 inner-text="title | Uppercase | Reverse"></h1>
Modifiers can also take arguments which are passed within
parentheses (
and )
, and can be either int
, float
, str
or bool
. For example:
<h1 inner-text="some_var | SomeModifier(123, 'asd', true)"></h1>
Parses the value into a formatted date string.
<p inner-text="published_at | Date('YYYY-mm-dd')"></p>
Truncates the value to the specified length.
<p inner-text="{title | Truncate(10)}"></p>
This also works on collections, so you can use truncate
to limit items in an array as well.
You can add (or replace) parsers in HTMTL when creating a new instance of the Htmtl
class, like so:
from htmtl import Htmtl
from htmtl.parsers import InnerText
template = Htmtl('<p inner-text="Hello {who}"></p>', {'who': 'World'})
template.set_parsers([
InnerText,
])
html = template.to_html() # returns: <p>Hello World</p>
Prsers must extend the Parser
class, like so:
from typing import Optional
from dompa.nodes import Node, TextNode
from htmtl import Parser
class InnerText(Parser):
def traverse(self, node: Node) -> Optional[Node]:
if "inner-text" in node.attributes:
node.children = [TextNode(value=self.expression(node.attributes["inner-text"]))]
node.attributes.pop("inner-text")
return node
All parsers traverse the entire DOM tree to do whatever DOM manipulation they want. It's important to know that
a parser must have the traverse
method, and it must return a Node
, or None
if you want to remove the Node
.
HTMTL is built upon the Dompa HTML parser, so check that out for more granular info on things.
htmtl.parsers.GenericValue
- Parser the:*
attributes.htmtl.parsers.When
- Parser thewhen
attributes.htmtl.parsers.WhenNot
- Parser thewhen-not
attributes.htmtl.parsers.InnerPartial
- Parser theinner-partial
attributes.htmtl.parsers.InnerHtml
- Parser theinner-html
attributes.htmtl.parsers.InnerText
- Parser theinner-text
attributes.htmtl.parsers.OuterPartial
- Parser theouter-partial
attributes.htmtl.parsers.OuterHtml
- Parser theouter-html
attributes.htmtl.parsers.OuterText
- Parser theouter-text
attributes.htmtl.parsers.Iterate
- Parses theiterate
attributes.
You can add (or replace) modifiers in HTMTL when creating a new instance of the Htmtl
class, like so:
from htmtl import Htmtl
from htmtl.modifiers import Truncate
template = Htmtl('<p inner-text="Hello {who}"></p>', {'who': 'World'})
template.set_modifiers([
Truncate,
])
html = template.to_html() # returns: <p>Hello World</p>
Mdifiers must extend the Modifier
class, like so:
from typing import Any
from htmtl import Modifier
class Truncate(Modifier):
def modify(self, value: Any, opts: list[Any]) -> Any:
if isinstance(value, str) and len(opts) > 0:
if all([x in "1234567890" for x in opts[0]]):
char_limit = int(opts[0])
if len(value) > char_limit:
return f"{value[:char_limit - 3]}..."
return value
htmtl.modifiers.Truncate
- Truncates the value (both strings and collections).