A templating library for .NET projects designed to be easy to read, write and have good editor support without needing any additional editor plugins. It fully supports trimming and native AOT compilation.
- Simple syntax: Htmt is a superset of HTML/XML, so you can write your templates in any text editor.
- Interpolation: You can interpolate values from a data dictionary into your templates.
- Modifiers: You can modify the interpolated values using modifiers.
- Conditionals: You can show or hide blocks using simple or complex expressions.
- Partials: You can include other templates inside your templates.
- Loops: You can loop over arrays and objects in your data dictionary.
- Extendable: You can implement custom attribute parsers and expression modifiers.
<!DOCTYPE html>
<html>
<head>
<title x:inner-text="{title}"></title>
</head>
<body>
<h1 x:inner-text="{title}"></h1>
<div class="posts" x:if="posts">
<div x:for="posts" x:as="post">
<h2 class="post-title">
<a x:attr-href="/blog/{post.url}" x:inner-text="{post.title | capitalize}"></a>
</h2>
<div class="post-date" x:inner-text="{post.date | date:yyyy-MM-dd}"></div>
<div class="post-content" x:inner-html="{post.body}"></div>
</div>
</div>
</body>
</html>
dotnet add package Htmt
A simple example of how to use Htmt with default configuration to generate HTML output:
var template = "<h1 x:inner-text=\"{title}\"></h1>";
var data = new Dictionary<string, object?> { { "title", "Hello, World!" } };
var parser = new Htmt.Parser { Template = template, Data = data };
var html = parser.ToHtml();
You can also generate XML output via the ToXml()
method.
HTMT works by parsing attributes in the template. HTMT attributes start with either x:
or data-htmt-
. The x:
prefix is meant as a shorthand for data-htmt-
, so you can use either one. The
only difference is that x:
is not valid HTML, so if you want to write valid HTML, you should use data-htmt-
.
Sets the inner text of the element to the value of the attribute.
Htmt template:
<h1 x:inner-text="{title}"></h1>
Results in:
<h1>Hello, World!</h1>
Sets the inner HTML of the element to the value of the attribute.
Htmt template where content
is <p>Hello, World!</p>
:
<div x:inner-html="{content}"></div>
Results in:
<div>
<p>Hello, World!</p>
</div>
Sets the outer text of the element to the value of the attribute. This is useful if you want to replace the entire element with text.
Htmt template where title
is Hello, World!
:
<h1 x:outer-text="{title}"></h1>
Results in:
Hello, World!
Sets the outer HTML of the element to the value of the attribute. This is useful if you want to replace the entire element with HTML.
Htmt template where content
is <p>Hello, World!</p>
:
<div x:outer-html="{content}"></div>
Results in:
<p>Hello, World!</p>
Sets the inner HTML of the element to the value of the attribute, which is a partial template. This is useful if you want to include another template inside the current template.
Htmt template where partial
is <p>Hello, World!</p>
:
<div x:inner-partial="partial"></div>
Results in:
<div>
<p>Hello, World!</p>
</div>
The partial
key has to be inside the Data dictionary, and it has to be a string that contains a valid Htmt template.
The partial will inherit the data dictionary that the parent template has, so you can use the same data in the partial as you can in the parent template.
Inner partials also support interpolated expressions, so you can get partials dynamically, like so:
<div x:inner-partial="parts.{partial}"></div>
Where partial
is { "partial", "something" }
, and then parts.something
would be the partial template it will load.
Sets the outer HTML of the element to the value of the attribute, which is a partial template. This is useful if you want to replace the entire element with another template.
Htmt template where partial
is <p>Hello, World!</p>
:
<div x:outer-partial="partial"></div>
Results in:
<p>Hello, World!</p>
The partial
key has to be inside the Data dictionary, and it has to be a string that contains a valid Htmt template.
The partial will inherit the data dictionary that the parent template has, so you can use the same data in the partial as you can in the parent template.
Outer partials also support interpolated expressions, so you can get partials dynamically, like so:
<div x:outer-partial="parts.{partial}"></div>
Where partial
is { "partial", "something" }
, and then parts.something
would be the partial template it will load.
Removes the element if the attribute is falsey.
Htmt template where show
is false
:
<div x:if="show">Hello, World!</div>
Results in:
<!-- Empty -->
The if
attribute also supports complex boolean expressions, like so:
<div x:if="(show is true) and (title is 'Hello, World!')">Hello, World!</div>
This will only show the element if show
is true
and title
is Hello, World!
. The boolean expression validator
supports the following operators: is
, or
and and
. You can also use parentheses to group expressions,
in case you want to have more complex expressions.
- The
is
operator is used to compare two values, and it supports the following types of values:string
,int
,float
,bool
andnull
. - The
or
operator is used to combine two expressions with an OR operator. - The
and
operator is used to combine two expressions with an AND operator.
Removes the element if the attribute is truthy.
Htmt template where hide
is true
:
<div x:unless="hide">Hello, World!</div>
Results in:
<!-- Empty -->
The unless
attribute supports the same boolean expressions as the if
attribute.
Repeats the element for each item in the attribute.
Htmt template where items
is an array of strings:
<ul>
<li x:for="items" x:as="item" x:inner-text="{item}"></li>
</ul>
Results in:
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
Note that the x:as
or data-htmt-as
attribute is optional. If you just want to loop over a data structure,
but you don't care about using the data of each individual iteration, you can omit it.
Above are all the special attributes that do some logical operation, but you can also use the x:attr-*
attributes to set any attribute on an element to the value of the attribute.
For example, to set the href
attribute of an element, you can use the x:attr-href
attribute:
<a x:attr-href="/blog/{slug}">Hello, World!</a>
Results in:
<a href="/blog/hello-world">Hello, World!</a>
If slug
is hello-world
.
All interpolated values in expressions can be modified using modifiers. Modifiers are applied to the value of the attribute, and they can be chained, like so:
<h1 x:inner-text="{title | uppercase | reverse}"></h1>
Modifiers can also take arguments, like so:
<h1 x:inner-text="{title | truncate:10}"></h1>
Formats a date string using the specified format.
<p x:inner-text="{date | date:yyyy-MM-dd}"></p>
Converts the value to uppercase.
<p x:inner-text="{title | uppercase}"></p>
Converts the value to lowercase.
<p x:inner-text="{title | lowercase}"></p>
Capitalizes the first letter of the value.
<p x:inner-text="{title | capitalize}"></p>
Reverses the value.
<p x:inner-text="{title | reverse}"></p>
Truncates the value to the specified length.
<p x:inner-text="{title | truncate:10}"></p>
Returns the count of the value, if the value is a IEnumerable
or string
.
<p x:inner-text="{items | count}"></p>
You can add (or replace) attribute parsers in Htmt by adding them to the AttributeParsers
array,
when creating a new instance of the Parser
class.
var parser = new Htmt.Parser
{
Template = template,
Data = data,
AttributeParsers = [
new MyCustomAttributeParser()
]
};
A custom attribute parser must extend the BaseAttributeParser
parser, like so:
public class CustomAttributeParser : BaseAttributeParser
{
public override string XTag => "//*[@x:custom or @data-htmt-custom]";
public override void Parse(XmlNodeList? nodes)
{
foreach (XmlNode node in nodes)
{
// You can parse expressions here with ParseExpression(), and
// do anything you want with the nodes as you'd otherwise do with XmlDocument stuff.
}
}
}
The Parse
method is where the attribute parser should do its work, and the XTag
property should return the xtag pattern for the nodes it should parse.
To get an array of default attribute parsers, you can call Htmt.Parser.DefaultAttributeParsers()
,
if you want to add your custom attribute parsers to the default ones, but you can also mix and match however you like.
Htmt.AttributeParsers.InnerTextAttributeParser
- Parses thex:inner-text
/data-htmt-inner-text
attribute.Htmt.AttributeParsers.InnerHtmlAttributeParser
- Parses thex:inner-html
/data-htmt-inner-html
attribute.Htmt.AttributeParsers.OuterTextAttributeParser
- Parses thex:outer-text
/data-htmt-outer-text
attribute.Htmt.AttributeParsers.OuterHtmlAttributeParser
- Parses thex:outer-html
/data-htmt-outer-html
attribute.Htmt.AttributeParsers.InnerPartialAttributeParser
- Parses thex:inner-partial
/data-htmt-inner-partial
attribute.Htmt.AttributeParsers.OuterPartialAttributeParser
- Parses thex:outer-partial
/data-htmt-outer-partial
attribute.Htmt.AttributeParsers.IfAttributeParser
- Parses thex:if
/data-htmt-if
attribute.Htmt.AttributeParsers.UnlessAttributeParser
- Parses thex:unless
/data-htmt-unless
attribute.Htmt.AttributeParsers.ForAttributeParser
- Parses thex:for
/data-htmt-for
attribute.Htmt.AttributeParsers.GenericValueAttributeParser
- Parses thex:attr-*
/data-htmt-attr-*
attributes.
You can add (or replace) modifiers in Htmt by adding them to the Modifiers
array,
when creating a new instance of the Parser
class.
var parser = new Htmt.Parser
{
Template = template,
Data = data,
Modifiers = [
new MyCustomModifier()
]
};
A custom modifier must implement the IExpressionModifier
interface:
public interface IExpressionModifier
{
public string Name { get; }
public object? Modify(object? value, string? args = null);
}
The Modify
method is where the modifier should do its work, and the Name
property should return the name of the modifier.
To get an array of default modifiers, you can call Htmt.Parser.DefaultExpressionModifiers()
,
if you want to add your custom modifiers to the default ones, but you can also mix and match however you like.
Htmt.ExpressionModifiers.DateExpressionModifier
- Formats a date string using the specified format.Htmt.ExpressionModifiers.UppercaseExpressionModifier
- Converts the value to uppercase.Htmt.ExpressionModifiers.LowercaseExpressionModifier
- Converts the value to lowercase.Htmt.ExpressionModifiers.CapitalizeExpressionModifier
- Capitalizes the first letter of the value.Htmt.ExpressionModifiers.ReverseExpressionModifier
- Reverses the value.Htmt.ExpressionModifiers.TruncateExpressionModifier
- Truncates the value to the specified length.Htmt.ExpressionModifiers.CountExpressionModifier
- Returns the count of the value.