Skip to content

Instantly share code, notes, and snippets.

@rmg
Created February 24, 2010 22:10
Show Gist options
  • Save rmg/313922 to your computer and use it in GitHub Desktop.
Save rmg/313922 to your computer and use it in GitHub Desktop.
A rope-like approach to building up a DOM and spitting it out as text in C
Sometimes you're stuck in C and you need to output HTML, but you don't
want to mess with strings, and you don't want to mess with XML...
This takes a rope-like approach to building up a DOM tree.
#include <stddef.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include "dom.h"
//
// NOTE: strdup(), malloc(), calloc(), et al are used all willy-nilly and
// without regard for errors or ever calling free()...
// THIS IS INTENTIONAL
//
/**
* strdup() isn't C99
*/
const char * dom_strdup(const char * in) {
size_t len = strlen(in) + 1;
char * out = malloc(len);
if (out) {
memcpy(out, in, len);
}
return out;
}
/**
* simple macro of initializing list nodes
*/
#define INIT_HEAD(_NAME_) _NAME_.next = _NAME_.prev = &(_NAME_)
/**
* list_add_tail - add a new entry
* @new_node: new entry to be added
* @head: list head to add it before
*
* Insert a new entry before the specified head.
* This is useful for implementing queues.
*/
static inline void list_add_tail(struct list_head *new_node, struct list_head *head)
{
head->prev->next = new_node;
new_node->prev = head->prev;
head->prev = new_node;
new_node->next = head;
}
/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
*
*/
#define container_of(ptr, type, member) ( (type *)((char*)ptr - offsetof(type, member)) )
/**
* list_for_each_entry - iterate over list of given type
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*/
#define list_for_each_entry(pos, head, member) \
for (pos = container_of((head)->next, __typeof__(*pos), member); \
&pos->member != (head); \
pos = container_of(pos->member.next, __typeof__(*pos), member))
/**
* list_empty - tests whether a list is empty
* @head: the list to test.
*/
static inline int list_empty(const struct list_head *head)
{
return head->next == head;
}
static inline node_t * Node(node_type t) {
node_t * node = calloc(1, sizeof(node_t));
node->type = t;
INIT_HEAD(node->h);
INIT_HEAD(node->attrs);
INIT_HEAD(node->classes);
INIT_HEAD(node->styles);
INIT_HEAD(node->children);
return node;
}
node_t * Tag(const char * type, ...) {
va_list vargs;
node_t * n;
node_t * tag = Node(TAG);
tag->name = dom_strdup(type);
va_start(vargs, type);
while ((n = va_arg(vargs, node_t*))) {
switch(n->type) {
case TAG:
case CDATA:
list_add_tail(&n->h, &tag->children);
break;
case ATTR:
list_add_tail(&n->h, &tag->attrs);
break;
case CLASS:
list_add_tail(&n->h, &tag->classes);
break;
case STYLE:
list_add_tail(&n->h, &tag->styles);
break;
default:
printf("Unhandled type\n");
break;
}
}
va_end(vargs);
return tag;
}
node_t * Cdata(const char * str) {
node_t * cdata = Node(CDATA);
cdata->value = dom_strdup(str);
return cdata;
}
node_t * Attr(const char * name, const char * value) {
node_t * attr = Node(ATTR);
attr->name = dom_strdup(name);
attr->value = dom_strdup(value);
return attr;
}
node_t * Class(const char * str) {
node_t * class = Node(CLASS);
class->name = dom_strdup(str);
return class;
}
node_t * Style(const char * name, const char * value) {
node_t * style = Node(STYLE);
style->name = dom_strdup(name);
style->value = dom_strdup(value);
return style;
}
node_t * add(node_t * base_node, node_t * new_node) {
if (base_node->type == TAG) {
switch(new_node->type) {
case TAG:
case CDATA:
list_add_tail(&new_node->h, &base_node->children);
break;
case ATTR:
list_add_tail(&new_node->h, &base_node->attrs);
break;
case CLASS:
list_add_tail(&new_node->h, &base_node->classes);
break;
case STYLE:
list_add_tail(&new_node->h, &base_node->styles);
break;
default:
printf("Unable to add to tag<%s>\n", base_node->name);
break;
}
} else {
printf("Unable to add to non-tag\n");
}
return base_node;
}
void render(node_t *n) {
render_i(n, 0);
}
void render_i(node_t *n, int i) {
if (n->type == TAG) {
node_t *t;
printf("%*s<%s", i, (i ? " " : ""), n->name);
list_for_each_entry(t, &n->attrs, h) {
if (t->value) {
printf(" %s=\"%s\"", t->name, t->value);
} else {
printf(" %s", t->name);
}
}
if (!list_empty(&n->classes)) {
printf(" class=\"");
list_for_each_entry(t, &n->classes, h)
printf("%s ", t->name);
printf("\"");
}
if (!list_empty(&n->styles)) {
printf(" style=\"");
list_for_each_entry(t, &n->styles, h)
printf("%s: %s,", t->name, t->value);
printf("\"");
}
printf(">\n");
list_for_each_entry(t, &n->children, h) {
render_i(t, i+4);
}
printf("%*s</%s>\n", i, (i ? " " : ""), n->name);
} else if (n->type == CDATA) {
printf("%*s%s\n", i, (i ? " " : ""), n->value);
} else {
printf("Unable to render\n");
}
}
typedef enum {
TAG,
CDATA,
ATTR,
CLASS,
STYLE
} node_type;
struct list_head {
struct list_head *next, *prev;
};
typedef struct {
node_type type;
const char * name;
const char * value;
struct list_head attrs;
struct list_head classes;
struct list_head styles;
struct list_head children;
struct list_head h;
} node_t;
node_t * Tag(const char * type, ...);
node_t * Cdata(const char * str);
node_t * Attr(const char * name, const char * value);
node_t * Class(const char * str);
node_t * Style(const char * name, const char * value);
node_t * add(node_t * base_node, node_t * new_node);
void render_i(node_t *n, int i);
void render(node_t *n);
#include "dom.h"
// You can imagine how this would get completely out of hand if the
// "constructors" were implemented as macros instead of functions...
int main(void) {
#ifdef PEDANTIC
/* This code compiles with -pedantic */
node_t * html =
Tag("html",
Tag("head", Tag("title", Cdata("New!"), 0), 0),
Tag("body",
Attr("background", "img.jpg"),
Tag("div", Class("outer-box"),
Tag("div", Class("menu-box"),
Tag("ul", Class("menu-list"),
Tag("li",
Tag("a", Attr("href", "#1"),
Cdata("Page 1"),
0),
0),
Tag("li",
Tag("a", Attr("href", "#2"),
Cdata("Page 2"),
0),
0),
0),
0),
Tag("div", Class("body-box"),
Tag("p", Style("border", "1px"), Style("color", "red"),
Cdata("This is the page!"),
0),
0),
Tag("div", Class("footer-box"),
Tag("span", Class("legal"),
Cdata("Copyright..."),
0),
0),
0),
0),
0);
node_t * table = Tag("table", 0);
for (int i = 0; i < 5; i++) {
node_t * row = Tag("tr", 0);
for (char x = 'a'; x < 'c'; x++) {
add(row, Tag("td", Cdata("X"), 0));
}
add(table, row);
}
#else
/* This version demonstrates cleaner looking code, but doesn't compiles with -pedantic */
#define T(args...) Tag(args, 0)
node_t * html =
T("html",
T("head",
T("title",
Cdata("New!"))),
T("body", Attr("background", "img.jpg"),
T("div", Class("outer-box"),
T("div", Class("menu-box"),
T("ul", Class("menu-list"),
T("li",
T("a", Attr("href", "#1"),
Cdata("Page 1"))),
T("li",
T("a", Attr("href", "#2"),
Cdata("Page 2"))))),
T("div", Class("body-box"),
T("p", Style("border", "1px"), Style("color", "red"),
Cdata("This is the page!"))),
T("div", Class("footer-box"),
T("span", Class("legal"),
Cdata("Copyright..."))))));
node_t * table = T("table");
for (int i = 0; i < 5; i++) {
node_t * row = T("tr");
for (char x = 'a'; x < 'c'; x++) {
add(row, T("td", Cdata("X")));
}
add(table, row);
}
#endif
render(add(html, table));
render(html);
return 0;
}
# A rope-like HTML/SGML/XML tree generator
# Initializers inside "for (...)" is technically only as of C99
CFLAGS = --std=c99
# If we can live with the trailing 0's in the function calls, go pedantic
ifdef PEDANTIC
CFLAGS += -pedantic -Werror -DPEDANTIC
endif
# And now for something COMPLETELY fun
#CC=pcc
test: domtest
./domtest
domtest: domtest.o dom.o
clean:
$(RM) *.o domtest
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment