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, lexer: *xml.Lexer ) !Property.List { var iter = xml.TagParser.init(lexer); _ = try iter.begin("properties"); var temp_properties: std.ArrayList(Property) = .empty; while (try iter.next()) |node| { if (node.isTag("property")) { const property = try Property.initFromXml(arena, lexer); try temp_properties.append(scratch.allocator(), property); continue; } try iter.skip(); } try iter.finish("properties"); 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 getBool(self: List, name: []const u8) ?bool { if (self.get(name)) |value| { if (value == .bool) { return value.bool; } } return null; } }; pub fn init(name: []const u8, value: Value) Property { return Property{ .name = name, .value = value }; } pub fn initFromXml(arena: std.mem.Allocator, lexer: *xml.Lexer) !Property { var iter = xml.TagParser.init(lexer); const attrs = try iter.begin("property"); 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") } }; try iter.finish("property"); 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 }), \\ ); }