Created
February 24, 2010 22:10
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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"); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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