CGI program v C, který generuje XHTML pomocí libxml2

Tento kód jsem kdysi napsal pro generování HTML stránek v cgitu (alternativa GITWEBu napsaná v C). Je to wrapper kolem libxml2 API pro generování XHTML dokumentu. API libxml2 je poněkud zmatené a tak jsem vytvořil něco snáze použitelného.

/* header */
 
#include <glib.h>
#include <libxml/tree.h>
 
typedef struct _page page;
 
struct _page
{
  char* title_text;
 
  xmlDocPtr doc;
  xmlNodePtr head;
  xmlNodePtr body;
  xmlNodePtr title;
  xmlNodePtr content;
};
 
extern page* page_new(const char* title);
extern void page_emit(page* p);
 
extern xmlNodePtr add_node(xmlNodePtr parent, const char* name, const char* cont, ...);
extern void add_text(xmlNodePtr parent, const char* text);
extern void set_attr(xmlNodePtr node, const char* name, const char* value);
extern void page_add_meta(page* p, const char* name, const char* content);
extern xmlNodePtr add_link(xmlNodePtr parent, const char* href, const char* text,
  const char* title);
extern xmlNodePtr add_node_id(xmlNodePtr parent, const char* name, const char* id);
extern xmlNodePtr add_node_class(xmlNodePtr parent, const char* name, const char* class);
extern xmlNodePtr add_hidden_input(xmlNodePtr parent, const char* name, const char* data);
extern void add_text_date(xmlNodePtr parent, time_t secs, char *format);
extern void add_span_age(xmlNodePtr parent, time_t t, time_t max_relative, char *format);
extern void add_html(xmlNodePtr parent, const char* html);
 
/* code */
 
#include <string.h>
 
static xmlChar* ensure_utf8(const char* str)
{
  char* good_str;
 
  if (str == NULL)
    return NULL;
 
  // don't do anything with valid utf8
  if (g_utf8_validate(str, -1, NULL))
    return BAD_CAST str;
 
  // convert string to UTF-8, fixing invalid characters in the process
  good_str = g_convert_with_fallback(str, -1, "UTF-8", "UTF-8", "_",
    NULL, NULL, NULL);
  if (good_str == NULL)
    return BAD_CAST "[conversion failed]";
 
  return BAD_CAST good_str;
}
 
xmlNodePtr add_node(xmlNodePtr parent, const char* name, const char* cont, ...)
{
  xmlNodePtr node;
  va_list args;
 
  node = xmlNewNode(NULL, BAD_CAST ensure_utf8(name));
  if (parent)
    xmlAddChild(parent, node);
 
  va_start(args, cont);
  while (1) {
    char *prop_name, *prop_value;
 
    prop_name = va_arg(args, char*);
    if (prop_name == 0)
      break;
    prop_value = va_arg(args, char*);
    if (prop_value == 0)
      continue;
    set_attr(node, prop_name, prop_value);
  }
  va_end(args);
 
  if (cont)
    add_text(node, cont);
 
  return node;
}
 
void add_text(xmlNodePtr parent, const char* text)
{
  xmlAddChild(parent, xmlNewText(ensure_utf8(text)));
}
 
void set_attr(xmlNodePtr node, const char* name, const char* value)
{
  xmlSetProp(node, ensure_utf8(name), ensure_utf8(value));
}
 
void page_add_meta(page* p, const char* name, const char* content)
{
  add_node(p->head, "meta", NULL, "name", name, "content", content, 0);
}
 
xmlNodePtr add_link(xmlNodePtr parent, const char* href, const char* text,
  const char* title)
{
  return add_node(parent, "a", text, "href", href, "title", title, 0);
}
 
xmlNodePtr add_node_id(xmlNodePtr parent, const char* name, const char* id)
{
  return add_node(parent, name, NULL, "id", id, 0);
}
 
xmlNodePtr add_node_class(xmlNodePtr parent, const char* name, const char* class)
{
  return add_node(parent, name, NULL, "class", class, 0);
}
 
xmlNodePtr add_hidden_input(xmlNodePtr parent, const char* name, const char* data)
{
  return add_node(parent, "input", NULL, "type", "hidden", "name", name, "value", data, 0);
}
 
void add_html(xmlNodePtr parent, const char* html)
{
  int len = strlen(html)+8;
  char* buffer = malloc(len);
  snprintf(buffer, len, "<r>%s</r>", ensure_utf8(html));
  xmlDocPtr doc = xmlReadMemory(buffer, len-1, 0, 0,
    XML_PARSE_NOWARNING|XML_PARSE_NOERROR|XML_PARSE_NONET);
  if (doc == 0)
    return;
  xmlNodePtr nl = xmlDocCopyNodeList(parent->doc, xmlDocGetRootElement(doc)->children);
  xmlAddChildList(parent, nl);
  xmlFreeDoc(doc);
}
 
page* page_new(const char* title)
{
  xmlNodePtr root;
  page* p = malloc(sizeof(page));
 
  p->doc = xmlNewDoc(BAD_CAST "1.0");
  xmlCreateIntSubset(p->doc, BAD_CAST "html",
    BAD_CAST "-//W3C//DTD XHTML 1.0 Strict//EN",
    BAD_CAST "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd");
 
  root = add_node(NULL, "html", NULL, "lang", "en", 0);
  xmlDocSetRootElement(p->doc, root);
 
  p->head = add_node(root, "head", NULL, 0);
  p->title = add_node(p->head, "title", title, 0);
  p->body = add_node(root, "body", NULL, 0);
 
  page_add_meta(p, "generator", g_strdup_printf("cgit v%s", "1.0"));
  add_node(p->head, "link", 0, "rel", "stylesheet", "type", "text/css",
    "href", "style.css", 0);
 
  return p;
}
 
void page_emit(page* p)
{
  char *buf, *out;
  int size;
 
  xmlDocDumpFormatMemoryEnc(p->doc, (xmlChar**)&buf, &size, "UTF-8", 1);
  /* remove <?xml ?> line to make IE happy */
  out = strchr(buf, '\n');
  if (out == NULL)
    return;
  out++;
  size -= (out - buf);
 
  /* send headers */
  fprintf(stdout,
    "Content-Type: text/html; charset=UTF-8\r\n"
    "Content-Length: %d\r\n"
    "\r\n",
    size);
  fwrite(out, size, 1, stdout);
  fflush(stdout);
}
 
int main()
{
  page* p = page_new("test");
  add_node(p->body, "div", "Content text", NULL);
  page_emit(p);
}

Kód si můžete také stáhnout a zkompilovat pomocí příkazu:

gcc -o cgi cgi.c `pkg-config --libs --cflags glib-2.0 libxml-2.0`