aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAnhgelus Morhtuuzh <william@herges.fr>2026-04-16 22:33:44 +0200
committerAnhgelus Morhtuuzh <william@herges.fr>2026-04-16 22:33:44 +0200
commit6df64050b1442a5f3a0f566cd816639ac1fd298f (patch)
tree81de54495e09c501d7d2839828523eaabb7a0569 /src
parent11cc71f3b59fa62fd2fb2cb3b84e689317fb1268 (diff)
feat(dom): element generator
Diffstat (limited to 'src')
-rw-r--r--src/dom/Element.zig150
-rw-r--r--src/lexer/Lexed.zig (renamed from src/lexer/lexed.zig)0
-rw-r--r--src/lexer/Lexer.zig (renamed from src/lexer/lexer.zig)4
-rw-r--r--src/root.zig3
4 files changed, 154 insertions, 3 deletions
diff --git a/src/dom/Element.zig b/src/dom/Element.zig
new file mode 100644
index 0000000..ff8a3d1
--- /dev/null
+++ b/src/dom/Element.zig
@@ -0,0 +1,150 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const eql = std.mem.eql;
+
+pub const Kind = enum {
+ void,
+ content,
+ literal,
+};
+
+const Self = @This();
+
+kind: Kind,
+tag: ?[]const u8 = null,
+attributes: std.StringArrayHashMap([]const u8),
+class_list: std.BufSet,
+content: ?[]*Self = null,
+literal: ?[]const u8 = null,
+
+pub fn init(alloc: Allocator, knd: Kind, tag: []const u8) Self {
+ return .{
+ .kind = knd,
+ .tag = tag,
+ .attributes = .init(alloc),
+ .class_list = .init(alloc),
+ };
+}
+
+pub fn initLit(alloc: Allocator, literal: []const u8) Self {
+ return .{ .kind = .literal, .literal = literal, .attributes = .init(alloc), .class_list = .init(alloc) };
+}
+
+pub fn deinit(self: *Self) void {
+ self.attributes.deinit();
+ self.class_list.deinit();
+}
+
+pub fn render(self: *Self, alloc: Allocator) !std.ArrayList(u8) {
+ var attr = try self.renderAttribute(alloc);
+ defer attr.deinit(alloc);
+ var acc = try std.ArrayList(u8).initCapacity(alloc, 2);
+ errdefer acc.deinit(alloc);
+ if (self.tag) |tag| {
+ try acc.append(alloc, '<');
+ try acc.appendSlice(alloc, tag);
+ try acc.appendSlice(alloc, attr.items);
+ try acc.append(alloc, '>');
+ }
+ switch (self.kind) {
+ .void => return acc,
+ .content => {
+ if (self.content) |content| {
+ for (content) |it| {
+ var sub = try it.render(alloc);
+ defer sub.deinit(alloc);
+ try acc.appendSlice(alloc, sub.items);
+ }
+ }
+ },
+ .literal => try acc.appendSlice(alloc, self.literal.?),
+ }
+ if (self.tag) |tag| {
+ try acc.appendSlice(alloc, "</");
+ try acc.appendSlice(alloc, tag);
+ try acc.append(alloc, '>');
+ }
+ return acc;
+}
+
+fn renderAttribute(self: *Self, alloc: Allocator) !std.ArrayList(u8) {
+ var iter = self.attributes.iterator();
+ if (iter.len == 0) return .empty;
+ var acc = try std.ArrayList(u8).initCapacity(alloc, 2);
+ errdefer acc.deinit(alloc);
+ try acc.append(alloc, ' ');
+ var i: usize = 0;
+ while (iter.next()) |it| : (i += 1) {
+ try acc.appendSlice(alloc, it.key_ptr.*);
+ try acc.appendSlice(alloc, "=\"");
+ // MISSING ESCAPING!!!
+ try acc.appendSlice(alloc, it.value_ptr.*);
+ try acc.append(alloc, '"');
+ if (i < iter.len - 1) try acc.append(alloc, ' ');
+ }
+ return acc;
+}
+
+pub fn setAttribute(self: *Self, k: []const u8, v: []const u8) !void {
+ try self.attributes.put(k, v);
+}
+
+pub fn removeAttribute(self: *Self, k: []const u8) void {
+ _ = self.attributes.orderedRemove(k);
+}
+
+pub fn hasAttribute(self: *Self, k: []const u8) bool {
+ return self.attributes.contains(k);
+}
+
+fn doTest(alloc: Allocator, el: *Self, exp: []const u8) !void {
+ var rendered = try el.render(alloc);
+ defer rendered.deinit(alloc);
+ std.testing.expect(eql(u8, rendered.items, exp)) catch |err| {
+ std.debug.print("{s}\n", .{rendered.items});
+ return err;
+ };
+}
+
+test "void element" {
+ var arena = std.heap.DebugAllocator(.{}).init;
+ defer _ = arena.deinit();
+ const alloc = arena.allocator();
+
+ var br = init(alloc, .void, "br");
+ defer br.deinit();
+
+ try doTest(alloc, &br, "<br>");
+
+ var img = init(alloc, .void, "img");
+ defer img.deinit();
+ try img.setAttribute("src", "foo");
+ try img.setAttribute("alt", "bar");
+
+ try doTest(alloc, &img, "<img src=\"foo\" alt=\"bar\">");
+}
+
+test "content element" {
+ var arena = std.heap.DebugAllocator(.{}).init;
+ defer _ = arena.deinit();
+ const alloc = arena.allocator();
+
+ var p = init(alloc, .content, "p");
+ defer p.deinit();
+
+ var content = initLit(alloc, "hello world");
+ defer content.deinit();
+ var in = [_]*Self{&content};
+ p.content = &in;
+
+ try doTest(alloc, &content, "hello world");
+ try doTest(alloc, &p, "<p>hello world</p>");
+
+ var div = init(alloc, .content, "div");
+ defer div.deinit();
+ try div.setAttribute("class", "foo-bar");
+ var in2 = [_]*Self{&p, &content};
+ div.content = &in2;
+
+ try doTest(alloc, &div, "<div class=\"foo-bar\"><p>hello world</p>hello world</div>");
+}
diff --git a/src/lexer/lexed.zig b/src/lexer/Lexed.zig
index b7c3b2c..b7c3b2c 100644
--- a/src/lexer/lexed.zig
+++ b/src/lexer/Lexed.zig
diff --git a/src/lexer/lexer.zig b/src/lexer/Lexer.zig
index 1144ebc..2705347 100644
--- a/src/lexer/lexer.zig
+++ b/src/lexer/Lexer.zig
@@ -2,7 +2,7 @@ const std = @import("std");
const Allocator = std.mem.Allocator;
const eql = std.mem.eql;
const unicode = std.unicode;
-const Lexed = @import("lexed.zig");
+const Lexed = @import("Lexed.zig");
iter: unicode.Utf8Iterator,
force_lit: bool = false,
@@ -60,7 +60,7 @@ pub fn next(self: *Self, alloc: Allocator) Error!?Lexed {
acc.deinit(alloc);
return null;
};
- return Lexed.init(alloc, kind, acc);
+ return .init(alloc, kind, acc);
}
const kindRes = struct {
diff --git a/src/root.zig b/src/root.zig
index 2bcf565..43a0d7d 100644
--- a/src/root.zig
+++ b/src/root.zig
@@ -1,5 +1,6 @@
const std = @import("std");
-pub const lexer = @import("lexer/lexer.zig");
+pub const lexer = @import("lexer/Lexer.zig");
+pub const element = @import("dom/Element.zig");
pub fn bufferedPrint() !void {
// Stdout is for the actual output of your application, for example if you