From 4e5b81c1688080ea770184f54c66ebe82482b692 Mon Sep 17 00:00:00 2001 From: frosty Date: Sat, 19 Sep 2026 23:40:53 -0400 Subject: feat: add html.append helper --- src/Dom/Dom.c | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 109 insertions(+), 12 deletions(-) (limited to 'src/Dom') diff --git a/src/Dom/Dom.c b/src/Dom/Dom.c index 4eb3ad4..67817aa 100644 --- a/src/Dom/Dom.c +++ b/src/Dom/Dom.c @@ -6,10 +6,18 @@ #include #include +#define HTML_NODE_METATABLE "yapssg.html_node" + static int absolute_index(lua_State *lua, int index) { return index < 0 ? lua_gettop(lua) + index + 1 : index; } +static void set_html_node_metatable(lua_State *lua, int table_index) { + table_index = absolute_index(lua, table_index); + luaL_getmetatable(lua, HTML_NODE_METATABLE); + lua_setmetatable(lua, table_index); +} + static int table_is_node(lua_State *lua, int table_index) { int is_node; @@ -25,15 +33,16 @@ static int table_is_node(lua_State *lua, int table_index) { return is_node; } -static void append_element_child(lua_State *lua, int children_index, - int child_index, size_t *child_count, - unsigned int depth) { +static void append_normalized_child(lua_State *lua, int children_index, + int child_index, size_t *child_count, + unsigned int depth, + const char *function_name) { size_t array_length; size_t index; int child_type; if (!lua_checkstack(lua, 2)) { - luaL_error(lua, "html.element children are nested too deeply"); + luaL_error(lua, "%s children are nested too deeply", function_name); } children_index = absolute_index(lua, children_index); child_index = absolute_index(lua, child_index); @@ -45,7 +54,7 @@ static void append_element_child(lua_State *lua, int children_index, if (child_type == LUA_TSTRING || (child_type == LUA_TTABLE && table_is_node(lua, child_index))) { if (*child_count == INT_MAX) { - luaL_error(lua, "html.element has too many children"); + luaL_error(lua, "%s has too many children", function_name); } ++*child_count; lua_pushvalue(lua, child_index); @@ -54,21 +63,22 @@ static void append_element_child(lua_State *lua, int children_index, } if (child_type != LUA_TTABLE) { luaL_error(lua, - "html.element child must be a string, node, child array, or " - "nil (got %s)", - lua_typename(lua, child_type)); + "%s child must be a string, node, child array, or nil (got " + "%s)", + function_name, lua_typename(lua, child_type)); } if (depth == 100) { - luaL_error(lua, "html.element children are nested too deeply"); + luaL_error(lua, "%s children are nested too deeply", function_name); } array_length = lua_objlen(lua, child_index); if (array_length > INT_MAX) { - luaL_error(lua, "html.element child array is too large"); + luaL_error(lua, "%s child array is too large", function_name); } for (index = 1; index <= array_length; ++index) { lua_rawgeti(lua, child_index, (int)index); - append_element_child(lua, children_index, -1, child_count, depth + 1); + append_normalized_child(lua, children_index, -1, child_count, depth + 1, + function_name); lua_pop(lua, 1); } } @@ -104,10 +114,84 @@ static int html_element(lua_State *lua) { lua_newtable(lua); children_index = lua_gettop(lua); for (index = 3; index <= argument_count; ++index) { - append_element_child(lua, children_index, index, &child_count, 0); + append_normalized_child(lua, children_index, index, &child_count, 0, + "html.element"); } lua_setfield(lua, -2, "children"); + set_html_node_metatable(lua, -1); + + return 1; +} + +static int html_append(lua_State *lua) { + size_t new_child_count = 0; + size_t existing_child_count; + size_t index; + int argument_count = lua_gettop(lua); + int new_children_index; + int children_index; + const char *tag; + + luaL_checktype(lua, 1, LUA_TTABLE); + lua_getfield(lua, 1, "tag"); + tag = lua_tostring(lua, -1); + if (tag == NULL || tag[0] == '\0') { + return luaL_argerror(lua, 1, "element expected"); + } + lua_pop(lua, 1); + + lua_newtable(lua); + new_children_index = lua_gettop(lua); + for (index = 2; index <= (size_t)argument_count; ++index) { + append_normalized_child(lua, new_children_index, (int)index, + &new_child_count, 0, "html.append"); + } + if (new_child_count == 0) { + lua_pushvalue(lua, 1); + return 1; + } + lua_getfield(lua, 1, "children"); + if (lua_isnil(lua, -1)) { + lua_pop(lua, 1); + lua_newtable(lua); + lua_pushvalue(lua, -1); + lua_setfield(lua, 1, "children"); + } else if (!lua_istable(lua, -1)) { + return luaL_error(lua, "html.append element children must be a table"); + } + children_index = lua_gettop(lua); + existing_child_count = lua_objlen(lua, children_index); + if (existing_child_count > INT_MAX) { + return luaL_error(lua, "html.append element has too many children"); + } + + if (existing_child_count == 0) { + size_t text_length; + + lua_getfield(lua, 1, "text"); + if (lua_type(lua, -1) == LUA_TSTRING) { + lua_tolstring(lua, -1, &text_length); + if (text_length > 0) { + lua_pushvalue(lua, -1); + lua_rawseti(lua, children_index, 1); + existing_child_count = 1; + } + } + lua_pop(lua, 1); + } + + if (new_child_count > INT_MAX - existing_child_count) { + return luaL_error(lua, "html.append element has too many children"); + } + for (index = 1; index <= new_child_count; ++index) { + lua_rawgeti(lua, new_children_index, (int)index); + lua_rawseti(lua, children_index, (int)(existing_child_count + index)); + } + lua_pushnil(lua); + lua_setfield(lua, 1, "text"); + + lua_pushvalue(lua, 1); return 1; } @@ -222,9 +306,21 @@ static int html_find_by_class(lua_State *lua) { } void dom_register_html(lua_State *lua) { + if (luaL_newmetatable(lua, HTML_NODE_METATABLE)) { + lua_newtable(lua); + lua_pushcfunction(lua, html_append); + lua_setfield(lua, -2, "append"); + lua_setfield(lua, -2, "__index"); + lua_pushboolean(lua, 0); + lua_setfield(lua, -2, "__metatable"); + } + lua_pop(lua, 1); + lua_newtable(lua); lua_pushcfunction(lua, html_element); lua_setfield(lua, -2, "element"); + lua_pushcfunction(lua, html_append); + lua_setfield(lua, -2, "append"); lua_pushcfunction(lua, html_find_by_class); lua_setfield(lua, -2, "find_by_class"); lua_setglobal(lua, "html"); @@ -357,6 +453,7 @@ static void push_element(lua_State *lua, const xmlNode *node) { lua_newtable(lua); element_index = lua_gettop(lua); + set_html_node_metatable(lua, element_index); 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); -- cgit v1.3