(*
This file is part of licht.
licht 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 3 of the License, or
(at your option) any later version.
licht 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 licht. If not, see .
*)
module T = Tools
module NS = Odf_ns
let u = UTF8.from_utf8string
type t
let load_xml catalog input = begin
let source = Xmlm.make_input ~enc:(Some `UTF_8) (`Channel input) in
let sheet = OdfLoader.load catalog source in
sheet
end
let load catalog file =
let tmp_file = Filename.temp_file "content" ".xml" in
Unix.unlink tmp_file;
let zip = Zip.open_in file in
let content = Zip.find_entry zip "content.xml" in
Zip.copy_entry_to_file zip content tmp_file;
let input = open_in_bin tmp_file in
Tools.try_finally
(fun () -> load_xml catalog input)
(fun () ->
close_in input;
Unix.unlink tmp_file;
Zip.close_in zip
)
let write_type ovalue_type cvalue_type attrs output value = begin
let attrs =
(NS.ovalue_type_attr, ovalue_type)::
(NS.cvalue_type_attr, cvalue_type)::
attrs in
Xmlm.output output (`El_start (NS.table_cell_node, attrs));
Xmlm.output output (`El_start (NS.text_node, []));
Xmlm.output output (`Data value);
Xmlm.output output `El_end;
Xmlm.output output `El_end;
end
(* Writers for differents types *)
let write_num = write_type "float" "float"
let write_str = write_type "string" "string"
let write_bool = write_type "bool" "bool"
let write_error = write_type "string" "error"
let write_date = write_type "date" "date"
module BasicWriter = ScTypes.Type.Eval(struct
type 'a t = (Xmlm.attribute list -> Xmlm.output -> unit)
type 'a obs = 'a t
let str s attrs output = write_str attrs output (UTF8.to_utf8string s)
let bool b attrs output = write_bool attrs output (string_of_bool b)
let num n attrs output =
let f = DataType.Num.to_float n in
let value = string_of_float f in
write_num ((NS.value_attr, value)::attrs) output value
let date d attrs output =
let value = DataType.Date.to_string d in
write_date ((NS.date_value_attr, value)::attrs) output value
let observe value attrs output = value attrs output
end)
let write_basic: type a. 'b list -> Xmlm.output -> a ScTypes.Type.t -> unit = fun attrs output types ->
BasicWriter.eval types attrs output
let write_formula output attrs f = begin function
| ScTypes.Result.Ok x -> write_basic attrs output x
| ScTypes.Result.Error exn -> write_str attrs output "#NAME?"
end
(** Print a reference *)
module Show_ref = struct
type 'a t = UTF8.Buffer.buffer -> unit
type 'a obs = UTF8.Buffer.buffer -> unit
let cell t buffer =
UTF8.Buffer.add_string buffer @@ u"[.";
UTF8.Buffer.add_string buffer @@ Cell.to_string t;
UTF8.Buffer.add_string buffer @@ u"]"
let range c1 c2 buffer =
UTF8.Buffer.add_string buffer @@ u"[.";
UTF8.Buffer.add_string buffer @@ Cell.to_string c1;
UTF8.Buffer.add_string buffer @@ u":.";
UTF8.Buffer.add_string buffer @@ Cell.to_string c2;
UTF8.Buffer.add_string buffer @@ u"]"
let observe elem buffer = elem buffer
end
module Show_type = struct
type 'a t = UTF8.Buffer.buffer -> unit
type 'a obs = UTF8.Buffer.buffer -> unit
let str s buffer =
UTF8.Printf.bprintf buffer "\"%s\"" (UTF8.to_utf8string s)
let num n buffer =
let f = DataType.Num.to_float n in
UTF8.Buffer.add_string buffer @@ u(string_of_float f)
let date n buffer = DataType.Date.to_string n
|> u
|> UTF8.Buffer.add_string buffer
let bool b buffer =
UTF8.Buffer.add_string buffer @@ u(string_of_bool b)
let observe elem buffer = elem buffer
end
module ExpressionPrinter = ScTypes.Expr.Eval(Show_expr.Show_Expr(Show_ref)(Show_type))
let write_cell output value = begin function
| Expression.Undefined -> ()
| Expression.Basic b -> write_basic [] output b
| Expression.Formula (Expression.Expression f) ->
let buffer = UTF8.Buffer.create 10 in
ExpressionPrinter.eval f buffer;
let formula = UTF8.Buffer.contents buffer
|> UTF8.to_utf8string in
write_formula output [(NS.formula_attr, ("of:=" ^formula))] f value
| Expression.Formula (Expression.Error (i, s)) ->
write_error [(NS.formula_attr, ("of:" ^ (UTF8.to_utf8string s)))] output (UTF8.to_utf8string s)
end
(** Jump to the wanted position *)
let goto output (from_x, from_y) (to_x, to_y) :unit = begin
(** Insert as many rows than required *)
let insert_rows count = begin
(* Close the previous openend rows *)
Xmlm.output output `El_end;
if (count > 1) then (
Xmlm.output output (
`El_start (
NS.table_row_node, [(NS.table_row_repeat_attr, string_of_int (count-1))]));
Xmlm.output output (`El_start (NS.table_cell_node, []));
Xmlm.output output `El_end;
Xmlm.output output `El_end;
);
Xmlm.output output (`El_start (NS.table_row_node, []));
1
end
(** Insert as many cells as required *)
and insert_cells count = begin
Xmlm.output output (
`El_start (
NS.table_cell_node, [(NS.number_columns_repeat_attr, string_of_int count)]));
Xmlm.output output `El_end;
end in
(* Insert empty rows or columns until the desired position *)
let jump_row = to_y - from_y in
let from_x' =
if jump_row > 0 then
insert_rows jump_row
else
from_x in
let jump_cell = to_x - from_x' in
if jump_cell > 0 then
insert_cells jump_cell
end
(** Write the cell content and return the updated position *)
let f output cursor position (expr, value) = begin
goto output cursor position;
(* Write the value *)
write_cell output value expr;
(* Return the update position *)
Tools.Tuple2.map1 ((+) 1) position
end
let save sheet file = begin
let tmp_file = Filename.temp_file "content" ".xml" in
Unix.unlink tmp_file;
let out_channel = open_out_bin tmp_file in
let zip = Zip.open_out file in
let manifest = "\
\
\
" in
Tools.try_finally (fun () ->
let output = Xmlm.make_output (`Channel out_channel) in
Xmlm.output output (`Dtd None);
Xmlm.output output (`El_start (NS.document_content_node, NS.name_spaces()));
Xmlm.output output (`El_start (NS.body_node, []));
Xmlm.output output (`El_start (NS.spreadsheet_node, []));
Xmlm.output output (`El_start (NS.table_node, []));
Xmlm.output output (`El_start (NS.table_row_node, []));
ignore (Sheet.fold (f output) (1,1) sheet);
Xmlm.output output `El_end;
Xmlm.output output `El_end;
Xmlm.output output `El_end;
Xmlm.output output `El_end;
Xmlm.output output `El_end;
close_out out_channel;
Zip.copy_file_to_entry tmp_file zip "content.xml";
Zip.add_entry manifest zip "META-INF/manifest.xml"
) (fun () ->
Zip.close_out zip;
Unix.unlink tmp_file
)
end