Plasma GitLab Archive
Projects Blog Knowledge

(*
 * <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.
 *
 * 
 *)

This web site is published by Informatikbüro Gerd Stolpmann
Powered by Caml