-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcsv.ur
131 lines (113 loc) · 4.63 KB
/
csv.ur
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
fun csvFold [fs] [acc] (f : $fs -> acc -> acc)
(injs : $(map sql_injectable fs)) (reads : $(map read fs)) (fl : folder fs) =
let
fun doLine line acc =
let
val (commas, total, line, acc') =
@foldR [read] [fn r => string -> int * int * string * $r]
(fn [nm ::_] [t ::_] [r ::_] [[nm] ~ r] (_ : read t) acc line =>
case String.split line #"," of
None =>
let
val (commas, total, line, r) = acc line
in
(commas, total+1, "", {nm = readError (String.trim line)} ++ r)
end
| Some (token, line) =>
let
val (commas, total, line, r) = acc line
in
(commas+1, total+1, line, {nm = readError (String.trim token)} ++ r)
end)
(fn line => (0, 0, line, {})) fl reads line
in
if commas <> total-1 || line <> "" then
error <xml>Wrong number of commas in CSV input ({[commas]}, {[total]}, {[line]})</xml>
else
f acc' acc
end
fun loop input acc =
case String.split input #"\n" of
None =>
(case input of
"" => acc
| _ => doLine input acc)
| Some (line, input) =>
loop input (doLine line acc)
in
loop
end
fun parse [fs] (injs : $(map sql_injectable fs)) (reads : $(map read fs)) (fl : folder fs)
(input : string) =
@csvFold (fn r acc => r :: acc) injs reads fl input []
fun importTable [fs] [cs] (injs : $(map sql_injectable fs)) (reads : $(map read fs)) (fl : folder fs)
(tab : sql_table fs cs) (input : string) =
List.app (@Sql.easy_insert injs fl tab) (@parse injs reads fl input)
open Bootstrap3
functor Generate1(M : sig
con fs :: {Type}
con tab :: Name
val query : sql_query [] [] [tab = fs] []
val fl : folder fs
val shows : $(map show fs)
val labels : $(map (fn _ => string) fs)
val mayAccess : transaction bool
val filename : string
end) = struct
open M
type a = unit
fun escape' s =
case s of
"" => ""
| _ =>
let
val ch = String.sub s 0
val rest = String.suffix s 1
in
case ch of
#"\"" => "\"\"" ^ escape' rest
| _ => String.str ch ^ escape' rest
end
fun escape s =
if String.all (fn ch => ch <> #"," && ch <> #"\"" && ch <> #"\n" && ch <> #"\r") s then
s
else
if String.all (fn ch => ch <> #"\"") s then
"\"" ^ s ^ "\""
else
"\"" ^ escape' s ^ "\""
val build =
Basis.query query (fn r acc =>
return
(acc ^ @foldR2 [show] [ident] [fn _ => string]
(fn [nm ::_] [t ::_] [r ::_] [[nm] ~ r] (_ : show t) (x : t) acc =>
case acc of
"" => escape (show x)
| _ => escape (show x) ^ "," ^ acc)
"" fl shows r.tab ^ "\n"))
(@foldR [fn _ => string] [fn _ => string]
(fn [nm ::_] [t ::_] [r ::_] [[nm] ~ r] s acc =>
case acc of
"" => escape s
| _ => escape s ^ "," ^ acc)
"" fl labels ^ "\n")
fun generate () : transaction page =
ma <- mayAccess;
if not ma then
error <xml>Access denied</xml>
else
csv <- build;
setHeader (blessResponseHeader "Content-Disposition")
("attachment; filename=" ^ filename);
returnBlob (textBlob csv) (blessMime "text/csv")
val create = return ()
fun onload () = return ()
fun render _ () = <xml>
<form>
<submit value="Generate" class="btn btn-primary" action={generate}/>
</form>
</xml>
val ui = {Create = create,
Onload = onload,
Render = render}
end