Skip to content
/ ctml Public

Lightweight header-only C HTML Templating engine

License

Notifications You must be signed in to change notification settings

tdaron/ctml

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

43 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CTML

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.

Features

  • 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.

Quick Example

With shorthands

(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");
            }
        }
    }
}
 

Without shorthands

#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 = ...

Usage

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.

Components

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.

Configuration

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"

Custom Attributes

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.

Shorthands

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");
    }
}

Limitations

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. °°

About

Lightweight header-only C HTML Templating engine

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages