aboutsummaryrefslogtreecommitdiff
path: root/src/Dom/Dom.c
diff options
context:
space:
mode:
authorfrosty <gabriel@bwaaa.monster>2026-09-16 16:32:06 -0400
committerfrosty <gabriel@bwaaa.monster>2026-09-16 16:32:06 -0400
commit4c8d764d171ec77ff626cec273c44ec7e7a995fc (patch)
treedc36193e611ece53c8f7e74d1dea71f8d9e7004e /src/Dom/Dom.c
downloadyapssg-4c8d764d171ec77ff626cec273c44ec7e7a995fc.tar.gz
init: initHEADmaster
Diffstat (limited to 'src/Dom/Dom.c')
-rw-r--r--src/Dom/Dom.c303
1 files changed, 303 insertions, 0 deletions
diff --git a/src/Dom/Dom.c b/src/Dom/Dom.c
new file mode 100644
index 0000000..ede7ddd
--- /dev/null
+++ b/src/Dom/Dom.c
@@ -0,0 +1,303 @@
+#include "Dom/Dom.h"
+
+#include <lauxlib.h>
+#include <limits.h>
+#include <stddef.h>
+#include <string.h>
+#include <strings.h>
+
+static int absolute_index(lua_State *lua, int index) {
+ return index < 0 ? lua_gettop(lua) + index + 1 : index;
+}
+
+static int is_lua_element(const xmlNode *node) {
+ return node->type == XML_ELEMENT_NODE &&
+ strcasecmp((const char *)node->name, "lua") == 0;
+}
+
+static void set_string_field(lua_State *lua, int table_index, const char *name,
+ const char *value) {
+ lua_pushstring(lua, value);
+ lua_setfield(lua, table_index, name);
+}
+
+static int has_structured_children(const xmlNode *node) {
+ const xmlNode *child;
+
+ for (child = node->children; child != NULL; child = child->next) {
+ if (is_lua_element(child)) {
+ continue;
+ }
+ if (child->type != XML_TEXT_NODE && child->type != XML_CDATA_SECTION_NODE) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static void push_element(lua_State *lua, const xmlNode *node);
+
+static void push_special_node(lua_State *lua, const xmlNode *node) {
+ lua_newtable(lua);
+ if (node->type == XML_COMMENT_NODE) {
+ set_string_field(lua, -2, "kind", "comment");
+ } else {
+ set_string_field(lua, -2, "kind", "processing_instruction");
+ set_string_field(lua, -2, "target", (const char *)node->name);
+ }
+ set_string_field(lua, -2, "text",
+ node->content == NULL ? "" : (const char *)node->content);
+}
+
+static void add_child_alias(lua_State *lua, int element_index,
+ const xmlNode *child, int child_index) {
+ const char *name = (const char *)child->name;
+
+ lua_getfield(lua, element_index, name);
+ if (lua_isnil(lua, -1)) {
+ lua_pop(lua, 1);
+ lua_pushvalue(lua, child_index);
+ lua_setfield(lua, element_index, name);
+ return;
+ }
+ lua_pop(lua, 1);
+}
+
+static void push_children(lua_State *lua, const xmlNode *node,
+ int element_index, int include_text) {
+ const xmlNode *child;
+ int children_index;
+ int array_index = 1;
+
+ lua_newtable(lua);
+ children_index = lua_gettop(lua);
+
+ for (child = node->children; child != NULL; child = child->next) {
+ if (is_lua_element(child)) {
+ continue;
+ }
+
+ if (child->type == XML_ELEMENT_NODE) {
+ int child_index;
+
+ push_element(lua, child);
+ child_index = lua_gettop(lua);
+ lua_pushvalue(lua, child_index);
+ lua_rawseti(lua, children_index, array_index++);
+ add_child_alias(lua, element_index, child, child_index);
+ lua_pop(lua, 1);
+ } else if (include_text && (child->type == XML_TEXT_NODE ||
+ child->type == XML_CDATA_SECTION_NODE)) {
+ lua_pushstring(
+ lua, child->content == NULL ? "" : (const char *)child->content);
+ lua_rawseti(lua, children_index, array_index++);
+ } else if (child->type == XML_COMMENT_NODE || child->type == XML_PI_NODE) {
+ push_special_node(lua, child);
+ lua_rawseti(lua, children_index, array_index++);
+ }
+ }
+
+ lua_setfield(lua, element_index, "children");
+}
+
+static void push_attributes(lua_State *lua, const xmlNode *node,
+ int element_index) {
+ const xmlAttr *attribute;
+
+ lua_newtable(lua);
+ for (attribute = node->properties; attribute != NULL;
+ attribute = attribute->next) {
+ xmlChar *value = xmlNodeListGetString(node->doc, attribute->children, 1);
+
+ lua_pushstring(lua, value == NULL ? "" : (const char *)value);
+ lua_setfield(lua, -2, (const char *)attribute->name);
+ xmlFree(value);
+ }
+ lua_setfield(lua, element_index, "attributes");
+}
+
+static void push_text(lua_State *lua, const xmlNode *node, int element_index) {
+ const xmlNode *child;
+ luaL_Buffer buffer;
+
+ luaL_buffinit(lua, &buffer);
+ for (child = node->children; child != NULL; child = child->next) {
+ if (child->type == XML_TEXT_NODE || child->type == XML_CDATA_SECTION_NODE) {
+ luaL_addstring(
+ &buffer, child->content == NULL ? "" : (const char *)child->content);
+ }
+ }
+ luaL_pushresult(&buffer);
+ lua_setfield(lua, element_index, "text");
+}
+
+static void push_element(lua_State *lua, const xmlNode *node) {
+ int element_index;
+ int structured_children = has_structured_children(node);
+
+ lua_newtable(lua);
+ element_index = lua_gettop(lua);
+ set_string_field(lua, element_index, "tag", (const char *)node->name);
+ push_attributes(lua, node, element_index);
+ push_children(lua, node, element_index, structured_children);
+ if (!structured_children) {
+ push_text(lua, node, element_index);
+ }
+}
+
+void dom_push_page(lua_State *lua, const xmlNode *root) {
+ push_element(lua, root);
+}
+
+static xmlNodePtr node_from_table(lua_State *lua, int table_index, int *valid);
+
+static int add_attributes(lua_State *lua, int table_index, xmlNodePtr node) {
+ int attributes_index;
+ int valid = 1;
+
+ lua_getfield(lua, table_index, "attributes");
+ attributes_index = lua_gettop(lua);
+ if (lua_istable(lua, -1)) {
+ lua_pushnil(lua);
+ while (valid && lua_next(lua, -2) != 0) {
+ if (lua_type(lua, -2) != LUA_TSTRING ||
+ lua_type(lua, -1) != LUA_TSTRING ||
+ xmlNewProp(node, BAD_CAST lua_tostring(lua, -2),
+ BAD_CAST lua_tostring(lua, -1)) == NULL) {
+ valid = 0;
+ }
+ lua_pop(lua, 1);
+ }
+ }
+ lua_settop(lua, attributes_index - 1);
+ return valid ? 0 : -1;
+}
+
+static int append_lua_child(lua_State *lua, xmlNodePtr parent,
+ int child_index) {
+ xmlNodePtr child = NULL;
+ int valid = 1;
+
+ if (lua_type(lua, child_index) == LUA_TSTRING) {
+ child = xmlNewText(BAD_CAST lua_tostring(lua, child_index));
+ } else if (lua_istable(lua, child_index)) {
+ child = node_from_table(lua, child_index, &valid);
+ } else {
+ valid = 0;
+ }
+
+ if (!valid || child == NULL) {
+ xmlFreeNode(child);
+ return -1;
+ }
+ if (xmlAddChild(parent, child) == NULL) {
+ xmlFreeNode(child);
+ return -1;
+ }
+ return 0;
+}
+
+static int add_children(lua_State *lua, int table_index, xmlNodePtr node) {
+ size_t index;
+ size_t length;
+ int result = 0;
+
+ lua_getfield(lua, table_index, "children");
+ length = lua_istable(lua, -1) ? lua_objlen(lua, -1) : 0;
+
+ if (length > INT_MAX) {
+ lua_pop(lua, 1);
+ return -1;
+ }
+
+ for (index = 1; result == 0 && index <= length; ++index) {
+ lua_rawgeti(lua, -1, (int)index);
+ result = append_lua_child(lua, node, lua_gettop(lua));
+ lua_pop(lua, 1);
+ }
+ lua_pop(lua, 1);
+
+ if (result == 0 && length == 0) {
+ lua_getfield(lua, table_index, "text");
+ if (lua_type(lua, -1) == LUA_TSTRING) {
+ xmlNodePtr text = xmlNewText(BAD_CAST lua_tostring(lua, -1));
+
+ if (text == NULL || xmlAddChild(node, text) == NULL) {
+ xmlFreeNode(text);
+ result = -1;
+ }
+ }
+ lua_pop(lua, 1);
+ }
+ return result;
+}
+
+static xmlNodePtr special_node_from_table(lua_State *lua, int table_index,
+ const char *kind, int *valid) {
+ xmlNodePtr node;
+ const char *text;
+
+ lua_getfield(lua, table_index, "text");
+ text = lua_tostring(lua, -1);
+
+ if (strcmp(kind, "comment") == 0) {
+ node = xmlNewComment(BAD_CAST(text == NULL ? "" : text));
+ } else if (strcmp(kind, "processing_instruction") == 0) {
+ const char *target;
+
+ lua_getfield(lua, table_index, "target");
+ target = lua_tostring(lua, -1);
+ node = target == NULL || target[0] == '\0'
+ ? NULL
+ : xmlNewPI(BAD_CAST target, BAD_CAST(text == NULL ? "" : text));
+ lua_pop(lua, 1);
+ } else {
+ node = NULL;
+ }
+
+ lua_pop(lua, 1);
+ if (node == NULL) {
+ *valid = 0;
+ }
+ return node;
+}
+
+static xmlNodePtr node_from_table(lua_State *lua, int table_index, int *valid) {
+ const char *kind;
+ const char *tag;
+ xmlNodePtr node;
+
+ table_index = absolute_index(lua, table_index);
+ lua_getfield(lua, table_index, "kind");
+ kind = lua_tostring(lua, -1);
+ if (kind != NULL) {
+ node = special_node_from_table(lua, table_index, kind, valid);
+ lua_pop(lua, 1);
+ return node;
+ }
+ lua_pop(lua, 1);
+
+ lua_getfield(lua, table_index, "tag");
+ tag = lua_tostring(lua, -1);
+ node = tag == NULL || tag[0] == '\0' ? NULL : xmlNewNode(NULL, BAD_CAST tag);
+ lua_pop(lua, 1);
+
+ if (node == NULL || add_attributes(lua, table_index, node) != 0 ||
+ add_children(lua, table_index, node) != 0) {
+ xmlFreeNode(node);
+ *valid = 0;
+ return NULL;
+ }
+ return node;
+}
+
+xmlNodePtr dom_page_from_lua(lua_State *lua, int table_index) {
+ int valid = 1;
+ xmlNodePtr root = node_from_table(lua, table_index, &valid);
+
+ if (!valid) {
+ xmlFreeNode(root);
+ return NULL;
+ }
+ return root;
+}