163 lines
4.1 KiB
Zig
163 lines
4.1 KiB
Zig
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" }),
|
|
\\ <property name="solid" value="hello"/>
|
|
);
|
|
|
|
try expectParsedEquals(
|
|
Property.init("solid", .{ .string = "hello" }),
|
|
\\ <property name="solid" type="string" value="hello"/>
|
|
);
|
|
|
|
try expectParsedEquals(
|
|
Property.init("integer", .{ .int = 123 }),
|
|
\\ <property name="integer" type="int" value="123"/>
|
|
);
|
|
|
|
try expectParsedEquals(
|
|
Property.init("boolean", .{ .bool = true }),
|
|
\\ <property name="boolean" type="bool" value="true"/>
|
|
);
|
|
|
|
try expectParsedEquals(
|
|
Property.init("boolean", .{ .bool = false }),
|
|
\\ <property name="boolean" type="bool" value="false"/>
|
|
);
|
|
}
|