This library is a small single header file (~150-200 SLoC of C) HTML templating engine for the C programming language.
CTML is macro-based as the objective is to provide a nice to use DSL inside of the C programming language.
- Directly embedded inside C code
- Really lightweight
- Not any dependencies (not even libc) except for formatting functions that are opt-in
- C functions as components
As this library is a header-only library, at (only) one place
in your code you need to define CTML_IMPLEMENTATION to include
the implementation of the library.
(only supports a subset of html tags defined in ctml_short.h)
#include <stdio.h>
#define CTML_PRETTY
#define CTML_IMPLEMENTATION
#include "ctml.h"
#include "ctml_short.h"
int main() {
ctml(
.sink=(ctmlSink)printf
) {
html(.lang="en") {
div(.class="nice") {
ctml_text("hello, world");
}
}
}
}
#include <stdio.h>
#define CTML_PRETTY
#define CTML_IMPLEMENTATION
#include "ctml.h"
int main() {
ctml(
.sink=(ctmlSink)printf
) {
h(html, .lang="en") {
h(div, .class="nice") {
ctml_text("hello, world");
}
}
}
}
This code will print this html on stdout:
<html lang="en">
<div class="nice">
hello, world
</div>
</html>NOTE: The sink function is a void *(sink) (char*, void* userData) where ctml
will send the generated HTML.
CTML might send data in multiple batches and not only once with the
full generated HTML.
The sink function can also take void* userData with user data
given to ctml() using .userData = ...
The api of the library is really simple. It only consists of few macros and 1 type.
The first macro is h(tag, attributes*) that is used to created
an HTML tag. You also have hh(tag, attributes*) to create self closed
tags.
You'll also need the ctml macro that will create a context
containing the sink as well as the indentation state.
Two last 4 macros allow you to put text inside of the HTML.
You can use ctml_text(char* text) to put some text in the
HTML. This will be escaped by default. You can also use
ctml_textf(char* text, ...) that accepts formating like
printf and co.
The last ones are ctml_raw(char* text) and ctml_rawf(char* text, ...)
that works the same way than ctml_text except they do not do any escaping.
IMPORTANT: ctml_raw(f) does NOT escape anything.
Formatting macros depends on libc to work. (Using snprintf under the hood).
The only type you should care about is CTML_Context as explained
in the Components section.
Internally, ctml uses a context called ctx by default (this can be modified using
Configuration ) and it must be passed around whenever you want to
call a function as a component inside your HTML generation (only if this function also wants to
generate HTML in the same context as the function calling it).
Creating components boils down to just calling C functions. As said before, the only requirement is for you to pass the ctml context across components this way:
void some_button(CTML_Context* ctx) {
h(button, .class="my-btn") {ctml_raw("click me");}
}
void my_ui() {
ctml(.sink=...) {
h(div) {
// NOTE HERE THE CTX
some_button(ctx);
}
}
}The ctx variable is created by the ctml macro. You just need
to pass it around.
This library can be configured using some macros.
CTML_PRETTY will enable pretty print of the HTML.
CTML_NOLIBC will disable all libc dependent stuff (only
the ctml_rawf macro)
CTML_CUSTOM_ATTRIBUTES as explained in Custom Attributes.
To avoid calling the sink loads of times, ctml will bufferize the output.
The size of this buffer can be parametrized using CTML_SINK_BUFSIZE.
The default value is 1024 bytes. Setting it to 1 or 0 will disable
buffering.
Last but not least, as explained in Components ctml internally
uses a context called ctx by default. But you might want to use this name for
something else, so the name used by ctml can be configured using CTML_CTX_NAME
Those macros must be defined BEFORE including ctml.h and must be
repeated each time you inclde it (especially CTML_CUSTOM_ATTRIBUTES).
Creating your own header defining your ctml settings is recommended.
#include "ctml_config.h"
#include "ctml.h"As seen in the example, some attributes are supported by default
like class or id but one might want to use custom/other
attributes that are not already defined inside the library.
A special macro CTML_CUSTOM_ATTRIBUTES exists to let you add those
to the library. It must be used before including ctml.h and
shall be repeated whenever you include it.
It is based on X-macros and let you add attributes this way:
#define CTML_CUSTOM_ATTRIBUTES X(checked);X(color);XL(da, data-attr);This adds attributes checked and color to the engine.
You can also use XL to define shorthands. Like da that will
be replaced by data-attr inside of the generated HTML. (This
XL macro is also useful for attributes names that are not valid
C variable names, like data-attr because of the dash.
Writing h(tag, attributes) can also be replaced by tag(attributes)
if you also include the file ctml_short.h.
This is a file that only declare aliases to make the code more readable. For instance, here is a small snippet using those:
div(.id="truth") {
h1(.class="ctml") {
ctml_text("ctml is great");
}
}Because of the macro system and how the API is designed, it is not (yet?) possible to call
ctml() function multiple times in a row otherwise the ctx (or any custom name set using
CTML_CTX_NAME) variable would be defined multiple times. I currently still does not have
a proper solution to this; The easiest workaround would be to simply avoid calling ctml
multiple times (as it normally should not happen that often), and simply divide it using function
calls to avoid name collisions.
One could also put curly braces around ctml calls to make each of them have their own scope. °°