const std = @import("std"); const xml = @import("./xml.zig"); const Property = @This(); pub const Type = enum { string, int, bool, const map: std.StaticStringMap(Type) = .initComptime(.{ .{ "string", .string }, .{ "int", .int }, .{ "bool", .bool }, }); }; pub const Value = union(Type) { string: []const u8, int: i32, bool: bool }; name: []const u8, value: Value, pub const List = struct { items: []Property, pub const empty = List{ .items = &[0]Property{} }; pub fn initFromXml( arena: std.mem.Allocator, scratch: *std.heap.ArenaAllocator, doc: xml.Document, element_id: xml.Element.Id ) !Property.List { _ = try doc.getTagExpect(element_id, "properties"); var temp_properties: std.ArrayList(Property) = .empty; var iter = doc.iterChildren(element_id); while (iter.next()) |child_id| { const child = doc.getTag(child_id) orelse continue; const child_name = child.variant.tag.name; if (std.mem.eql(u8, child_name, "property")) { const property = try Property.initFromXml(arena, doc, child_id); try temp_properties.append(scratch.allocator(), property); } } const properties = try arena.dupe(Property, temp_properties.items); return List{ .items = properties }; } pub fn get(self: List, name: []const u8) ?Value { for (self.items) |item| { if (std.mem.eql(u8, item.name, name)) { return item.value; } } return null; } pub fn getString(self: List, name: []const u8) ?[]const u8 { if (self.get(name)) |value| { return value.string; } return null; } }; pub fn init(name: []const u8, value: Value) Property { return Property{ .name = name, .value = value }; } pub fn initFromXml(arena: std.mem.Allocator, doc: xml.Document, element_id: xml.Element.Id) !Property { const tag = try doc.getTagExpect(element_id, "property"); const attrs = tag.variant.tag.attributes; const name = try attrs.getDupe(arena, "name") orelse return error.MissingName; const prop_type_str = attrs.get("type") orelse "string"; const value_str = attrs.get("value") orelse ""; const prop_type = Type.map.get(prop_type_str) orelse return error.UnknownPropertyType; const value = switch(prop_type) { .string => Value{ .string = try arena.dupe(u8, value_str) }, .int => Value{ .int = try std.fmt.parseInt(i32, value_str, 10) }, .bool => Value{ .bool = std.mem.eql(u8, value_str, "true") } }; return Property{ .name = name, .value = value }; } fn expectParsedEquals(expected: Property, body: []const u8) !void { const allocator = std.testing.allocator; var ctx: xml.Lexer.TestingContext = undefined; ctx.init(allocator, body); defer ctx.deinit(); var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); const parsed = try initFromXml(arena.allocator(), &ctx.lexer); try std.testing.expectEqualDeep(expected, parsed); } test Property { try expectParsedEquals( Property.init("solid", .{ .string = "hello" }), \\ ); try expectParsedEquals( Property.init("solid", .{ .string = "hello" }), \\ ); try expectParsedEquals( Property.init("integer", .{ .int = 123 }), \\ ); try expectParsedEquals( Property.init("boolean", .{ .bool = true }), \\ ); try expectParsedEquals( Property.init("boolean", .{ .bool = false }), \\ ); }