namespace Caribou { private class XmlDeserializer : Object { public static string? get_layout_file_inner (string data_dir, string group, string variant) { string[] files = {@"$(group)_$(variant).xml", @"$(group).xml"}; foreach (string fn in files) { string layout_fn = GLib.Path.build_filename (data_dir, fn); GLib.File fp = GLib.File.new_for_path (layout_fn); if (fp.query_exists ()) return layout_fn; } return null; } public static string get_layout_file (string keyboard_type, string group, string variant) throws IOError { Gee.ArrayList dirs = new Gee.ArrayList (); string custom_dir = Environment.get_variable("CARIBOU_LAYOUTS_DIR"); if (custom_dir != null) dirs.add (Path.build_filename (custom_dir, "layouts", keyboard_type)); dirs.add (Path.build_filename (Environment.get_user_data_dir (), "caribou", "layouts", keyboard_type)); foreach (string dir in Environment.get_system_data_dirs ()) { dirs.add (Path.build_filename ( dir, "caribou", "layouts", keyboard_type)); } // If no such keyboard type is found, default to "touch" dirs.add (Path.build_filename (Environment.get_user_data_dir (), "caribou", "layouts", "touch")); foreach (string dir in Environment.get_system_data_dirs ()) { dirs.add (Path.build_filename ( dir, "caribou", "layouts", "touch")); } foreach (string data_dir in dirs) { string fn = get_layout_file_inner (data_dir, group, variant); if (fn != null) return fn; } // If no layout file is found, default to US foreach (string data_dir in dirs) { string fn = get_layout_file_inner (data_dir, "us", ""); if (fn != null) return fn; } // Should not be reached, but needed to make valac happy throw new IOError.NOT_FOUND ( "Could not find layout file for %s %s", group, variant); } public static GroupModel? load_group_from_file (string filename) { Xml.Doc* doc; try { doc = Xml.Parser.parse_file (filename); if (doc == null) throw new IOError.FAILED ( "Cannot load XML text reader for %s", filename); } catch (GLib.Error e) { stdout.printf ("Failed to load %s: %s\n", filename, e.message); return null; } // Use dummy group/variant names. GroupModel grp = new GroupModel ("us", ""); Xml.Node* node = doc->children; create_levels_from_xml (grp, node); delete doc; Xml.Parser.cleanup (); return grp; } public static GroupModel? load_group (string keyboard_type, string group, string variant) { string filename; try { filename = get_layout_file (keyboard_type, group, variant); } catch (GLib.Error e) { stdout.printf ("Failed to load keyboard file %s/%s: %s\n", keyboard_type, GroupModel.create_group_name (group, variant), e.message); return null; } var grp = load_group_from_file (filename); if (grp != null) { grp.group = group; grp.variant = variant; } return grp; } public static void create_levels_from_xml (GroupModel group, Xml.Node* node) { assert (node->name == "layout"); for (Xml.Node* iter = node->children; iter != null; iter = iter->next) { if (iter->type != Xml.ElementType.ELEMENT_NODE) continue; string levelname = iter->get_prop ("name"); string mode = iter->get_prop ("mode"); LevelModel level = new LevelModel(mode); load_rows (level, iter); group.add_level(levelname, level); } } public static void load_rows (LevelModel level, Xml.Node* node) { assert (node->name == "level"); for (Xml.Node* i = node->children; i != null; i = i->next) { if (i->type != Xml.ElementType.ELEMENT_NODE) continue; RowModel row = new RowModel (); level.add_row (row); for (Xml.Node* i2 = i->children; i2 != null; i2 = i2->next) { if (i2->type != Xml.ElementType.ELEMENT_NODE) continue; if (i2->name == "key") { load_column (row, i->get_prop ("align"), i); break; } load_column (row, i2->get_prop ("align"), i2); } } } public static void load_column (RowModel row, string? align, Xml.Node* node) { ColumnModel column = new ColumnModel (); row.add_column (column); for (Xml.Node* i = node->children; i != null; i = i->next) { if (i->type != Xml.ElementType.ELEMENT_NODE) continue; column.add_key (load_key (i, align)); } } public static KeyModel load_key (Xml.Node* node, string? align) { assert (node->name == "key"); string name = node->get_prop ("name"); assert (name != null); string? text = node->get_prop ("text"); string? label_override = node->get_prop ("label"); KeyModel key = new KeyModel (name, text); if (label_override != null) key.label = label_override; if (align != null) key.align = align; for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) { if (prop->name == "toggle") key.toggle = prop->children->content; else if (prop->name == "align") key.align = prop->children->content; else if (prop->name == "width") key.width = double.parse (prop->children->content); else if (prop->name == "repeatable" && text == null) key.repeatable = prop->children->content == "yes"; } for (Xml.Node* i = node->children; i != null; i = i->next) { if (i->type != Xml.ElementType.ELEMENT_NODE) continue; key.add_subkey (load_key (i, null)); } return key; } } }