1 /* Copyright 2009 Yorba Foundation
3 * This software is licensed under the GNU Lesser General Public License
4 * (version 2.1 or later). See the COPYING file in this distribution.
11 public class LoaderHandler : Object {
12 public signal void load_error(string error_message);
13 public signal void complete();
15 public LoaderHandler() {
18 public virtual bool commit_library(string[] attr_names, string[] attr_values) {
22 public virtual bool commit_marina(string[] attr_names, string[] attr_values) {
26 public virtual bool commit_track(string[] attr_names, string[] attr_values) {
30 public virtual bool commit_clip(string[] attr_names, string[] attr_values) {
34 public virtual bool commit_clipfile(string[] attr_names, string[] attr_values) {
38 public virtual bool commit_time_signature_entry(string[] attr_names, string[] attr_values) {
42 public virtual bool commit_tempo_entry(string[] attr_names, string[] attr_values) {
46 public virtual bool commit_click(string[] attr_names, string[] attr_values) {
50 public virtual bool commit_library_preference(string[] attr_names, string[] attr_values) {
54 public virtual void leave_library() {
57 public virtual void leave_marina() {
60 public virtual void leave_track() {
63 public virtual void leave_clip() {
66 public virtual void leave_clipfile() {
71 // TODO: Move these classes into separate file
72 public class XmlTreeLoader {
73 XmlElement current_element = null;
74 public XmlElement root = null;
76 public XmlTreeLoader(string document) {
77 MarkupParser parser = { xml_start_element, xml_end_element, null, null };
78 MarkupParseContext context =
79 new MarkupParseContext(parser, (MarkupParseFlags) 0, this, null);
81 context.parse(document, document.length);
82 } catch (MarkupError e) {
87 void xml_start_element(GLib.MarkupParseContext c, string name,
88 string[] attr_names, string[] attr_values) {
89 Model.XmlElement new_element = new Model.XmlElement(name, attr_names, attr_values, current_element);
93 assert(current_element != null);
94 current_element.add_child(new_element);
97 current_element = new_element;
100 void xml_end_element(GLib.MarkupParseContext c, string name) {
101 assert(current_element != null);
102 current_element = current_element.parent;
106 // ProjectBuilder is responsible for verifying the structure of the XML document.
107 // Subclasses of this class will be responsible for checking the attributes of
108 // any particular element.
109 class ProjectBuilder : Object {
110 LoaderHandler handler;
112 public signal void error_occurred(string error);
114 public ProjectBuilder(LoaderHandler handler) {
115 this.handler = handler;
118 bool check_name(string expected_name, XmlElement node) {
119 if (node.name == expected_name) {
123 error_occurred("expected %s, got %s".printf(expected_name, node.name));
127 void handle_clip(XmlElement clip) {
128 if (check_name("clip", clip)) {
129 if (handler.commit_clip(clip.attribute_names, clip.attribute_values)) {
130 if (clip.children.size != 0) {
131 error_occurred("clip cannot have children");
133 handler.leave_clip();
138 void handle_track(XmlElement track) {
139 if (check_name("track", track)) {
140 emit(this, Facility.LOADING, Level.VERBOSE, "loading track");
141 if (handler.commit_track(track.attribute_names, track.attribute_values)) {
142 foreach (XmlElement child in track.children) {
145 handler.leave_track();
150 void handle_preference(XmlElement preference) {
151 if ("click" == preference.name) {
152 handler.commit_click(preference.attribute_names, preference.attribute_values);
153 } else if ("library" == preference.name) {
154 handler.commit_library_preference(
155 preference.attribute_names, preference.attribute_values);
157 error_occurred("Unknown preference: %s".printf(preference.name));
161 void handle_time_signature(XmlElement time_signature) {
162 foreach (XmlElement child in time_signature.children) {
163 if (check_name("entry", child)) {
164 if (!handler.commit_time_signature_entry(child.attribute_names,
165 child.attribute_values)) {
166 error_occurred("Improper time signature node");
172 void handle_tempo(XmlElement tempo) {
173 foreach (XmlElement child in tempo.children) {
174 if (check_name("entry", child)) {
175 if (!handler.commit_tempo_entry(child.attribute_names, child.attribute_values)) {
176 error_occurred("Improper tempo node");
182 void handle_map(XmlElement map) {
187 case "time_signature":
188 handle_time_signature(map);
191 error_occurred("improper map node");
196 void handle_library(XmlElement library) {
197 if (handler.commit_library(library.attribute_names, library.attribute_values)) {
198 foreach (XmlElement child in library.children) {
199 if (!handler.commit_clipfile(child.attribute_names, child.attribute_values))
200 error_occurred("Improper library node");
202 handler.leave_library();
206 void handle_tracks(XmlElement tracks) {
207 foreach (XmlElement child in tracks.children) {
212 void handle_preferences(XmlElement preferences) {
213 foreach (XmlElement child in preferences.children) {
214 handle_preference(child);
217 void handle_maps(XmlElement maps) {
218 foreach (XmlElement child in maps.children) {
222 public bool check_project(XmlElement? root) {
224 error_occurred("Invalid XML file!");
228 if (check_name("marina", root) &&
229 handler.commit_marina(root.attribute_names, root.attribute_values)) {
230 if (root.children.size != 3 && root.children.size != 4) {
231 error_occurred("Improper number of children!");
235 if (!check_name("library", root.children[0]) ||
236 !check_name("tracks", root.children[1]) ||
237 !check_name("preferences", root.children[2]))
240 if (root.children.size == 4 && !check_name("maps", root.children[3])) {
248 public void build_project(XmlElement? root) {
249 handle_library(root.children[0]);
250 handle_tracks(root.children[1]);
251 handle_preferences(root.children[2]);
252 if (root.children.size == 4) {
253 handle_maps(root.children[3]);
256 handler.leave_marina();
260 public class XmlElement {
261 public string name { get; private set; }
263 public string[] attribute_names;
265 public string[] attribute_values;
267 public Gee.ArrayList<XmlElement> children { get { return _children; } }
269 public weak XmlElement? parent { get; private set; }
271 private Gee.ArrayList<XmlElement> _children;
272 public XmlElement(string name, string[] attribute_names, string[] attribute_values,
273 XmlElement? parent) {
276 this.attribute_names = copy_array(attribute_names);
277 this.attribute_values = copy_array(attribute_values);
278 this.parent = parent;
279 this._children = new Gee.ArrayList<XmlElement>();
282 public void add_child(XmlElement child_element) {
283 _children.add(child_element);
287 public class ProjectLoader : Object {
289 LoaderHandler loader_handler;
292 bool project_load_completed = false;
293 bool load_completed_fired = false;
294 bool handler_completed = false;
296 public signal void load_started(string filename);
297 public signal void load_complete();
298 public signal void load_error(string error);
300 public ProjectLoader(LoaderHandler loader_handler, string? file_name) {
301 this.file_name = file_name;
302 this.loader_handler = loader_handler;
303 loader_handler.load_error.connect(on_load_error);
304 loader_handler.complete.connect(on_handler_complete);
307 void on_load_error(string error) {
308 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_load_error");
312 void on_handler_complete() {
313 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_handler_complete");
314 handler_completed = true;
315 if (project_load_completed && !load_completed_fired) {
316 load_completed_fired = true;
323 FileUtils.get_contents(file_name, out text, out text_len);
324 } catch (FileError e) {
325 emit(this, Facility.LOADING, Level.MEDIUM,
326 "error loading %s: %s".printf(file_name, e.message));
327 load_error(e.message);
331 emit(this, Facility.LOADING, Level.VERBOSE, "Building tree for %s".printf(file_name));
332 XmlTreeLoader tree_loader = new XmlTreeLoader(text);
334 ProjectBuilder builder = new ProjectBuilder(loader_handler);
335 builder.error_occurred.connect(on_load_error);
337 if (builder.check_project(tree_loader.root)) {
338 emit(this, Facility.LOADING, Level.VERBOSE, "project checked out. starting load");
339 load_started(file_name);
340 builder.build_project(tree_loader.root);
341 project_load_completed = true;
342 if (handler_completed && !load_completed_fired) {
343 load_completed_fired = true;
348 emit(this, Facility.LOADING, Level.INFO, "project did not check out. stopping.");