{"id":17555504,"url":"https://github.com/askonomm/htmt","last_synced_at":"2025-04-24T04:08:28.134Z","repository":{"id":257822272,"uuid":"871047225","full_name":"askonomm/htmt","owner":"askonomm","description":"A templating library with native AOT support for .NET projects.","archived":false,"fork":false,"pushed_at":"2024-11-07T22:15:14.000Z","size":79,"stargazers_count":10,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-24T04:07:58.789Z","etag":null,"topics":["aot","csharp","csharp-library","templating"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/askonomm.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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":"2024-10-11T07:00:03.000Z","updated_at":"2024-12-04T01:06:47.000Z","dependencies_parsed_at":"2024-10-27T19:58:48.956Z","dependency_job_id":null,"html_url":"https://github.com/askonomm/htmt","commit_stats":{"total_commits":30,"total_committers":1,"mean_commits":30.0,"dds":0.0,"last_synced_commit":"41467c01ecd3c7189732b724dd6be516040be2df"},"previous_names":["askonomm/htmt"],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/askonomm%2Fhtmt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/askonomm%2Fhtmt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/askonomm%2Fhtmt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/askonomm%2Fhtmt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/askonomm","download_url":"https://codeload.github.com/askonomm/htmt/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250560052,"owners_count":21450172,"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":["aot","csharp","csharp-library","templating"],"created_at":"2024-10-21T07:06:01.416Z","updated_at":"2025-04-24T04:08:28.104Z","avatar_url":"https://github.com/askonomm.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"﻿# Htmt\n\n![](https://github.com/askonomm/htmt/actions/workflows/test.yml/badge.svg)\n\nA templating library for .NET projects designed to be easy to read, write and have good editor support\nwithout needing any additional editor plugins. It fully supports trimming and native AOT compilation.\n\n## Features\n\n- **Simple syntax**: Htmt is a superset of HTML/XML, so you can write your templates in any text editor.\n- **Interpolation**: You can interpolate values from a data dictionary into your templates.\n- **Modifiers**: You can modify the interpolated values using modifiers.\n- **Conditionals**: You can show or hide blocks using simple or complex expressions.\n- **Partials**: You can include other templates inside your templates.\n- **Loops**: You can loop over arrays and objects in your data dictionary.\n- **Extendable**: You can implement custom attribute parsers and expression modifiers.\n\n## Example syntax\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n    \u003chead\u003e\n        \u003ctitle x:inner-text=\"{title}\"\u003e\u003c/title\u003e\n    \u003c/head\u003e\n    \u003cbody\u003e\n        \u003ch1 x:inner-text=\"{title}\"\u003e\u003c/h1\u003e\n        \n        \u003cdiv class=\"posts\" x:if=\"posts\"\u003e\n            \u003cdiv x:for=\"posts\" x:as=\"post\"\u003e\n                \u003ch2 class=\"post-title\"\u003e\n                    \u003ca x:attr-href=\"/blog/{post.url}\" x:inner-text=\"{post.title | capitalize}\"\u003e\u003c/a\u003e\n                \u003c/h2\u003e\n                \u003cdiv class=\"post-date\" x:inner-text=\"{post.date | date:yyyy-MM-dd}\"\u003e\u003c/div\u003e\n                \u003cdiv class=\"post-content\" x:inner-html=\"{post.body}\"\u003e\u003c/div\u003e\n            \u003c/div\u003e\n        \u003c/div\u003e\n    \u003c/body\u003e\n\u003c/html\u003e\n```\n\n## Installation\n\n```bash\ndotnet add package Htmt\n```\n\n## Usage\n\nA simple example of how to use Htmt with default configuration to generate HTML output:\n\n```csharp\nvar template = \"\u003ch1 x:inner-text=\\\"{title}\\\"\u003e\u003c/h1\u003e\";\nvar data = new Dictionary\u003cstring, object?\u003e { { \"title\", \"Hello, World!\" } };\nvar parser = new Htmt.Parser { Template = template, Data = data };\nvar html = parser.ToHtml();\n```\n\nYou can also generate XML output via the `ToXml()` method.\n\n## Attributes\n\nHTMT 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 \nonly difference is that `x:` is not valid HTML, so if you want to write valid HTML, you should use `data-htmt-`.\n\n### `x:inner-text` or `data-htmt-inner-text`\n\nSets the inner text of the element to the value of the attribute.\n\nHtmt template:\n\n```html\n\u003ch1 x:inner-text=\"{title}\"\u003e\u003c/h1\u003e\n```\n\nResults in:\n\n```html\n\u003ch1\u003eHello, World!\u003c/h1\u003e\n```\n\n### `x:inner-html` or `data-htmt-inner-html`\n\nSets the inner HTML of the element to the value of the attribute.\n\nHtmt template where `content` is `\u003cp\u003eHello, World!\u003c/p\u003e`:\n\n```html\n\u003cdiv x:inner-html=\"{content}\"\u003e\u003c/div\u003e\n```\n\nResults in:\n\n```html\n\u003cdiv\u003e\n    \u003cp\u003eHello, World!\u003c/p\u003e\n\u003c/div\u003e\n```\n\n### `x:outer-text` or `data-htmt-outer-text`\n\nSets the outer text of the element to the value of the attribute.\nThis is useful if you want to replace the entire element with text.\n\nHtmt template where `title` is `Hello, World!`:\n\n```html\n\u003ch1 x:outer-text=\"{title}\"\u003e\u003c/h1\u003e\n```\n\nResults in:\n\n```html\nHello, World!\n```\n\n### `x:outer-html` or `data-htmt-outer-html`\n\nSets the outer HTML of the element to the value of the attribute.\nThis is useful if you want to replace the entire element with HTML.\n\nHtmt template where `content` is `\u003cp\u003eHello, World!\u003c/p\u003e`:\n\n```html\n\u003cdiv x:outer-html=\"{content}\"\u003e\u003c/div\u003e\n```\n\nResults in:\n\n```html\n\u003cp\u003eHello, World!\u003c/p\u003e\n```\n\n### `x:inner-partial` or `data-htmt-inner-partial`\n\nSets the inner HTML of the element to the value of the attribute, which is a partial template.\nThis is useful if you want to include another template inside the current template.\n\nHtmt template where `partial` is `\u003cp\u003eHello, World!\u003c/p\u003e`:\n\n```html\n\u003cdiv x:inner-partial=\"partial\"\u003e\u003c/div\u003e\n```\n\nResults in:\n\n```html\n\u003cdiv\u003e\n    \u003cp\u003eHello, World!\u003c/p\u003e\n\u003c/div\u003e\n```\n\nThe `partial` key has to be inside the Data dictionary, and it has to be a string that contains a valid Htmt template.\n\nThe 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.\n\nInner partials also support interpolated expressions, so you can get partials dynamically, like so:\n\n```html\n\u003cdiv x:inner-partial=\"parts.{partial}\"\u003e\u003c/div\u003e\n```\n\nWhere `partial` is `{ \"partial\", \"something\" }`, and then `parts.something` would be the partial template it will load.\n\n### `x:outer-partial` or `data-htmt-outer-partial`\n\nSets the outer HTML of the element to the value of the attribute, which is a partial template.\nThis is useful if you want to replace the entire element with another template.\n\nHtmt template where `partial` is `\u003cp\u003eHello, World!\u003c/p\u003e`:\n\n```html\n\u003cdiv x:outer-partial=\"partial\"\u003e\u003c/div\u003e\n```\n\nResults in:\n\n```html\n\u003cp\u003eHello, World!\u003c/p\u003e\n```\n\nThe `partial` key has to be inside the Data dictionary, and it has to be a string that contains a valid Htmt template.\n\nThe 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.\n\nOuter partials also support interpolated expressions, so you can get partials dynamically, like so:\n\n```html\n\u003cdiv x:outer-partial=\"parts.{partial}\"\u003e\u003c/div\u003e\n```\n\nWhere `partial` is `{ \"partial\", \"something\" }`, and then `parts.something` would be the partial template it will load.\n\n### `x:if` or `data-htmt-if`\n\nRemoves the element if the attribute is falsey.\n\nHtmt template where `show` is `false`:\n\n```html\n\u003cdiv x:if=\"show\"\u003eHello, World!\u003c/div\u003e\n```\n\nResults in:\n\n```html\n\u003c!-- Empty --\u003e\n```\n\nThe `if` attribute also supports complex boolean expressions, like so:\n\n```html\n\u003cdiv x:if=\"(show is true) and (title is 'Hello, World!')\"\u003eHello, World!\u003c/div\u003e\n```\n\nThis will only show the element if `show` is `true` and `title` is `Hello, World!`. The boolean expression validator\nsupports the following operators: `is`, `or` and `and`. You can also use parentheses to group expressions,\nin case you want to have more complex expressions.\n\n- The `is` operator is used to compare two values, and it supports the following types of values: `string`, `int`, `float`, `bool` and `null`.\n- The `or` operator is used to combine two expressions with an OR operator.\n- The `and` operator is used to combine two expressions with an AND operator.\n\n### `x:unless` or `data-htmt-unless`\n\nRemoves the element if the attribute is truthy.\n\nHtmt template where `hide` is `true`:\n\n```html\n\u003cdiv x:unless=\"hide\"\u003eHello, World!\u003c/div\u003e\n```\n\nResults in:\n\n```html\n\u003c!-- Empty --\u003e\n```\n\nThe `unless` attribute supports the same boolean expressions as the `if` attribute.\n\n### `x:for` or `data-htmt-for`\n\nRepeats the element for each item in the attribute.\n\nHtmt template where `items` is an array of strings:\n\n```html\n\u003cul\u003e\n    \u003cli x:for=\"items\" x:as=\"item\" x:inner-text=\"{item}\"\u003e\u003c/li\u003e\n\u003c/ul\u003e\n```\n\nResults in:\n\n```html\n\u003cul\u003e\n    \u003cli\u003eItem 1\u003c/li\u003e\n    \u003cli\u003eItem 2\u003c/li\u003e\n    \u003cli\u003eItem 3\u003c/li\u003e\n\u003c/ul\u003e\n```\n\nNote that the `x:as` or `data-htmt-as` attribute is optional. If you just want to loop over a data structure,\nbut you don't care about using the data of each individual iteration, you can omit it.\n\n### `x:attr-*` or `data-htmt-attr-*` (Generic Value Attributes)\n\nAbove 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.\n\nFor example, to set the `href` attribute of an element, you can use the `x:attr-href` attribute:\n\n```html\n\u003ca x:attr-href=\"/blog/{slug}\"\u003eHello, World!\u003c/a\u003e\n```\n\nResults in:\n\n```html\n\u003ca href=\"/blog/hello-world\"\u003eHello, World!\u003c/a\u003e\n```\n\nIf `slug` is `hello-world`.\n\n## Modifiers\n\nAll 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:\n\n```html\n\u003ch1 x:inner-text=\"{title | uppercase | reverse}\"\u003e\u003c/h1\u003e\n```\n\nModifiers can also take arguments, like so:\n\n```html\n\u003ch1 x:inner-text=\"{title | truncate:10}\"\u003e\u003c/h1\u003e\n```\n\n### `date`\n\nFormats a date string using the specified format.\n\n```html\n\u003cp x:inner-text=\"{date | date:yyyy-MM-dd}\"\u003e\u003c/p\u003e\n```\n\n### `uppercase`\n\nConverts the value to uppercase.\n\n```html\n\u003cp x:inner-text=\"{title | uppercase}\"\u003e\u003c/p\u003e\n```\n\n### `lowercase`\n\nConverts the value to lowercase.\n\n```html\n\u003cp x:inner-text=\"{title | lowercase}\"\u003e\u003c/p\u003e\n```\n\n### `capitalize`\n\nCapitalizes the first letter of the value.\n\n```html\n\u003cp x:inner-text=\"{title | capitalize}\"\u003e\u003c/p\u003e\n```\n\n### `reverse`\n\nReverses the value.\n\n```html\n\u003cp x:inner-text=\"{title | reverse}\"\u003e\u003c/p\u003e\n```\n\n### `truncate`\n\nTruncates the value to the specified length.\n\n```html\n\u003cp x:inner-text=\"{title | truncate:10}\"\u003e\u003c/p\u003e\n```\n\n### `count`\n\nReturns the count of the value, if the value is a `IEnumerable` or `string`.\n\n```html\n\u003cp x:inner-text=\"{items | count}\"\u003e\u003c/p\u003e\n```\n\n\n## Extending\n\n### Attribute Parsers\n\nYou can add (or replace) attribute parsers in Htmt by adding them to the `AttributeParsers` array,\nwhen creating a new instance of the `Parser` class.\n\n```csharp\nvar parser = new Htmt.Parser\n{\n    Template = template,\n    Data = data,\n    AttributeParsers = [\n        new MyCustomAttributeParser()\n    ]\n};\n```\n\nA custom attribute parser must extend the `BaseAttributeParser` parser, like so:\n\n```csharp\npublic class CustomAttributeParser : BaseAttributeParser\n{\n    public override string XTag =\u003e \"//*[@x:custom or @data-htmt-custom]\";\n    \n    public override void Parse(XmlNodeList? nodes)\n    {\n        foreach (XmlNode node in nodes)\n        {\n            // You can parse expressions here with ParseExpression(), and \n            // do anything you want with the nodes as you'd otherwise do with XmlDocument stuff.\n        }\n    }\n}\n```\n\nThe `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.\n\nTo get an array of default attribute parsers, you can call `Htmt.Parser.DefaultAttributeParsers()`,\nif you want to add your custom attribute parsers to the default ones, but you can also mix and match however you like.\n\n#### List of built-in attribute parsers\n\n- `Htmt.AttributeParsers.InnerTextAttributeParser` - Parses the `x:inner-text` / `data-htmt-inner-text` attribute.\n- `Htmt.AttributeParsers.InnerHtmlAttributeParser` - Parses the `x:inner-html` / `data-htmt-inner-html` attribute.\n- `Htmt.AttributeParsers.OuterTextAttributeParser` - Parses the `x:outer-text` / `data-htmt-outer-text` attribute.\n- `Htmt.AttributeParsers.OuterHtmlAttributeParser` - Parses the `x:outer-html` / `data-htmt-outer-html` attribute.\n- `Htmt.AttributeParsers.InnerPartialAttributeParser` - Parses the `x:inner-partial` / `data-htmt-inner-partial` attribute.\n- `Htmt.AttributeParsers.OuterPartialAttributeParser` - Parses the `x:outer-partial` / `data-htmt-outer-partial` attribute.\n- `Htmt.AttributeParsers.IfAttributeParser` - Parses the `x:if` / `data-htmt-if` attribute.\n- `Htmt.AttributeParsers.UnlessAttributeParser` - Parses the `x:unless` / `data-htmt-unless` attribute.\n- `Htmt.AttributeParsers.ForAttributeParser` - Parses the `x:for` / `data-htmt-for` attribute.\n- `Htmt.AttributeParsers.GenericValueAttributeParser` - Parses the `x:attr-*` / `data-htmt-attr-*` attributes.\n\n### Modifiers\n\nYou can add (or replace) modifiers in Htmt by adding them to the `Modifiers` array,\nwhen creating a new instance of the `Parser` class.\n\n```csharp\nvar parser = new Htmt.Parser\n{\n    Template = template,\n    Data = data,\n    Modifiers = [\n        new MyCustomModifier()\n    ]\n};\n```\n\nA custom modifier must implement the `IExpressionModifier` interface:\n\n```csharp\npublic interface IExpressionModifier\n{\n    public string Name { get; }\n\n    public object? Modify(object? value, string? args = null);\n}\n```\n\nThe `Modify` method is where the modifier should do its work, and the `Name` property should return the name of the modifier.\n\nTo get an array of default modifiers, you can call `Htmt.Parser.DefaultExpressionModifiers()`,\n\nif you want to add your custom modifiers to the default ones, but you can also mix and match however you like.\n\n#### List of built-in modifiers\n\n- `Htmt.ExpressionModifiers.DateExpressionModifier` - Formats a date string using the specified format.\n- `Htmt.ExpressionModifiers.UppercaseExpressionModifier` - Converts the value to uppercase.\n- `Htmt.ExpressionModifiers.LowercaseExpressionModifier` - Converts the value to lowercase.\n- `Htmt.ExpressionModifiers.CapitalizeExpressionModifier` - Capitalizes the first letter of the value.\n- `Htmt.ExpressionModifiers.ReverseExpressionModifier` - Reverses the value.\n- `Htmt.ExpressionModifiers.TruncateExpressionModifier` - Truncates the value to the specified length.\n- `Htmt.ExpressionModifiers.CountExpressionModifier` - Returns the count of the value.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faskonomm%2Fhtmt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faskonomm%2Fhtmt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faskonomm%2Fhtmt/lists"}