(* * <COPYRIGHT> * Copyright 2003 Gerd Stolpmann * * <GPL> * This file is part of WTimer. * * WTimer is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * WTimer is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with WDialog; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * </> *) (* $Id: db_xml.ml,v 1.3 2003/03/23 11:59:14 gerd Exp $ * ---------------------------------------------------------------------- * *) open Db.Connection open Db.Types open Db_types.Types open Db_types open Pxp_types open Pxp_yacc open Pxp_reader open Pxp_document let wef_namespace = "http://www.ocaml-programming.de/formal/wef-namespace" ;; let wef_public_id = "-//ocaml-programming.de//DTD WEF 1//EN";; let wef_system_id = "http://www.ocaml-programming.de/formal/wef-dtd-1";; let indent = [| ""; " "; " "; " "; " "; " " |] ;; let out_indent out level = out # output_string (indent.(level)) ;; let unsafe_chars_xml = "<>\"'&\r\n\t";; let encode = Netencoding.Html.encode ~in_enc:Const.internal_charset ~out_enc:`Enc_utf8 ~prefer_name:false ~unsafe_chars:unsafe_chars_xml () ;; let encode_bool = function false -> "no" | true -> "yes" ;; let output_decl root_name out = out # output_string "<?xml version='1.0' encoding='UTF-8'?>\n"; out # output_string ("<!DOCTYPE " ^ root_name ^ " PUBLIC '" ^ wef_public_id ^ "' '" ^ wef_system_id ^ "'>\n\n") ;; let output_users ?(level = 0) ?users db out = let user_list = match users with None -> Db.User.list db | Some l -> List.map (Db.User.get db) l in out_indent out level; out # output_string "<wef:users"; if level = 0 then out # output_string (" xmlns:wef='" ^ wef_namespace ^ "'>\n") else out # output_string ">\n"; List.iter (fun user -> out_indent out (level+1); out # output_string "<wef:user\n"; ( match user.u_name with User n -> out_indent out (level+2); out # output_string ("name='" ^ encode n ^ "'\n"); ); out_indent out (level+2); out # output_string ("description='" ^ encode user.u_description ^ "'\n"); ( match user.u_password with None -> () | Some pw -> out_indent out (level+2); out # output_string ("password='" ^ encode pw ^ "'\n"); ); out_indent out (level+2); out # output_string ("login='" ^ encode_bool user.u_login ^ "'\n"); out_indent out (level+2); out # output_string ("admin='" ^ encode_bool user.u_admin ^ "'/>\n"); ) user_list; out_indent out level; out # output_string "</wef:users>\n" ;; let very_early = Date.from_string "1900-01-01";; (* TODO *) let very_late = Date.from_string "2100-12-31";; (* TODO *) let output_instances ?(level = 0) ?users ?instances ?(start_date = very_early) ?(end_date = very_late) db out = let inst_list = match instances with None -> Db.Instance.list db | Some l -> List.map (Db.Instance.get db) l in let user_name_list = match users with None -> List.map (fun user -> user.u_name) (Db.User.list db) | Some l -> List.iter (fun u -> ignore(Db.User.get db u)) l; (* assert that users exist *) l in out_indent out level; out # output_string "<wef:instances"; if level = 0 then out # output_string (" xmlns:wef='" ^ wef_namespace ^ "'>\n") else out # output_string ">\n"; List.iter (fun inst -> ( match inst.i_name with Instance n -> out_indent out (level+1); out # output_string ("<wef:instance\n"); out_indent out (level+2); out # output_string ("name='" ^ encode n ^ "'\n"); out_indent out (level+2); out # output_string ("description='" ^ encode inst.i_description ^ "'>\n"); ); let perms = Db.Permission.get db inst.i_name in if perms.p_set <> [] then begin out_indent out (level+2); out # output_string "<wef:permission>\n"; List.iter (fun (User u, p) -> if List.mem (User u) user_name_list then begin out_indent out (level+3); out # output_string ("<wef:allow user='" ^ encode u ^ "' right='" ^ ( match p with `Write -> "write" | `Read -> "read" | `Owner -> "owner" ) ^ "'/>\n"); end ) perms.p_set; out_indent out (level+2); out # output_string "</wef:permission>\n"; end; let dates = Db.Entry.list db inst.i_name start_date end_date in List.iter (fun date -> out_indent out (level+2); out # output_string ("<wef:day date='" ^ Date.to_string date ^ "'>\n"); let day = Db.Entry.get db inst.i_name date in List.iter (fun entry -> out_indent out (level+3); out # output_string "<wef:entry\n"; ( match entry.e_start with None -> () | Some x -> out_indent out (level+4); out # output_string ("start-time='" ^ Time.to_string x ^ "'\n"); ); ( match entry.e_end with None -> () | Some x -> out_indent out (level+4); out # output_string ("end-time='" ^ Time.to_string x ^ "'\n"); ); ( match entry.e_duration with None -> () | Some x -> out_indent out (level+4); out # output_string ("duration='" ^ Interval.to_string x ^ "'\n"); ); out_indent out (level+4); out # output_string ("project='" ^ encode entry.e_project ^ "'\n"); out_indent out (level+4); out # output_string ("description='" ^ encode entry.e_description ^ "'/>\n"); ) day.d_entries; out_indent out (level+2); out # output_string "</wef:day>\n"; ) dates; out_indent out (level+1); out # output_string "</wef:instance>\n"; ) inst_list; out_indent out level; out # output_string "</wef:instances>\n" ;; let export_users ?users db out = output_decl "wef:users" out; output_users ~level:0 ?users db out ;; let export_instances ?instances ?start_date ?end_date db out = output_decl "wef:instances" out; output_instances ~level:0 ?instances ?start_date ?end_date db out ;; let export_dataset ?users ?instances ?start_date ?end_date db out = output_decl "wef:dataset" out; out # output_string ("<wef:dataset xmlns:wef='" ^ wef_namespace ^ "'>\n"); output_users ~level:1 ?users db out; output_instances ~level:1 ?users ?instances ?start_date ?end_date db out; out # output_string "</wef:dataset>\n"; ;; type conflict_strategy = [ `Fail | `Overwrite | `Only_add ] ;; exception Conflict of string ;; let import ?(onconflict = `Fail) db inp = let rec trav_dataset node = assert(node # node_type = T_element "wef:dataset"); node # iter_nodes trav_start and trav_users node = assert(node # node_type = T_element "wef:users"); node # iter_nodes trav_user and trav_user node = assert(node # node_type = T_element "wef:user"); let name = node # required_string_attribute "name" in let description = node # required_string_attribute "description" in let password = node # optional_string_attribute "password" in let login = node # required_string_attribute "login" = "yes" in let admin = node # required_string_attribute "admin" = "yes" in let user = { u_name = User name; u_description = description; u_password = password; u_login = login; u_admin = admin; } in ( try let old_user = Db.User.get db (User name) in (* User exists! *) if old_user <> user then ( match onconflict with `Fail -> raise (Conflict("User " ^ name)) | `Overwrite -> Db.User.update db user | `Only_add -> () ) with No_such_user _ -> Db.User.insert db user ) and trav_instances node = assert(node # node_type = T_element "wef:instances"); node # iter_nodes trav_instance and trav_instance node = assert(node # node_type = T_element "wef:instance"); let name = node # required_string_attribute "name" in let description = node # required_string_attribute "description" in let inst = { i_name = Instance name; i_description = description } in let inst' = ( try let old_inst = Db.Instance.get db (Instance name) in (* Instance exists! *) if old_inst <> inst then ( match onconflict with `Fail -> raise (Conflict("Instance " ^ name)) | `Overwrite -> Db.Instance.update db inst; inst | `Only_add -> old_inst ) else inst with No_such_instance _ -> Db.Instance.insert db inst; inst ) in node # iter_nodes (trav_perm_or_day inst') and trav_perm_or_day inst node = match node # node_type with T_element "wef:permission" -> trav_permission inst node | T_element "wef:day" -> trav_day inst node | _ -> assert false and trav_permission inst node = assert(node # node_type = T_element "wef:permission"); node # iter_nodes (trav_allow inst) and trav_allow inst node = assert(node # node_type = T_element "wef:allow"); let uname = node # required_string_attribute "user" in let right = match node # required_string_attribute "right" with "read" -> `Read | "write" -> `Write | "owner" -> `Owner | _ -> assert false in let pset = Db.Permission.get db inst.i_name in if List.mem_assoc (User uname) pset.p_set then ( (* There is already a right for this user *) let old_right = List.assoc (User uname) pset.p_set in if old_right <> right then ( match onconflict with `Fail -> raise (Conflict("Instance " ^ string_of_inst_name inst.i_name ^ ", permission of user " ^ uname)) | `Overwrite -> let pset' = { p_instance = inst.i_name; p_set = List.map (fun (u,r) -> if u = User uname then (u, right) else (u, r) ) pset.p_set } in Db.Permission.update db pset' | `Only_add -> () ) ) else ( let pset' = { p_instance = inst.i_name; p_set = (User uname, right) :: pset.p_set } in Db.Permission.update db pset' ) and trav_day inst node = assert(node # node_type = T_element "wef:day"); let date_str = node # required_string_attribute "date" in let date = Date.from_string date_str in let entries_as_tuples = Array.of_list (List.map (trav_entry date inst) (node # sub_nodes)) in let entries = Array.init (Array.length entries_as_tuples) (fun k -> let (start_time, end_time, duration, project, description) = entries_as_tuples.(k) in { e_id = None; e_instance = inst.i_name; e_day = date; e_index = k; e_start = start_time; e_end = end_time; e_duration = duration; e_project = project; e_description = description; } ) in let old_day = Db.Entry.get db inst.i_name date in if old_day.d_entries <> [] then ( (* Compare old_entries and entries: *) let old_entries' = Array.of_list (List.map (fun e -> { e with e_id = None }) old_day.d_entries) in if old_entries' <> entries then ( match onconflict with `Fail -> raise (Conflict("Instance " ^ string_of_inst_name inst.i_name ^ ", date " ^ date_str)) | `Overwrite -> let day = { d_instance = inst.i_name; d_day = date; d_entries = Array.to_list entries; } in Db.Entry.update db day | `Only_add -> () ) ) else ( let day = { d_instance = inst.i_name; d_day = date; d_entries = Array.to_list entries; } in Db.Entry.update db day ) and trav_entry date inst node = assert(node # node_type = T_element "wef:entry"); let start_time = node # optional_string_attribute "start-time" in let end_time = node # optional_string_attribute "end-time" in let duration = node # optional_string_attribute "duration" in let project = node # required_string_attribute "project" in let description = node # required_string_attribute "description" in ((match start_time with Some d -> Some(Time.from_string d) | None -> None), (match end_time with Some d -> Some(Time.from_string d) | None -> None), (match duration with Some d -> Some(Interval.from_string d) | None -> None), project, description) and trav_start node = match node # node_type with T_element "wef:dataset" -> trav_dataset node | T_element "wef:users" -> trav_users node | T_element "wef:instances" -> trav_instances node | _ -> failwith "Invalid XML starting point" in let config = { default_namespace_config with (* warner = XXX; *) (* TODO *) encoding = Const.internal_charset; recognize_standalone_declaration = false; } in let toplevel_id = Private(allocate_private_id()) in let wef_dtd_file = Filename.concat Const.ui_dir "wef.dtd" in let resolver = new combine [ lookup_public_id_as_file [ wef_public_id, wef_dtd_file ]; lookup_system_id_as_file [ wef_system_id, wef_dtd_file ]; new resolve_as_file (); new resolve_read_this_channel ~id:toplevel_id inp; ] in let doc = parse_document_entity config (ExtID(toplevel_id, resolver)) default_spec in trav_start doc#root ;; (* ====================================================================== * History: * * $Log: db_xml.ml,v $ * Revision 1.3 2003/03/23 11:59:14 gerd * GPL * * Revision 1.2 2003/02/07 14:18:52 gerd * wef.dtd is taken from install dir * * Revision 1.1 2003/01/16 00:31:10 gerd * Initial revision. * * *)